11da177e4SLinus Torvalds /* SF16FMI radio driver for Linux radio support 21da177e4SLinus Torvalds * heavily based on rtrack driver... 31da177e4SLinus Torvalds * (c) 1997 M. Kirkwood 41da177e4SLinus Torvalds * (c) 1998 Petr Vandrovec, vandrove@vc.cvut.cz 51da177e4SLinus Torvalds * 61da177e4SLinus Torvalds * Fitted to new interface by Alan Cox <alan.cox@linux.org> 71da177e4SLinus Torvalds * Made working and cleaned up functions <mikael.hedin@irf.se> 81da177e4SLinus Torvalds * Support for ISAPnP by Ladislav Michl <ladis@psi.cz> 91da177e4SLinus Torvalds * 101da177e4SLinus Torvalds * Notes on the hardware 111da177e4SLinus Torvalds * 121da177e4SLinus Torvalds * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); 131da177e4SLinus Torvalds * No volume control - only mute/unmute - you have to use line volume 141da177e4SLinus Torvalds * control on SB-part of SF16FMI 151da177e4SLinus Torvalds * 161da177e4SLinus Torvalds */ 171da177e4SLinus Torvalds 181da177e4SLinus Torvalds #include <linux/kernel.h> /* __setup */ 191da177e4SLinus Torvalds #include <linux/module.h> /* Modules */ 201da177e4SLinus Torvalds #include <linux/init.h> /* Initdata */ 21fb911ee8SPeter Osterlund #include <linux/ioport.h> /* request_region */ 221da177e4SLinus Torvalds #include <linux/delay.h> /* udelay */ 231da177e4SLinus Torvalds #include <linux/videodev.h> /* kernel radio structs */ 241da177e4SLinus Torvalds #include <linux/isapnp.h> 251da177e4SLinus Torvalds #include <asm/io.h> /* outb, outb_p */ 261da177e4SLinus Torvalds #include <asm/uaccess.h> /* copy to/from user */ 27*3593cab5SIngo Molnar #include <linux/mutex.h> 281da177e4SLinus Torvalds 291da177e4SLinus Torvalds struct fmi_device 301da177e4SLinus Torvalds { 311da177e4SLinus Torvalds int port; 321da177e4SLinus Torvalds int curvol; /* 1 or 0 */ 331da177e4SLinus Torvalds unsigned long curfreq; /* freq in kHz */ 341da177e4SLinus Torvalds __u32 flags; 351da177e4SLinus Torvalds }; 361da177e4SLinus Torvalds 371da177e4SLinus Torvalds static int io = -1; 381da177e4SLinus Torvalds static int radio_nr = -1; 391da177e4SLinus Torvalds static struct pnp_dev *dev = NULL; 40*3593cab5SIngo Molnar static struct mutex lock; 411da177e4SLinus Torvalds 421da177e4SLinus Torvalds /* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */ 431da177e4SLinus Torvalds /* It is only useful to give freq in intervall of 800 (=0.05Mhz), 441da177e4SLinus Torvalds * other bits will be truncated, e.g 92.7400016 -> 92.7, but 451da177e4SLinus Torvalds * 92.7400017 -> 92.75 461da177e4SLinus Torvalds */ 471da177e4SLinus Torvalds #define RSF16_ENCODE(x) ((x)/800+214) 481da177e4SLinus Torvalds #define RSF16_MINFREQ 87*16000 491da177e4SLinus Torvalds #define RSF16_MAXFREQ 108*16000 501da177e4SLinus Torvalds 511da177e4SLinus Torvalds static void outbits(int bits, unsigned int data, int port) 521da177e4SLinus Torvalds { 531da177e4SLinus Torvalds while(bits--) { 541da177e4SLinus Torvalds if(data & 1) { 551da177e4SLinus Torvalds outb(5, port); 561da177e4SLinus Torvalds udelay(6); 571da177e4SLinus Torvalds outb(7, port); 581da177e4SLinus Torvalds udelay(6); 591da177e4SLinus Torvalds } else { 601da177e4SLinus Torvalds outb(1, port); 611da177e4SLinus Torvalds udelay(6); 621da177e4SLinus Torvalds outb(3, port); 631da177e4SLinus Torvalds udelay(6); 641da177e4SLinus Torvalds } 651da177e4SLinus Torvalds data>>=1; 661da177e4SLinus Torvalds } 671da177e4SLinus Torvalds } 681da177e4SLinus Torvalds 691da177e4SLinus Torvalds static inline void fmi_mute(int port) 701da177e4SLinus Torvalds { 71*3593cab5SIngo Molnar mutex_lock(&lock); 721da177e4SLinus Torvalds outb(0x00, port); 73*3593cab5SIngo Molnar mutex_unlock(&lock); 741da177e4SLinus Torvalds } 751da177e4SLinus Torvalds 761da177e4SLinus Torvalds static inline void fmi_unmute(int port) 771da177e4SLinus Torvalds { 78*3593cab5SIngo Molnar mutex_lock(&lock); 791da177e4SLinus Torvalds outb(0x08, port); 80*3593cab5SIngo Molnar mutex_unlock(&lock); 811da177e4SLinus Torvalds } 821da177e4SLinus Torvalds 831da177e4SLinus Torvalds static inline int fmi_setfreq(struct fmi_device *dev) 841da177e4SLinus Torvalds { 851da177e4SLinus Torvalds int myport = dev->port; 861da177e4SLinus Torvalds unsigned long freq = dev->curfreq; 871da177e4SLinus Torvalds 88*3593cab5SIngo Molnar mutex_lock(&lock); 891da177e4SLinus Torvalds 901da177e4SLinus Torvalds outbits(16, RSF16_ENCODE(freq), myport); 911da177e4SLinus Torvalds outbits(8, 0xC0, myport); 921da177e4SLinus Torvalds msleep(143); /* was schedule_timeout(HZ/7) */ 93*3593cab5SIngo Molnar mutex_unlock(&lock); 941da177e4SLinus Torvalds if (dev->curvol) fmi_unmute(myport); 951da177e4SLinus Torvalds return 0; 961da177e4SLinus Torvalds } 971da177e4SLinus Torvalds 981da177e4SLinus Torvalds static inline int fmi_getsigstr(struct fmi_device *dev) 991da177e4SLinus Torvalds { 1001da177e4SLinus Torvalds int val; 1011da177e4SLinus Torvalds int res; 1021da177e4SLinus Torvalds int myport = dev->port; 1031da177e4SLinus Torvalds 1041da177e4SLinus Torvalds 105*3593cab5SIngo Molnar mutex_lock(&lock); 1061da177e4SLinus Torvalds val = dev->curvol ? 0x08 : 0x00; /* unmute/mute */ 1071da177e4SLinus Torvalds outb(val, myport); 1081da177e4SLinus Torvalds outb(val | 0x10, myport); 1091da177e4SLinus Torvalds msleep(143); /* was schedule_timeout(HZ/7) */ 1101da177e4SLinus Torvalds res = (int)inb(myport+1); 1111da177e4SLinus Torvalds outb(val, myport); 1121da177e4SLinus Torvalds 113*3593cab5SIngo Molnar mutex_unlock(&lock); 1141da177e4SLinus Torvalds return (res & 2) ? 0 : 0xFFFF; 1151da177e4SLinus Torvalds } 1161da177e4SLinus Torvalds 1171da177e4SLinus Torvalds static int fmi_do_ioctl(struct inode *inode, struct file *file, 1181da177e4SLinus Torvalds unsigned int cmd, void *arg) 1191da177e4SLinus Torvalds { 1201da177e4SLinus Torvalds struct video_device *dev = video_devdata(file); 1211da177e4SLinus Torvalds struct fmi_device *fmi=dev->priv; 1221da177e4SLinus Torvalds 1231da177e4SLinus Torvalds switch(cmd) 1241da177e4SLinus Torvalds { 1251da177e4SLinus Torvalds case VIDIOCGCAP: 1261da177e4SLinus Torvalds { 1271da177e4SLinus Torvalds struct video_capability *v = arg; 1281da177e4SLinus Torvalds memset(v,0,sizeof(*v)); 1291da177e4SLinus Torvalds strcpy(v->name, "SF16-FMx radio"); 1301da177e4SLinus Torvalds v->type=VID_TYPE_TUNER; 1311da177e4SLinus Torvalds v->channels=1; 1321da177e4SLinus Torvalds v->audios=1; 1331da177e4SLinus Torvalds return 0; 1341da177e4SLinus Torvalds } 1351da177e4SLinus Torvalds case VIDIOCGTUNER: 1361da177e4SLinus Torvalds { 1371da177e4SLinus Torvalds struct video_tuner *v = arg; 1381da177e4SLinus Torvalds int mult; 1391da177e4SLinus Torvalds 1401da177e4SLinus Torvalds if(v->tuner) /* Only 1 tuner */ 1411da177e4SLinus Torvalds return -EINVAL; 1421da177e4SLinus Torvalds strcpy(v->name, "FM"); 1431da177e4SLinus Torvalds mult = (fmi->flags & VIDEO_TUNER_LOW) ? 1 : 1000; 1441da177e4SLinus Torvalds v->rangelow = RSF16_MINFREQ/mult; 1451da177e4SLinus Torvalds v->rangehigh = RSF16_MAXFREQ/mult; 1461da177e4SLinus Torvalds v->flags=fmi->flags; 1471da177e4SLinus Torvalds v->mode=VIDEO_MODE_AUTO; 1481da177e4SLinus Torvalds v->signal = fmi_getsigstr(fmi); 1491da177e4SLinus Torvalds return 0; 1501da177e4SLinus Torvalds } 1511da177e4SLinus Torvalds case VIDIOCSTUNER: 1521da177e4SLinus Torvalds { 1531da177e4SLinus Torvalds struct video_tuner *v = arg; 1541da177e4SLinus Torvalds if(v->tuner!=0) 1551da177e4SLinus Torvalds return -EINVAL; 1561da177e4SLinus Torvalds fmi->flags = v->flags & VIDEO_TUNER_LOW; 1571da177e4SLinus Torvalds /* Only 1 tuner so no setting needed ! */ 1581da177e4SLinus Torvalds return 0; 1591da177e4SLinus Torvalds } 1601da177e4SLinus Torvalds case VIDIOCGFREQ: 1611da177e4SLinus Torvalds { 1621da177e4SLinus Torvalds unsigned long *freq = arg; 1631da177e4SLinus Torvalds *freq = fmi->curfreq; 1641da177e4SLinus Torvalds if (!(fmi->flags & VIDEO_TUNER_LOW)) 1651da177e4SLinus Torvalds *freq /= 1000; 1661da177e4SLinus Torvalds return 0; 1671da177e4SLinus Torvalds } 1681da177e4SLinus Torvalds case VIDIOCSFREQ: 1691da177e4SLinus Torvalds { 1701da177e4SLinus Torvalds unsigned long *freq = arg; 1711da177e4SLinus Torvalds if (!(fmi->flags & VIDEO_TUNER_LOW)) 1721da177e4SLinus Torvalds *freq *= 1000; 1731da177e4SLinus Torvalds if (*freq < RSF16_MINFREQ || *freq > RSF16_MAXFREQ ) 1741da177e4SLinus Torvalds return -EINVAL; 1751da177e4SLinus Torvalds /*rounding in steps of 800 to match th freq 1761da177e4SLinus Torvalds that will be used */ 1771da177e4SLinus Torvalds fmi->curfreq = (*freq/800)*800; 1781da177e4SLinus Torvalds fmi_setfreq(fmi); 1791da177e4SLinus Torvalds return 0; 1801da177e4SLinus Torvalds } 1811da177e4SLinus Torvalds case VIDIOCGAUDIO: 1821da177e4SLinus Torvalds { 1831da177e4SLinus Torvalds struct video_audio *v = arg; 1841da177e4SLinus Torvalds memset(v,0,sizeof(*v)); 1851da177e4SLinus Torvalds v->flags=( (!fmi->curvol)*VIDEO_AUDIO_MUTE | VIDEO_AUDIO_MUTABLE); 1861da177e4SLinus Torvalds strcpy(v->name, "Radio"); 1871da177e4SLinus Torvalds v->mode=VIDEO_SOUND_STEREO; 1881da177e4SLinus Torvalds return 0; 1891da177e4SLinus Torvalds } 1901da177e4SLinus Torvalds case VIDIOCSAUDIO: 1911da177e4SLinus Torvalds { 1921da177e4SLinus Torvalds struct video_audio *v = arg; 1931da177e4SLinus Torvalds if(v->audio) 1941da177e4SLinus Torvalds return -EINVAL; 1951da177e4SLinus Torvalds fmi->curvol= v->flags&VIDEO_AUDIO_MUTE ? 0 : 1; 1961da177e4SLinus Torvalds fmi->curvol ? 1971da177e4SLinus Torvalds fmi_unmute(fmi->port) : fmi_mute(fmi->port); 1981da177e4SLinus Torvalds return 0; 1991da177e4SLinus Torvalds } 2001da177e4SLinus Torvalds case VIDIOCGUNIT: 2011da177e4SLinus Torvalds { 2021da177e4SLinus Torvalds struct video_unit *v = arg; 2031da177e4SLinus Torvalds v->video=VIDEO_NO_UNIT; 2041da177e4SLinus Torvalds v->vbi=VIDEO_NO_UNIT; 2051da177e4SLinus Torvalds v->radio=dev->minor; 2061da177e4SLinus Torvalds v->audio=0; /* How do we find out this??? */ 2071da177e4SLinus Torvalds v->teletext=VIDEO_NO_UNIT; 2081da177e4SLinus Torvalds return 0; 2091da177e4SLinus Torvalds } 2101da177e4SLinus Torvalds default: 2111da177e4SLinus Torvalds return -ENOIOCTLCMD; 2121da177e4SLinus Torvalds } 2131da177e4SLinus Torvalds } 2141da177e4SLinus Torvalds 2151da177e4SLinus Torvalds static int fmi_ioctl(struct inode *inode, struct file *file, 2161da177e4SLinus Torvalds unsigned int cmd, unsigned long arg) 2171da177e4SLinus Torvalds { 2181da177e4SLinus Torvalds return video_usercopy(inode, file, cmd, arg, fmi_do_ioctl); 2191da177e4SLinus Torvalds } 2201da177e4SLinus Torvalds 2211da177e4SLinus Torvalds static struct fmi_device fmi_unit; 2221da177e4SLinus Torvalds 2231da177e4SLinus Torvalds static struct file_operations fmi_fops = { 2241da177e4SLinus Torvalds .owner = THIS_MODULE, 2251da177e4SLinus Torvalds .open = video_exclusive_open, 2261da177e4SLinus Torvalds .release = video_exclusive_release, 2271da177e4SLinus Torvalds .ioctl = fmi_ioctl, 2280d0fbf81SArnd Bergmann .compat_ioctl = v4l_compat_ioctl32, 2291da177e4SLinus Torvalds .llseek = no_llseek, 2301da177e4SLinus Torvalds }; 2311da177e4SLinus Torvalds 2321da177e4SLinus Torvalds static struct video_device fmi_radio= 2331da177e4SLinus Torvalds { 2341da177e4SLinus Torvalds .owner = THIS_MODULE, 2351da177e4SLinus Torvalds .name = "SF16FMx radio", 2361da177e4SLinus Torvalds .type = VID_TYPE_TUNER, 2371da177e4SLinus Torvalds .hardware = VID_HARDWARE_SF16MI, 2381da177e4SLinus Torvalds .fops = &fmi_fops, 2391da177e4SLinus Torvalds }; 2401da177e4SLinus Torvalds 2411da177e4SLinus Torvalds /* ladis: this is my card. does any other types exist? */ 2421da177e4SLinus Torvalds static struct isapnp_device_id id_table[] __devinitdata = { 2431da177e4SLinus Torvalds { ISAPNP_ANY_ID, ISAPNP_ANY_ID, 2441da177e4SLinus Torvalds ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0}, 2451da177e4SLinus Torvalds { ISAPNP_CARD_END, }, 2461da177e4SLinus Torvalds }; 2471da177e4SLinus Torvalds 2481da177e4SLinus Torvalds MODULE_DEVICE_TABLE(isapnp, id_table); 2491da177e4SLinus Torvalds 2501da177e4SLinus Torvalds static int isapnp_fmi_probe(void) 2511da177e4SLinus Torvalds { 2521da177e4SLinus Torvalds int i = 0; 2531da177e4SLinus Torvalds 2541da177e4SLinus Torvalds while (id_table[i].card_vendor != 0 && dev == NULL) { 2551da177e4SLinus Torvalds dev = pnp_find_dev(NULL, id_table[i].vendor, 2561da177e4SLinus Torvalds id_table[i].function, NULL); 2571da177e4SLinus Torvalds i++; 2581da177e4SLinus Torvalds } 2591da177e4SLinus Torvalds 2601da177e4SLinus Torvalds if (!dev) 2611da177e4SLinus Torvalds return -ENODEV; 2621da177e4SLinus Torvalds if (pnp_device_attach(dev) < 0) 2631da177e4SLinus Torvalds return -EAGAIN; 2641da177e4SLinus Torvalds if (pnp_activate_dev(dev) < 0) { 2651da177e4SLinus Torvalds printk ("radio-sf16fmi: PnP configure failed (out of resources?)\n"); 2661da177e4SLinus Torvalds pnp_device_detach(dev); 2671da177e4SLinus Torvalds return -ENOMEM; 2681da177e4SLinus Torvalds } 2691da177e4SLinus Torvalds if (!pnp_port_valid(dev, 0)) { 2701da177e4SLinus Torvalds pnp_device_detach(dev); 2711da177e4SLinus Torvalds return -ENODEV; 2721da177e4SLinus Torvalds } 2731da177e4SLinus Torvalds 2741da177e4SLinus Torvalds i = pnp_port_start(dev, 0); 2751da177e4SLinus Torvalds printk ("radio-sf16fmi: PnP reports card at %#x\n", i); 2761da177e4SLinus Torvalds 2771da177e4SLinus Torvalds return i; 2781da177e4SLinus Torvalds } 2791da177e4SLinus Torvalds 2801da177e4SLinus Torvalds static int __init fmi_init(void) 2811da177e4SLinus Torvalds { 2821da177e4SLinus Torvalds if (io < 0) 2831da177e4SLinus Torvalds io = isapnp_fmi_probe(); 2841da177e4SLinus Torvalds if (io < 0) { 2851da177e4SLinus Torvalds printk(KERN_ERR "radio-sf16fmi: No PnP card found.\n"); 2861da177e4SLinus Torvalds return io; 2871da177e4SLinus Torvalds } 2881da177e4SLinus Torvalds if (!request_region(io, 2, "radio-sf16fmi")) { 2891da177e4SLinus Torvalds printk(KERN_ERR "radio-sf16fmi: port 0x%x already in use\n", io); 2901da177e4SLinus Torvalds return -EBUSY; 2911da177e4SLinus Torvalds } 2921da177e4SLinus Torvalds 2931da177e4SLinus Torvalds fmi_unit.port = io; 2941da177e4SLinus Torvalds fmi_unit.curvol = 0; 2951da177e4SLinus Torvalds fmi_unit.curfreq = 0; 2961da177e4SLinus Torvalds fmi_unit.flags = VIDEO_TUNER_LOW; 2971da177e4SLinus Torvalds fmi_radio.priv = &fmi_unit; 2981da177e4SLinus Torvalds 299*3593cab5SIngo Molnar mutex_init(&lock); 3001da177e4SLinus Torvalds 3011da177e4SLinus Torvalds if (video_register_device(&fmi_radio, VFL_TYPE_RADIO, radio_nr) == -1) { 3021da177e4SLinus Torvalds release_region(io, 2); 3031da177e4SLinus Torvalds return -EINVAL; 3041da177e4SLinus Torvalds } 3051da177e4SLinus Torvalds 3061da177e4SLinus Torvalds printk(KERN_INFO "SF16FMx radio card driver at 0x%x\n", io); 3071da177e4SLinus Torvalds /* mute card - prevents noisy bootups */ 3081da177e4SLinus Torvalds fmi_mute(io); 3091da177e4SLinus Torvalds return 0; 3101da177e4SLinus Torvalds } 3111da177e4SLinus Torvalds 3121da177e4SLinus Torvalds MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood"); 3131da177e4SLinus Torvalds MODULE_DESCRIPTION("A driver for the SF16MI radio."); 3141da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 3151da177e4SLinus Torvalds 3161da177e4SLinus Torvalds module_param(io, int, 0); 3171da177e4SLinus Torvalds MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)"); 3181da177e4SLinus Torvalds module_param(radio_nr, int, 0); 3191da177e4SLinus Torvalds 3201da177e4SLinus Torvalds static void __exit fmi_cleanup_module(void) 3211da177e4SLinus Torvalds { 3221da177e4SLinus Torvalds video_unregister_device(&fmi_radio); 3231da177e4SLinus Torvalds release_region(io, 2); 3241da177e4SLinus Torvalds if (dev) 3251da177e4SLinus Torvalds pnp_device_detach(dev); 3261da177e4SLinus Torvalds } 3271da177e4SLinus Torvalds 3281da177e4SLinus Torvalds module_init(fmi_init); 3291da177e4SLinus Torvalds module_exit(fmi_cleanup_module); 330