109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
26b39246cSHans Verkuil /*
36b39246cSHans Verkuil * Zoltrix Radio Plus driver
46b39246cSHans Verkuil * Copyright 1998 C. van Schaik <carl@leg.uct.ac.za>
51da177e4SLinus Torvalds *
61da177e4SLinus Torvalds * BUGS
71da177e4SLinus Torvalds * Due to the inconsistency in reading from the signal flags
81da177e4SLinus Torvalds * it is difficult to get an accurate tuned signal.
91da177e4SLinus Torvalds *
101da177e4SLinus Torvalds * It seems that the card is not linear to 0 volume. It cuts off
111da177e4SLinus Torvalds * at a low volume, and it is not possible (at least I have not found)
121da177e4SLinus Torvalds * to get fine volume control over the low volume range.
131da177e4SLinus Torvalds *
141da177e4SLinus Torvalds * Some code derived from code by Romolo Manfredini
151da177e4SLinus Torvalds * romolo@bicnet.it
161da177e4SLinus Torvalds *
171da177e4SLinus Torvalds * 1999-05-06 - (C. van Schaik)
181da177e4SLinus Torvalds * - Make signal strength and stereo scans
191da177e4SLinus Torvalds * kinder to cpu while in delay
201da177e4SLinus Torvalds * 1999-01-05 - (C. van Schaik)
211da177e4SLinus Torvalds * - Changed tuning to 1/160Mhz accuracy
221da177e4SLinus Torvalds * - Added stereo support
231da177e4SLinus Torvalds * (card defaults to stereo)
241da177e4SLinus Torvalds * (can explicitly force mono on the card)
251da177e4SLinus Torvalds * (can detect if station is in stereo)
261da177e4SLinus Torvalds * - Added unmute function
271da177e4SLinus Torvalds * - Reworked ioctl functions
281da177e4SLinus Torvalds * 2002-07-15 - Fix Stereo typo
292ab65299SMauro Carvalho Chehab *
302ab65299SMauro Carvalho Chehab * 2006-07-24 - Converted to V4L2 API
3132590819SMauro Carvalho Chehab * by Mauro Carvalho Chehab <mchehab@kernel.org>
326b39246cSHans Verkuil *
336b39246cSHans Verkuil * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
346b39246cSHans Verkuil *
356b39246cSHans Verkuil * Note that this is the driver for the Zoltrix Radio Plus.
366b39246cSHans Verkuil * This driver does not work for the Zoltrix Radio Plus 108 or the
376b39246cSHans Verkuil * Zoltrix Radio Plus for Windows.
386b39246cSHans Verkuil *
396b39246cSHans Verkuil * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
401da177e4SLinus Torvalds */
411da177e4SLinus Torvalds
421da177e4SLinus Torvalds #include <linux/module.h> /* Modules */
431da177e4SLinus Torvalds #include <linux/init.h> /* Initdata */
44fb911ee8SPeter Osterlund #include <linux/ioport.h> /* request_region */
451da177e4SLinus Torvalds #include <linux/delay.h> /* udelay, msleep */
462ab65299SMauro Carvalho Chehab #include <linux/videodev2.h> /* kernel radio structs */
47ec632c8aSHans Verkuil #include <linux/mutex.h>
48ec632c8aSHans Verkuil #include <linux/io.h> /* outb, outb_p */
499f1dfccfSHans Verkuil #include <linux/slab.h>
50ec632c8aSHans Verkuil #include <media/v4l2-device.h>
5135ea11ffSHans Verkuil #include <media/v4l2-ioctl.h>
526b39246cSHans Verkuil #include "radio-isa.h"
531da177e4SLinus Torvalds
54ec632c8aSHans Verkuil MODULE_AUTHOR("C. van Schaik");
55ec632c8aSHans Verkuil MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
56ec632c8aSHans Verkuil MODULE_LICENSE("GPL");
576b39246cSHans Verkuil MODULE_VERSION("0.1.99");
582ab65299SMauro Carvalho Chehab
591da177e4SLinus Torvalds #ifndef CONFIG_RADIO_ZOLTRIX_PORT
601da177e4SLinus Torvalds #define CONFIG_RADIO_ZOLTRIX_PORT -1
611da177e4SLinus Torvalds #endif
621da177e4SLinus Torvalds
636b39246cSHans Verkuil #define ZOLTRIX_MAX 2
641da177e4SLinus Torvalds
656b39246cSHans Verkuil static int io[ZOLTRIX_MAX] = { [0] = CONFIG_RADIO_ZOLTRIX_PORT,
666b39246cSHans Verkuil [1 ... (ZOLTRIX_MAX - 1)] = -1 };
676b39246cSHans Verkuil static int radio_nr[ZOLTRIX_MAX] = { [0 ... (ZOLTRIX_MAX - 1)] = -1 };
686b39246cSHans Verkuil
696b39246cSHans Verkuil module_param_array(io, int, NULL, 0444);
706b39246cSHans Verkuil MODULE_PARM_DESC(io, "I/O addresses of the Zoltrix Radio Plus card (0x20c or 0x30c)");
716b39246cSHans Verkuil module_param_array(radio_nr, int, NULL, 0444);
726b39246cSHans Verkuil MODULE_PARM_DESC(radio_nr, "Radio device numbers");
73ec632c8aSHans Verkuil
74ec632c8aSHans Verkuil struct zoltrix {
756b39246cSHans Verkuil struct radio_isa_card isa;
761da177e4SLinus Torvalds int curvol;
776b39246cSHans Verkuil bool muted;
781da177e4SLinus Torvalds };
791da177e4SLinus Torvalds
zoltrix_alloc(void)806b39246cSHans Verkuil static struct radio_isa_card *zoltrix_alloc(void)
811da177e4SLinus Torvalds {
826b39246cSHans Verkuil struct zoltrix *zol = kzalloc(sizeof(*zol), GFP_KERNEL);
836b39246cSHans Verkuil
846b39246cSHans Verkuil return zol ? &zol->isa : NULL;
856b39246cSHans Verkuil }
866b39246cSHans Verkuil
zoltrix_s_mute_volume(struct radio_isa_card * isa,bool mute,int vol)876b39246cSHans Verkuil static int zoltrix_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
886b39246cSHans Verkuil {
896b39246cSHans Verkuil struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
906b39246cSHans Verkuil
91ec632c8aSHans Verkuil zol->curvol = vol;
926b39246cSHans Verkuil zol->muted = mute;
936b39246cSHans Verkuil if (mute || vol == 0) {
946b39246cSHans Verkuil outb(0, isa->io);
956b39246cSHans Verkuil outb(0, isa->io);
966b39246cSHans Verkuil inb(isa->io + 3); /* Zoltrix needs to be read to confirm */
971da177e4SLinus Torvalds return 0;
981da177e4SLinus Torvalds }
991da177e4SLinus Torvalds
1006b39246cSHans Verkuil outb(vol - 1, isa->io);
1011da177e4SLinus Torvalds msleep(10);
1026b39246cSHans Verkuil inb(isa->io + 2);
1031da177e4SLinus Torvalds return 0;
1041da177e4SLinus Torvalds }
1051da177e4SLinus Torvalds
1061da177e4SLinus Torvalds /* tunes the radio to the desired frequency */
zoltrix_s_frequency(struct radio_isa_card * isa,u32 freq)1076b39246cSHans Verkuil static int zoltrix_s_frequency(struct radio_isa_card *isa, u32 freq)
1086b39246cSHans Verkuil {
1096b39246cSHans Verkuil struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
1106b39246cSHans Verkuil struct v4l2_device *v4l2_dev = &isa->v4l2_dev;
1111da177e4SLinus Torvalds unsigned long long bitmask, f, m;
1126b39246cSHans Verkuil bool stereo = isa->stereo;
1131da177e4SLinus Torvalds int i;
1141da177e4SLinus Torvalds
115b4be2048SAlexey Klimov if (freq == 0) {
116ec632c8aSHans Verkuil v4l2_warn(v4l2_dev, "cannot set a frequency of 0.\n");
117b4be2048SAlexey Klimov return -EINVAL;
118b4be2048SAlexey Klimov }
119b4be2048SAlexey Klimov
1201da177e4SLinus Torvalds m = (freq / 160 - 8800) * 2;
1211da177e4SLinus Torvalds f = (unsigned long long)m + 0x4d1c;
1221da177e4SLinus Torvalds
1231da177e4SLinus Torvalds bitmask = 0xc480402c10080000ull;
1241da177e4SLinus Torvalds i = 45;
1251da177e4SLinus Torvalds
1266b39246cSHans Verkuil outb(0, isa->io);
1276b39246cSHans Verkuil outb(0, isa->io);
1286b39246cSHans Verkuil inb(isa->io + 3); /* Zoltrix needs to be read to confirm */
1291da177e4SLinus Torvalds
1306b39246cSHans Verkuil outb(0x40, isa->io);
1316b39246cSHans Verkuil outb(0xc0, isa->io);
1321da177e4SLinus Torvalds
1331da177e4SLinus Torvalds bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31));
1341da177e4SLinus Torvalds while (i--) {
1351da177e4SLinus Torvalds if ((bitmask & 0x8000000000000000ull) != 0) {
1366b39246cSHans Verkuil outb(0x80, isa->io);
1371da177e4SLinus Torvalds udelay(50);
1386b39246cSHans Verkuil outb(0x00, isa->io);
1391da177e4SLinus Torvalds udelay(50);
1406b39246cSHans Verkuil outb(0x80, isa->io);
1411da177e4SLinus Torvalds udelay(50);
1421da177e4SLinus Torvalds } else {
1436b39246cSHans Verkuil outb(0xc0, isa->io);
1441da177e4SLinus Torvalds udelay(50);
1456b39246cSHans Verkuil outb(0x40, isa->io);
1461da177e4SLinus Torvalds udelay(50);
1476b39246cSHans Verkuil outb(0xc0, isa->io);
1481da177e4SLinus Torvalds udelay(50);
1491da177e4SLinus Torvalds }
1501da177e4SLinus Torvalds bitmask *= 2;
1511da177e4SLinus Torvalds }
1521da177e4SLinus Torvalds /* termination sequence */
1536b39246cSHans Verkuil outb(0x80, isa->io);
1546b39246cSHans Verkuil outb(0xc0, isa->io);
1556b39246cSHans Verkuil outb(0x40, isa->io);
1561da177e4SLinus Torvalds udelay(1000);
1576b39246cSHans Verkuil inb(isa->io + 2);
1581da177e4SLinus Torvalds udelay(1000);
1591da177e4SLinus Torvalds
1606b39246cSHans Verkuil return zoltrix_s_mute_volume(isa, zol->muted, zol->curvol);
1611da177e4SLinus Torvalds }
1621da177e4SLinus Torvalds
1631da177e4SLinus Torvalds /* Get signal strength */
zoltrix_g_rxsubchans(struct radio_isa_card * isa)1646b39246cSHans Verkuil static u32 zoltrix_g_rxsubchans(struct radio_isa_card *isa)
1651da177e4SLinus Torvalds {
1666b39246cSHans Verkuil struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
1671da177e4SLinus Torvalds int a, b;
1681da177e4SLinus Torvalds
1696b39246cSHans Verkuil outb(0x00, isa->io); /* This stuff I found to do nothing */
1706b39246cSHans Verkuil outb(zol->curvol, isa->io);
1711da177e4SLinus Torvalds msleep(20);
1721da177e4SLinus Torvalds
1736b39246cSHans Verkuil a = inb(isa->io);
1741da177e4SLinus Torvalds msleep(10);
1756b39246cSHans Verkuil b = inb(isa->io);
1761da177e4SLinus Torvalds
1776b39246cSHans Verkuil return (a == b && a == 0xcf) ?
1786b39246cSHans Verkuil V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
1796b39246cSHans Verkuil }
1806b39246cSHans Verkuil
zoltrix_g_signal(struct radio_isa_card * isa)1816b39246cSHans Verkuil static u32 zoltrix_g_signal(struct radio_isa_card *isa)
1826b39246cSHans Verkuil {
1836b39246cSHans Verkuil struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
1846b39246cSHans Verkuil int a, b;
1856b39246cSHans Verkuil
1866b39246cSHans Verkuil outb(0x00, isa->io); /* This stuff I found to do nothing */
1876b39246cSHans Verkuil outb(zol->curvol, isa->io);
1886b39246cSHans Verkuil msleep(20);
1896b39246cSHans Verkuil
1906b39246cSHans Verkuil a = inb(isa->io);
1916b39246cSHans Verkuil msleep(10);
1926b39246cSHans Verkuil b = inb(isa->io);
1931da177e4SLinus Torvalds
1941da177e4SLinus Torvalds if (a != b)
195ec632c8aSHans Verkuil return 0;
1961da177e4SLinus Torvalds
197ec632c8aSHans Verkuil /* I found this out by playing with a binary scanner on the card io */
1986b39246cSHans Verkuil return (a == 0xcf || a == 0xdf || a == 0xef) ? 0xffff : 0;
1991da177e4SLinus Torvalds }
2001da177e4SLinus Torvalds
zoltrix_s_stereo(struct radio_isa_card * isa,bool stereo)2016b39246cSHans Verkuil static int zoltrix_s_stereo(struct radio_isa_card *isa, bool stereo)
2021da177e4SLinus Torvalds {
2036b39246cSHans Verkuil return zoltrix_s_frequency(isa, isa->freq);
2041da177e4SLinus Torvalds }
2051da177e4SLinus Torvalds
2066b39246cSHans Verkuil static const struct radio_isa_ops zoltrix_ops = {
2076b39246cSHans Verkuil .alloc = zoltrix_alloc,
2086b39246cSHans Verkuil .s_mute_volume = zoltrix_s_mute_volume,
2096b39246cSHans Verkuil .s_frequency = zoltrix_s_frequency,
2106b39246cSHans Verkuil .s_stereo = zoltrix_s_stereo,
2116b39246cSHans Verkuil .g_rxsubchans = zoltrix_g_rxsubchans,
2126b39246cSHans Verkuil .g_signal = zoltrix_g_signal,
2131da177e4SLinus Torvalds };
2141da177e4SLinus Torvalds
2156b39246cSHans Verkuil static const int zoltrix_ioports[] = { 0x20c, 0x30c };
2166b39246cSHans Verkuil
2176b39246cSHans Verkuil static struct radio_isa_driver zoltrix_driver = {
2186b39246cSHans Verkuil .driver = {
2196b39246cSHans Verkuil .match = radio_isa_match,
2206b39246cSHans Verkuil .probe = radio_isa_probe,
2216b39246cSHans Verkuil .remove = radio_isa_remove,
2226b39246cSHans Verkuil .driver = {
2236b39246cSHans Verkuil .name = "radio-zoltrix",
2246b39246cSHans Verkuil },
2256b39246cSHans Verkuil },
2266b39246cSHans Verkuil .io_params = io,
2276b39246cSHans Verkuil .radio_nr_params = radio_nr,
2286b39246cSHans Verkuil .io_ports = zoltrix_ioports,
2296b39246cSHans Verkuil .num_of_io_ports = ARRAY_SIZE(zoltrix_ioports),
2306b39246cSHans Verkuil .region_size = 2,
2316b39246cSHans Verkuil .card = "Zoltrix Radio Plus",
2326b39246cSHans Verkuil .ops = &zoltrix_ops,
2336b39246cSHans Verkuil .has_stereo = true,
2346b39246cSHans Verkuil .max_volume = 15,
2351da177e4SLinus Torvalds };
2361da177e4SLinus Torvalds
zoltrix_init(void)2371da177e4SLinus Torvalds static int __init zoltrix_init(void)
2381da177e4SLinus Torvalds {
2396b39246cSHans Verkuil return isa_register_driver(&zoltrix_driver.driver, ZOLTRIX_MAX);
2401da177e4SLinus Torvalds }
2411da177e4SLinus Torvalds
zoltrix_exit(void)242ec632c8aSHans Verkuil static void __exit zoltrix_exit(void)
2431da177e4SLinus Torvalds {
2446b39246cSHans Verkuil isa_unregister_driver(&zoltrix_driver.driver);
2451da177e4SLinus Torvalds }
2461da177e4SLinus Torvalds
2471da177e4SLinus Torvalds module_init(zoltrix_init);
248ec632c8aSHans Verkuil module_exit(zoltrix_exit);
2491da177e4SLinus Torvalds
250