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