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 */ 291da177e4SLinus Torvalds #include <linux/ioport.h> /* check_region, 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 */ 341da177e4SLinus Torvalds #include <linux/config.h> /* CONFIG_RADIO_AZTECH_PORT */ 351da177e4SLinus Torvalds 361da177e4SLinus Torvalds /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ 371da177e4SLinus Torvalds 381da177e4SLinus Torvalds #ifndef CONFIG_RADIO_AZTECH_PORT 391da177e4SLinus Torvalds #define CONFIG_RADIO_AZTECH_PORT -1 401da177e4SLinus Torvalds #endif 411da177e4SLinus Torvalds 421da177e4SLinus Torvalds static int io = CONFIG_RADIO_AZTECH_PORT; 431da177e4SLinus Torvalds static int radio_nr = -1; 441da177e4SLinus Torvalds static int radio_wait_time = 1000; 451da177e4SLinus Torvalds static struct semaphore lock; 461da177e4SLinus Torvalds 471da177e4SLinus Torvalds struct az_device 481da177e4SLinus Torvalds { 491da177e4SLinus Torvalds int curvol; 501da177e4SLinus Torvalds unsigned long curfreq; 511da177e4SLinus Torvalds int stereo; 521da177e4SLinus Torvalds }; 531da177e4SLinus Torvalds 541da177e4SLinus Torvalds static int volconvert(int level) 551da177e4SLinus Torvalds { 561da177e4SLinus Torvalds level>>=14; /* Map 16bits down to 2 bit */ 571da177e4SLinus Torvalds level&=3; 581da177e4SLinus Torvalds 591da177e4SLinus Torvalds /* convert to card-friendly values */ 601da177e4SLinus Torvalds switch (level) 611da177e4SLinus Torvalds { 621da177e4SLinus Torvalds case 0: 631da177e4SLinus Torvalds return 0; 641da177e4SLinus Torvalds case 1: 651da177e4SLinus Torvalds return 1; 661da177e4SLinus Torvalds case 2: 671da177e4SLinus Torvalds return 4; 681da177e4SLinus Torvalds case 3: 691da177e4SLinus Torvalds return 5; 701da177e4SLinus Torvalds } 711da177e4SLinus Torvalds return 0; /* Quieten gcc */ 721da177e4SLinus Torvalds } 731da177e4SLinus Torvalds 741da177e4SLinus Torvalds static void send_0_byte (struct az_device *dev) 751da177e4SLinus Torvalds { 761da177e4SLinus Torvalds udelay(radio_wait_time); 771da177e4SLinus Torvalds outb_p(2+volconvert(dev->curvol), io); 781da177e4SLinus Torvalds outb_p(64+2+volconvert(dev->curvol), io); 791da177e4SLinus Torvalds } 801da177e4SLinus Torvalds 811da177e4SLinus Torvalds static void send_1_byte (struct az_device *dev) 821da177e4SLinus Torvalds { 831da177e4SLinus Torvalds udelay (radio_wait_time); 841da177e4SLinus Torvalds outb_p(128+2+volconvert(dev->curvol), io); 851da177e4SLinus Torvalds outb_p(128+64+2+volconvert(dev->curvol), io); 861da177e4SLinus Torvalds } 871da177e4SLinus Torvalds 881da177e4SLinus Torvalds static int az_setvol(struct az_device *dev, int vol) 891da177e4SLinus Torvalds { 901da177e4SLinus Torvalds down(&lock); 911da177e4SLinus Torvalds outb (volconvert(vol), io); 921da177e4SLinus Torvalds up(&lock); 931da177e4SLinus Torvalds return 0; 941da177e4SLinus Torvalds } 951da177e4SLinus Torvalds 961da177e4SLinus Torvalds /* thanks to Michael Dwyer for giving me a dose of clues in 971da177e4SLinus Torvalds * the signal strength department.. 981da177e4SLinus Torvalds * 991da177e4SLinus Torvalds * This card has a stereo bit - bit 0 set = mono, not set = stereo 1001da177e4SLinus Torvalds * It also has a "signal" bit - bit 1 set = bad signal, not set = good 1011da177e4SLinus Torvalds * 1021da177e4SLinus Torvalds */ 1031da177e4SLinus Torvalds 1041da177e4SLinus Torvalds static int az_getsigstr(struct az_device *dev) 1051da177e4SLinus Torvalds { 1061da177e4SLinus Torvalds if (inb(io) & 2) /* bit set = no signal present */ 1071da177e4SLinus Torvalds return 0; 1081da177e4SLinus Torvalds return 1; /* signal present */ 1091da177e4SLinus Torvalds } 1101da177e4SLinus Torvalds 1111da177e4SLinus Torvalds static int az_getstereo(struct az_device *dev) 1121da177e4SLinus Torvalds { 1131da177e4SLinus Torvalds if (inb(io) & 1) /* bit set = mono */ 1141da177e4SLinus Torvalds return 0; 1151da177e4SLinus Torvalds return 1; /* stereo */ 1161da177e4SLinus Torvalds } 1171da177e4SLinus Torvalds 1181da177e4SLinus Torvalds static int az_setfreq(struct az_device *dev, unsigned long frequency) 1191da177e4SLinus Torvalds { 1201da177e4SLinus Torvalds int i; 1211da177e4SLinus Torvalds 1221da177e4SLinus Torvalds frequency += 171200; /* Add 10.7 MHz IF */ 1231da177e4SLinus Torvalds frequency /= 800; /* Convert to 50 kHz units */ 1241da177e4SLinus Torvalds 1251da177e4SLinus Torvalds down(&lock); 1261da177e4SLinus Torvalds 1271da177e4SLinus Torvalds send_0_byte (dev); /* 0: LSB of frequency */ 1281da177e4SLinus Torvalds 1291da177e4SLinus Torvalds for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ 1301da177e4SLinus Torvalds if (frequency & (1 << i)) 1311da177e4SLinus Torvalds send_1_byte (dev); 1321da177e4SLinus Torvalds else 1331da177e4SLinus Torvalds send_0_byte (dev); 1341da177e4SLinus Torvalds 1351da177e4SLinus Torvalds send_0_byte (dev); /* 14: test bit - always 0 */ 1361da177e4SLinus Torvalds send_0_byte (dev); /* 15: test bit - always 0 */ 1371da177e4SLinus Torvalds send_0_byte (dev); /* 16: band data 0 - always 0 */ 1381da177e4SLinus Torvalds if (dev->stereo) /* 17: stereo (1 to enable) */ 1391da177e4SLinus Torvalds send_1_byte (dev); 1401da177e4SLinus Torvalds else 1411da177e4SLinus Torvalds send_0_byte (dev); 1421da177e4SLinus Torvalds 1431da177e4SLinus Torvalds send_1_byte (dev); /* 18: band data 1 - unknown */ 1441da177e4SLinus Torvalds send_0_byte (dev); /* 19: time base - always 0 */ 1451da177e4SLinus Torvalds send_0_byte (dev); /* 20: spacing (0 = 25 kHz) */ 1461da177e4SLinus Torvalds send_1_byte (dev); /* 21: spacing (1 = 25 kHz) */ 1471da177e4SLinus Torvalds send_0_byte (dev); /* 22: spacing (0 = 25 kHz) */ 1481da177e4SLinus Torvalds send_1_byte (dev); /* 23: AM/FM (FM = 1, always) */ 1491da177e4SLinus Torvalds 1501da177e4SLinus Torvalds /* latch frequency */ 1511da177e4SLinus Torvalds 1521da177e4SLinus Torvalds udelay (radio_wait_time); 1531da177e4SLinus Torvalds outb_p(128+64+volconvert(dev->curvol), io); 1541da177e4SLinus Torvalds 1551da177e4SLinus Torvalds up(&lock); 1561da177e4SLinus Torvalds 1571da177e4SLinus Torvalds return 0; 1581da177e4SLinus Torvalds } 1591da177e4SLinus Torvalds 1601da177e4SLinus Torvalds static int az_do_ioctl(struct inode *inode, struct file *file, 1611da177e4SLinus Torvalds unsigned int cmd, void *arg) 1621da177e4SLinus Torvalds { 1631da177e4SLinus Torvalds struct video_device *dev = video_devdata(file); 1641da177e4SLinus Torvalds struct az_device *az = dev->priv; 1651da177e4SLinus Torvalds 1661da177e4SLinus Torvalds switch(cmd) 1671da177e4SLinus Torvalds { 1681da177e4SLinus Torvalds case VIDIOCGCAP: 1691da177e4SLinus Torvalds { 1701da177e4SLinus Torvalds struct video_capability *v = arg; 1711da177e4SLinus Torvalds memset(v,0,sizeof(*v)); 1721da177e4SLinus Torvalds v->type=VID_TYPE_TUNER; 1731da177e4SLinus Torvalds v->channels=1; 1741da177e4SLinus Torvalds v->audios=1; 1751da177e4SLinus Torvalds strcpy(v->name, "Aztech Radio"); 1761da177e4SLinus Torvalds return 0; 1771da177e4SLinus Torvalds } 1781da177e4SLinus Torvalds case VIDIOCGTUNER: 1791da177e4SLinus Torvalds { 1801da177e4SLinus Torvalds struct video_tuner *v = arg; 1811da177e4SLinus Torvalds if(v->tuner) /* Only 1 tuner */ 1821da177e4SLinus Torvalds return -EINVAL; 1831da177e4SLinus Torvalds v->rangelow=(87*16000); 1841da177e4SLinus Torvalds v->rangehigh=(108*16000); 1851da177e4SLinus Torvalds v->flags=VIDEO_TUNER_LOW; 1861da177e4SLinus Torvalds v->mode=VIDEO_MODE_AUTO; 1871da177e4SLinus Torvalds v->signal=0xFFFF*az_getsigstr(az); 1881da177e4SLinus Torvalds if(az_getstereo(az)) 1891da177e4SLinus Torvalds v->flags|=VIDEO_TUNER_STEREO_ON; 1901da177e4SLinus Torvalds strcpy(v->name, "FM"); 1911da177e4SLinus Torvalds return 0; 1921da177e4SLinus Torvalds } 1931da177e4SLinus Torvalds case VIDIOCSTUNER: 1941da177e4SLinus Torvalds { 1951da177e4SLinus Torvalds struct video_tuner *v = arg; 1961da177e4SLinus Torvalds if(v->tuner!=0) 1971da177e4SLinus Torvalds return -EINVAL; 1981da177e4SLinus Torvalds return 0; 1991da177e4SLinus Torvalds } 2001da177e4SLinus Torvalds case VIDIOCGFREQ: 2011da177e4SLinus Torvalds { 2021da177e4SLinus Torvalds unsigned long *freq = arg; 2031da177e4SLinus Torvalds *freq = az->curfreq; 2041da177e4SLinus Torvalds return 0; 2051da177e4SLinus Torvalds } 2061da177e4SLinus Torvalds case VIDIOCSFREQ: 2071da177e4SLinus Torvalds { 2081da177e4SLinus Torvalds unsigned long *freq = arg; 2091da177e4SLinus Torvalds az->curfreq = *freq; 2101da177e4SLinus Torvalds az_setfreq(az, az->curfreq); 2111da177e4SLinus Torvalds return 0; 2121da177e4SLinus Torvalds } 2131da177e4SLinus Torvalds case VIDIOCGAUDIO: 2141da177e4SLinus Torvalds { 2151da177e4SLinus Torvalds struct video_audio *v = arg; 2161da177e4SLinus Torvalds memset(v,0, sizeof(*v)); 2171da177e4SLinus Torvalds v->flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; 2181da177e4SLinus Torvalds if(az->stereo) 2191da177e4SLinus Torvalds v->mode=VIDEO_SOUND_STEREO; 2201da177e4SLinus Torvalds else 2211da177e4SLinus Torvalds v->mode=VIDEO_SOUND_MONO; 2221da177e4SLinus Torvalds v->volume=az->curvol; 2231da177e4SLinus Torvalds v->step=16384; 2241da177e4SLinus Torvalds strcpy(v->name, "Radio"); 2251da177e4SLinus Torvalds return 0; 2261da177e4SLinus Torvalds } 2271da177e4SLinus Torvalds case VIDIOCSAUDIO: 2281da177e4SLinus Torvalds { 2291da177e4SLinus Torvalds struct video_audio *v = arg; 2301da177e4SLinus Torvalds if(v->audio) 2311da177e4SLinus Torvalds return -EINVAL; 2321da177e4SLinus Torvalds az->curvol=v->volume; 2331da177e4SLinus Torvalds 2341da177e4SLinus Torvalds az->stereo=(v->mode&VIDEO_SOUND_STEREO)?1:0; 2351da177e4SLinus Torvalds if(v->flags&VIDEO_AUDIO_MUTE) 2361da177e4SLinus Torvalds az_setvol(az,0); 2371da177e4SLinus Torvalds else 2381da177e4SLinus Torvalds az_setvol(az,az->curvol); 2391da177e4SLinus Torvalds return 0; 2401da177e4SLinus Torvalds } 2411da177e4SLinus Torvalds default: 2421da177e4SLinus Torvalds return -ENOIOCTLCMD; 2431da177e4SLinus Torvalds } 2441da177e4SLinus Torvalds } 2451da177e4SLinus Torvalds 2461da177e4SLinus Torvalds static int az_ioctl(struct inode *inode, struct file *file, 2471da177e4SLinus Torvalds unsigned int cmd, unsigned long arg) 2481da177e4SLinus Torvalds { 2491da177e4SLinus Torvalds return video_usercopy(inode, file, cmd, arg, az_do_ioctl); 2501da177e4SLinus Torvalds } 2511da177e4SLinus Torvalds 2521da177e4SLinus Torvalds static struct az_device aztech_unit; 2531da177e4SLinus Torvalds 2541da177e4SLinus Torvalds static struct file_operations aztech_fops = { 2551da177e4SLinus Torvalds .owner = THIS_MODULE, 2561da177e4SLinus Torvalds .open = video_exclusive_open, 2571da177e4SLinus Torvalds .release = video_exclusive_release, 2581da177e4SLinus Torvalds .ioctl = az_ioctl, 2591da177e4SLinus Torvalds .llseek = no_llseek, 2601da177e4SLinus Torvalds }; 2611da177e4SLinus Torvalds 2621da177e4SLinus Torvalds static struct video_device aztech_radio= 2631da177e4SLinus Torvalds { 2641da177e4SLinus Torvalds .owner = THIS_MODULE, 2651da177e4SLinus Torvalds .name = "Aztech radio", 2661da177e4SLinus Torvalds .type = VID_TYPE_TUNER, 2671da177e4SLinus Torvalds .hardware = VID_HARDWARE_AZTECH, 2681da177e4SLinus Torvalds .fops = &aztech_fops, 2691da177e4SLinus Torvalds }; 2701da177e4SLinus Torvalds 2711da177e4SLinus Torvalds static int __init aztech_init(void) 2721da177e4SLinus Torvalds { 2731da177e4SLinus Torvalds if(io==-1) 2741da177e4SLinus Torvalds { 2751da177e4SLinus Torvalds printk(KERN_ERR "You must set an I/O address with io=0x???\n"); 2761da177e4SLinus Torvalds return -EINVAL; 2771da177e4SLinus Torvalds } 2781da177e4SLinus Torvalds 2791da177e4SLinus Torvalds if (!request_region(io, 2, "aztech")) 2801da177e4SLinus Torvalds { 2811da177e4SLinus Torvalds printk(KERN_ERR "aztech: port 0x%x already in use\n", io); 2821da177e4SLinus Torvalds return -EBUSY; 2831da177e4SLinus Torvalds } 2841da177e4SLinus Torvalds 2851da177e4SLinus Torvalds init_MUTEX(&lock); 2861da177e4SLinus Torvalds aztech_radio.priv=&aztech_unit; 2871da177e4SLinus Torvalds 2881da177e4SLinus Torvalds if(video_register_device(&aztech_radio, VFL_TYPE_RADIO, radio_nr)==-1) 2891da177e4SLinus Torvalds { 2901da177e4SLinus Torvalds release_region(io,2); 2911da177e4SLinus Torvalds return -EINVAL; 2921da177e4SLinus Torvalds } 2931da177e4SLinus Torvalds 2941da177e4SLinus Torvalds printk(KERN_INFO "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n"); 2951da177e4SLinus Torvalds /* mute card - prevents noisy bootups */ 2961da177e4SLinus Torvalds outb (0, io); 2971da177e4SLinus Torvalds return 0; 2981da177e4SLinus Torvalds } 2991da177e4SLinus Torvalds 3001da177e4SLinus Torvalds MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); 3011da177e4SLinus Torvalds MODULE_DESCRIPTION("A driver for the Aztech radio card."); 3021da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 3031da177e4SLinus Torvalds 3041da177e4SLinus Torvalds module_param(io, int, 0); 3051da177e4SLinus Torvalds module_param(radio_nr, int, 0); 3061da177e4SLinus Torvalds MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)"); 3071da177e4SLinus Torvalds 3081da177e4SLinus Torvalds static void __exit aztech_cleanup(void) 3091da177e4SLinus Torvalds { 3101da177e4SLinus Torvalds video_unregister_device(&aztech_radio); 3111da177e4SLinus Torvalds release_region(io,2); 3121da177e4SLinus Torvalds } 3131da177e4SLinus Torvalds 3141da177e4SLinus Torvalds module_init(aztech_init); 3151da177e4SLinus Torvalds module_exit(aztech_cleanup); 316