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