109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
21da177e4SLinus Torvalds /* Terratec ActiveRadio ISA Standalone card driver for Linux radio support
31da177e4SLinus Torvalds  * (c) 1999 R. Offermanns (rolf@offermanns.de)
41da177e4SLinus Torvalds  * based on the aimslab radio driver from M. Kirkwood
51da177e4SLinus Torvalds  * many thanks to Michael Becker and Friedhelm Birth (from TerraTec)
61da177e4SLinus Torvalds  *
71da177e4SLinus Torvalds  *
81da177e4SLinus Torvalds  * History:
91da177e4SLinus Torvalds  * 1999-05-21	First preview release
101da177e4SLinus Torvalds  *
111da177e4SLinus Torvalds  *  Notes on the hardware:
121da177e4SLinus Torvalds  *  There are two "main" chips on the card:
131da177e4SLinus Torvalds  *  - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf)
141da177e4SLinus Torvalds  *  - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf)
151da177e4SLinus Torvalds  *  (you can get the datasheet at the above links)
161da177e4SLinus Torvalds  *
171da177e4SLinus Torvalds  *  Frequency control is done digitally -- ie out(port,encodefreq(95.8));
181da177e4SLinus Torvalds  *  Volume Control is done digitally
191da177e4SLinus Torvalds  *
2032c51836SHans Verkuil  * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
2132590819SMauro Carvalho Chehab  * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@kernel.org>
221da177e4SLinus Torvalds  */
231da177e4SLinus Torvalds 
241da177e4SLinus Torvalds #include <linux/module.h>	/* Modules			*/
251da177e4SLinus Torvalds #include <linux/init.h>		/* Initdata			*/
26fb911ee8SPeter Osterlund #include <linux/ioport.h>	/* request_region		*/
2755ac7b69SMauro Carvalho Chehab #include <linux/videodev2.h>	/* kernel radio structs		*/
285ac3d5bfSHans Verkuil #include <linux/mutex.h>
295ac3d5bfSHans Verkuil #include <linux/io.h>		/* outb, outb_p			*/
309f1dfccfSHans Verkuil #include <linux/slab.h>
315ac3d5bfSHans Verkuil #include <media/v4l2-device.h>
325ac3d5bfSHans Verkuil #include <media/v4l2-ioctl.h>
3332c51836SHans Verkuil #include "radio-isa.h"
345ac3d5bfSHans Verkuil 
3532c51836SHans Verkuil MODULE_AUTHOR("R. Offermans & others");
365ac3d5bfSHans Verkuil MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card.");
375ac3d5bfSHans Verkuil MODULE_LICENSE("GPL");
3832c51836SHans Verkuil MODULE_VERSION("0.1.99");
395ac3d5bfSHans Verkuil 
4032c51836SHans Verkuil /* Note: there seems to be only one possible port (0x590), but without
4132c51836SHans Verkuil    hardware this is hard to verify. For now, this is the only one we will
4232c51836SHans Verkuil    support. */
4332c51836SHans Verkuil static int io = 0x590;
445ac3d5bfSHans Verkuil static int radio_nr = -1;
455ac3d5bfSHans Verkuil 
4632c51836SHans Verkuil module_param(radio_nr, int, 0444);
4732c51836SHans Verkuil MODULE_PARM_DESC(radio_nr, "Radio device number");
4855ac7b69SMauro Carvalho Chehab 
491da177e4SLinus Torvalds #define WRT_DIS		0x00
501da177e4SLinus Torvalds #define CLK_OFF		0x00
511da177e4SLinus Torvalds #define IIC_DATA	0x01
521da177e4SLinus Torvalds #define IIC_CLK		0x02
531da177e4SLinus Torvalds #define DATA		0x04
541da177e4SLinus Torvalds #define CLK_ON		0x08
551da177e4SLinus Torvalds #define WRT_EN		0x10
561da177e4SLinus Torvalds 
terratec_alloc(void)5732c51836SHans Verkuil static struct radio_isa_card *terratec_alloc(void)
581da177e4SLinus Torvalds {
5932c51836SHans Verkuil 	return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL);
6032c51836SHans Verkuil }
611da177e4SLinus Torvalds 
terratec_s_mute_volume(struct radio_isa_card * isa,bool mute,int vol)6232c51836SHans Verkuil static int terratec_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
631da177e4SLinus Torvalds {
641da177e4SLinus Torvalds 	int i;
655ac3d5bfSHans Verkuil 
6632c51836SHans Verkuil 	if (mute)
6732c51836SHans Verkuil 		vol = 0;
6832c51836SHans Verkuil 	vol = vol + (vol * 32); /* change both channels */
695ac3d5bfSHans Verkuil 	for (i = 0; i < 8; i++) {
7032c51836SHans Verkuil 		if (vol & (0x80 >> i))
7132c51836SHans Verkuil 			outb(0x80, isa->io + 1);
725ac3d5bfSHans Verkuil 		else
7332c51836SHans Verkuil 			outb(0x00, isa->io + 1);
741da177e4SLinus Torvalds 	}
751da177e4SLinus Torvalds 	return 0;
761da177e4SLinus Torvalds }
771da177e4SLinus Torvalds 
781da177e4SLinus Torvalds 
791da177e4SLinus Torvalds /* this is the worst part in this driver */
801da177e4SLinus Torvalds /* many more or less strange things are going on here, but hey, it works :) */
811da177e4SLinus Torvalds 
terratec_s_frequency(struct radio_isa_card * isa,u32 freq)8232c51836SHans Verkuil static int terratec_s_frequency(struct radio_isa_card *isa, u32 freq)
831da177e4SLinus Torvalds {
841da177e4SLinus Torvalds 	int i;
851da177e4SLinus Torvalds 	int temp;
861da177e4SLinus Torvalds 	long rest;
871da177e4SLinus Torvalds 	unsigned char buffer[25];		/* we have to bit shift 25 registers */
881da177e4SLinus Torvalds 
8932c51836SHans Verkuil 	freq = freq / 160;			/* convert the freq. to a nice to handle value */
905ac3d5bfSHans Verkuil 	memset(buffer, 0, sizeof(buffer));
915ac3d5bfSHans Verkuil 
925ac3d5bfSHans Verkuil 	rest = freq * 10 + 10700;	/* I once had understood what is going on here */
931da177e4SLinus Torvalds 					/* maybe some wise guy (friedhelm?) can comment this stuff */
941da177e4SLinus Torvalds 	i = 13;
951da177e4SLinus Torvalds 	temp = 102400;
965ac3d5bfSHans Verkuil 	while (rest != 0) {
971da177e4SLinus Torvalds 		if (rest % temp  == rest)
981da177e4SLinus Torvalds 			buffer[i] = 0;
995ac3d5bfSHans Verkuil 		else {
1001da177e4SLinus Torvalds 			buffer[i] = 1;
1011da177e4SLinus Torvalds 			rest = rest - temp;
1021da177e4SLinus Torvalds 		}
1031da177e4SLinus Torvalds 		i--;
1041da177e4SLinus Torvalds 		temp = temp / 2;
1051da177e4SLinus Torvalds 	}
1061da177e4SLinus Torvalds 
1075ac3d5bfSHans Verkuil 	for (i = 24; i > -1; i--) {	/* bit shift the values to the radiocard */
1085ac3d5bfSHans Verkuil 		if (buffer[i] == 1) {
10932c51836SHans Verkuil 			outb(WRT_EN | DATA, isa->io);
11032c51836SHans Verkuil 			outb(WRT_EN | DATA | CLK_ON, isa->io);
11132c51836SHans Verkuil 			outb(WRT_EN | DATA, isa->io);
1125ac3d5bfSHans Verkuil 		} else {
11332c51836SHans Verkuil 			outb(WRT_EN | 0x00, isa->io);
11432c51836SHans Verkuil 			outb(WRT_EN | 0x00 | CLK_ON, isa->io);
1155ac3d5bfSHans Verkuil 		}
1165ac3d5bfSHans Verkuil 	}
11732c51836SHans Verkuil 	outb(0x00, isa->io);
1181da177e4SLinus Torvalds 	return 0;
1191da177e4SLinus Torvalds }
1201da177e4SLinus Torvalds 
terratec_g_signal(struct radio_isa_card * isa)12132c51836SHans Verkuil static u32 terratec_g_signal(struct radio_isa_card *isa)
1221da177e4SLinus Torvalds {
12332c51836SHans Verkuil 	/* bit set = no signal present	*/
12432c51836SHans Verkuil 	return (inb(isa->io) & 2) ? 0 : 0xffff;
1251da177e4SLinus Torvalds }
1261da177e4SLinus Torvalds 
12732c51836SHans Verkuil static const struct radio_isa_ops terratec_ops = {
12832c51836SHans Verkuil 	.alloc = terratec_alloc,
12932c51836SHans Verkuil 	.s_mute_volume = terratec_s_mute_volume,
13032c51836SHans Verkuil 	.s_frequency = terratec_s_frequency,
13132c51836SHans Verkuil 	.g_signal = terratec_g_signal,
1321da177e4SLinus Torvalds };
1331da177e4SLinus Torvalds 
13432c51836SHans Verkuil static const int terratec_ioports[] = { 0x590 };
13532c51836SHans Verkuil 
13632c51836SHans Verkuil static struct radio_isa_driver terratec_driver = {
13732c51836SHans Verkuil 	.driver = {
13832c51836SHans Verkuil 		.match		= radio_isa_match,
13932c51836SHans Verkuil 		.probe		= radio_isa_probe,
14032c51836SHans Verkuil 		.remove		= radio_isa_remove,
14132c51836SHans Verkuil 		.driver		= {
14232c51836SHans Verkuil 			.name	= "radio-terratec",
14332c51836SHans Verkuil 		},
14432c51836SHans Verkuil 	},
14532c51836SHans Verkuil 	.io_params = &io,
14632c51836SHans Verkuil 	.radio_nr_params = &radio_nr,
14732c51836SHans Verkuil 	.io_ports = terratec_ioports,
14832c51836SHans Verkuil 	.num_of_io_ports = ARRAY_SIZE(terratec_ioports),
14932c51836SHans Verkuil 	.region_size = 2,
15032c51836SHans Verkuil 	.card = "TerraTec ActiveRadio",
15132c51836SHans Verkuil 	.ops = &terratec_ops,
15232c51836SHans Verkuil 	.has_stereo = true,
15332c51836SHans Verkuil 	.max_volume = 10,
1541da177e4SLinus Torvalds };
1551da177e4SLinus Torvalds 
terratec_init(void)1561da177e4SLinus Torvalds static int __init terratec_init(void)
1571da177e4SLinus Torvalds {
15832c51836SHans Verkuil 	return isa_register_driver(&terratec_driver.driver, 1);
1591da177e4SLinus Torvalds }
1601da177e4SLinus Torvalds 
terratec_exit(void)1615ac3d5bfSHans Verkuil static void __exit terratec_exit(void)
1621da177e4SLinus Torvalds {
16332c51836SHans Verkuil 	isa_unregister_driver(&terratec_driver.driver);
1641da177e4SLinus Torvalds }
1651da177e4SLinus Torvalds 
1661da177e4SLinus Torvalds module_init(terratec_init);
1675ac3d5bfSHans Verkuil module_exit(terratec_exit);
1681da177e4SLinus Torvalds 
169