xref: /OK3568_Linux_fs/kernel/drivers/media/radio/radio-cadet.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card
3*4882a593Smuzhiyun  *
4*4882a593Smuzhiyun  * by Fred Gleason <fredg@wava.com>
5*4882a593Smuzhiyun  * Version 0.3.3
6*4882a593Smuzhiyun  *
7*4882a593Smuzhiyun  * (Loosely) based on code for the Aztech radio card by
8*4882a593Smuzhiyun  *
9*4882a593Smuzhiyun  * Russell Kroll    (rkroll@exploits.org)
10*4882a593Smuzhiyun  * Quay Ly
11*4882a593Smuzhiyun  * Donald Song
12*4882a593Smuzhiyun  * Jason Lewis      (jlewis@twilight.vtc.vsc.edu)
13*4882a593Smuzhiyun  * Scott McGrath    (smcgrath@twilight.vtc.vsc.edu)
14*4882a593Smuzhiyun  * William McGrath  (wmcgrath@twilight.vtc.vsc.edu)
15*4882a593Smuzhiyun  *
16*4882a593Smuzhiyun  * History:
17*4882a593Smuzhiyun  * 2000-04-29	Russell Kroll <rkroll@exploits.org>
18*4882a593Smuzhiyun  *		Added ISAPnP detection for Linux 2.3/2.4
19*4882a593Smuzhiyun  *
20*4882a593Smuzhiyun  * 2001-01-10	Russell Kroll <rkroll@exploits.org>
21*4882a593Smuzhiyun  *		Removed dead CONFIG_RADIO_CADET_PORT code
22*4882a593Smuzhiyun  *		PnP detection on load is now default (no args necessary)
23*4882a593Smuzhiyun  *
24*4882a593Smuzhiyun  * 2002-01-17	Adam Belay <ambx1@neo.rr.com>
25*4882a593Smuzhiyun  *		Updated to latest pnp code
26*4882a593Smuzhiyun  *
27*4882a593Smuzhiyun  * 2003-01-31	Alan Cox <alan@lxorguk.ukuu.org.uk>
28*4882a593Smuzhiyun  *		Cleaned up locking, delay code, general odds and ends
29*4882a593Smuzhiyun  *
30*4882a593Smuzhiyun  * 2006-07-30	Hans J. Koch <koch@hjk-az.de>
31*4882a593Smuzhiyun  *		Changed API to V4L2
32*4882a593Smuzhiyun  */
33*4882a593Smuzhiyun 
34*4882a593Smuzhiyun #include <linux/module.h>	/* Modules			*/
35*4882a593Smuzhiyun #include <linux/init.h>		/* Initdata			*/
36*4882a593Smuzhiyun #include <linux/ioport.h>	/* request_region		*/
37*4882a593Smuzhiyun #include <linux/delay.h>	/* udelay			*/
38*4882a593Smuzhiyun #include <linux/videodev2.h>	/* V4L2 API defs		*/
39*4882a593Smuzhiyun #include <linux/param.h>
40*4882a593Smuzhiyun #include <linux/pnp.h>
41*4882a593Smuzhiyun #include <linux/sched.h>
42*4882a593Smuzhiyun #include <linux/io.h>		/* outb, outb_p			*/
43*4882a593Smuzhiyun #include <media/v4l2-device.h>
44*4882a593Smuzhiyun #include <media/v4l2-ioctl.h>
45*4882a593Smuzhiyun #include <media/v4l2-ctrls.h>
46*4882a593Smuzhiyun #include <media/v4l2-fh.h>
47*4882a593Smuzhiyun #include <media/v4l2-event.h>
48*4882a593Smuzhiyun 
49*4882a593Smuzhiyun MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
50*4882a593Smuzhiyun MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");
51*4882a593Smuzhiyun MODULE_LICENSE("GPL");
52*4882a593Smuzhiyun MODULE_VERSION("0.3.4");
53*4882a593Smuzhiyun 
54*4882a593Smuzhiyun static int io = -1;		/* default to isapnp activation */
55*4882a593Smuzhiyun static int radio_nr = -1;
56*4882a593Smuzhiyun 
57*4882a593Smuzhiyun module_param(io, int, 0);
58*4882a593Smuzhiyun MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
59*4882a593Smuzhiyun module_param(radio_nr, int, 0);
60*4882a593Smuzhiyun 
61*4882a593Smuzhiyun #define RDS_BUFFER 256
62*4882a593Smuzhiyun #define RDS_RX_FLAG 1
63*4882a593Smuzhiyun #define MBS_RX_FLAG 2
64*4882a593Smuzhiyun 
65*4882a593Smuzhiyun struct cadet {
66*4882a593Smuzhiyun 	struct v4l2_device v4l2_dev;
67*4882a593Smuzhiyun 	struct video_device vdev;
68*4882a593Smuzhiyun 	struct v4l2_ctrl_handler ctrl_handler;
69*4882a593Smuzhiyun 	int io;
70*4882a593Smuzhiyun 	bool is_fm_band;
71*4882a593Smuzhiyun 	u32 curfreq;
72*4882a593Smuzhiyun 	int tunestat;
73*4882a593Smuzhiyun 	int sigstrength;
74*4882a593Smuzhiyun 	wait_queue_head_t read_queue;
75*4882a593Smuzhiyun 	struct timer_list readtimer;
76*4882a593Smuzhiyun 	u8 rdsin, rdsout, rdsstat;
77*4882a593Smuzhiyun 	unsigned char rdsbuf[RDS_BUFFER];
78*4882a593Smuzhiyun 	struct mutex lock;
79*4882a593Smuzhiyun 	int reading;
80*4882a593Smuzhiyun };
81*4882a593Smuzhiyun 
82*4882a593Smuzhiyun static struct cadet cadet_card;
83*4882a593Smuzhiyun 
84*4882a593Smuzhiyun /*
85*4882a593Smuzhiyun  * Signal Strength Threshold Values
86*4882a593Smuzhiyun  * The V4L API spec does not define any particular unit for the signal
87*4882a593Smuzhiyun  * strength value.  These values are in microvolts of RF at the tuner's input.
88*4882a593Smuzhiyun  */
89*4882a593Smuzhiyun static u16 sigtable[2][4] = {
90*4882a593Smuzhiyun 	{ 1835, 2621,  4128, 65535 },
91*4882a593Smuzhiyun 	{ 2185, 4369, 13107, 65535 },
92*4882a593Smuzhiyun };
93*4882a593Smuzhiyun 
94*4882a593Smuzhiyun static const struct v4l2_frequency_band bands[] = {
95*4882a593Smuzhiyun 	{
96*4882a593Smuzhiyun 		.index = 0,
97*4882a593Smuzhiyun 		.type = V4L2_TUNER_RADIO,
98*4882a593Smuzhiyun 		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
99*4882a593Smuzhiyun 		.rangelow = 8320,      /* 520 kHz */
100*4882a593Smuzhiyun 		.rangehigh = 26400,    /* 1650 kHz */
101*4882a593Smuzhiyun 		.modulation = V4L2_BAND_MODULATION_AM,
102*4882a593Smuzhiyun 	}, {
103*4882a593Smuzhiyun 		.index = 1,
104*4882a593Smuzhiyun 		.type = V4L2_TUNER_RADIO,
105*4882a593Smuzhiyun 		.capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS |
106*4882a593Smuzhiyun 			V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_LOW |
107*4882a593Smuzhiyun 			V4L2_TUNER_CAP_FREQ_BANDS,
108*4882a593Smuzhiyun 		.rangelow = 1400000,   /* 87.5 MHz */
109*4882a593Smuzhiyun 		.rangehigh = 1728000,  /* 108.0 MHz */
110*4882a593Smuzhiyun 		.modulation = V4L2_BAND_MODULATION_FM,
111*4882a593Smuzhiyun 	},
112*4882a593Smuzhiyun };
113*4882a593Smuzhiyun 
114*4882a593Smuzhiyun 
cadet_getstereo(struct cadet * dev)115*4882a593Smuzhiyun static int cadet_getstereo(struct cadet *dev)
116*4882a593Smuzhiyun {
117*4882a593Smuzhiyun 	int ret = V4L2_TUNER_SUB_MONO;
118*4882a593Smuzhiyun 
119*4882a593Smuzhiyun 	if (!dev->is_fm_band)	/* Only FM has stereo capability! */
120*4882a593Smuzhiyun 		return V4L2_TUNER_SUB_MONO;
121*4882a593Smuzhiyun 
122*4882a593Smuzhiyun 	outb(7, dev->io);          /* Select tuner control */
123*4882a593Smuzhiyun 	if ((inb(dev->io + 1) & 0x40) == 0)
124*4882a593Smuzhiyun 		ret = V4L2_TUNER_SUB_STEREO;
125*4882a593Smuzhiyun 	return ret;
126*4882a593Smuzhiyun }
127*4882a593Smuzhiyun 
cadet_gettune(struct cadet * dev)128*4882a593Smuzhiyun static unsigned cadet_gettune(struct cadet *dev)
129*4882a593Smuzhiyun {
130*4882a593Smuzhiyun 	int curvol, i;
131*4882a593Smuzhiyun 	unsigned fifo = 0;
132*4882a593Smuzhiyun 
133*4882a593Smuzhiyun 	/*
134*4882a593Smuzhiyun 	 * Prepare for read
135*4882a593Smuzhiyun 	 */
136*4882a593Smuzhiyun 
137*4882a593Smuzhiyun 	outb(7, dev->io);       /* Select tuner control */
138*4882a593Smuzhiyun 	curvol = inb(dev->io + 1); /* Save current volume/mute setting */
139*4882a593Smuzhiyun 	outb(0x00, dev->io + 1);  /* Ensure WRITE-ENABLE is LOW */
140*4882a593Smuzhiyun 	dev->tunestat = 0xffff;
141*4882a593Smuzhiyun 
142*4882a593Smuzhiyun 	/*
143*4882a593Smuzhiyun 	 * Read the shift register
144*4882a593Smuzhiyun 	 */
145*4882a593Smuzhiyun 	for (i = 0; i < 25; i++) {
146*4882a593Smuzhiyun 		fifo = (fifo << 1) | ((inb(dev->io + 1) >> 7) & 0x01);
147*4882a593Smuzhiyun 		if (i < 24) {
148*4882a593Smuzhiyun 			outb(0x01, dev->io + 1);
149*4882a593Smuzhiyun 			dev->tunestat &= inb(dev->io + 1);
150*4882a593Smuzhiyun 			outb(0x00, dev->io + 1);
151*4882a593Smuzhiyun 		}
152*4882a593Smuzhiyun 	}
153*4882a593Smuzhiyun 
154*4882a593Smuzhiyun 	/*
155*4882a593Smuzhiyun 	 * Restore volume/mute setting
156*4882a593Smuzhiyun 	 */
157*4882a593Smuzhiyun 	outb(curvol, dev->io + 1);
158*4882a593Smuzhiyun 	return fifo;
159*4882a593Smuzhiyun }
160*4882a593Smuzhiyun 
cadet_getfreq(struct cadet * dev)161*4882a593Smuzhiyun static unsigned cadet_getfreq(struct cadet *dev)
162*4882a593Smuzhiyun {
163*4882a593Smuzhiyun 	int i;
164*4882a593Smuzhiyun 	unsigned freq = 0, test, fifo = 0;
165*4882a593Smuzhiyun 
166*4882a593Smuzhiyun 	/*
167*4882a593Smuzhiyun 	 * Read current tuning
168*4882a593Smuzhiyun 	 */
169*4882a593Smuzhiyun 	fifo = cadet_gettune(dev);
170*4882a593Smuzhiyun 
171*4882a593Smuzhiyun 	/*
172*4882a593Smuzhiyun 	 * Convert to actual frequency
173*4882a593Smuzhiyun 	 */
174*4882a593Smuzhiyun 	if (!dev->is_fm_band)    /* AM */
175*4882a593Smuzhiyun 		return ((fifo & 0x7fff) - 450) * 16;
176*4882a593Smuzhiyun 
177*4882a593Smuzhiyun 	test = 12500;
178*4882a593Smuzhiyun 	for (i = 0; i < 14; i++) {
179*4882a593Smuzhiyun 		if ((fifo & 0x01) != 0)
180*4882a593Smuzhiyun 			freq += test;
181*4882a593Smuzhiyun 		test = test << 1;
182*4882a593Smuzhiyun 		fifo = fifo >> 1;
183*4882a593Smuzhiyun 	}
184*4882a593Smuzhiyun 	freq -= 10700000;           /* IF frequency is 10.7 MHz */
185*4882a593Smuzhiyun 	freq = (freq * 16) / 1000;   /* Make it 1/16 kHz */
186*4882a593Smuzhiyun 	return freq;
187*4882a593Smuzhiyun }
188*4882a593Smuzhiyun 
cadet_settune(struct cadet * dev,unsigned fifo)189*4882a593Smuzhiyun static void cadet_settune(struct cadet *dev, unsigned fifo)
190*4882a593Smuzhiyun {
191*4882a593Smuzhiyun 	int i;
192*4882a593Smuzhiyun 	unsigned test;
193*4882a593Smuzhiyun 
194*4882a593Smuzhiyun 	outb(7, dev->io);                /* Select tuner control */
195*4882a593Smuzhiyun 	/*
196*4882a593Smuzhiyun 	 * Write the shift register
197*4882a593Smuzhiyun 	 */
198*4882a593Smuzhiyun 	test = 0;
199*4882a593Smuzhiyun 	test = (fifo >> 23) & 0x02;      /* Align data for SDO */
200*4882a593Smuzhiyun 	test |= 0x1c;                /* SDM=1, SWE=1, SEN=1, SCK=0 */
201*4882a593Smuzhiyun 	outb(7, dev->io);                /* Select tuner control */
202*4882a593Smuzhiyun 	outb(test, dev->io + 1);           /* Initialize for write */
203*4882a593Smuzhiyun 	for (i = 0; i < 25; i++) {
204*4882a593Smuzhiyun 		test |= 0x01;              /* Toggle SCK High */
205*4882a593Smuzhiyun 		outb(test, dev->io + 1);
206*4882a593Smuzhiyun 		test &= 0xfe;              /* Toggle SCK Low */
207*4882a593Smuzhiyun 		outb(test, dev->io + 1);
208*4882a593Smuzhiyun 		fifo = fifo << 1;            /* Prepare the next bit */
209*4882a593Smuzhiyun 		test = 0x1c | ((fifo >> 23) & 0x02);
210*4882a593Smuzhiyun 		outb(test, dev->io + 1);
211*4882a593Smuzhiyun 	}
212*4882a593Smuzhiyun }
213*4882a593Smuzhiyun 
cadet_setfreq(struct cadet * dev,unsigned freq)214*4882a593Smuzhiyun static void cadet_setfreq(struct cadet *dev, unsigned freq)
215*4882a593Smuzhiyun {
216*4882a593Smuzhiyun 	unsigned fifo;
217*4882a593Smuzhiyun 	int i, j, test;
218*4882a593Smuzhiyun 	int curvol;
219*4882a593Smuzhiyun 
220*4882a593Smuzhiyun 	freq = clamp(freq, bands[dev->is_fm_band].rangelow,
221*4882a593Smuzhiyun 			   bands[dev->is_fm_band].rangehigh);
222*4882a593Smuzhiyun 	dev->curfreq = freq;
223*4882a593Smuzhiyun 	/*
224*4882a593Smuzhiyun 	 * Formulate a fifo command
225*4882a593Smuzhiyun 	 */
226*4882a593Smuzhiyun 	fifo = 0;
227*4882a593Smuzhiyun 	if (dev->is_fm_band) {    /* FM */
228*4882a593Smuzhiyun 		test = 102400;
229*4882a593Smuzhiyun 		freq = freq / 16;       /* Make it kHz */
230*4882a593Smuzhiyun 		freq += 10700;               /* IF is 10700 kHz */
231*4882a593Smuzhiyun 		for (i = 0; i < 14; i++) {
232*4882a593Smuzhiyun 			fifo = fifo << 1;
233*4882a593Smuzhiyun 			if (freq >= test) {
234*4882a593Smuzhiyun 				fifo |= 0x01;
235*4882a593Smuzhiyun 				freq -= test;
236*4882a593Smuzhiyun 			}
237*4882a593Smuzhiyun 			test = test >> 1;
238*4882a593Smuzhiyun 		}
239*4882a593Smuzhiyun 	} else {	/* AM */
240*4882a593Smuzhiyun 		fifo = (freq / 16) + 450;	/* Make it kHz */
241*4882a593Smuzhiyun 		fifo |= 0x100000;		/* Select AM Band */
242*4882a593Smuzhiyun 	}
243*4882a593Smuzhiyun 
244*4882a593Smuzhiyun 	/*
245*4882a593Smuzhiyun 	 * Save current volume/mute setting
246*4882a593Smuzhiyun 	 */
247*4882a593Smuzhiyun 
248*4882a593Smuzhiyun 	outb(7, dev->io);                /* Select tuner control */
249*4882a593Smuzhiyun 	curvol = inb(dev->io + 1);
250*4882a593Smuzhiyun 
251*4882a593Smuzhiyun 	/*
252*4882a593Smuzhiyun 	 * Tune the card
253*4882a593Smuzhiyun 	 */
254*4882a593Smuzhiyun 	for (j = 3; j > -1; j--) {
255*4882a593Smuzhiyun 		cadet_settune(dev, fifo | (j << 16));
256*4882a593Smuzhiyun 
257*4882a593Smuzhiyun 		outb(7, dev->io);         /* Select tuner control */
258*4882a593Smuzhiyun 		outb(curvol, dev->io + 1);
259*4882a593Smuzhiyun 
260*4882a593Smuzhiyun 		msleep(100);
261*4882a593Smuzhiyun 
262*4882a593Smuzhiyun 		cadet_gettune(dev);
263*4882a593Smuzhiyun 		if ((dev->tunestat & 0x40) == 0) {   /* Tuned */
264*4882a593Smuzhiyun 			dev->sigstrength = sigtable[dev->is_fm_band][j];
265*4882a593Smuzhiyun 			goto reset_rds;
266*4882a593Smuzhiyun 		}
267*4882a593Smuzhiyun 	}
268*4882a593Smuzhiyun 	dev->sigstrength = 0;
269*4882a593Smuzhiyun reset_rds:
270*4882a593Smuzhiyun 	outb(3, dev->io);
271*4882a593Smuzhiyun 	outb(inb(dev->io + 1) & 0x7f, dev->io + 1);
272*4882a593Smuzhiyun }
273*4882a593Smuzhiyun 
cadet_has_rds_data(struct cadet * dev)274*4882a593Smuzhiyun static bool cadet_has_rds_data(struct cadet *dev)
275*4882a593Smuzhiyun {
276*4882a593Smuzhiyun 	bool result;
277*4882a593Smuzhiyun 
278*4882a593Smuzhiyun 	mutex_lock(&dev->lock);
279*4882a593Smuzhiyun 	result = dev->rdsin != dev->rdsout;
280*4882a593Smuzhiyun 	mutex_unlock(&dev->lock);
281*4882a593Smuzhiyun 	return result;
282*4882a593Smuzhiyun }
283*4882a593Smuzhiyun 
284*4882a593Smuzhiyun 
cadet_handler(struct timer_list * t)285*4882a593Smuzhiyun static void cadet_handler(struct timer_list *t)
286*4882a593Smuzhiyun {
287*4882a593Smuzhiyun 	struct cadet *dev = from_timer(dev, t, readtimer);
288*4882a593Smuzhiyun 
289*4882a593Smuzhiyun 	/* Service the RDS fifo */
290*4882a593Smuzhiyun 	if (mutex_trylock(&dev->lock)) {
291*4882a593Smuzhiyun 		outb(0x3, dev->io);       /* Select RDS Decoder Control */
292*4882a593Smuzhiyun 		if ((inb(dev->io + 1) & 0x20) != 0)
293*4882a593Smuzhiyun 			pr_err("cadet: RDS fifo overflow\n");
294*4882a593Smuzhiyun 		outb(0x80, dev->io);      /* Select RDS fifo */
295*4882a593Smuzhiyun 
296*4882a593Smuzhiyun 		while ((inb(dev->io) & 0x80) != 0) {
297*4882a593Smuzhiyun 			dev->rdsbuf[dev->rdsin] = inb(dev->io + 1);
298*4882a593Smuzhiyun 			if (dev->rdsin + 1 != dev->rdsout)
299*4882a593Smuzhiyun 				dev->rdsin++;
300*4882a593Smuzhiyun 		}
301*4882a593Smuzhiyun 		mutex_unlock(&dev->lock);
302*4882a593Smuzhiyun 	}
303*4882a593Smuzhiyun 
304*4882a593Smuzhiyun 	/*
305*4882a593Smuzhiyun 	 * Service pending read
306*4882a593Smuzhiyun 	 */
307*4882a593Smuzhiyun 	if (cadet_has_rds_data(dev))
308*4882a593Smuzhiyun 		wake_up_interruptible(&dev->read_queue);
309*4882a593Smuzhiyun 
310*4882a593Smuzhiyun 	/*
311*4882a593Smuzhiyun 	 * Clean up and exit
312*4882a593Smuzhiyun 	 */
313*4882a593Smuzhiyun 	dev->readtimer.expires = jiffies + msecs_to_jiffies(50);
314*4882a593Smuzhiyun 	add_timer(&dev->readtimer);
315*4882a593Smuzhiyun }
316*4882a593Smuzhiyun 
cadet_start_rds(struct cadet * dev)317*4882a593Smuzhiyun static void cadet_start_rds(struct cadet *dev)
318*4882a593Smuzhiyun {
319*4882a593Smuzhiyun 	dev->rdsstat = 1;
320*4882a593Smuzhiyun 	outb(0x80, dev->io);        /* Select RDS fifo */
321*4882a593Smuzhiyun 	timer_setup(&dev->readtimer, cadet_handler, 0);
322*4882a593Smuzhiyun 	dev->readtimer.expires = jiffies + msecs_to_jiffies(50);
323*4882a593Smuzhiyun 	add_timer(&dev->readtimer);
324*4882a593Smuzhiyun }
325*4882a593Smuzhiyun 
cadet_read(struct file * file,char __user * data,size_t count,loff_t * ppos)326*4882a593Smuzhiyun static ssize_t cadet_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
327*4882a593Smuzhiyun {
328*4882a593Smuzhiyun 	struct cadet *dev = video_drvdata(file);
329*4882a593Smuzhiyun 	unsigned char readbuf[RDS_BUFFER];
330*4882a593Smuzhiyun 	int i = 0;
331*4882a593Smuzhiyun 
332*4882a593Smuzhiyun 	mutex_lock(&dev->lock);
333*4882a593Smuzhiyun 	if (dev->rdsstat == 0)
334*4882a593Smuzhiyun 		cadet_start_rds(dev);
335*4882a593Smuzhiyun 	mutex_unlock(&dev->lock);
336*4882a593Smuzhiyun 
337*4882a593Smuzhiyun 	if (!cadet_has_rds_data(dev) && (file->f_flags & O_NONBLOCK))
338*4882a593Smuzhiyun 		return -EWOULDBLOCK;
339*4882a593Smuzhiyun 	i = wait_event_interruptible(dev->read_queue, cadet_has_rds_data(dev));
340*4882a593Smuzhiyun 	if (i)
341*4882a593Smuzhiyun 		return i;
342*4882a593Smuzhiyun 
343*4882a593Smuzhiyun 	mutex_lock(&dev->lock);
344*4882a593Smuzhiyun 	while (i < count && dev->rdsin != dev->rdsout)
345*4882a593Smuzhiyun 		readbuf[i++] = dev->rdsbuf[dev->rdsout++];
346*4882a593Smuzhiyun 	mutex_unlock(&dev->lock);
347*4882a593Smuzhiyun 
348*4882a593Smuzhiyun 	if (i && copy_to_user(data, readbuf, i))
349*4882a593Smuzhiyun 		return -EFAULT;
350*4882a593Smuzhiyun 	return i;
351*4882a593Smuzhiyun }
352*4882a593Smuzhiyun 
353*4882a593Smuzhiyun 
vidioc_querycap(struct file * file,void * priv,struct v4l2_capability * v)354*4882a593Smuzhiyun static int vidioc_querycap(struct file *file, void *priv,
355*4882a593Smuzhiyun 				struct v4l2_capability *v)
356*4882a593Smuzhiyun {
357*4882a593Smuzhiyun 	strscpy(v->driver, "ADS Cadet", sizeof(v->driver));
358*4882a593Smuzhiyun 	strscpy(v->card, "ADS Cadet", sizeof(v->card));
359*4882a593Smuzhiyun 	strscpy(v->bus_info, "ISA:radio-cadet", sizeof(v->bus_info));
360*4882a593Smuzhiyun 	return 0;
361*4882a593Smuzhiyun }
362*4882a593Smuzhiyun 
vidioc_g_tuner(struct file * file,void * priv,struct v4l2_tuner * v)363*4882a593Smuzhiyun static int vidioc_g_tuner(struct file *file, void *priv,
364*4882a593Smuzhiyun 				struct v4l2_tuner *v)
365*4882a593Smuzhiyun {
366*4882a593Smuzhiyun 	struct cadet *dev = video_drvdata(file);
367*4882a593Smuzhiyun 
368*4882a593Smuzhiyun 	if (v->index)
369*4882a593Smuzhiyun 		return -EINVAL;
370*4882a593Smuzhiyun 	v->type = V4L2_TUNER_RADIO;
371*4882a593Smuzhiyun 	strscpy(v->name, "Radio", sizeof(v->name));
372*4882a593Smuzhiyun 	v->capability = bands[0].capability | bands[1].capability;
373*4882a593Smuzhiyun 	v->rangelow = bands[0].rangelow;	   /* 520 kHz (start of AM band) */
374*4882a593Smuzhiyun 	v->rangehigh = bands[1].rangehigh;    /* 108.0 MHz (end of FM band) */
375*4882a593Smuzhiyun 	if (dev->is_fm_band) {
376*4882a593Smuzhiyun 		v->rxsubchans = cadet_getstereo(dev);
377*4882a593Smuzhiyun 		outb(3, dev->io);
378*4882a593Smuzhiyun 		outb(inb(dev->io + 1) & 0x7f, dev->io + 1);
379*4882a593Smuzhiyun 		mdelay(100);
380*4882a593Smuzhiyun 		outb(3, dev->io);
381*4882a593Smuzhiyun 		if (inb(dev->io + 1) & 0x80)
382*4882a593Smuzhiyun 			v->rxsubchans |= V4L2_TUNER_SUB_RDS;
383*4882a593Smuzhiyun 	} else {
384*4882a593Smuzhiyun 		v->rangelow = 8320;      /* 520 kHz */
385*4882a593Smuzhiyun 		v->rangehigh = 26400;    /* 1650 kHz */
386*4882a593Smuzhiyun 		v->rxsubchans = V4L2_TUNER_SUB_MONO;
387*4882a593Smuzhiyun 	}
388*4882a593Smuzhiyun 	v->audmode = V4L2_TUNER_MODE_STEREO;
389*4882a593Smuzhiyun 	v->signal = dev->sigstrength; /* We might need to modify scaling of this */
390*4882a593Smuzhiyun 	return 0;
391*4882a593Smuzhiyun }
392*4882a593Smuzhiyun 
vidioc_s_tuner(struct file * file,void * priv,const struct v4l2_tuner * v)393*4882a593Smuzhiyun static int vidioc_s_tuner(struct file *file, void *priv,
394*4882a593Smuzhiyun 				const struct v4l2_tuner *v)
395*4882a593Smuzhiyun {
396*4882a593Smuzhiyun 	return v->index ? -EINVAL : 0;
397*4882a593Smuzhiyun }
398*4882a593Smuzhiyun 
vidioc_enum_freq_bands(struct file * file,void * priv,struct v4l2_frequency_band * band)399*4882a593Smuzhiyun static int vidioc_enum_freq_bands(struct file *file, void *priv,
400*4882a593Smuzhiyun 				struct v4l2_frequency_band *band)
401*4882a593Smuzhiyun {
402*4882a593Smuzhiyun 	if (band->tuner)
403*4882a593Smuzhiyun 		return -EINVAL;
404*4882a593Smuzhiyun 	if (band->index >= ARRAY_SIZE(bands))
405*4882a593Smuzhiyun 		return -EINVAL;
406*4882a593Smuzhiyun 	*band = bands[band->index];
407*4882a593Smuzhiyun 	return 0;
408*4882a593Smuzhiyun }
409*4882a593Smuzhiyun 
vidioc_g_frequency(struct file * file,void * priv,struct v4l2_frequency * f)410*4882a593Smuzhiyun static int vidioc_g_frequency(struct file *file, void *priv,
411*4882a593Smuzhiyun 				struct v4l2_frequency *f)
412*4882a593Smuzhiyun {
413*4882a593Smuzhiyun 	struct cadet *dev = video_drvdata(file);
414*4882a593Smuzhiyun 
415*4882a593Smuzhiyun 	if (f->tuner)
416*4882a593Smuzhiyun 		return -EINVAL;
417*4882a593Smuzhiyun 	f->type = V4L2_TUNER_RADIO;
418*4882a593Smuzhiyun 	f->frequency = dev->curfreq;
419*4882a593Smuzhiyun 	return 0;
420*4882a593Smuzhiyun }
421*4882a593Smuzhiyun 
422*4882a593Smuzhiyun 
vidioc_s_frequency(struct file * file,void * priv,const struct v4l2_frequency * f)423*4882a593Smuzhiyun static int vidioc_s_frequency(struct file *file, void *priv,
424*4882a593Smuzhiyun 				const struct v4l2_frequency *f)
425*4882a593Smuzhiyun {
426*4882a593Smuzhiyun 	struct cadet *dev = video_drvdata(file);
427*4882a593Smuzhiyun 
428*4882a593Smuzhiyun 	if (f->tuner)
429*4882a593Smuzhiyun 		return -EINVAL;
430*4882a593Smuzhiyun 	dev->is_fm_band =
431*4882a593Smuzhiyun 		f->frequency >= (bands[0].rangehigh + bands[1].rangelow) / 2;
432*4882a593Smuzhiyun 	cadet_setfreq(dev, f->frequency);
433*4882a593Smuzhiyun 	return 0;
434*4882a593Smuzhiyun }
435*4882a593Smuzhiyun 
cadet_s_ctrl(struct v4l2_ctrl * ctrl)436*4882a593Smuzhiyun static int cadet_s_ctrl(struct v4l2_ctrl *ctrl)
437*4882a593Smuzhiyun {
438*4882a593Smuzhiyun 	struct cadet *dev = container_of(ctrl->handler, struct cadet, ctrl_handler);
439*4882a593Smuzhiyun 
440*4882a593Smuzhiyun 	switch (ctrl->id) {
441*4882a593Smuzhiyun 	case V4L2_CID_AUDIO_MUTE:
442*4882a593Smuzhiyun 		outb(7, dev->io);                /* Select tuner control */
443*4882a593Smuzhiyun 		if (ctrl->val)
444*4882a593Smuzhiyun 			outb(0x00, dev->io + 1);
445*4882a593Smuzhiyun 		else
446*4882a593Smuzhiyun 			outb(0x20, dev->io + 1);
447*4882a593Smuzhiyun 		return 0;
448*4882a593Smuzhiyun 	}
449*4882a593Smuzhiyun 	return -EINVAL;
450*4882a593Smuzhiyun }
451*4882a593Smuzhiyun 
cadet_open(struct file * file)452*4882a593Smuzhiyun static int cadet_open(struct file *file)
453*4882a593Smuzhiyun {
454*4882a593Smuzhiyun 	struct cadet *dev = video_drvdata(file);
455*4882a593Smuzhiyun 	int err;
456*4882a593Smuzhiyun 
457*4882a593Smuzhiyun 	mutex_lock(&dev->lock);
458*4882a593Smuzhiyun 	err = v4l2_fh_open(file);
459*4882a593Smuzhiyun 	if (err)
460*4882a593Smuzhiyun 		goto fail;
461*4882a593Smuzhiyun 	if (v4l2_fh_is_singular_file(file))
462*4882a593Smuzhiyun 		init_waitqueue_head(&dev->read_queue);
463*4882a593Smuzhiyun fail:
464*4882a593Smuzhiyun 	mutex_unlock(&dev->lock);
465*4882a593Smuzhiyun 	return err;
466*4882a593Smuzhiyun }
467*4882a593Smuzhiyun 
cadet_release(struct file * file)468*4882a593Smuzhiyun static int cadet_release(struct file *file)
469*4882a593Smuzhiyun {
470*4882a593Smuzhiyun 	struct cadet *dev = video_drvdata(file);
471*4882a593Smuzhiyun 
472*4882a593Smuzhiyun 	mutex_lock(&dev->lock);
473*4882a593Smuzhiyun 	if (v4l2_fh_is_singular_file(file) && dev->rdsstat) {
474*4882a593Smuzhiyun 		del_timer_sync(&dev->readtimer);
475*4882a593Smuzhiyun 		dev->rdsstat = 0;
476*4882a593Smuzhiyun 	}
477*4882a593Smuzhiyun 	v4l2_fh_release(file);
478*4882a593Smuzhiyun 	mutex_unlock(&dev->lock);
479*4882a593Smuzhiyun 	return 0;
480*4882a593Smuzhiyun }
481*4882a593Smuzhiyun 
cadet_poll(struct file * file,struct poll_table_struct * wait)482*4882a593Smuzhiyun static __poll_t cadet_poll(struct file *file, struct poll_table_struct *wait)
483*4882a593Smuzhiyun {
484*4882a593Smuzhiyun 	struct cadet *dev = video_drvdata(file);
485*4882a593Smuzhiyun 	__poll_t req_events = poll_requested_events(wait);
486*4882a593Smuzhiyun 	__poll_t res = v4l2_ctrl_poll(file, wait);
487*4882a593Smuzhiyun 
488*4882a593Smuzhiyun 	poll_wait(file, &dev->read_queue, wait);
489*4882a593Smuzhiyun 	if (dev->rdsstat == 0 && (req_events & (EPOLLIN | EPOLLRDNORM))) {
490*4882a593Smuzhiyun 		mutex_lock(&dev->lock);
491*4882a593Smuzhiyun 		if (dev->rdsstat == 0)
492*4882a593Smuzhiyun 			cadet_start_rds(dev);
493*4882a593Smuzhiyun 		mutex_unlock(&dev->lock);
494*4882a593Smuzhiyun 	}
495*4882a593Smuzhiyun 	if (cadet_has_rds_data(dev))
496*4882a593Smuzhiyun 		res |= EPOLLIN | EPOLLRDNORM;
497*4882a593Smuzhiyun 	return res;
498*4882a593Smuzhiyun }
499*4882a593Smuzhiyun 
500*4882a593Smuzhiyun 
501*4882a593Smuzhiyun static const struct v4l2_file_operations cadet_fops = {
502*4882a593Smuzhiyun 	.owner		= THIS_MODULE,
503*4882a593Smuzhiyun 	.open		= cadet_open,
504*4882a593Smuzhiyun 	.release	= cadet_release,
505*4882a593Smuzhiyun 	.read		= cadet_read,
506*4882a593Smuzhiyun 	.unlocked_ioctl	= video_ioctl2,
507*4882a593Smuzhiyun 	.poll		= cadet_poll,
508*4882a593Smuzhiyun };
509*4882a593Smuzhiyun 
510*4882a593Smuzhiyun static const struct v4l2_ioctl_ops cadet_ioctl_ops = {
511*4882a593Smuzhiyun 	.vidioc_querycap    = vidioc_querycap,
512*4882a593Smuzhiyun 	.vidioc_g_tuner     = vidioc_g_tuner,
513*4882a593Smuzhiyun 	.vidioc_s_tuner     = vidioc_s_tuner,
514*4882a593Smuzhiyun 	.vidioc_g_frequency = vidioc_g_frequency,
515*4882a593Smuzhiyun 	.vidioc_s_frequency = vidioc_s_frequency,
516*4882a593Smuzhiyun 	.vidioc_enum_freq_bands = vidioc_enum_freq_bands,
517*4882a593Smuzhiyun 	.vidioc_log_status  = v4l2_ctrl_log_status,
518*4882a593Smuzhiyun 	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
519*4882a593Smuzhiyun 	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
520*4882a593Smuzhiyun };
521*4882a593Smuzhiyun 
522*4882a593Smuzhiyun static const struct v4l2_ctrl_ops cadet_ctrl_ops = {
523*4882a593Smuzhiyun 	.s_ctrl = cadet_s_ctrl,
524*4882a593Smuzhiyun };
525*4882a593Smuzhiyun 
526*4882a593Smuzhiyun #ifdef CONFIG_PNP
527*4882a593Smuzhiyun 
528*4882a593Smuzhiyun static const struct pnp_device_id cadet_pnp_devices[] = {
529*4882a593Smuzhiyun 	/* ADS Cadet AM/FM Radio Card */
530*4882a593Smuzhiyun 	{.id = "MSM0c24", .driver_data = 0},
531*4882a593Smuzhiyun 	{.id = ""}
532*4882a593Smuzhiyun };
533*4882a593Smuzhiyun 
534*4882a593Smuzhiyun MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices);
535*4882a593Smuzhiyun 
cadet_pnp_probe(struct pnp_dev * dev,const struct pnp_device_id * dev_id)536*4882a593Smuzhiyun static int cadet_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id)
537*4882a593Smuzhiyun {
538*4882a593Smuzhiyun 	if (!dev)
539*4882a593Smuzhiyun 		return -ENODEV;
540*4882a593Smuzhiyun 	/* only support one device */
541*4882a593Smuzhiyun 	if (io > 0)
542*4882a593Smuzhiyun 		return -EBUSY;
543*4882a593Smuzhiyun 
544*4882a593Smuzhiyun 	if (!pnp_port_valid(dev, 0))
545*4882a593Smuzhiyun 		return -ENODEV;
546*4882a593Smuzhiyun 
547*4882a593Smuzhiyun 	io = pnp_port_start(dev, 0);
548*4882a593Smuzhiyun 
549*4882a593Smuzhiyun 	printk(KERN_INFO "radio-cadet: PnP reports device at %#x\n", io);
550*4882a593Smuzhiyun 
551*4882a593Smuzhiyun 	return io;
552*4882a593Smuzhiyun }
553*4882a593Smuzhiyun 
554*4882a593Smuzhiyun static struct pnp_driver cadet_pnp_driver = {
555*4882a593Smuzhiyun 	.name		= "radio-cadet",
556*4882a593Smuzhiyun 	.id_table	= cadet_pnp_devices,
557*4882a593Smuzhiyun 	.probe		= cadet_pnp_probe,
558*4882a593Smuzhiyun 	.remove		= NULL,
559*4882a593Smuzhiyun };
560*4882a593Smuzhiyun 
561*4882a593Smuzhiyun #else
562*4882a593Smuzhiyun static struct pnp_driver cadet_pnp_driver;
563*4882a593Smuzhiyun #endif
564*4882a593Smuzhiyun 
cadet_probe(struct cadet * dev)565*4882a593Smuzhiyun static void cadet_probe(struct cadet *dev)
566*4882a593Smuzhiyun {
567*4882a593Smuzhiyun 	static int iovals[8] = { 0x330, 0x332, 0x334, 0x336, 0x338, 0x33a, 0x33c, 0x33e };
568*4882a593Smuzhiyun 	int i;
569*4882a593Smuzhiyun 
570*4882a593Smuzhiyun 	for (i = 0; i < 8; i++) {
571*4882a593Smuzhiyun 		dev->io = iovals[i];
572*4882a593Smuzhiyun 		if (request_region(dev->io, 2, "cadet-probe")) {
573*4882a593Smuzhiyun 			cadet_setfreq(dev, bands[1].rangelow);
574*4882a593Smuzhiyun 			if (cadet_getfreq(dev) == bands[1].rangelow) {
575*4882a593Smuzhiyun 				release_region(dev->io, 2);
576*4882a593Smuzhiyun 				return;
577*4882a593Smuzhiyun 			}
578*4882a593Smuzhiyun 			release_region(dev->io, 2);
579*4882a593Smuzhiyun 		}
580*4882a593Smuzhiyun 	}
581*4882a593Smuzhiyun 	dev->io = -1;
582*4882a593Smuzhiyun }
583*4882a593Smuzhiyun 
584*4882a593Smuzhiyun /*
585*4882a593Smuzhiyun  * io should only be set if the user has used something like
586*4882a593Smuzhiyun  * isapnp (the userspace program) to initialize this card for us
587*4882a593Smuzhiyun  */
588*4882a593Smuzhiyun 
cadet_init(void)589*4882a593Smuzhiyun static int __init cadet_init(void)
590*4882a593Smuzhiyun {
591*4882a593Smuzhiyun 	struct cadet *dev = &cadet_card;
592*4882a593Smuzhiyun 	struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
593*4882a593Smuzhiyun 	struct v4l2_ctrl_handler *hdl;
594*4882a593Smuzhiyun 	int res = -ENODEV;
595*4882a593Smuzhiyun 
596*4882a593Smuzhiyun 	strscpy(v4l2_dev->name, "cadet", sizeof(v4l2_dev->name));
597*4882a593Smuzhiyun 	mutex_init(&dev->lock);
598*4882a593Smuzhiyun 
599*4882a593Smuzhiyun 	/* If a probe was requested then probe ISAPnP first (safest) */
600*4882a593Smuzhiyun 	if (io < 0)
601*4882a593Smuzhiyun 		pnp_register_driver(&cadet_pnp_driver);
602*4882a593Smuzhiyun 	dev->io = io;
603*4882a593Smuzhiyun 
604*4882a593Smuzhiyun 	/* If that fails then probe unsafely if probe is requested */
605*4882a593Smuzhiyun 	if (dev->io < 0)
606*4882a593Smuzhiyun 		cadet_probe(dev);
607*4882a593Smuzhiyun 
608*4882a593Smuzhiyun 	/* Else we bail out */
609*4882a593Smuzhiyun 	if (dev->io < 0) {
610*4882a593Smuzhiyun #ifdef MODULE
611*4882a593Smuzhiyun 		v4l2_err(v4l2_dev, "you must set an I/O address with io=0x330, 0x332, 0x334,\n");
612*4882a593Smuzhiyun 		v4l2_err(v4l2_dev, "0x336, 0x338, 0x33a, 0x33c or 0x33e\n");
613*4882a593Smuzhiyun #endif
614*4882a593Smuzhiyun 		goto fail;
615*4882a593Smuzhiyun 	}
616*4882a593Smuzhiyun 	if (!request_region(dev->io, 2, "cadet"))
617*4882a593Smuzhiyun 		goto fail;
618*4882a593Smuzhiyun 
619*4882a593Smuzhiyun 	res = v4l2_device_register(NULL, v4l2_dev);
620*4882a593Smuzhiyun 	if (res < 0) {
621*4882a593Smuzhiyun 		release_region(dev->io, 2);
622*4882a593Smuzhiyun 		v4l2_err(v4l2_dev, "could not register v4l2_device\n");
623*4882a593Smuzhiyun 		goto fail;
624*4882a593Smuzhiyun 	}
625*4882a593Smuzhiyun 
626*4882a593Smuzhiyun 	hdl = &dev->ctrl_handler;
627*4882a593Smuzhiyun 	v4l2_ctrl_handler_init(hdl, 2);
628*4882a593Smuzhiyun 	v4l2_ctrl_new_std(hdl, &cadet_ctrl_ops,
629*4882a593Smuzhiyun 			V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
630*4882a593Smuzhiyun 	v4l2_dev->ctrl_handler = hdl;
631*4882a593Smuzhiyun 	if (hdl->error) {
632*4882a593Smuzhiyun 		res = hdl->error;
633*4882a593Smuzhiyun 		v4l2_err(v4l2_dev, "Could not register controls\n");
634*4882a593Smuzhiyun 		goto err_hdl;
635*4882a593Smuzhiyun 	}
636*4882a593Smuzhiyun 
637*4882a593Smuzhiyun 	dev->is_fm_band = true;
638*4882a593Smuzhiyun 	dev->curfreq = bands[dev->is_fm_band].rangelow;
639*4882a593Smuzhiyun 	cadet_setfreq(dev, dev->curfreq);
640*4882a593Smuzhiyun 	strscpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
641*4882a593Smuzhiyun 	dev->vdev.v4l2_dev = v4l2_dev;
642*4882a593Smuzhiyun 	dev->vdev.fops = &cadet_fops;
643*4882a593Smuzhiyun 	dev->vdev.ioctl_ops = &cadet_ioctl_ops;
644*4882a593Smuzhiyun 	dev->vdev.release = video_device_release_empty;
645*4882a593Smuzhiyun 	dev->vdev.lock = &dev->lock;
646*4882a593Smuzhiyun 	dev->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
647*4882a593Smuzhiyun 				V4L2_CAP_READWRITE | V4L2_CAP_RDS_CAPTURE;
648*4882a593Smuzhiyun 	video_set_drvdata(&dev->vdev, dev);
649*4882a593Smuzhiyun 
650*4882a593Smuzhiyun 	res = video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr);
651*4882a593Smuzhiyun 	if (res < 0)
652*4882a593Smuzhiyun 		goto err_hdl;
653*4882a593Smuzhiyun 	v4l2_info(v4l2_dev, "ADS Cadet Radio Card at 0x%x\n", dev->io);
654*4882a593Smuzhiyun 	return 0;
655*4882a593Smuzhiyun err_hdl:
656*4882a593Smuzhiyun 	v4l2_ctrl_handler_free(hdl);
657*4882a593Smuzhiyun 	v4l2_device_unregister(v4l2_dev);
658*4882a593Smuzhiyun 	release_region(dev->io, 2);
659*4882a593Smuzhiyun fail:
660*4882a593Smuzhiyun 	pnp_unregister_driver(&cadet_pnp_driver);
661*4882a593Smuzhiyun 	return res;
662*4882a593Smuzhiyun }
663*4882a593Smuzhiyun 
cadet_exit(void)664*4882a593Smuzhiyun static void __exit cadet_exit(void)
665*4882a593Smuzhiyun {
666*4882a593Smuzhiyun 	struct cadet *dev = &cadet_card;
667*4882a593Smuzhiyun 
668*4882a593Smuzhiyun 	video_unregister_device(&dev->vdev);
669*4882a593Smuzhiyun 	v4l2_ctrl_handler_free(&dev->ctrl_handler);
670*4882a593Smuzhiyun 	v4l2_device_unregister(&dev->v4l2_dev);
671*4882a593Smuzhiyun 	outb(7, dev->io);	/* Mute */
672*4882a593Smuzhiyun 	outb(0x00, dev->io + 1);
673*4882a593Smuzhiyun 	release_region(dev->io, 2);
674*4882a593Smuzhiyun 	pnp_unregister_driver(&cadet_pnp_driver);
675*4882a593Smuzhiyun }
676*4882a593Smuzhiyun 
677*4882a593Smuzhiyun module_init(cadet_init);
678*4882a593Smuzhiyun module_exit(cadet_exit);
679*4882a593Smuzhiyun 
680