11da177e4SLinus Torvalds /* radio-aztech.c - Aztech radio card driver for Linux 2.2 21da177e4SLinus Torvalds * 31da177e4SLinus Torvalds * Adapted to support the Video for Linux API by 41da177e4SLinus Torvalds * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: 51da177e4SLinus Torvalds * 61da177e4SLinus Torvalds * Quay Ly 71da177e4SLinus Torvalds * Donald Song 81da177e4SLinus Torvalds * Jason Lewis (jlewis@twilight.vtc.vsc.edu) 91da177e4SLinus Torvalds * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) 101da177e4SLinus Torvalds * William McGrath (wmcgrath@twilight.vtc.vsc.edu) 111da177e4SLinus Torvalds * 121da177e4SLinus Torvalds * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/ 131da177e4SLinus Torvalds * along with more information on the card itself. 141da177e4SLinus Torvalds * 151da177e4SLinus Torvalds * History: 161da177e4SLinus Torvalds * 1999-02-24 Russell Kroll <rkroll@exploits.org> 171da177e4SLinus Torvalds * Fine tuning/VIDEO_TUNER_LOW 181da177e4SLinus Torvalds * Range expanded to 87-108 MHz (from 87.9-107.8) 191da177e4SLinus Torvalds * 201da177e4SLinus Torvalds * Notable changes from the original source: 211da177e4SLinus Torvalds * - includes stripped down to the essentials 221da177e4SLinus Torvalds * - for loops used as delays replaced with udelay() 231da177e4SLinus Torvalds * - #defines removed, changed to static values 241da177e4SLinus Torvalds * - tuning structure changed - no more character arrays, other changes 251da177e4SLinus Torvalds */ 261da177e4SLinus Torvalds 271da177e4SLinus Torvalds #include <linux/module.h> /* Modules */ 281da177e4SLinus Torvalds #include <linux/init.h> /* Initdata */ 29fb911ee8SPeter Osterlund #include <linux/ioport.h> /* request_region */ 301da177e4SLinus Torvalds #include <linux/delay.h> /* udelay */ 311da177e4SLinus Torvalds #include <asm/io.h> /* outb, outb_p */ 321da177e4SLinus Torvalds #include <asm/uaccess.h> /* copy to/from user */ 331da177e4SLinus Torvalds #include <linux/videodev.h> /* kernel radio structs */ 345e87efa3SMauro Carvalho Chehab #include <media/v4l2-common.h> 351da177e4SLinus Torvalds #include <linux/config.h> /* CONFIG_RADIO_AZTECH_PORT */ 361da177e4SLinus Torvalds 371da177e4SLinus Torvalds /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ 381da177e4SLinus Torvalds 391da177e4SLinus Torvalds #ifndef CONFIG_RADIO_AZTECH_PORT 401da177e4SLinus Torvalds #define CONFIG_RADIO_AZTECH_PORT -1 411da177e4SLinus Torvalds #endif 421da177e4SLinus Torvalds 431da177e4SLinus Torvalds static int io = CONFIG_RADIO_AZTECH_PORT; 441da177e4SLinus Torvalds static int radio_nr = -1; 451da177e4SLinus Torvalds static int radio_wait_time = 1000; 463593cab5SIngo Molnar static struct mutex lock; 471da177e4SLinus Torvalds 481da177e4SLinus Torvalds struct az_device 491da177e4SLinus Torvalds { 501da177e4SLinus Torvalds int curvol; 511da177e4SLinus Torvalds unsigned long curfreq; 521da177e4SLinus Torvalds int stereo; 531da177e4SLinus Torvalds }; 541da177e4SLinus Torvalds 551da177e4SLinus Torvalds static int volconvert(int level) 561da177e4SLinus Torvalds { 571da177e4SLinus Torvalds level>>=14; /* Map 16bits down to 2 bit */ 581da177e4SLinus Torvalds level&=3; 591da177e4SLinus Torvalds 601da177e4SLinus Torvalds /* convert to card-friendly values */ 611da177e4SLinus Torvalds switch (level) 621da177e4SLinus Torvalds { 631da177e4SLinus Torvalds case 0: 641da177e4SLinus Torvalds return 0; 651da177e4SLinus Torvalds case 1: 661da177e4SLinus Torvalds return 1; 671da177e4SLinus Torvalds case 2: 681da177e4SLinus Torvalds return 4; 691da177e4SLinus Torvalds case 3: 701da177e4SLinus Torvalds return 5; 711da177e4SLinus Torvalds } 721da177e4SLinus Torvalds return 0; /* Quieten gcc */ 731da177e4SLinus Torvalds } 741da177e4SLinus Torvalds 751da177e4SLinus Torvalds static void send_0_byte (struct az_device *dev) 761da177e4SLinus Torvalds { 771da177e4SLinus Torvalds udelay(radio_wait_time); 781da177e4SLinus Torvalds outb_p(2+volconvert(dev->curvol), io); 791da177e4SLinus Torvalds outb_p(64+2+volconvert(dev->curvol), io); 801da177e4SLinus Torvalds } 811da177e4SLinus Torvalds 821da177e4SLinus Torvalds static void send_1_byte (struct az_device *dev) 831da177e4SLinus Torvalds { 841da177e4SLinus Torvalds udelay (radio_wait_time); 851da177e4SLinus Torvalds outb_p(128+2+volconvert(dev->curvol), io); 861da177e4SLinus Torvalds outb_p(128+64+2+volconvert(dev->curvol), io); 871da177e4SLinus Torvalds } 881da177e4SLinus Torvalds 891da177e4SLinus Torvalds static int az_setvol(struct az_device *dev, int vol) 901da177e4SLinus Torvalds { 913593cab5SIngo Molnar mutex_lock(&lock); 921da177e4SLinus Torvalds outb (volconvert(vol), io); 933593cab5SIngo Molnar mutex_unlock(&lock); 941da177e4SLinus Torvalds return 0; 951da177e4SLinus Torvalds } 961da177e4SLinus Torvalds 971da177e4SLinus Torvalds /* thanks to Michael Dwyer for giving me a dose of clues in 981da177e4SLinus Torvalds * the signal strength department.. 991da177e4SLinus Torvalds * 1001da177e4SLinus Torvalds * This card has a stereo bit - bit 0 set = mono, not set = stereo 1011da177e4SLinus Torvalds * It also has a "signal" bit - bit 1 set = bad signal, not set = good 1021da177e4SLinus Torvalds * 1031da177e4SLinus Torvalds */ 1041da177e4SLinus Torvalds 1051da177e4SLinus Torvalds static int az_getsigstr(struct az_device *dev) 1061da177e4SLinus Torvalds { 1071da177e4SLinus Torvalds if (inb(io) & 2) /* bit set = no signal present */ 1081da177e4SLinus Torvalds return 0; 1091da177e4SLinus Torvalds return 1; /* signal present */ 1101da177e4SLinus Torvalds } 1111da177e4SLinus Torvalds 1121da177e4SLinus Torvalds static int az_getstereo(struct az_device *dev) 1131da177e4SLinus Torvalds { 1141da177e4SLinus Torvalds if (inb(io) & 1) /* bit set = mono */ 1151da177e4SLinus Torvalds return 0; 1161da177e4SLinus Torvalds return 1; /* stereo */ 1171da177e4SLinus Torvalds } 1181da177e4SLinus Torvalds 1191da177e4SLinus Torvalds static int az_setfreq(struct az_device *dev, unsigned long frequency) 1201da177e4SLinus Torvalds { 1211da177e4SLinus Torvalds int i; 1221da177e4SLinus Torvalds 1231da177e4SLinus Torvalds frequency += 171200; /* Add 10.7 MHz IF */ 1241da177e4SLinus Torvalds frequency /= 800; /* Convert to 50 kHz units */ 1251da177e4SLinus Torvalds 1263593cab5SIngo Molnar mutex_lock(&lock); 1271da177e4SLinus Torvalds 1281da177e4SLinus Torvalds send_0_byte (dev); /* 0: LSB of frequency */ 1291da177e4SLinus Torvalds 1301da177e4SLinus Torvalds for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ 1311da177e4SLinus Torvalds if (frequency & (1 << i)) 1321da177e4SLinus Torvalds send_1_byte (dev); 1331da177e4SLinus Torvalds else 1341da177e4SLinus Torvalds send_0_byte (dev); 1351da177e4SLinus Torvalds 1361da177e4SLinus Torvalds send_0_byte (dev); /* 14: test bit - always 0 */ 1371da177e4SLinus Torvalds send_0_byte (dev); /* 15: test bit - always 0 */ 1381da177e4SLinus Torvalds send_0_byte (dev); /* 16: band data 0 - always 0 */ 1391da177e4SLinus Torvalds if (dev->stereo) /* 17: stereo (1 to enable) */ 1401da177e4SLinus Torvalds send_1_byte (dev); 1411da177e4SLinus Torvalds else 1421da177e4SLinus Torvalds send_0_byte (dev); 1431da177e4SLinus Torvalds 1441da177e4SLinus Torvalds send_1_byte (dev); /* 18: band data 1 - unknown */ 1451da177e4SLinus Torvalds send_0_byte (dev); /* 19: time base - always 0 */ 1461da177e4SLinus Torvalds send_0_byte (dev); /* 20: spacing (0 = 25 kHz) */ 1471da177e4SLinus Torvalds send_1_byte (dev); /* 21: spacing (1 = 25 kHz) */ 1481da177e4SLinus Torvalds send_0_byte (dev); /* 22: spacing (0 = 25 kHz) */ 1491da177e4SLinus Torvalds send_1_byte (dev); /* 23: AM/FM (FM = 1, always) */ 1501da177e4SLinus Torvalds 1511da177e4SLinus Torvalds /* latch frequency */ 1521da177e4SLinus Torvalds 1531da177e4SLinus Torvalds udelay (radio_wait_time); 1541da177e4SLinus Torvalds outb_p(128+64+volconvert(dev->curvol), io); 1551da177e4SLinus Torvalds 1563593cab5SIngo Molnar mutex_unlock(&lock); 1571da177e4SLinus Torvalds 1581da177e4SLinus Torvalds return 0; 1591da177e4SLinus Torvalds } 1601da177e4SLinus Torvalds 1611da177e4SLinus Torvalds static int az_do_ioctl(struct inode *inode, struct file *file, 1621da177e4SLinus Torvalds unsigned int cmd, void *arg) 1631da177e4SLinus Torvalds { 1641da177e4SLinus Torvalds struct video_device *dev = video_devdata(file); 1651da177e4SLinus Torvalds struct az_device *az = dev->priv; 1661da177e4SLinus Torvalds 1671da177e4SLinus Torvalds switch(cmd) 1681da177e4SLinus Torvalds { 1691da177e4SLinus Torvalds case VIDIOCGCAP: 1701da177e4SLinus Torvalds { 1711da177e4SLinus Torvalds struct video_capability *v = arg; 1721da177e4SLinus Torvalds memset(v,0,sizeof(*v)); 1731da177e4SLinus Torvalds v->type=VID_TYPE_TUNER; 1741da177e4SLinus Torvalds v->channels=1; 1751da177e4SLinus Torvalds v->audios=1; 1761da177e4SLinus Torvalds strcpy(v->name, "Aztech Radio"); 1771da177e4SLinus Torvalds return 0; 1781da177e4SLinus Torvalds } 1791da177e4SLinus Torvalds case VIDIOCGTUNER: 1801da177e4SLinus Torvalds { 1811da177e4SLinus Torvalds struct video_tuner *v = arg; 1821da177e4SLinus Torvalds if(v->tuner) /* Only 1 tuner */ 1831da177e4SLinus Torvalds return -EINVAL; 1841da177e4SLinus Torvalds v->rangelow=(87*16000); 1851da177e4SLinus Torvalds v->rangehigh=(108*16000); 1861da177e4SLinus Torvalds v->flags=VIDEO_TUNER_LOW; 1871da177e4SLinus Torvalds v->mode=VIDEO_MODE_AUTO; 1881da177e4SLinus Torvalds v->signal=0xFFFF*az_getsigstr(az); 1891da177e4SLinus Torvalds if(az_getstereo(az)) 1901da177e4SLinus Torvalds v->flags|=VIDEO_TUNER_STEREO_ON; 1911da177e4SLinus Torvalds strcpy(v->name, "FM"); 1921da177e4SLinus Torvalds return 0; 1931da177e4SLinus Torvalds } 1941da177e4SLinus Torvalds case VIDIOCSTUNER: 1951da177e4SLinus Torvalds { 1961da177e4SLinus Torvalds struct video_tuner *v = arg; 1971da177e4SLinus Torvalds if(v->tuner!=0) 1981da177e4SLinus Torvalds return -EINVAL; 1991da177e4SLinus Torvalds return 0; 2001da177e4SLinus Torvalds } 2011da177e4SLinus Torvalds case VIDIOCGFREQ: 2021da177e4SLinus Torvalds { 2031da177e4SLinus Torvalds unsigned long *freq = arg; 2041da177e4SLinus Torvalds *freq = az->curfreq; 2051da177e4SLinus Torvalds return 0; 2061da177e4SLinus Torvalds } 2071da177e4SLinus Torvalds case VIDIOCSFREQ: 2081da177e4SLinus Torvalds { 2091da177e4SLinus Torvalds unsigned long *freq = arg; 2101da177e4SLinus Torvalds az->curfreq = *freq; 2111da177e4SLinus Torvalds az_setfreq(az, az->curfreq); 2121da177e4SLinus Torvalds return 0; 2131da177e4SLinus Torvalds } 2141da177e4SLinus Torvalds case VIDIOCGAUDIO: 2151da177e4SLinus Torvalds { 2161da177e4SLinus Torvalds struct video_audio *v = arg; 2171da177e4SLinus Torvalds memset(v,0, sizeof(*v)); 2181da177e4SLinus Torvalds v->flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; 2191da177e4SLinus Torvalds if(az->stereo) 2201da177e4SLinus Torvalds v->mode=VIDEO_SOUND_STEREO; 2211da177e4SLinus Torvalds else 2221da177e4SLinus Torvalds v->mode=VIDEO_SOUND_MONO; 2231da177e4SLinus Torvalds v->volume=az->curvol; 2241da177e4SLinus Torvalds v->step=16384; 2251da177e4SLinus Torvalds strcpy(v->name, "Radio"); 2261da177e4SLinus Torvalds return 0; 2271da177e4SLinus Torvalds } 2281da177e4SLinus Torvalds case VIDIOCSAUDIO: 2291da177e4SLinus Torvalds { 2301da177e4SLinus Torvalds struct video_audio *v = arg; 2311da177e4SLinus Torvalds if(v->audio) 2321da177e4SLinus Torvalds return -EINVAL; 2331da177e4SLinus Torvalds az->curvol=v->volume; 2341da177e4SLinus Torvalds 2351da177e4SLinus Torvalds az->stereo=(v->mode&VIDEO_SOUND_STEREO)?1:0; 2361da177e4SLinus Torvalds if(v->flags&VIDEO_AUDIO_MUTE) 2371da177e4SLinus Torvalds az_setvol(az,0); 2381da177e4SLinus Torvalds else 2391da177e4SLinus Torvalds az_setvol(az,az->curvol); 2401da177e4SLinus Torvalds return 0; 2411da177e4SLinus Torvalds } 2421da177e4SLinus Torvalds default: 2431da177e4SLinus Torvalds return -ENOIOCTLCMD; 2441da177e4SLinus Torvalds } 2451da177e4SLinus Torvalds } 2461da177e4SLinus Torvalds 2471da177e4SLinus Torvalds static int az_ioctl(struct inode *inode, struct file *file, 2481da177e4SLinus Torvalds unsigned int cmd, unsigned long arg) 2491da177e4SLinus Torvalds { 2501da177e4SLinus Torvalds return video_usercopy(inode, file, cmd, arg, az_do_ioctl); 2511da177e4SLinus Torvalds } 2521da177e4SLinus Torvalds 2531da177e4SLinus Torvalds static struct az_device aztech_unit; 2541da177e4SLinus Torvalds 2551da177e4SLinus Torvalds static struct file_operations aztech_fops = { 2561da177e4SLinus Torvalds .owner = THIS_MODULE, 2571da177e4SLinus Torvalds .open = video_exclusive_open, 2581da177e4SLinus Torvalds .release = video_exclusive_release, 2591da177e4SLinus Torvalds .ioctl = az_ioctl, 2600d0fbf81SArnd Bergmann .compat_ioctl = v4l_compat_ioctl32, 2611da177e4SLinus Torvalds .llseek = no_llseek, 2621da177e4SLinus Torvalds }; 2631da177e4SLinus Torvalds 2641da177e4SLinus Torvalds static struct video_device aztech_radio= 2651da177e4SLinus Torvalds { 2661da177e4SLinus Torvalds .owner = THIS_MODULE, 2671da177e4SLinus Torvalds .name = "Aztech radio", 2681da177e4SLinus Torvalds .type = VID_TYPE_TUNER, 2691da177e4SLinus Torvalds .hardware = VID_HARDWARE_AZTECH, 2701da177e4SLinus Torvalds .fops = &aztech_fops, 2711da177e4SLinus Torvalds }; 2721da177e4SLinus Torvalds 2731da177e4SLinus Torvalds static int __init aztech_init(void) 2741da177e4SLinus Torvalds { 2751da177e4SLinus Torvalds if(io==-1) 2761da177e4SLinus Torvalds { 2771da177e4SLinus Torvalds printk(KERN_ERR "You must set an I/O address with io=0x???\n"); 2781da177e4SLinus Torvalds return -EINVAL; 2791da177e4SLinus Torvalds } 2801da177e4SLinus Torvalds 2811da177e4SLinus Torvalds if (!request_region(io, 2, "aztech")) 2821da177e4SLinus Torvalds { 2831da177e4SLinus Torvalds printk(KERN_ERR "aztech: port 0x%x already in use\n", io); 2841da177e4SLinus Torvalds return -EBUSY; 2851da177e4SLinus Torvalds } 2861da177e4SLinus Torvalds 2873593cab5SIngo Molnar mutex_init(&lock); 2881da177e4SLinus Torvalds aztech_radio.priv=&aztech_unit; 2891da177e4SLinus Torvalds 2901da177e4SLinus Torvalds if(video_register_device(&aztech_radio, VFL_TYPE_RADIO, radio_nr)==-1) 2911da177e4SLinus Torvalds { 2921da177e4SLinus Torvalds release_region(io,2); 2931da177e4SLinus Torvalds return -EINVAL; 2941da177e4SLinus Torvalds } 2951da177e4SLinus Torvalds 2961da177e4SLinus Torvalds printk(KERN_INFO "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n"); 2971da177e4SLinus Torvalds /* mute card - prevents noisy bootups */ 2981da177e4SLinus Torvalds outb (0, io); 2991da177e4SLinus Torvalds return 0; 3001da177e4SLinus Torvalds } 3011da177e4SLinus Torvalds 3021da177e4SLinus Torvalds MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); 3031da177e4SLinus Torvalds MODULE_DESCRIPTION("A driver for the Aztech radio card."); 3041da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 3051da177e4SLinus Torvalds 3061da177e4SLinus Torvalds module_param(io, int, 0); 3071da177e4SLinus Torvalds module_param(radio_nr, int, 0); 3081da177e4SLinus Torvalds MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)"); 3091da177e4SLinus Torvalds 3101da177e4SLinus Torvalds static void __exit aztech_cleanup(void) 3111da177e4SLinus Torvalds { 3121da177e4SLinus Torvalds video_unregister_device(&aztech_radio); 3131da177e4SLinus Torvalds release_region(io,2); 3141da177e4SLinus Torvalds } 3151da177e4SLinus Torvalds 3161da177e4SLinus Torvalds module_init(aztech_init); 3171da177e4SLinus Torvalds module_exit(aztech_cleanup); 318