V4L/DVB (4405): Add missing KConfig I2C dependencies
[deliverable/linux.git] / drivers / media / radio / radio-cadet.c
CommitLineData
1da177e4
LT
1/* 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
4286c6f6 11 * Jason Lewis (jlewis@twilight.vtc.vsc.edu)
1da177e4
LT
12 * 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 */
fb911ee8 32#include <linux/ioport.h> /* request_region */
1da177e4
LT
33#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 */
5e87efa3 37#include <media/v4l2-common.h>
1da177e4
LT
38#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
4286c6f6 59 * The V4L API spec does not define any particular unit for the signal
1da177e4
LT
60 * 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{
4286c6f6 66 int rdsstat=0;
1da177e4
LT
67
68 spin_lock(&cadet_io_lock);
4286c6f6 69 outb(3,io); /* Select Decoder Control/Status */
1da177e4
LT
70 outb(inb(io+1)&0x7f,io+1); /* Reset RDS detection */
71 spin_unlock(&cadet_io_lock);
4286c6f6 72
1da177e4
LT
73 msleep(100);
74
4286c6f6
MCC
75 spin_lock(&cadet_io_lock);
76 outb(3,io); /* Select Decoder Control/Status */
1da177e4 77 if((inb(io+1)&0x80)!=0) {
4286c6f6 78 rdsstat|=VIDEO_TUNER_RDS_ON;
1da177e4
LT
79 }
80 if((inb(io+1)&0x10)!=0) {
4286c6f6 81 rdsstat|=VIDEO_TUNER_MBS_ON;
1da177e4
LT
82 }
83 spin_unlock(&cadet_io_lock);
84 return rdsstat;
85}
86
87static int cadet_getstereo(void)
88{
89 int ret = 0;
4286c6f6
MCC
90 if(curtuner != 0) /* Only FM has stereo capability! */
91 return 0;
1da177e4
LT
92
93 spin_lock(&cadet_io_lock);
4286c6f6 94 outb(7,io); /* Select tuner control */
1da177e4 95 if( (inb(io+1) & 0x40) == 0)
4286c6f6
MCC
96 ret = 1;
97 spin_unlock(&cadet_io_lock);
98 return ret;
1da177e4
LT
99}
100
101static unsigned cadet_gettune(void)
102{
4286c6f6 103 int curvol,i;
1da177e4
LT
104 unsigned fifo=0;
105
4286c6f6
MCC
106 /*
107 * Prepare for read
108 */
1da177e4
LT
109
110 spin_lock(&cadet_io_lock);
4286c6f6
MCC
111
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 */
1da177e4
LT
115 tunestat=0xffff;
116
4286c6f6
MCC
117 /*
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);
1da177e4 124 tunestat&=inb(io+1);
4286c6f6
MCC
125 outb(0x00,io+1);
126 }
127 }
128
129 /*
130 * Restore volume/mute setting
131 */
132 outb(curvol,io+1);
1da177e4
LT
133 spin_unlock(&cadet_io_lock);
134
135 return fifo;
136}
137
138static unsigned cadet_getfreq(void)
139{
4286c6f6
MCC
140 int i;
141 unsigned freq=0,test,fifo=0;
1da177e4
LT
142
143 /*
144 * Read current tuning
145 */
146 fifo=cadet_gettune();
147
4286c6f6
MCC
148 /*
149 * Convert to actual frequency
150 */
1da177e4 151 if(curtuner==0) { /* FM */
4286c6f6
MCC
152 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 */
1da177e4
LT
162 }
163 if(curtuner==1) { /* AM */
4286c6f6 164 freq=((fifo&0x7fff)-2010)*16;
1da177e4
LT
165 }
166
4286c6f6 167 return freq;
1da177e4
LT
168}
169
170static void cadet_settune(unsigned fifo)
171{
4286c6f6
MCC
172 int i;
173 unsigned test;
1da177e4
LT
174
175 spin_lock(&cadet_io_lock);
4286c6f6 176
1da177e4
LT
177 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++) {
4286c6f6 187 test|=0x01; /* Toggle SCK High */
1da177e4
LT
188 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{
4286c6f6
MCC
200 unsigned fifo;
201 int i,j,test;
202 int curvol;
1da177e4 203
4286c6f6
MCC
204 /*
205 * Formulate a fifo command
206 */
1da177e4
LT
207 fifo=0;
208 if(curtuner==0) { /* FM */
4286c6f6
MCC
209 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 }
1da177e4
LT
220 }
221 if(curtuner==1) { /* AM */
4286c6f6 222 fifo=(freq/16)+2010; /* Make it kHz */
1da177e4
LT
223 fifo|=0x100000; /* Select AM Band */
224 }
225
4286c6f6
MCC
226 /*
227 * Save current volume/mute setting
228 */
1da177e4
LT
229
230 spin_lock(&cadet_io_lock);
231 outb(7,io); /* Select tuner control */
4286c6f6
MCC
232 curvol=inb(io+1);
233 spin_unlock(&cadet_io_lock);
1da177e4
LT
234
235 /*
236 * Tune the card
237 */
238 for(j=3;j>-1;j--) {
4286c6f6
MCC
239 cadet_settune(fifo|(j<<16));
240
241 spin_lock(&cadet_io_lock);
1da177e4
LT
242 outb(7,io); /* Select tuner control */
243 outb(curvol,io+1);
244 spin_unlock(&cadet_io_lock);
4286c6f6 245
1da177e4
LT
246 msleep(100);
247
248 cadet_gettune();
249 if((tunestat & 0x40) == 0) { /* Tuned */
4286c6f6 250 sigstrength=sigtable[curtuner][j];
1da177e4
LT
251 return;
252 }
253 }
254 sigstrength=0;
255}
256
257
258static int cadet_getvol(void)
259{
260 int ret = 0;
4286c6f6 261
1da177e4 262 spin_lock(&cadet_io_lock);
4286c6f6
MCC
263
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;
1da177e4
LT
270}
271
272
273static void cadet_setvol(int vol)
274{
275 spin_lock(&cadet_io_lock);
4286c6f6
MCC
276 outb(7,io); /* Select tuner control */
277 if(vol>0)
278 outb(0x20,io+1);
279 else
280 outb(0x00,io+1);
1da177e4 281 spin_unlock(&cadet_io_lock);
4286c6f6 282}
1da177e4
LT
283
284static void cadet_handler(unsigned long data)
285{
286 /*
287 * Service the RDS fifo
288 */
289
290 if(spin_trylock(&cadet_io_lock))
291 {
4286c6f6 292 outb(0x3,io); /* Select RDS Decoder Control */
1da177e4 293 if((inb(io+1)&0x20)!=0) {
4286c6f6 294 printk(KERN_CRIT "cadet: RDS fifo overflow\n");
1da177e4
LT
295 }
296 outb(0x80,io); /* Select RDS fifo */
297 while((inb(io)&0x80)!=0) {
4286c6f6 298 rdsbuf[rdsin]=inb(io+1);
1da177e4 299 if(rdsin==rdsout)
4286c6f6 300 printk(KERN_WARNING "cadet: RDS buffer overflow\n");
1da177e4
LT
301 else
302 rdsin++;
303 }
304 spin_unlock(&cadet_io_lock);
305 }
306
307 /*
308 * Service pending read
309 */
310 if( rdsin!=rdsout)
4286c6f6 311 wake_up_interruptible(&read_queue);
1da177e4 312
4286c6f6 313 /*
1da177e4
LT
314 * 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{
4286c6f6 328 int i=0;
1da177e4
LT
329 unsigned char readbuf[RDS_BUFFER];
330
4286c6f6 331 if(rdsstat==0) {
1da177e4 332 spin_lock(&cadet_io_lock);
4286c6f6 333 rdsstat=1;
1da177e4
LT
334 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) {
4286c6f6
MCC
343 if (file->f_flags & O_NONBLOCK)
344 return -EWOULDBLOCK;
345 interruptible_sleep_on(&read_queue);
346 }
1da177e4 347 while( i<count && rdsin!=rdsout)
4286c6f6 348 readbuf[i++]=rdsbuf[rdsout++];
1da177e4
LT
349
350 if (copy_to_user(data,readbuf,i))
4286c6f6 351 return -EFAULT;
1da177e4
LT
352 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) {
4286c6f6
MCC
379 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 }
1da177e4 390 v->flags|=cadet_getrds();
4286c6f6
MCC
391 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;
1da177e4
LT
402 }
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 }
4286c6f6 411 curtuner=v->tuner;
1da177e4
LT
412 return 0;
413 }
414 case VIDIOCGFREQ:
415 {
4286c6f6 416 unsigned long *freq = arg;
1da177e4
LT
417 *freq = cadet_getfreq();
418 return 0;
419 }
420 case VIDIOCSFREQ:
421 {
4286c6f6 422 unsigned long *freq = arg;
1da177e4 423 if((curtuner==0)&&((*freq<1400)||(*freq>1728))) {
4286c6f6 424 return -EINVAL;
1da177e4
LT
425 }
426 if((curtuner==1)&&((*freq<8320)||(*freq>26400))) {
4286c6f6 427 return -EINVAL;
1da177e4
LT
428 }
429 cadet_setfreq(*freq);
430 return 0;
431 }
432 case VIDIOCGAUDIO:
4286c6f6 433 {
1da177e4
LT
434 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) {
4286c6f6 438 v->mode=VIDEO_SOUND_MONO;
1da177e4
LT
439 } else {
440 v->mode=VIDEO_SOUND_STEREO;
441 }
442 v->volume=cadet_getvol();
443 v->step=0xffff;
444 strcpy(v->name, "Radio");
4286c6f6 445 return 0;
1da177e4
LT
446 }
447 case VIDIOCSAUDIO:
448 {
449 struct video_audio *v = arg;
4286c6f6 450 if(v->audio)
1da177e4
LT
451 return -EINVAL;
452 cadet_setvol(v->volume);
4286c6f6 453 if(v->flags&VIDEO_AUDIO_MUTE)
1da177e4
LT
454 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,
0d0fbf81 494 .compat_ioctl = v4l_compat_ioctl32,
1da177e4
LT
495 .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{
4286c6f6 543 static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
1da177e4
LT
544 int i;
545
546 for(i=0;i<8;i++) {
4286c6f6 547 io=iovals[i];
f1ac046d 548 if (request_region(io, 2, "cadet-probe")) {
4286c6f6 549 cadet_setfreq(1410);
1da177e4
LT
550 if(cadet_getfreq()==1410) {
551 release_region(io, 2);
4286c6f6 552 return io;
1da177e4
LT
553 }
554 release_region(io, 2);
555 }
556 }
557 return -1;
558}
559
4286c6f6 560/*
1da177e4
LT
561 * 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);
4286c6f6 568
1da177e4
LT
569 /*
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 */
4286c6f6
MCC
583
584 if(io < 0) {
585#ifdef MODULE
1da177e4
LT
586 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
587#endif
4286c6f6 588 goto fail;
1da177e4
LT
589 }
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
This page took 0.183919 seconds and 5 git commands to generate.