|  | /* | 
|  | * AimsLab RadioTrack (aka RadioVeveal) driver | 
|  | * | 
|  | * Copyright 1997 M. Kirkwood | 
|  | * | 
|  | * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> | 
|  | * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> | 
|  | * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> | 
|  | * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> | 
|  | * | 
|  | * Notes on the hardware (reverse engineered from other peoples' | 
|  | * reverse engineering of AIMS' code :-) | 
|  | * | 
|  | *  Frequency control is done digitally -- ie out(port,encodefreq(95.8)); | 
|  | * | 
|  | *  The signal strength query is unsurprisingly inaccurate.  And it seems | 
|  | *  to indicate that (on my card, at least) the frequency setting isn't | 
|  | *  too great.  (I have to tune up .025MHz from what the freq should be | 
|  | *  to get a report that the thing is tuned.) | 
|  | * | 
|  | *  Volume control is (ugh) analogue: | 
|  | *   out(port, start_increasing_volume); | 
|  | *   wait(a_wee_while); | 
|  | *   out(port, stop_changing_the_volume); | 
|  | * | 
|  | * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h>	/* Modules 			*/ | 
|  | #include <linux/init.h>		/* Initdata			*/ | 
|  | #include <linux/ioport.h>	/* request_region		*/ | 
|  | #include <linux/delay.h>	/* msleep			*/ | 
|  | #include <linux/videodev2.h>	/* kernel radio structs		*/ | 
|  | #include <linux/io.h>		/* outb, outb_p			*/ | 
|  | #include <linux/slab.h> | 
|  | #include <media/v4l2-device.h> | 
|  | #include <media/v4l2-ioctl.h> | 
|  | #include <media/v4l2-ctrls.h> | 
|  | #include "radio-isa.h" | 
|  | #include "lm7000.h" | 
|  |  | 
|  | MODULE_AUTHOR("M. Kirkwood"); | 
|  | MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card."); | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_VERSION("1.0.0"); | 
|  |  | 
|  | #ifndef CONFIG_RADIO_RTRACK_PORT | 
|  | #define CONFIG_RADIO_RTRACK_PORT -1 | 
|  | #endif | 
|  |  | 
|  | #define RTRACK_MAX 2 | 
|  |  | 
|  | static int io[RTRACK_MAX] = { [0] = CONFIG_RADIO_RTRACK_PORT, | 
|  | [1 ... (RTRACK_MAX - 1)] = -1 }; | 
|  | static int radio_nr[RTRACK_MAX]	= { [0 ... (RTRACK_MAX - 1)] = -1 }; | 
|  |  | 
|  | module_param_array(io, int, NULL, 0444); | 
|  | MODULE_PARM_DESC(io, "I/O addresses of the RadioTrack card (0x20f or 0x30f)"); | 
|  | module_param_array(radio_nr, int, NULL, 0444); | 
|  | MODULE_PARM_DESC(radio_nr, "Radio device numbers"); | 
|  |  | 
|  | struct rtrack { | 
|  | struct radio_isa_card isa; | 
|  | int curvol; | 
|  | }; | 
|  |  | 
|  | static struct radio_isa_card *rtrack_alloc(void) | 
|  | { | 
|  | struct rtrack *rt = kzalloc(sizeof(struct rtrack), GFP_KERNEL); | 
|  |  | 
|  | if (rt) | 
|  | rt->curvol = 0xff; | 
|  | return rt ? &rt->isa : NULL; | 
|  | } | 
|  |  | 
|  | #define AIMS_BIT_TUN_CE		(1 << 0) | 
|  | #define AIMS_BIT_TUN_CLK	(1 << 1) | 
|  | #define AIMS_BIT_TUN_DATA	(1 << 2) | 
|  | #define AIMS_BIT_VOL_CE		(1 << 3) | 
|  | #define AIMS_BIT_TUN_STRQ	(1 << 4) | 
|  | /* bit 5 is not connected */ | 
|  | #define AIMS_BIT_VOL_UP		(1 << 6)	/* active low */ | 
|  | #define AIMS_BIT_VOL_DN		(1 << 7)	/* active low */ | 
|  |  | 
|  | static void rtrack_set_pins(void *handle, u8 pins) | 
|  | { | 
|  | struct radio_isa_card *isa = handle; | 
|  | struct rtrack *rt = container_of(isa, struct rtrack, isa); | 
|  | u8 bits = AIMS_BIT_VOL_DN | AIMS_BIT_VOL_UP | AIMS_BIT_TUN_STRQ; | 
|  |  | 
|  | if (!v4l2_ctrl_g_ctrl(rt->isa.mute)) | 
|  | bits |= AIMS_BIT_VOL_CE; | 
|  |  | 
|  | if (pins & LM7000_DATA) | 
|  | bits |= AIMS_BIT_TUN_DATA; | 
|  | if (pins & LM7000_CLK) | 
|  | bits |= AIMS_BIT_TUN_CLK; | 
|  | if (pins & LM7000_CE) | 
|  | bits |= AIMS_BIT_TUN_CE; | 
|  |  | 
|  | outb_p(bits, rt->isa.io); | 
|  | } | 
|  |  | 
|  | static int rtrack_s_frequency(struct radio_isa_card *isa, u32 freq) | 
|  | { | 
|  | lm7000_set_freq(freq, isa, rtrack_set_pins); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static u32 rtrack_g_signal(struct radio_isa_card *isa) | 
|  | { | 
|  | /* bit set = no signal present */ | 
|  | return 0xffff * !(inb(isa->io) & 2); | 
|  | } | 
|  |  | 
|  | static int rtrack_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) | 
|  | { | 
|  | struct rtrack *rt = container_of(isa, struct rtrack, isa); | 
|  | int curvol = rt->curvol; | 
|  |  | 
|  | if (mute) { | 
|  | outb(0xd0, isa->io);	/* volume steady + sigstr + off	*/ | 
|  | return 0; | 
|  | } | 
|  | if (vol == 0) {			/* volume = 0 means mute the card */ | 
|  | outb(0x48, isa->io);	/* volume down but still "on"	*/ | 
|  | msleep(curvol * 3);	/* make sure it's totally down	*/ | 
|  | } else if (curvol < vol) { | 
|  | outb(0x98, isa->io);	/* volume up + sigstr + on	*/ | 
|  | for (; curvol < vol; curvol++) | 
|  | mdelay(3); | 
|  | } else if (curvol > vol) { | 
|  | outb(0x58, isa->io);	/* volume down + sigstr + on	*/ | 
|  | for (; curvol > vol; curvol--) | 
|  | mdelay(3); | 
|  | } | 
|  | outb(0xd8, isa->io);		/* volume steady + sigstr + on	*/ | 
|  | rt->curvol = vol; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Mute card - prevents noisy bootups */ | 
|  | static int rtrack_initialize(struct radio_isa_card *isa) | 
|  | { | 
|  | /* this ensures that the volume is all the way up  */ | 
|  | outb(0x90, isa->io);	/* volume up but still "on"	*/ | 
|  | msleep(3000);		/* make sure it's totally up	*/ | 
|  | outb(0xc0, isa->io);	/* steady volume, mute card	*/ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct radio_isa_ops rtrack_ops = { | 
|  | .alloc = rtrack_alloc, | 
|  | .init = rtrack_initialize, | 
|  | .s_mute_volume = rtrack_s_mute_volume, | 
|  | .s_frequency = rtrack_s_frequency, | 
|  | .g_signal = rtrack_g_signal, | 
|  | }; | 
|  |  | 
|  | static const int rtrack_ioports[] = { 0x20f, 0x30f }; | 
|  |  | 
|  | static struct radio_isa_driver rtrack_driver = { | 
|  | .driver = { | 
|  | .match		= radio_isa_match, | 
|  | .probe		= radio_isa_probe, | 
|  | .remove		= radio_isa_remove, | 
|  | .driver		= { | 
|  | .name	= "radio-aimslab", | 
|  | }, | 
|  | }, | 
|  | .io_params = io, | 
|  | .radio_nr_params = radio_nr, | 
|  | .io_ports = rtrack_ioports, | 
|  | .num_of_io_ports = ARRAY_SIZE(rtrack_ioports), | 
|  | .region_size = 2, | 
|  | .card = "AIMSlab RadioTrack/RadioReveal", | 
|  | .ops = &rtrack_ops, | 
|  | .has_stereo = true, | 
|  | .max_volume = 0xff, | 
|  | }; | 
|  |  | 
|  | static int __init rtrack_init(void) | 
|  | { | 
|  | return isa_register_driver(&rtrack_driver.driver, RTRACK_MAX); | 
|  | } | 
|  |  | 
|  | static void __exit rtrack_exit(void) | 
|  | { | 
|  | isa_unregister_driver(&rtrack_driver.driver); | 
|  | } | 
|  |  | 
|  | module_init(rtrack_init); | 
|  | module_exit(rtrack_exit); |