blob: 8641aec7baf87093e8faf1ea90331d913abe0ca9 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card
2 *
3 * by Fred Gleason <fredg@wava.com>
4 * Version 0.3.3
5 *
6 * (Loosely) based on code for the Aztech radio card by
7 *
8 * Russell Kroll (rkroll@exploits.org)
9 * Quay Ly
10 * Donald Song
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030011 * Jason Lewis (jlewis@twilight.vtc.vsc.edu)
Linus Torvalds1da177e2005-04-16 15:20:36 -070012 * Scott McGrath (smcgrath@twilight.vtc.vsc.edu)
13 * William McGrath (wmcgrath@twilight.vtc.vsc.edu)
14 *
15 * History:
16 * 2000-04-29 Russell Kroll <rkroll@exploits.org>
17 * Added ISAPnP detection for Linux 2.3/2.4
18 *
19 * 2001-01-10 Russell Kroll <rkroll@exploits.org>
20 * Removed dead CONFIG_RADIO_CADET_PORT code
21 * PnP detection on load is now default (no args necessary)
22 *
23 * 2002-01-17 Adam Belay <ambx1@neo.rr.com>
24 * Updated to latest pnp code
25 *
26 * 2003-01-31 Alan Cox <alan@redhat.com>
27 * Cleaned up locking, delay code, general odds and ends
28 */
29
30#include <linux/module.h> /* Modules */
31#include <linux/init.h> /* Initdata */
Peter Osterlundfb911ee2005-09-13 01:25:15 -070032#include <linux/ioport.h> /* request_region */
Linus Torvalds1da177e2005-04-16 15:20:36 -070033#include <linux/delay.h> /* udelay */
34#include <asm/io.h> /* outb, outb_p */
35#include <asm/uaccess.h> /* copy to/from user */
36#include <linux/videodev.h> /* kernel radio structs */
Mauro Carvalho Chehab5e87efa2006-06-05 10:26:32 -030037#include <media/v4l2-common.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070038#include <linux/param.h>
39#include <linux/pnp.h>
40
41#define RDS_BUFFER 256
42
43static int io=-1; /* default to isapnp activation */
44static int radio_nr = -1;
45static int users=0;
46static int curtuner=0;
47static int tunestat=0;
48static int sigstrength=0;
49static wait_queue_head_t read_queue;
50static struct timer_list readtimer;
51static __u8 rdsin=0,rdsout=0,rdsstat=0;
52static unsigned char rdsbuf[RDS_BUFFER];
53static spinlock_t cadet_io_lock;
54
55static int cadet_probe(void);
56
57/*
58 * Signal Strength Threshold Values
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030059 * The V4L API spec does not define any particular unit for the signal
Linus Torvalds1da177e2005-04-16 15:20:36 -070060 * strength value. These values are in microvolts of RF at the tuner's input.
61 */
62static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}};
63
64static int cadet_getrds(void)
65{
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030066 int rdsstat=0;
Linus Torvalds1da177e2005-04-16 15:20:36 -070067
68 spin_lock(&cadet_io_lock);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030069 outb(3,io); /* Select Decoder Control/Status */
Linus Torvalds1da177e2005-04-16 15:20:36 -070070 outb(inb(io+1)&0x7f,io+1); /* Reset RDS detection */
71 spin_unlock(&cadet_io_lock);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030072
Linus Torvalds1da177e2005-04-16 15:20:36 -070073 msleep(100);
74
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030075 spin_lock(&cadet_io_lock);
76 outb(3,io); /* Select Decoder Control/Status */
Linus Torvalds1da177e2005-04-16 15:20:36 -070077 if((inb(io+1)&0x80)!=0) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030078 rdsstat|=VIDEO_TUNER_RDS_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -070079 }
80 if((inb(io+1)&0x10)!=0) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030081 rdsstat|=VIDEO_TUNER_MBS_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -070082 }
83 spin_unlock(&cadet_io_lock);
84 return rdsstat;
85}
86
87static int cadet_getstereo(void)
88{
89 int ret = 0;
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030090 if(curtuner != 0) /* Only FM has stereo capability! */
91 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -070092
93 spin_lock(&cadet_io_lock);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030094 outb(7,io); /* Select tuner control */
Linus Torvalds1da177e2005-04-16 15:20:36 -070095 if( (inb(io+1) & 0x40) == 0)
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030096 ret = 1;
97 spin_unlock(&cadet_io_lock);
98 return ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -070099}
100
101static unsigned cadet_gettune(void)
102{
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300103 int curvol,i;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700104 unsigned fifo=0;
105
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300106 /*
107 * Prepare for read
108 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700109
110 spin_lock(&cadet_io_lock);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300111
112 outb(7,io); /* Select tuner control */
113 curvol=inb(io+1); /* Save current volume/mute setting */
114 outb(0x00,io+1); /* Ensure WRITE-ENABLE is LOW */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700115 tunestat=0xffff;
116
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300117 /*
118 * Read the shift register
119 */
120 for(i=0;i<25;i++) {
121 fifo=(fifo<<1)|((inb(io+1)>>7)&0x01);
122 if(i<24) {
123 outb(0x01,io+1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700124 tunestat&=inb(io+1);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300125 outb(0x00,io+1);
126 }
127 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700128
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300129 /*
130 * Restore volume/mute setting
131 */
132 outb(curvol,io+1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700133 spin_unlock(&cadet_io_lock);
134
135 return fifo;
136}
137
138static unsigned cadet_getfreq(void)
139{
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300140 int i;
141 unsigned freq=0,test,fifo=0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700142
143 /*
144 * Read current tuning
145 */
146 fifo=cadet_gettune();
147
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300148 /*
149 * Convert to actual frequency
150 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700151 if(curtuner==0) { /* FM */
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300152 test=12500;
153 for(i=0;i<14;i++) {
154 if((fifo&0x01)!=0) {
155 freq+=test;
156 }
157 test=test<<1;
158 fifo=fifo>>1;
159 }
160 freq-=10700000; /* IF frequency is 10.7 MHz */
161 freq=(freq*16)/1000000; /* Make it 1/16 MHz */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700162 }
163 if(curtuner==1) { /* AM */
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300164 freq=((fifo&0x7fff)-2010)*16;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700165 }
166
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300167 return freq;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700168}
169
170static void cadet_settune(unsigned fifo)
171{
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300172 int i;
173 unsigned test;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700174
175 spin_lock(&cadet_io_lock);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300176
Linus Torvalds1da177e2005-04-16 15:20:36 -0700177 outb(7,io); /* Select tuner control */
178 /*
179 * Write the shift register
180 */
181 test=0;
182 test=(fifo>>23)&0x02; /* Align data for SDO */
183 test|=0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */
184 outb(7,io); /* Select tuner control */
185 outb(test,io+1); /* Initialize for write */
186 for(i=0;i<25;i++) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300187 test|=0x01; /* Toggle SCK High */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700188 outb(test,io+1);
189 test&=0xfe; /* Toggle SCK Low */
190 outb(test,io+1);
191 fifo=fifo<<1; /* Prepare the next bit */
192 test=0x1c|((fifo>>23)&0x02);
193 outb(test,io+1);
194 }
195 spin_unlock(&cadet_io_lock);
196}
197
198static void cadet_setfreq(unsigned freq)
199{
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300200 unsigned fifo;
201 int i,j,test;
202 int curvol;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700203
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300204 /*
205 * Formulate a fifo command
206 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700207 fifo=0;
208 if(curtuner==0) { /* FM */
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300209 test=102400;
210 freq=(freq*1000)/16; /* Make it kHz */
211 freq+=10700; /* IF is 10700 kHz */
212 for(i=0;i<14;i++) {
213 fifo=fifo<<1;
214 if(freq>=test) {
215 fifo|=0x01;
216 freq-=test;
217 }
218 test=test>>1;
219 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700220 }
221 if(curtuner==1) { /* AM */
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300222 fifo=(freq/16)+2010; /* Make it kHz */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700223 fifo|=0x100000; /* Select AM Band */
224 }
225
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300226 /*
227 * Save current volume/mute setting
228 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700229
230 spin_lock(&cadet_io_lock);
231 outb(7,io); /* Select tuner control */
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300232 curvol=inb(io+1);
233 spin_unlock(&cadet_io_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700234
235 /*
236 * Tune the card
237 */
238 for(j=3;j>-1;j--) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300239 cadet_settune(fifo|(j<<16));
240
241 spin_lock(&cadet_io_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700242 outb(7,io); /* Select tuner control */
243 outb(curvol,io+1);
244 spin_unlock(&cadet_io_lock);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300245
Linus Torvalds1da177e2005-04-16 15:20:36 -0700246 msleep(100);
247
248 cadet_gettune();
249 if((tunestat & 0x40) == 0) { /* Tuned */
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300250 sigstrength=sigtable[curtuner][j];
Linus Torvalds1da177e2005-04-16 15:20:36 -0700251 return;
252 }
253 }
254 sigstrength=0;
255}
256
257
258static int cadet_getvol(void)
259{
260 int ret = 0;
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300261
Linus Torvalds1da177e2005-04-16 15:20:36 -0700262 spin_lock(&cadet_io_lock);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300263
264 outb(7,io); /* Select tuner control */
265 if((inb(io + 1) & 0x20) != 0)
266 ret = 0xffff;
267
268 spin_unlock(&cadet_io_lock);
269 return ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700270}
271
272
273static void cadet_setvol(int vol)
274{
275 spin_lock(&cadet_io_lock);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300276 outb(7,io); /* Select tuner control */
277 if(vol>0)
278 outb(0x20,io+1);
279 else
280 outb(0x00,io+1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700281 spin_unlock(&cadet_io_lock);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300282}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700283
284static void cadet_handler(unsigned long data)
285{
286 /*
287 * Service the RDS fifo
288 */
289
290 if(spin_trylock(&cadet_io_lock))
291 {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300292 outb(0x3,io); /* Select RDS Decoder Control */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700293 if((inb(io+1)&0x20)!=0) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300294 printk(KERN_CRIT "cadet: RDS fifo overflow\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700295 }
296 outb(0x80,io); /* Select RDS fifo */
297 while((inb(io)&0x80)!=0) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300298 rdsbuf[rdsin]=inb(io+1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700299 if(rdsin==rdsout)
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300300 printk(KERN_WARNING "cadet: RDS buffer overflow\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700301 else
302 rdsin++;
303 }
304 spin_unlock(&cadet_io_lock);
305 }
306
307 /*
308 * Service pending read
309 */
310 if( rdsin!=rdsout)
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300311 wake_up_interruptible(&read_queue);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700312
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300313 /*
Linus Torvalds1da177e2005-04-16 15:20:36 -0700314 * Clean up and exit
315 */
316 init_timer(&readtimer);
317 readtimer.function=cadet_handler;
318 readtimer.data=(unsigned long)0;
319 readtimer.expires=jiffies+(HZ/20);
320 add_timer(&readtimer);
321}
322
323
324
325static ssize_t cadet_read(struct file *file, char __user *data,
326 size_t count, loff_t *ppos)
327{
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300328 int i=0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700329 unsigned char readbuf[RDS_BUFFER];
330
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300331 if(rdsstat==0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700332 spin_lock(&cadet_io_lock);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300333 rdsstat=1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700334 outb(0x80,io); /* Select RDS fifo */
335 spin_unlock(&cadet_io_lock);
336 init_timer(&readtimer);
337 readtimer.function=cadet_handler;
338 readtimer.data=(unsigned long)0;
339 readtimer.expires=jiffies+(HZ/20);
340 add_timer(&readtimer);
341 }
342 if(rdsin==rdsout) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300343 if (file->f_flags & O_NONBLOCK)
344 return -EWOULDBLOCK;
345 interruptible_sleep_on(&read_queue);
346 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700347 while( i<count && rdsin!=rdsout)
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300348 readbuf[i++]=rdsbuf[rdsout++];
Linus Torvalds1da177e2005-04-16 15:20:36 -0700349
350 if (copy_to_user(data,readbuf,i))
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300351 return -EFAULT;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700352 return i;
353}
354
355
356
357static int cadet_do_ioctl(struct inode *inode, struct file *file,
358 unsigned int cmd, void *arg)
359{
360 switch(cmd)
361 {
362 case VIDIOCGCAP:
363 {
364 struct video_capability *v = arg;
365 memset(v,0,sizeof(*v));
366 v->type=VID_TYPE_TUNER;
367 v->channels=2;
368 v->audios=1;
369 strcpy(v->name, "ADS Cadet");
370 return 0;
371 }
372 case VIDIOCGTUNER:
373 {
374 struct video_tuner *v = arg;
375 if((v->tuner<0)||(v->tuner>1)) {
376 return -EINVAL;
377 }
378 switch(v->tuner) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300379 case 0:
380 strcpy(v->name,"FM");
381 v->rangelow=1400; /* 87.5 MHz */
382 v->rangehigh=1728; /* 108.0 MHz */
383 v->flags=0;
384 v->mode=0;
385 v->mode|=VIDEO_MODE_AUTO;
386 v->signal=sigstrength;
387 if(cadet_getstereo()==1) {
388 v->flags|=VIDEO_TUNER_STEREO_ON;
389 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700390 v->flags|=cadet_getrds();
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300391 break;
392 case 1:
393 strcpy(v->name,"AM");
394 v->rangelow=8320; /* 520 kHz */
395 v->rangehigh=26400; /* 1650 kHz */
396 v->flags=0;
397 v->flags|=VIDEO_TUNER_LOW;
398 v->mode=0;
399 v->mode|=VIDEO_MODE_AUTO;
400 v->signal=sigstrength;
401 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700402 }
403 return 0;
404 }
405 case VIDIOCSTUNER:
406 {
407 struct video_tuner *v = arg;
408 if((v->tuner<0)||(v->tuner>1)) {
409 return -EINVAL;
410 }
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300411 curtuner=v->tuner;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700412 return 0;
413 }
414 case VIDIOCGFREQ:
415 {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300416 unsigned long *freq = arg;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700417 *freq = cadet_getfreq();
418 return 0;
419 }
420 case VIDIOCSFREQ:
421 {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300422 unsigned long *freq = arg;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700423 if((curtuner==0)&&((*freq<1400)||(*freq>1728))) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300424 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700425 }
426 if((curtuner==1)&&((*freq<8320)||(*freq>26400))) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300427 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700428 }
429 cadet_setfreq(*freq);
430 return 0;
431 }
432 case VIDIOCGAUDIO:
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300433 {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700434 struct video_audio *v = arg;
435 memset(v,0, sizeof(*v));
436 v->flags=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
437 if(cadet_getstereo()==0) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300438 v->mode=VIDEO_SOUND_MONO;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700439 } else {
440 v->mode=VIDEO_SOUND_STEREO;
441 }
442 v->volume=cadet_getvol();
443 v->step=0xffff;
444 strcpy(v->name, "Radio");
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300445 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700446 }
447 case VIDIOCSAUDIO:
448 {
449 struct video_audio *v = arg;
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300450 if(v->audio)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700451 return -EINVAL;
452 cadet_setvol(v->volume);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300453 if(v->flags&VIDEO_AUDIO_MUTE)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700454 cadet_setvol(0);
455 else
456 cadet_setvol(0xffff);
457 return 0;
458 }
459 default:
460 return -ENOIOCTLCMD;
461 }
462}
463
464static int cadet_ioctl(struct inode *inode, struct file *file,
465 unsigned int cmd, unsigned long arg)
466{
467 return video_usercopy(inode, file, cmd, arg, cadet_do_ioctl);
468}
469
470static int cadet_open(struct inode *inode, struct file *file)
471{
472 if(users)
473 return -EBUSY;
474 users++;
475 init_waitqueue_head(&read_queue);
476 return 0;
477}
478
479static int cadet_release(struct inode *inode, struct file *file)
480{
481 del_timer_sync(&readtimer);
482 rdsstat=0;
483 users--;
484 return 0;
485}
486
487
488static struct file_operations cadet_fops = {
489 .owner = THIS_MODULE,
490 .open = cadet_open,
491 .release = cadet_release,
492 .read = cadet_read,
493 .ioctl = cadet_ioctl,
Arnd Bergmann0d0fbf82006-01-09 15:24:57 -0200494 .compat_ioctl = v4l_compat_ioctl32,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700495 .llseek = no_llseek,
496};
497
498static struct video_device cadet_radio=
499{
500 .owner = THIS_MODULE,
501 .name = "Cadet radio",
502 .type = VID_TYPE_TUNER,
503 .hardware = VID_HARDWARE_CADET,
504 .fops = &cadet_fops,
505};
506
507static struct pnp_device_id cadet_pnp_devices[] = {
508 /* ADS Cadet AM/FM Radio Card */
509 {.id = "MSM0c24", .driver_data = 0},
510 {.id = ""}
511};
512
513MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices);
514
515static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id)
516{
517 if (!dev)
518 return -ENODEV;
519 /* only support one device */
520 if (io > 0)
521 return -EBUSY;
522
523 if (!pnp_port_valid(dev, 0)) {
524 return -ENODEV;
525 }
526
527 io = pnp_port_start(dev, 0);
528
529 printk ("radio-cadet: PnP reports device at %#x\n", io);
530
531 return io;
532}
533
534static struct pnp_driver cadet_pnp_driver = {
535 .name = "radio-cadet",
536 .id_table = cadet_pnp_devices,
537 .probe = cadet_pnp_probe,
538 .remove = NULL,
539};
540
541static int cadet_probe(void)
542{
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300543 static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
Linus Torvalds1da177e2005-04-16 15:20:36 -0700544 int i;
545
546 for(i=0;i<8;i++) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300547 io=iovals[i];
Alexey Dobriyanf1ac0462005-10-14 15:59:04 -0700548 if (request_region(io, 2, "cadet-probe")) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300549 cadet_setfreq(1410);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700550 if(cadet_getfreq()==1410) {
551 release_region(io, 2);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300552 return io;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700553 }
554 release_region(io, 2);
555 }
556 }
557 return -1;
558}
559
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300560/*
Linus Torvalds1da177e2005-04-16 15:20:36 -0700561 * io should only be set if the user has used something like
562 * isapnp (the userspace program) to initialize this card for us
563 */
564
565static int __init cadet_init(void)
566{
567 spin_lock_init(&cadet_io_lock);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300568
Linus Torvalds1da177e2005-04-16 15:20:36 -0700569 /*
570 * If a probe was requested then probe ISAPnP first (safest)
571 */
572 if (io < 0)
573 pnp_register_driver(&cadet_pnp_driver);
574 /*
575 * If that fails then probe unsafely if probe is requested
576 */
577 if(io < 0)
578 io = cadet_probe ();
579
580 /*
581 * Else we bail out
582 */
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300583
584 if(io < 0) {
585#ifdef MODULE
Linus Torvalds1da177e2005-04-16 15:20:36 -0700586 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
587#endif
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300588 goto fail;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700589 }
590 if (!request_region(io,2,"cadet"))
591 goto fail;
592 if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) {
593 release_region(io,2);
594 goto fail;
595 }
596 printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io);
597 return 0;
598fail:
599 pnp_unregister_driver(&cadet_pnp_driver);
600 return -1;
601}
602
603
604
605MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
606MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");
607MODULE_LICENSE("GPL");
608
609module_param(io, int, 0);
610MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
611module_param(radio_nr, int, 0);
612
613static void __exit cadet_cleanup_module(void)
614{
615 video_unregister_device(&cadet_radio);
616 release_region(io,2);
617 pnp_unregister_driver(&cadet_pnp_driver);
618}
619
620module_init(cadet_init);
621module_exit(cadet_cleanup_module);
622