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