]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/media/radio/radio-cadet.c
938856588831ca377ee73a0bb45185b5f53efb9a
[karo-tx-linux.git] / drivers / media / radio / radio-cadet.c
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
11  * Jason Lewis      (jlewis@twilight.vtc.vsc.edu)
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  * 2006-07-30   Hans J. Koch <koch@hjk-az.de>
30  *              Changed API to V4L2
31  */
32
33 #include <linux/module.h>       /* Modules                      */
34 #include <linux/init.h>         /* Initdata                     */
35 #include <linux/ioport.h>       /* request_region               */
36 #include <linux/delay.h>        /* udelay                       */
37 #include <asm/io.h>             /* outb, outb_p                 */
38 #include <asm/uaccess.h>        /* copy to/from user            */
39 #include <linux/videodev2.h>    /* V4L2 API defs                */
40 #include <media/v4l2-common.h>
41 #include <linux/param.h>
42 #include <linux/pnp.h>
43
44 #define RDS_BUFFER 256
45 #define RDS_RX_FLAG 1
46 #define MBS_RX_FLAG 2
47
48 #define CADET_VERSION KERNEL_VERSION(0,3,3)
49
50 static int io=-1;               /* default to isapnp activation */
51 static int radio_nr = -1;
52 static int users=0;
53 static int curtuner=0;
54 static int tunestat=0;
55 static int sigstrength=0;
56 static wait_queue_head_t read_queue;
57 static struct timer_list readtimer;
58 static __u8 rdsin=0,rdsout=0,rdsstat=0;
59 static unsigned char rdsbuf[RDS_BUFFER];
60 static spinlock_t cadet_io_lock;
61
62 static int cadet_probe(void);
63
64 /*
65  * Signal Strength Threshold Values
66  * The V4L API spec does not define any particular unit for the signal
67  * strength value.  These values are in microvolts of RF at the tuner's input.
68  */
69 static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}};
70
71
72 static int
73 cadet_getstereo(void)
74 {
75         int ret = V4L2_TUNER_SUB_MONO;
76         if(curtuner != 0)       /* Only FM has stereo capability! */
77                 return V4L2_TUNER_SUB_MONO;
78
79         spin_lock(&cadet_io_lock);
80         outb(7,io);          /* Select tuner control */
81         if( (inb(io+1) & 0x40) == 0)
82                 ret = V4L2_TUNER_SUB_STEREO;
83         spin_unlock(&cadet_io_lock);
84         return ret;
85 }
86
87 static unsigned
88 cadet_gettune(void)
89 {
90         int curvol,i;
91         unsigned fifo=0;
92
93         /*
94          * Prepare for read
95          */
96
97         spin_lock(&cadet_io_lock);
98
99         outb(7,io);       /* Select tuner control */
100         curvol=inb(io+1); /* Save current volume/mute setting */
101         outb(0x00,io+1);  /* Ensure WRITE-ENABLE is LOW */
102         tunestat=0xffff;
103
104         /*
105          * Read the shift register
106          */
107         for(i=0;i<25;i++) {
108                 fifo=(fifo<<1)|((inb(io+1)>>7)&0x01);
109                 if(i<24) {
110                         outb(0x01,io+1);
111                         tunestat&=inb(io+1);
112                         outb(0x00,io+1);
113                 }
114         }
115
116         /*
117          * Restore volume/mute setting
118          */
119         outb(curvol,io+1);
120         spin_unlock(&cadet_io_lock);
121
122         return fifo;
123 }
124
125 static unsigned
126 cadet_getfreq(void)
127 {
128         int i;
129         unsigned freq=0,test,fifo=0;
130
131         /*
132          * Read current tuning
133          */
134         fifo=cadet_gettune();
135
136         /*
137          * Convert to actual frequency
138          */
139         if(curtuner==0) {    /* FM */
140                 test=12500;
141                 for(i=0;i<14;i++) {
142                         if((fifo&0x01)!=0) {
143                                 freq+=test;
144                         }
145                         test=test<<1;
146                         fifo=fifo>>1;
147                 }
148                 freq-=10700000;           /* IF frequency is 10.7 MHz */
149                 freq=(freq*16)/1000000;   /* Make it 1/16 MHz */
150         }
151         if(curtuner==1) {    /* AM */
152                 freq=((fifo&0x7fff)-2010)*16;
153         }
154
155         return freq;
156 }
157
158 static void
159 cadet_settune(unsigned fifo)
160 {
161         int i;
162         unsigned test;
163
164         spin_lock(&cadet_io_lock);
165
166         outb(7,io);                /* Select tuner control */
167         /*
168          * Write the shift register
169          */
170         test=0;
171         test=(fifo>>23)&0x02;      /* Align data for SDO */
172         test|=0x1c;                /* SDM=1, SWE=1, SEN=1, SCK=0 */
173         outb(7,io);                /* Select tuner control */
174         outb(test,io+1);           /* Initialize for write */
175         for(i=0;i<25;i++) {
176                 test|=0x01;              /* Toggle SCK High */
177                 outb(test,io+1);
178                 test&=0xfe;              /* Toggle SCK Low */
179                 outb(test,io+1);
180                 fifo=fifo<<1;            /* Prepare the next bit */
181                 test=0x1c|((fifo>>23)&0x02);
182                 outb(test,io+1);
183         }
184         spin_unlock(&cadet_io_lock);
185 }
186
187 static void
188 cadet_setfreq(unsigned freq)
189 {
190         unsigned fifo;
191         int i,j,test;
192         int curvol;
193
194         /*
195          * Formulate a fifo command
196          */
197         fifo=0;
198         if(curtuner==0) {    /* FM */
199                 test=102400;
200                 freq=(freq*1000)/16;       /* Make it kHz */
201                 freq+=10700;               /* IF is 10700 kHz */
202                 for(i=0;i<14;i++) {
203                         fifo=fifo<<1;
204                         if(freq>=test) {
205                                 fifo|=0x01;
206                                 freq-=test;
207                         }
208                         test=test>>1;
209                 }
210         }
211         if(curtuner==1) {    /* AM */
212                 fifo=(freq/16)+2010;            /* Make it kHz */
213                 fifo|=0x100000;            /* Select AM Band */
214         }
215
216         /*
217          * Save current volume/mute setting
218          */
219
220         spin_lock(&cadet_io_lock);
221         outb(7,io);                /* Select tuner control */
222         curvol=inb(io+1);
223         spin_unlock(&cadet_io_lock);
224
225         /*
226          * Tune the card
227          */
228         for(j=3;j>-1;j--) {
229                 cadet_settune(fifo|(j<<16));
230
231                 spin_lock(&cadet_io_lock);
232                 outb(7,io);         /* Select tuner control */
233                 outb(curvol,io+1);
234                 spin_unlock(&cadet_io_lock);
235
236                 msleep(100);
237
238                 cadet_gettune();
239                 if((tunestat & 0x40) == 0) {   /* Tuned */
240                         sigstrength=sigtable[curtuner][j];
241                         return;
242                 }
243         }
244         sigstrength=0;
245 }
246
247
248 static int
249 cadet_getvol(void)
250 {
251         int ret = 0;
252
253         spin_lock(&cadet_io_lock);
254
255         outb(7,io);                /* Select tuner control */
256         if((inb(io + 1) & 0x20) != 0)
257                 ret = 0xffff;
258
259         spin_unlock(&cadet_io_lock);
260         return ret;
261 }
262
263
264 static void
265 cadet_setvol(int vol)
266 {
267         spin_lock(&cadet_io_lock);
268         outb(7,io);                /* Select tuner control */
269         if(vol>0)
270                 outb(0x20,io+1);
271         else
272                 outb(0x00,io+1);
273         spin_unlock(&cadet_io_lock);
274 }
275
276 static void
277 cadet_handler(unsigned long data)
278 {
279         /*
280          * Service the RDS fifo
281          */
282
283         if(spin_trylock(&cadet_io_lock))
284         {
285                 outb(0x3,io);       /* Select RDS Decoder Control */
286                 if((inb(io+1)&0x20)!=0) {
287                         printk(KERN_CRIT "cadet: RDS fifo overflow\n");
288                 }
289                 outb(0x80,io);      /* Select RDS fifo */
290                 while((inb(io)&0x80)!=0) {
291                         rdsbuf[rdsin]=inb(io+1);
292                         if(rdsin==rdsout)
293                                 printk(KERN_WARNING "cadet: RDS buffer overflow\n");
294                         else
295                                 rdsin++;
296                 }
297                 spin_unlock(&cadet_io_lock);
298         }
299
300         /*
301          * Service pending read
302          */
303         if( rdsin!=rdsout)
304                 wake_up_interruptible(&read_queue);
305
306         /*
307          * Clean up and exit
308          */
309         init_timer(&readtimer);
310         readtimer.function=cadet_handler;
311         readtimer.data=(unsigned long)0;
312         readtimer.expires=jiffies+(HZ/20);
313         add_timer(&readtimer);
314 }
315
316
317
318 static ssize_t
319 cadet_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
320 {
321         int i=0;
322         unsigned char readbuf[RDS_BUFFER];
323
324         if(rdsstat==0) {
325                 spin_lock(&cadet_io_lock);
326                 rdsstat=1;
327                 outb(0x80,io);        /* Select RDS fifo */
328                 spin_unlock(&cadet_io_lock);
329                 init_timer(&readtimer);
330                 readtimer.function=cadet_handler;
331                 readtimer.data=(unsigned long)0;
332                 readtimer.expires=jiffies+(HZ/20);
333                 add_timer(&readtimer);
334         }
335         if(rdsin==rdsout) {
336                 if (file->f_flags & O_NONBLOCK)
337                         return -EWOULDBLOCK;
338                 interruptible_sleep_on(&read_queue);
339         }
340         while( i<count && rdsin!=rdsout)
341                 readbuf[i++]=rdsbuf[rdsout++];
342
343         if (copy_to_user(data,readbuf,i))
344                 return -EFAULT;
345         return i;
346 }
347
348
349
350 static int cadet_do_ioctl(struct inode *inode, struct file *file,
351                           unsigned int cmd, void *arg)
352 {
353         switch(cmd)
354         {
355                 case VIDIOC_QUERYCAP:
356                 {
357                         struct v4l2_capability *cap = arg;
358                         memset(cap,0,sizeof(*cap));
359                         cap->capabilities =
360                                 V4L2_CAP_TUNER |
361                                 V4L2_CAP_READWRITE;
362                         cap->version = CADET_VERSION;
363                         strcpy(cap->driver, "ADS Cadet");
364                         strcpy(cap->card, "ADS Cadet");
365                         return 0;
366                 }
367                 case VIDIOC_G_TUNER:
368                 {
369                         struct v4l2_tuner *t = arg;
370                         memset(t,0,sizeof(*t));
371                         t->type = V4L2_TUNER_RADIO;
372                         switch (t->index)
373                         {
374                                 case 0: strcpy(t->name, "FM");
375                                         t->capability = V4L2_TUNER_CAP_STEREO;
376                                         t->rangelow = 1400;     /* 87.5 MHz */
377                                         t->rangehigh = 1728;    /* 108.0 MHz */
378                                         t->rxsubchans=cadet_getstereo();
379                                         switch (t->rxsubchans){
380                                                 case V4L2_TUNER_SUB_MONO:
381                                                         t->audmode = V4L2_TUNER_MODE_MONO;
382                                                         break;
383                                                 case V4L2_TUNER_SUB_STEREO:
384                                                         t->audmode = V4L2_TUNER_MODE_STEREO;
385                                                         break;
386                                                 default: ;
387                                         }
388                                         break;
389                                 case 1: strcpy(t->name, "AM");
390                                         t->capability = V4L2_TUNER_CAP_LOW;
391                                         t->rangelow = 8320;      /* 520 kHz */
392                                         t->rangehigh = 26400;    /* 1650 kHz */
393                                         t->rxsubchans = V4L2_TUNER_SUB_MONO;
394                                         t->audmode = V4L2_TUNER_MODE_MONO;
395                                         break;
396                                 default:
397                                         return -EINVAL;
398                         }
399
400                         t->signal = sigstrength; /* We might need to modify scaling of this */
401                         return 0;
402                 }
403                 case VIDIOC_S_TUNER:
404                 {
405                         struct v4l2_tuner *t = arg;
406                         if((t->index != 0)&&(t->index != 1))
407                                 return -EINVAL;
408
409                         curtuner = t->index;
410                         return 0;
411                 }
412                 case VIDIOC_G_FREQUENCY:
413                 {
414                         struct v4l2_frequency *f = arg;
415                         memset(f,0,sizeof(*f));
416                         f->tuner = curtuner;
417                         f->type = V4L2_TUNER_RADIO;
418                         f->frequency = cadet_getfreq();
419                         return 0;
420                 }
421                 case VIDIOC_S_FREQUENCY:
422                 {
423                         struct v4l2_frequency *f = arg;
424                         if (f->type != V4L2_TUNER_RADIO){
425                                 return -EINVAL;
426                         }
427                         if((curtuner==0)&&((f->frequency<1400)||(f->frequency>1728))) {
428                                 return -EINVAL;
429                         }
430                         if((curtuner==1)&&((f->frequency<8320)||(f->frequency>26400))) {
431                                 return -EINVAL;
432                         }
433                         cadet_setfreq(f->frequency);
434                         return 0;
435                 }
436                 case VIDIOC_G_CTRL:
437                 {
438                         struct v4l2_control *c = arg;
439                         switch (c->id){
440                                 case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */
441                                         c->value = (cadet_getvol() == 0);
442                                         break;
443                                 case V4L2_CID_AUDIO_VOLUME:
444                                         c->value = cadet_getvol();
445                                         break;
446                                 default:
447                                         return -EINVAL;
448                         }
449                         return 0;
450                 }
451                 case VIDIOC_S_CTRL:
452                 {
453                         struct v4l2_control *c = arg;
454                         switch (c->id){
455                                 case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */
456                                         if (c->value) cadet_setvol(0);
457                                                 else cadet_setvol(0xffff);
458                                         break;
459                                 case V4L2_CID_AUDIO_VOLUME:
460                                         cadet_setvol(c->value);
461                                         break;
462                                 default:
463                                         return -EINVAL;
464                         }
465                         return 0;
466                 }
467
468                 default:
469                         return -ENOIOCTLCMD;
470         }
471 }
472
473 static int
474 cadet_ioctl(struct inode *inode, struct file *file,
475                        unsigned int cmd, unsigned long arg)
476 {
477         return video_usercopy(inode, file, cmd, arg, cadet_do_ioctl);
478 }
479
480 static int
481 cadet_open(struct inode *inode, struct file *file)
482 {
483         users++;
484         if (1 == users) init_waitqueue_head(&read_queue);
485         return 0;
486 }
487
488 static int
489 cadet_release(struct inode *inode, struct file *file)
490 {
491         users--;
492         if (0 == users){
493                 del_timer_sync(&readtimer);
494                 rdsstat=0;
495         }
496         return 0;
497 }
498
499 static unsigned int
500 cadet_poll(struct file *file, struct poll_table_struct *wait)
501 {
502         poll_wait(file,&read_queue,wait);
503         if(rdsin != rdsout)
504                 return POLLIN | POLLRDNORM;
505         return 0;
506 }
507
508
509 static struct file_operations cadet_fops = {
510         .owner          = THIS_MODULE,
511         .open           = cadet_open,
512         .release        = cadet_release,
513         .read           = cadet_read,
514         .ioctl          = cadet_ioctl,
515         .poll           = cadet_poll,
516         .compat_ioctl   = v4l_compat_ioctl32,
517         .llseek         = no_llseek,
518 };
519
520 static struct video_device cadet_radio=
521 {
522         .owner          = THIS_MODULE,
523         .name           = "Cadet radio",
524         .type           = VID_TYPE_TUNER,
525         .fops           = &cadet_fops,
526 };
527
528 static struct pnp_device_id cadet_pnp_devices[] = {
529         /* ADS Cadet AM/FM Radio Card */
530         {.id = "MSM0c24", .driver_data = 0},
531         {.id = ""}
532 };
533
534 MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices);
535
536 static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id)
537 {
538         if (!dev)
539                 return -ENODEV;
540         /* only support one device */
541         if (io > 0)
542                 return -EBUSY;
543
544         if (!pnp_port_valid(dev, 0)) {
545                 return -ENODEV;
546         }
547
548         io = pnp_port_start(dev, 0);
549
550         printk ("radio-cadet: PnP reports device at %#x\n", io);
551
552         return io;
553 }
554
555 static struct pnp_driver cadet_pnp_driver = {
556         .name           = "radio-cadet",
557         .id_table       = cadet_pnp_devices,
558         .probe          = cadet_pnp_probe,
559         .remove         = NULL,
560 };
561
562 static int cadet_probe(void)
563 {
564         static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
565         int i;
566
567         for(i=0;i<8;i++) {
568                 io=iovals[i];
569                 if (request_region(io, 2, "cadet-probe")) {
570                         cadet_setfreq(1410);
571                         if(cadet_getfreq()==1410) {
572                                 release_region(io, 2);
573                                 return io;
574                         }
575                         release_region(io, 2);
576                 }
577         }
578         return -1;
579 }
580
581 /*
582  * io should only be set if the user has used something like
583  * isapnp (the userspace program) to initialize this card for us
584  */
585
586 static int __init cadet_init(void)
587 {
588         spin_lock_init(&cadet_io_lock);
589
590         /*
591          *      If a probe was requested then probe ISAPnP first (safest)
592          */
593         if (io < 0)
594                 pnp_register_driver(&cadet_pnp_driver);
595         /*
596          *      If that fails then probe unsafely if probe is requested
597          */
598         if(io < 0)
599                 io = cadet_probe ();
600
601         /*
602          *      Else we bail out
603          */
604
605         if(io < 0) {
606 #ifdef MODULE
607                 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
608 #endif
609                 goto fail;
610         }
611         if (!request_region(io,2,"cadet"))
612                 goto fail;
613         if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) {
614                 release_region(io,2);
615                 goto fail;
616         }
617         printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io);
618         return 0;
619 fail:
620         pnp_unregister_driver(&cadet_pnp_driver);
621         return -1;
622 }
623
624
625
626 MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
627 MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");
628 MODULE_LICENSE("GPL");
629
630 module_param(io, int, 0);
631 MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
632 module_param(radio_nr, int, 0);
633
634 static void __exit cadet_cleanup_module(void)
635 {
636         video_unregister_device(&cadet_radio);
637         release_region(io,2);
638         pnp_unregister_driver(&cadet_pnp_driver);
639 }
640
641 module_init(cadet_init);
642 module_exit(cadet_cleanup_module);
643