11da177e4SLinus Torvalds /* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card 21da177e4SLinus Torvalds * 31da177e4SLinus Torvalds * by Fred Gleason <fredg@wava.com> 41da177e4SLinus Torvalds * Version 0.3.3 51da177e4SLinus Torvalds * 61da177e4SLinus Torvalds * (Loosely) based on code for the Aztech radio card by 71da177e4SLinus Torvalds * 81da177e4SLinus Torvalds * Russell Kroll (rkroll@exploits.org) 91da177e4SLinus Torvalds * Quay Ly 101da177e4SLinus Torvalds * Donald Song 111da177e4SLinus Torvalds * Jason Lewis (jlewis@twilight.vtc.vsc.edu) 121da177e4SLinus Torvalds * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) 131da177e4SLinus Torvalds * William McGrath (wmcgrath@twilight.vtc.vsc.edu) 141da177e4SLinus Torvalds * 151da177e4SLinus Torvalds * History: 161da177e4SLinus Torvalds * 2000-04-29 Russell Kroll <rkroll@exploits.org> 171da177e4SLinus Torvalds * Added ISAPnP detection for Linux 2.3/2.4 181da177e4SLinus Torvalds * 191da177e4SLinus Torvalds * 2001-01-10 Russell Kroll <rkroll@exploits.org> 201da177e4SLinus Torvalds * Removed dead CONFIG_RADIO_CADET_PORT code 211da177e4SLinus Torvalds * PnP detection on load is now default (no args necessary) 221da177e4SLinus Torvalds * 231da177e4SLinus Torvalds * 2002-01-17 Adam Belay <ambx1@neo.rr.com> 241da177e4SLinus Torvalds * Updated to latest pnp code 251da177e4SLinus Torvalds * 26d9b01449SAlan Cox * 2003-01-31 Alan Cox <alan@lxorguk.ukuu.org.uk> 271da177e4SLinus Torvalds * Cleaned up locking, delay code, general odds and ends 28c0c7fa09SHans J. Koch * 29c0c7fa09SHans J. Koch * 2006-07-30 Hans J. Koch <koch@hjk-az.de> 30c0c7fa09SHans J. Koch * Changed API to V4L2 311da177e4SLinus Torvalds */ 321da177e4SLinus Torvalds 33d591b9ccSAndrew Morton #include <linux/version.h> 341da177e4SLinus Torvalds #include <linux/module.h> /* Modules */ 351da177e4SLinus Torvalds #include <linux/init.h> /* Initdata */ 36fb911ee8SPeter Osterlund #include <linux/ioport.h> /* request_region */ 371da177e4SLinus Torvalds #include <linux/delay.h> /* udelay */ 381da177e4SLinus Torvalds #include <asm/io.h> /* outb, outb_p */ 391da177e4SLinus Torvalds #include <asm/uaccess.h> /* copy to/from user */ 40c0c7fa09SHans J. Koch #include <linux/videodev2.h> /* V4L2 API defs */ 415e87efa3SMauro Carvalho Chehab #include <media/v4l2-common.h> 4235ea11ffSHans Verkuil #include <media/v4l2-ioctl.h> 431da177e4SLinus Torvalds #include <linux/param.h> 441da177e4SLinus Torvalds #include <linux/pnp.h> 451da177e4SLinus Torvalds 461da177e4SLinus Torvalds #define RDS_BUFFER 256 47c0c7fa09SHans J. Koch #define RDS_RX_FLAG 1 48c0c7fa09SHans J. Koch #define MBS_RX_FLAG 2 49c0c7fa09SHans J. Koch 50c0c7fa09SHans J. Koch #define CADET_VERSION KERNEL_VERSION(0,3,3) 511da177e4SLinus Torvalds 52c1c4fd3eSDouglas Landgraf static struct v4l2_queryctrl radio_qctrl[] = { 53c1c4fd3eSDouglas Landgraf { 54c1c4fd3eSDouglas Landgraf .id = V4L2_CID_AUDIO_MUTE, 55c1c4fd3eSDouglas Landgraf .name = "Mute", 56c1c4fd3eSDouglas Landgraf .minimum = 0, 57c1c4fd3eSDouglas Landgraf .maximum = 1, 58c1c4fd3eSDouglas Landgraf .default_value = 1, 59c1c4fd3eSDouglas Landgraf .type = V4L2_CTRL_TYPE_BOOLEAN, 60c1c4fd3eSDouglas Landgraf },{ 61c1c4fd3eSDouglas Landgraf .id = V4L2_CID_AUDIO_VOLUME, 62c1c4fd3eSDouglas Landgraf .name = "Volume", 63c1c4fd3eSDouglas Landgraf .minimum = 0, 64c1c4fd3eSDouglas Landgraf .maximum = 0xff, 65c1c4fd3eSDouglas Landgraf .step = 1, 66c1c4fd3eSDouglas Landgraf .default_value = 0xff, 67c1c4fd3eSDouglas Landgraf .type = V4L2_CTRL_TYPE_INTEGER, 68c1c4fd3eSDouglas Landgraf } 69c1c4fd3eSDouglas Landgraf }; 70c1c4fd3eSDouglas Landgraf 711da177e4SLinus Torvalds static int io=-1; /* default to isapnp activation */ 721da177e4SLinus Torvalds static int radio_nr = -1; 73ff699e6bSDouglas Schilling Landgraf static int users; 74ff699e6bSDouglas Schilling Landgraf static int curtuner; 75ff699e6bSDouglas Schilling Landgraf static int tunestat; 76ff699e6bSDouglas Schilling Landgraf static int sigstrength; 771da177e4SLinus Torvalds static wait_queue_head_t read_queue; 781da177e4SLinus Torvalds static struct timer_list readtimer; 79ff699e6bSDouglas Schilling Landgraf static __u8 rdsin, rdsout, rdsstat; 801da177e4SLinus Torvalds static unsigned char rdsbuf[RDS_BUFFER]; 811da177e4SLinus Torvalds static spinlock_t cadet_io_lock; 821da177e4SLinus Torvalds 831da177e4SLinus Torvalds static int cadet_probe(void); 841da177e4SLinus Torvalds 851da177e4SLinus Torvalds /* 861da177e4SLinus Torvalds * Signal Strength Threshold Values 871da177e4SLinus Torvalds * The V4L API spec does not define any particular unit for the signal 881da177e4SLinus Torvalds * strength value. These values are in microvolts of RF at the tuner's input. 891da177e4SLinus Torvalds */ 901da177e4SLinus Torvalds static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}}; 911da177e4SLinus Torvalds 92c0c7fa09SHans J. Koch 93c0c7fa09SHans J. Koch static int 94c0c7fa09SHans J. Koch cadet_getstereo(void) 951da177e4SLinus Torvalds { 96c0c7fa09SHans J. Koch int ret = V4L2_TUNER_SUB_MONO; 971da177e4SLinus Torvalds if(curtuner != 0) /* Only FM has stereo capability! */ 98c0c7fa09SHans J. Koch return V4L2_TUNER_SUB_MONO; 991da177e4SLinus Torvalds 1001da177e4SLinus Torvalds spin_lock(&cadet_io_lock); 1011da177e4SLinus Torvalds outb(7,io); /* Select tuner control */ 1021da177e4SLinus Torvalds if( (inb(io+1) & 0x40) == 0) 103c0c7fa09SHans J. Koch ret = V4L2_TUNER_SUB_STEREO; 1041da177e4SLinus Torvalds spin_unlock(&cadet_io_lock); 1051da177e4SLinus Torvalds return ret; 1061da177e4SLinus Torvalds } 1071da177e4SLinus Torvalds 108c0c7fa09SHans J. Koch static unsigned 109c0c7fa09SHans J. Koch cadet_gettune(void) 1101da177e4SLinus Torvalds { 1111da177e4SLinus Torvalds int curvol,i; 1121da177e4SLinus Torvalds unsigned fifo=0; 1131da177e4SLinus Torvalds 1141da177e4SLinus Torvalds /* 1151da177e4SLinus Torvalds * Prepare for read 1161da177e4SLinus Torvalds */ 1171da177e4SLinus Torvalds 1181da177e4SLinus Torvalds spin_lock(&cadet_io_lock); 1191da177e4SLinus Torvalds 1201da177e4SLinus Torvalds outb(7,io); /* Select tuner control */ 1211da177e4SLinus Torvalds curvol=inb(io+1); /* Save current volume/mute setting */ 1221da177e4SLinus Torvalds outb(0x00,io+1); /* Ensure WRITE-ENABLE is LOW */ 1231da177e4SLinus Torvalds tunestat=0xffff; 1241da177e4SLinus Torvalds 1251da177e4SLinus Torvalds /* 1261da177e4SLinus Torvalds * Read the shift register 1271da177e4SLinus Torvalds */ 1281da177e4SLinus Torvalds for(i=0;i<25;i++) { 1291da177e4SLinus Torvalds fifo=(fifo<<1)|((inb(io+1)>>7)&0x01); 1301da177e4SLinus Torvalds if(i<24) { 1311da177e4SLinus Torvalds outb(0x01,io+1); 1321da177e4SLinus Torvalds tunestat&=inb(io+1); 1331da177e4SLinus Torvalds outb(0x00,io+1); 1341da177e4SLinus Torvalds } 1351da177e4SLinus Torvalds } 1361da177e4SLinus Torvalds 1371da177e4SLinus Torvalds /* 1381da177e4SLinus Torvalds * Restore volume/mute setting 1391da177e4SLinus Torvalds */ 1401da177e4SLinus Torvalds outb(curvol,io+1); 1411da177e4SLinus Torvalds spin_unlock(&cadet_io_lock); 1421da177e4SLinus Torvalds 1431da177e4SLinus Torvalds return fifo; 1441da177e4SLinus Torvalds } 1451da177e4SLinus Torvalds 146c0c7fa09SHans J. Koch static unsigned 147c0c7fa09SHans J. Koch cadet_getfreq(void) 1481da177e4SLinus Torvalds { 1491da177e4SLinus Torvalds int i; 1501da177e4SLinus Torvalds unsigned freq=0,test,fifo=0; 1511da177e4SLinus Torvalds 1521da177e4SLinus Torvalds /* 1531da177e4SLinus Torvalds * Read current tuning 1541da177e4SLinus Torvalds */ 1551da177e4SLinus Torvalds fifo=cadet_gettune(); 1561da177e4SLinus Torvalds 1571da177e4SLinus Torvalds /* 1581da177e4SLinus Torvalds * Convert to actual frequency 1591da177e4SLinus Torvalds */ 1601da177e4SLinus Torvalds if(curtuner==0) { /* FM */ 1611da177e4SLinus Torvalds test=12500; 1621da177e4SLinus Torvalds for(i=0;i<14;i++) { 1631da177e4SLinus Torvalds if((fifo&0x01)!=0) { 1641da177e4SLinus Torvalds freq+=test; 1651da177e4SLinus Torvalds } 1661da177e4SLinus Torvalds test=test<<1; 1671da177e4SLinus Torvalds fifo=fifo>>1; 1681da177e4SLinus Torvalds } 1691da177e4SLinus Torvalds freq-=10700000; /* IF frequency is 10.7 MHz */ 1701da177e4SLinus Torvalds freq=(freq*16)/1000000; /* Make it 1/16 MHz */ 1711da177e4SLinus Torvalds } 1721da177e4SLinus Torvalds if(curtuner==1) { /* AM */ 1731da177e4SLinus Torvalds freq=((fifo&0x7fff)-2010)*16; 1741da177e4SLinus Torvalds } 1751da177e4SLinus Torvalds 1761da177e4SLinus Torvalds return freq; 1771da177e4SLinus Torvalds } 1781da177e4SLinus Torvalds 179c0c7fa09SHans J. Koch static void 180c0c7fa09SHans J. Koch cadet_settune(unsigned fifo) 1811da177e4SLinus Torvalds { 1821da177e4SLinus Torvalds int i; 1831da177e4SLinus Torvalds unsigned test; 1841da177e4SLinus Torvalds 1851da177e4SLinus Torvalds spin_lock(&cadet_io_lock); 1861da177e4SLinus Torvalds 1871da177e4SLinus Torvalds outb(7,io); /* Select tuner control */ 1881da177e4SLinus Torvalds /* 1891da177e4SLinus Torvalds * Write the shift register 1901da177e4SLinus Torvalds */ 1911da177e4SLinus Torvalds test=0; 1921da177e4SLinus Torvalds test=(fifo>>23)&0x02; /* Align data for SDO */ 1931da177e4SLinus Torvalds test|=0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */ 1941da177e4SLinus Torvalds outb(7,io); /* Select tuner control */ 1951da177e4SLinus Torvalds outb(test,io+1); /* Initialize for write */ 1961da177e4SLinus Torvalds for(i=0;i<25;i++) { 1971da177e4SLinus Torvalds test|=0x01; /* Toggle SCK High */ 1981da177e4SLinus Torvalds outb(test,io+1); 1991da177e4SLinus Torvalds test&=0xfe; /* Toggle SCK Low */ 2001da177e4SLinus Torvalds outb(test,io+1); 2011da177e4SLinus Torvalds fifo=fifo<<1; /* Prepare the next bit */ 2021da177e4SLinus Torvalds test=0x1c|((fifo>>23)&0x02); 2031da177e4SLinus Torvalds outb(test,io+1); 2041da177e4SLinus Torvalds } 2051da177e4SLinus Torvalds spin_unlock(&cadet_io_lock); 2061da177e4SLinus Torvalds } 2071da177e4SLinus Torvalds 208c0c7fa09SHans J. Koch static void 209c0c7fa09SHans J. Koch cadet_setfreq(unsigned freq) 2101da177e4SLinus Torvalds { 2111da177e4SLinus Torvalds unsigned fifo; 2121da177e4SLinus Torvalds int i,j,test; 2131da177e4SLinus Torvalds int curvol; 2141da177e4SLinus Torvalds 2151da177e4SLinus Torvalds /* 2161da177e4SLinus Torvalds * Formulate a fifo command 2171da177e4SLinus Torvalds */ 2181da177e4SLinus Torvalds fifo=0; 2191da177e4SLinus Torvalds if(curtuner==0) { /* FM */ 2201da177e4SLinus Torvalds test=102400; 2211da177e4SLinus Torvalds freq=(freq*1000)/16; /* Make it kHz */ 2221da177e4SLinus Torvalds freq+=10700; /* IF is 10700 kHz */ 2231da177e4SLinus Torvalds for(i=0;i<14;i++) { 2241da177e4SLinus Torvalds fifo=fifo<<1; 2251da177e4SLinus Torvalds if(freq>=test) { 2261da177e4SLinus Torvalds fifo|=0x01; 2271da177e4SLinus Torvalds freq-=test; 2281da177e4SLinus Torvalds } 2291da177e4SLinus Torvalds test=test>>1; 2301da177e4SLinus Torvalds } 2311da177e4SLinus Torvalds } 2321da177e4SLinus Torvalds if(curtuner==1) { /* AM */ 2331da177e4SLinus Torvalds fifo=(freq/16)+2010; /* Make it kHz */ 2341da177e4SLinus Torvalds fifo|=0x100000; /* Select AM Band */ 2351da177e4SLinus Torvalds } 2361da177e4SLinus Torvalds 2371da177e4SLinus Torvalds /* 2381da177e4SLinus Torvalds * Save current volume/mute setting 2391da177e4SLinus Torvalds */ 2401da177e4SLinus Torvalds 2411da177e4SLinus Torvalds spin_lock(&cadet_io_lock); 2421da177e4SLinus Torvalds outb(7,io); /* Select tuner control */ 2431da177e4SLinus Torvalds curvol=inb(io+1); 2441da177e4SLinus Torvalds spin_unlock(&cadet_io_lock); 2451da177e4SLinus Torvalds 2461da177e4SLinus Torvalds /* 2471da177e4SLinus Torvalds * Tune the card 2481da177e4SLinus Torvalds */ 2491da177e4SLinus Torvalds for(j=3;j>-1;j--) { 2501da177e4SLinus Torvalds cadet_settune(fifo|(j<<16)); 2511da177e4SLinus Torvalds 2521da177e4SLinus Torvalds spin_lock(&cadet_io_lock); 2531da177e4SLinus Torvalds outb(7,io); /* Select tuner control */ 2541da177e4SLinus Torvalds outb(curvol,io+1); 2551da177e4SLinus Torvalds spin_unlock(&cadet_io_lock); 2561da177e4SLinus Torvalds 2571da177e4SLinus Torvalds msleep(100); 2581da177e4SLinus Torvalds 2591da177e4SLinus Torvalds cadet_gettune(); 2601da177e4SLinus Torvalds if((tunestat & 0x40) == 0) { /* Tuned */ 2611da177e4SLinus Torvalds sigstrength=sigtable[curtuner][j]; 2621da177e4SLinus Torvalds return; 2631da177e4SLinus Torvalds } 2641da177e4SLinus Torvalds } 2651da177e4SLinus Torvalds sigstrength=0; 2661da177e4SLinus Torvalds } 2671da177e4SLinus Torvalds 2681da177e4SLinus Torvalds 269c0c7fa09SHans J. Koch static int 270c0c7fa09SHans J. Koch cadet_getvol(void) 2711da177e4SLinus Torvalds { 2721da177e4SLinus Torvalds int ret = 0; 2731da177e4SLinus Torvalds 2741da177e4SLinus Torvalds spin_lock(&cadet_io_lock); 2751da177e4SLinus Torvalds 2761da177e4SLinus Torvalds outb(7,io); /* Select tuner control */ 2771da177e4SLinus Torvalds if((inb(io + 1) & 0x20) != 0) 2781da177e4SLinus Torvalds ret = 0xffff; 2791da177e4SLinus Torvalds 2801da177e4SLinus Torvalds spin_unlock(&cadet_io_lock); 2811da177e4SLinus Torvalds return ret; 2821da177e4SLinus Torvalds } 2831da177e4SLinus Torvalds 2841da177e4SLinus Torvalds 285c0c7fa09SHans J. Koch static void 286c0c7fa09SHans J. Koch cadet_setvol(int vol) 2871da177e4SLinus Torvalds { 2881da177e4SLinus Torvalds spin_lock(&cadet_io_lock); 2891da177e4SLinus Torvalds outb(7,io); /* Select tuner control */ 2901da177e4SLinus Torvalds if(vol>0) 2911da177e4SLinus Torvalds outb(0x20,io+1); 2921da177e4SLinus Torvalds else 2931da177e4SLinus Torvalds outb(0x00,io+1); 2941da177e4SLinus Torvalds spin_unlock(&cadet_io_lock); 2951da177e4SLinus Torvalds } 2961da177e4SLinus Torvalds 297c0c7fa09SHans J. Koch static void 298c0c7fa09SHans J. Koch cadet_handler(unsigned long data) 2991da177e4SLinus Torvalds { 3001da177e4SLinus Torvalds /* 3011da177e4SLinus Torvalds * Service the RDS fifo 3021da177e4SLinus Torvalds */ 3031da177e4SLinus Torvalds 3041da177e4SLinus Torvalds if(spin_trylock(&cadet_io_lock)) 3051da177e4SLinus Torvalds { 3061da177e4SLinus Torvalds outb(0x3,io); /* Select RDS Decoder Control */ 3071da177e4SLinus Torvalds if((inb(io+1)&0x20)!=0) { 3081da177e4SLinus Torvalds printk(KERN_CRIT "cadet: RDS fifo overflow\n"); 3091da177e4SLinus Torvalds } 3101da177e4SLinus Torvalds outb(0x80,io); /* Select RDS fifo */ 3111da177e4SLinus Torvalds while((inb(io)&0x80)!=0) { 3121da177e4SLinus Torvalds rdsbuf[rdsin]=inb(io+1); 3131da177e4SLinus Torvalds if(rdsin==rdsout) 3141da177e4SLinus Torvalds printk(KERN_WARNING "cadet: RDS buffer overflow\n"); 3151da177e4SLinus Torvalds else 3161da177e4SLinus Torvalds rdsin++; 3171da177e4SLinus Torvalds } 3181da177e4SLinus Torvalds spin_unlock(&cadet_io_lock); 3191da177e4SLinus Torvalds } 3201da177e4SLinus Torvalds 3211da177e4SLinus Torvalds /* 3221da177e4SLinus Torvalds * Service pending read 3231da177e4SLinus Torvalds */ 3241da177e4SLinus Torvalds if( rdsin!=rdsout) 3251da177e4SLinus Torvalds wake_up_interruptible(&read_queue); 3261da177e4SLinus Torvalds 3271da177e4SLinus Torvalds /* 3281da177e4SLinus Torvalds * Clean up and exit 3291da177e4SLinus Torvalds */ 3301da177e4SLinus Torvalds init_timer(&readtimer); 3311da177e4SLinus Torvalds readtimer.function=cadet_handler; 3321da177e4SLinus Torvalds readtimer.data=(unsigned long)0; 333a2d66a37SMauro Carvalho Chehab readtimer.expires=jiffies+msecs_to_jiffies(50); 3341da177e4SLinus Torvalds add_timer(&readtimer); 3351da177e4SLinus Torvalds } 3361da177e4SLinus Torvalds 3371da177e4SLinus Torvalds 3381da177e4SLinus Torvalds 339c0c7fa09SHans J. Koch static ssize_t 340c0c7fa09SHans J. Koch cadet_read(struct file *file, char __user *data, size_t count, loff_t *ppos) 3411da177e4SLinus Torvalds { 3421da177e4SLinus Torvalds int i=0; 3431da177e4SLinus Torvalds unsigned char readbuf[RDS_BUFFER]; 3441da177e4SLinus Torvalds 3451da177e4SLinus Torvalds if(rdsstat==0) { 3461da177e4SLinus Torvalds spin_lock(&cadet_io_lock); 3471da177e4SLinus Torvalds rdsstat=1; 3481da177e4SLinus Torvalds outb(0x80,io); /* Select RDS fifo */ 3491da177e4SLinus Torvalds spin_unlock(&cadet_io_lock); 3501da177e4SLinus Torvalds init_timer(&readtimer); 3511da177e4SLinus Torvalds readtimer.function=cadet_handler; 3521da177e4SLinus Torvalds readtimer.data=(unsigned long)0; 353a2d66a37SMauro Carvalho Chehab readtimer.expires=jiffies+msecs_to_jiffies(50); 3541da177e4SLinus Torvalds add_timer(&readtimer); 3551da177e4SLinus Torvalds } 3561da177e4SLinus Torvalds if(rdsin==rdsout) { 3571da177e4SLinus Torvalds if (file->f_flags & O_NONBLOCK) 3581da177e4SLinus Torvalds return -EWOULDBLOCK; 3591da177e4SLinus Torvalds interruptible_sleep_on(&read_queue); 3601da177e4SLinus Torvalds } 3611da177e4SLinus Torvalds while( i<count && rdsin!=rdsout) 3621da177e4SLinus Torvalds readbuf[i++]=rdsbuf[rdsout++]; 3631da177e4SLinus Torvalds 3641da177e4SLinus Torvalds if (copy_to_user(data,readbuf,i)) 3651da177e4SLinus Torvalds return -EFAULT; 3661da177e4SLinus Torvalds return i; 3671da177e4SLinus Torvalds } 3681da177e4SLinus Torvalds 3691da177e4SLinus Torvalds 370c1c4fd3eSDouglas Landgraf static int vidioc_querycap(struct file *file, void *priv, 371c1c4fd3eSDouglas Landgraf struct v4l2_capability *v) 3721da177e4SLinus Torvalds { 373c1c4fd3eSDouglas Landgraf v->capabilities = 374c0c7fa09SHans J. Koch V4L2_CAP_TUNER | 375c0c7fa09SHans J. Koch V4L2_CAP_READWRITE; 376c1c4fd3eSDouglas Landgraf v->version = CADET_VERSION; 377c1c4fd3eSDouglas Landgraf strcpy(v->driver, "ADS Cadet"); 378c1c4fd3eSDouglas Landgraf strcpy(v->card, "ADS Cadet"); 3791da177e4SLinus Torvalds return 0; 3801da177e4SLinus Torvalds } 381c1c4fd3eSDouglas Landgraf 382c1c4fd3eSDouglas Landgraf static int vidioc_g_tuner(struct file *file, void *priv, 383c1c4fd3eSDouglas Landgraf struct v4l2_tuner *v) 3841da177e4SLinus Torvalds { 385c1c4fd3eSDouglas Landgraf v->type = V4L2_TUNER_RADIO; 386c1c4fd3eSDouglas Landgraf switch (v->index) { 387c1c4fd3eSDouglas Landgraf case 0: 388c1c4fd3eSDouglas Landgraf strcpy(v->name, "FM"); 389c1c4fd3eSDouglas Landgraf v->capability = V4L2_TUNER_CAP_STEREO; 390c1c4fd3eSDouglas Landgraf v->rangelow = 1400; /* 87.5 MHz */ 391c1c4fd3eSDouglas Landgraf v->rangehigh = 1728; /* 108.0 MHz */ 392c1c4fd3eSDouglas Landgraf v->rxsubchans=cadet_getstereo(); 393c1c4fd3eSDouglas Landgraf switch (v->rxsubchans){ 394c0c7fa09SHans J. Koch case V4L2_TUNER_SUB_MONO: 395c1c4fd3eSDouglas Landgraf v->audmode = V4L2_TUNER_MODE_MONO; 3961da177e4SLinus Torvalds break; 397c0c7fa09SHans J. Koch case V4L2_TUNER_SUB_STEREO: 398c1c4fd3eSDouglas Landgraf v->audmode = V4L2_TUNER_MODE_STEREO; 3991da177e4SLinus Torvalds break; 400c0c7fa09SHans J. Koch default: ; 4011da177e4SLinus Torvalds } 402c0c7fa09SHans J. Koch break; 403c1c4fd3eSDouglas Landgraf case 1: 404c1c4fd3eSDouglas Landgraf strcpy(v->name, "AM"); 405c1c4fd3eSDouglas Landgraf v->capability = V4L2_TUNER_CAP_LOW; 406c1c4fd3eSDouglas Landgraf v->rangelow = 8320; /* 520 kHz */ 407c1c4fd3eSDouglas Landgraf v->rangehigh = 26400; /* 1650 kHz */ 408c1c4fd3eSDouglas Landgraf v->rxsubchans = V4L2_TUNER_SUB_MONO; 409c1c4fd3eSDouglas Landgraf v->audmode = V4L2_TUNER_MODE_MONO; 410c0c7fa09SHans J. Koch break; 411c0c7fa09SHans J. Koch default: 4121da177e4SLinus Torvalds return -EINVAL; 4131da177e4SLinus Torvalds } 414c1c4fd3eSDouglas Landgraf v->signal = sigstrength; /* We might need to modify scaling of this */ 4151da177e4SLinus Torvalds return 0; 4161da177e4SLinus Torvalds } 417c1c4fd3eSDouglas Landgraf 418c1c4fd3eSDouglas Landgraf static int vidioc_s_tuner(struct file *file, void *priv, 419c1c4fd3eSDouglas Landgraf struct v4l2_tuner *v) 4201da177e4SLinus Torvalds { 421c1c4fd3eSDouglas Landgraf if((v->index != 0)&&(v->index != 1)) 422c0c7fa09SHans J. Koch return -EINVAL; 423c1c4fd3eSDouglas Landgraf curtuner = v->index; 4241da177e4SLinus Torvalds return 0; 4251da177e4SLinus Torvalds } 426c1c4fd3eSDouglas Landgraf 427c1c4fd3eSDouglas Landgraf static int vidioc_g_frequency(struct file *file, void *priv, 428c1c4fd3eSDouglas Landgraf struct v4l2_frequency *f) 4291da177e4SLinus Torvalds { 430c0c7fa09SHans J. Koch f->tuner = curtuner; 431c0c7fa09SHans J. Koch f->type = V4L2_TUNER_RADIO; 432c0c7fa09SHans J. Koch f->frequency = cadet_getfreq(); 433c0c7fa09SHans J. Koch return 0; 434c0c7fa09SHans J. Koch } 435c1c4fd3eSDouglas Landgraf 436c1c4fd3eSDouglas Landgraf 437c1c4fd3eSDouglas Landgraf static int vidioc_s_frequency(struct file *file, void *priv, 438c1c4fd3eSDouglas Landgraf struct v4l2_frequency *f) 439c0c7fa09SHans J. Koch { 440c1c4fd3eSDouglas Landgraf if (f->type != V4L2_TUNER_RADIO) 4411da177e4SLinus Torvalds return -EINVAL; 442c1c4fd3eSDouglas Landgraf if((curtuner==0)&&((f->frequency<1400)||(f->frequency>1728))) 4431da177e4SLinus Torvalds return -EINVAL; 444c1c4fd3eSDouglas Landgraf if((curtuner==1)&&((f->frequency<8320)||(f->frequency>26400))) 4451da177e4SLinus Torvalds return -EINVAL; 446c0c7fa09SHans J. Koch cadet_setfreq(f->frequency); 4471da177e4SLinus Torvalds return 0; 4481da177e4SLinus Torvalds } 449c1c4fd3eSDouglas Landgraf 450c1c4fd3eSDouglas Landgraf static int vidioc_queryctrl(struct file *file, void *priv, 451c1c4fd3eSDouglas Landgraf struct v4l2_queryctrl *qc) 452c0c7fa09SHans J. Koch { 453c1c4fd3eSDouglas Landgraf int i; 454c1c4fd3eSDouglas Landgraf 455c1c4fd3eSDouglas Landgraf for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { 456c1c4fd3eSDouglas Landgraf if (qc->id && qc->id == radio_qctrl[i].id) { 457c1c4fd3eSDouglas Landgraf memcpy(qc, &(radio_qctrl[i]), 458c1c4fd3eSDouglas Landgraf sizeof(*qc)); 459c0c7fa09SHans J. Koch return 0; 460c0c7fa09SHans J. Koch } 461c1c4fd3eSDouglas Landgraf } 462c1c4fd3eSDouglas Landgraf return -EINVAL; 463c1c4fd3eSDouglas Landgraf } 464c1c4fd3eSDouglas Landgraf 465c1c4fd3eSDouglas Landgraf static int vidioc_g_ctrl(struct file *file, void *priv, 466c1c4fd3eSDouglas Landgraf struct v4l2_control *ctrl) 467c0c7fa09SHans J. Koch { 468c1c4fd3eSDouglas Landgraf switch (ctrl->id){ 469c0c7fa09SHans J. Koch case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */ 470c1c4fd3eSDouglas Landgraf ctrl->value = (cadet_getvol() == 0); 471c0c7fa09SHans J. Koch break; 472c0c7fa09SHans J. Koch case V4L2_CID_AUDIO_VOLUME: 473c1c4fd3eSDouglas Landgraf ctrl->value = cadet_getvol(); 474c0c7fa09SHans J. Koch break; 475c0c7fa09SHans J. Koch default: 476c0c7fa09SHans J. Koch return -EINVAL; 477c0c7fa09SHans J. Koch } 478c0c7fa09SHans J. Koch return 0; 479c0c7fa09SHans J. Koch } 480c0c7fa09SHans J. Koch 481c1c4fd3eSDouglas Landgraf static int vidioc_s_ctrl(struct file *file, void *priv, 482c1c4fd3eSDouglas Landgraf struct v4l2_control *ctrl) 483c1c4fd3eSDouglas Landgraf { 484c1c4fd3eSDouglas Landgraf switch (ctrl->id){ 485c1c4fd3eSDouglas Landgraf case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */ 486c1c4fd3eSDouglas Landgraf if (ctrl->value) 487c1c4fd3eSDouglas Landgraf cadet_setvol(0); 488c1c4fd3eSDouglas Landgraf else 489c1c4fd3eSDouglas Landgraf cadet_setvol(0xffff); 490c1c4fd3eSDouglas Landgraf break; 491c1c4fd3eSDouglas Landgraf case V4L2_CID_AUDIO_VOLUME: 492c1c4fd3eSDouglas Landgraf cadet_setvol(ctrl->value); 493c1c4fd3eSDouglas Landgraf break; 4941da177e4SLinus Torvalds default: 495c1c4fd3eSDouglas Landgraf return -EINVAL; 4961da177e4SLinus Torvalds } 497c1c4fd3eSDouglas Landgraf return 0; 4981da177e4SLinus Torvalds } 4991da177e4SLinus Torvalds 500c1c4fd3eSDouglas Landgraf static int vidioc_g_audio(struct file *file, void *priv, 501c1c4fd3eSDouglas Landgraf struct v4l2_audio *a) 5021da177e4SLinus Torvalds { 503c1c4fd3eSDouglas Landgraf if (a->index > 1) 504c1c4fd3eSDouglas Landgraf return -EINVAL; 505c1c4fd3eSDouglas Landgraf strcpy(a->name, "Radio"); 506c1c4fd3eSDouglas Landgraf a->capability = V4L2_AUDCAP_STEREO; 507c1c4fd3eSDouglas Landgraf return 0; 508c1c4fd3eSDouglas Landgraf } 509c1c4fd3eSDouglas Landgraf 510c1c4fd3eSDouglas Landgraf static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) 511c1c4fd3eSDouglas Landgraf { 512c1c4fd3eSDouglas Landgraf *i = 0; 513c1c4fd3eSDouglas Landgraf return 0; 514c1c4fd3eSDouglas Landgraf } 515c1c4fd3eSDouglas Landgraf 516c1c4fd3eSDouglas Landgraf static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) 517c1c4fd3eSDouglas Landgraf { 518c1c4fd3eSDouglas Landgraf if (i != 0) 519c1c4fd3eSDouglas Landgraf return -EINVAL; 520c1c4fd3eSDouglas Landgraf return 0; 521c1c4fd3eSDouglas Landgraf } 522c1c4fd3eSDouglas Landgraf 523c1c4fd3eSDouglas Landgraf static int vidioc_s_audio(struct file *file, void *priv, 524c1c4fd3eSDouglas Landgraf struct v4l2_audio *a) 525c1c4fd3eSDouglas Landgraf { 526c1c4fd3eSDouglas Landgraf if (a->index != 0) 527c1c4fd3eSDouglas Landgraf return -EINVAL; 528c1c4fd3eSDouglas Landgraf return 0; 5291da177e4SLinus Torvalds } 5301da177e4SLinus Torvalds 531c0c7fa09SHans J. Koch static int 532c0c7fa09SHans J. Koch cadet_open(struct inode *inode, struct file *file) 5331da177e4SLinus Torvalds { 5341da177e4SLinus Torvalds users++; 535c0c7fa09SHans J. Koch if (1 == users) init_waitqueue_head(&read_queue); 5361da177e4SLinus Torvalds return 0; 5371da177e4SLinus Torvalds } 5381da177e4SLinus Torvalds 539c0c7fa09SHans J. Koch static int 540c0c7fa09SHans J. Koch cadet_release(struct inode *inode, struct file *file) 5411da177e4SLinus Torvalds { 542c0c7fa09SHans J. Koch users--; 543c0c7fa09SHans J. Koch if (0 == users){ 5441da177e4SLinus Torvalds del_timer_sync(&readtimer); 5451da177e4SLinus Torvalds rdsstat=0; 546c0c7fa09SHans J. Koch } 547c0c7fa09SHans J. Koch return 0; 548c0c7fa09SHans J. Koch } 549c0c7fa09SHans J. Koch 550c0c7fa09SHans J. Koch static unsigned int 551c0c7fa09SHans J. Koch cadet_poll(struct file *file, struct poll_table_struct *wait) 552c0c7fa09SHans J. Koch { 553c0c7fa09SHans J. Koch poll_wait(file,&read_queue,wait); 554c0c7fa09SHans J. Koch if(rdsin != rdsout) 555c0c7fa09SHans J. Koch return POLLIN | POLLRDNORM; 5561da177e4SLinus Torvalds return 0; 5571da177e4SLinus Torvalds } 5581da177e4SLinus Torvalds 5591da177e4SLinus Torvalds 560fa027c2aSArjan van de Ven static const struct file_operations cadet_fops = { 5611da177e4SLinus Torvalds .owner = THIS_MODULE, 5621da177e4SLinus Torvalds .open = cadet_open, 5631da177e4SLinus Torvalds .release = cadet_release, 5641da177e4SLinus Torvalds .read = cadet_read, 565c1c4fd3eSDouglas Landgraf .ioctl = video_ioctl2, 566c0c7fa09SHans J. Koch .poll = cadet_poll, 567078ff795SDouglas Schilling Landgraf #ifdef CONFIG_COMPAT 5680d0fbf81SArnd Bergmann .compat_ioctl = v4l_compat_ioctl32, 569078ff795SDouglas Schilling Landgraf #endif 5701da177e4SLinus Torvalds .llseek = no_llseek, 5711da177e4SLinus Torvalds }; 5721da177e4SLinus Torvalds 573a399810cSHans Verkuil static const struct v4l2_ioctl_ops cadet_ioctl_ops = { 574c1c4fd3eSDouglas Landgraf .vidioc_querycap = vidioc_querycap, 575c1c4fd3eSDouglas Landgraf .vidioc_g_tuner = vidioc_g_tuner, 576c1c4fd3eSDouglas Landgraf .vidioc_s_tuner = vidioc_s_tuner, 577c1c4fd3eSDouglas Landgraf .vidioc_g_frequency = vidioc_g_frequency, 578c1c4fd3eSDouglas Landgraf .vidioc_s_frequency = vidioc_s_frequency, 579c1c4fd3eSDouglas Landgraf .vidioc_queryctrl = vidioc_queryctrl, 580c1c4fd3eSDouglas Landgraf .vidioc_g_ctrl = vidioc_g_ctrl, 581c1c4fd3eSDouglas Landgraf .vidioc_s_ctrl = vidioc_s_ctrl, 582c1c4fd3eSDouglas Landgraf .vidioc_g_audio = vidioc_g_audio, 583c1c4fd3eSDouglas Landgraf .vidioc_s_audio = vidioc_s_audio, 584c1c4fd3eSDouglas Landgraf .vidioc_g_input = vidioc_g_input, 585c1c4fd3eSDouglas Landgraf .vidioc_s_input = vidioc_s_input, 5861da177e4SLinus Torvalds }; 5871da177e4SLinus Torvalds 588a399810cSHans Verkuil static struct video_device cadet_radio = { 589a399810cSHans Verkuil .name = "Cadet radio", 590a399810cSHans Verkuil .fops = &cadet_fops, 591a399810cSHans Verkuil .ioctl_ops = &cadet_ioctl_ops, 592aa5e90afSHans Verkuil .release = video_device_release_empty, 593a399810cSHans Verkuil }; 594a399810cSHans Verkuil 595044dfc99SBjorn Helgaas #ifdef CONFIG_PNP 596044dfc99SBjorn Helgaas 5971da177e4SLinus Torvalds static struct pnp_device_id cadet_pnp_devices[] = { 5981da177e4SLinus Torvalds /* ADS Cadet AM/FM Radio Card */ 5991da177e4SLinus Torvalds {.id = "MSM0c24", .driver_data = 0}, 6001da177e4SLinus Torvalds {.id = ""} 6011da177e4SLinus Torvalds }; 6021da177e4SLinus Torvalds 6031da177e4SLinus Torvalds MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices); 6041da177e4SLinus Torvalds 6051da177e4SLinus Torvalds static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id) 6061da177e4SLinus Torvalds { 6071da177e4SLinus Torvalds if (!dev) 6081da177e4SLinus Torvalds return -ENODEV; 6091da177e4SLinus Torvalds /* only support one device */ 6101da177e4SLinus Torvalds if (io > 0) 6111da177e4SLinus Torvalds return -EBUSY; 6121da177e4SLinus Torvalds 6131da177e4SLinus Torvalds if (!pnp_port_valid(dev, 0)) { 6141da177e4SLinus Torvalds return -ENODEV; 6151da177e4SLinus Torvalds } 6161da177e4SLinus Torvalds 6171da177e4SLinus Torvalds io = pnp_port_start(dev, 0); 6181da177e4SLinus Torvalds 6191da177e4SLinus Torvalds printk ("radio-cadet: PnP reports device at %#x\n", io); 6201da177e4SLinus Torvalds 6211da177e4SLinus Torvalds return io; 6221da177e4SLinus Torvalds } 6231da177e4SLinus Torvalds 6241da177e4SLinus Torvalds static struct pnp_driver cadet_pnp_driver = { 6251da177e4SLinus Torvalds .name = "radio-cadet", 6261da177e4SLinus Torvalds .id_table = cadet_pnp_devices, 6271da177e4SLinus Torvalds .probe = cadet_pnp_probe, 6281da177e4SLinus Torvalds .remove = NULL, 6291da177e4SLinus Torvalds }; 6301da177e4SLinus Torvalds 631044dfc99SBjorn Helgaas #else 632044dfc99SBjorn Helgaas static struct pnp_driver cadet_pnp_driver; 633044dfc99SBjorn Helgaas #endif 634044dfc99SBjorn Helgaas 6351da177e4SLinus Torvalds static int cadet_probe(void) 6361da177e4SLinus Torvalds { 6371da177e4SLinus Torvalds static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e}; 6381da177e4SLinus Torvalds int i; 6391da177e4SLinus Torvalds 6401da177e4SLinus Torvalds for(i=0;i<8;i++) { 6411da177e4SLinus Torvalds io=iovals[i]; 642f1ac046dSAlexey Dobriyan if (request_region(io, 2, "cadet-probe")) { 6431da177e4SLinus Torvalds cadet_setfreq(1410); 6441da177e4SLinus Torvalds if(cadet_getfreq()==1410) { 6451da177e4SLinus Torvalds release_region(io, 2); 6461da177e4SLinus Torvalds return io; 6471da177e4SLinus Torvalds } 6481da177e4SLinus Torvalds release_region(io, 2); 6491da177e4SLinus Torvalds } 6501da177e4SLinus Torvalds } 6511da177e4SLinus Torvalds return -1; 6521da177e4SLinus Torvalds } 6531da177e4SLinus Torvalds 6541da177e4SLinus Torvalds /* 6551da177e4SLinus Torvalds * io should only be set if the user has used something like 6561da177e4SLinus Torvalds * isapnp (the userspace program) to initialize this card for us 6571da177e4SLinus Torvalds */ 6581da177e4SLinus Torvalds 6591da177e4SLinus Torvalds static int __init cadet_init(void) 6601da177e4SLinus Torvalds { 6611da177e4SLinus Torvalds spin_lock_init(&cadet_io_lock); 6621da177e4SLinus Torvalds 6631da177e4SLinus Torvalds /* 6641da177e4SLinus Torvalds * If a probe was requested then probe ISAPnP first (safest) 6651da177e4SLinus Torvalds */ 6661da177e4SLinus Torvalds if (io < 0) 6671da177e4SLinus Torvalds pnp_register_driver(&cadet_pnp_driver); 6681da177e4SLinus Torvalds /* 6691da177e4SLinus Torvalds * If that fails then probe unsafely if probe is requested 6701da177e4SLinus Torvalds */ 6711da177e4SLinus Torvalds if(io < 0) 6721da177e4SLinus Torvalds io = cadet_probe (); 6731da177e4SLinus Torvalds 6741da177e4SLinus Torvalds /* 6751da177e4SLinus Torvalds * Else we bail out 6761da177e4SLinus Torvalds */ 6771da177e4SLinus Torvalds 6781da177e4SLinus Torvalds if(io < 0) { 6791da177e4SLinus Torvalds #ifdef MODULE 6801da177e4SLinus Torvalds printk(KERN_ERR "You must set an I/O address with io=0x???\n"); 6811da177e4SLinus Torvalds #endif 6821da177e4SLinus Torvalds goto fail; 6831da177e4SLinus Torvalds } 6841da177e4SLinus Torvalds if (!request_region(io,2,"cadet")) 6851da177e4SLinus Torvalds goto fail; 686cba99ae8SHans Verkuil if (video_register_device(&cadet_radio, VFL_TYPE_RADIO, radio_nr) < 0) { 6871da177e4SLinus Torvalds release_region(io,2); 6881da177e4SLinus Torvalds goto fail; 6891da177e4SLinus Torvalds } 6901da177e4SLinus Torvalds printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io); 6911da177e4SLinus Torvalds return 0; 6921da177e4SLinus Torvalds fail: 6931da177e4SLinus Torvalds pnp_unregister_driver(&cadet_pnp_driver); 6941da177e4SLinus Torvalds return -1; 6951da177e4SLinus Torvalds } 6961da177e4SLinus Torvalds 6971da177e4SLinus Torvalds 6981da177e4SLinus Torvalds 6991da177e4SLinus Torvalds MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); 7001da177e4SLinus Torvalds MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card."); 7011da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 7021da177e4SLinus Torvalds 7031da177e4SLinus Torvalds module_param(io, int, 0); 7041da177e4SLinus Torvalds MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)"); 7051da177e4SLinus Torvalds module_param(radio_nr, int, 0); 7061da177e4SLinus Torvalds 7071da177e4SLinus Torvalds static void __exit cadet_cleanup_module(void) 7081da177e4SLinus Torvalds { 7091da177e4SLinus Torvalds video_unregister_device(&cadet_radio); 7101da177e4SLinus Torvalds release_region(io,2); 7111da177e4SLinus Torvalds pnp_unregister_driver(&cadet_pnp_driver); 7121da177e4SLinus Torvalds } 7131da177e4SLinus Torvalds 7141da177e4SLinus Torvalds module_init(cadet_init); 7151da177e4SLinus Torvalds module_exit(cadet_cleanup_module); 7161da177e4SLinus Torvalds 717