1 /* SF16FMI radio driver for Linux radio support 2 * heavily based on rtrack driver... 3 * (c) 1997 M. Kirkwood 4 * (c) 1998 Petr Vandrovec, vandrove@vc.cvut.cz 5 * 6 * Fitted to new interface by Alan Cox <alan.cox@linux.org> 7 * Made working and cleaned up functions <mikael.hedin@irf.se> 8 * Support for ISAPnP by Ladislav Michl <ladis@psi.cz> 9 * 10 * Notes on the hardware 11 * 12 * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); 13 * No volume control - only mute/unmute - you have to use line volume 14 * control on SB-part of SF16FMI 15 * 16 */ 17 18 #include <linux/kernel.h> /* __setup */ 19 #include <linux/module.h> /* Modules */ 20 #include <linux/init.h> /* Initdata */ 21 #include <linux/ioport.h> /* check_region, request_region */ 22 #include <linux/delay.h> /* udelay */ 23 #include <linux/videodev.h> /* kernel radio structs */ 24 #include <linux/isapnp.h> 25 #include <asm/io.h> /* outb, outb_p */ 26 #include <asm/uaccess.h> /* copy to/from user */ 27 #include <asm/semaphore.h> 28 29 struct fmi_device 30 { 31 int port; 32 int curvol; /* 1 or 0 */ 33 unsigned long curfreq; /* freq in kHz */ 34 __u32 flags; 35 }; 36 37 static int io = -1; 38 static int radio_nr = -1; 39 static struct pnp_dev *dev = NULL; 40 static struct semaphore lock; 41 42 /* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */ 43 /* It is only useful to give freq in intervall of 800 (=0.05Mhz), 44 * other bits will be truncated, e.g 92.7400016 -> 92.7, but 45 * 92.7400017 -> 92.75 46 */ 47 #define RSF16_ENCODE(x) ((x)/800+214) 48 #define RSF16_MINFREQ 87*16000 49 #define RSF16_MAXFREQ 108*16000 50 51 static void outbits(int bits, unsigned int data, int port) 52 { 53 while(bits--) { 54 if(data & 1) { 55 outb(5, port); 56 udelay(6); 57 outb(7, port); 58 udelay(6); 59 } else { 60 outb(1, port); 61 udelay(6); 62 outb(3, port); 63 udelay(6); 64 } 65 data>>=1; 66 } 67 } 68 69 static inline void fmi_mute(int port) 70 { 71 down(&lock); 72 outb(0x00, port); 73 up(&lock); 74 } 75 76 static inline void fmi_unmute(int port) 77 { 78 down(&lock); 79 outb(0x08, port); 80 up(&lock); 81 } 82 83 static inline int fmi_setfreq(struct fmi_device *dev) 84 { 85 int myport = dev->port; 86 unsigned long freq = dev->curfreq; 87 88 down(&lock); 89 90 outbits(16, RSF16_ENCODE(freq), myport); 91 outbits(8, 0xC0, myport); 92 msleep(143); /* was schedule_timeout(HZ/7) */ 93 up(&lock); 94 if (dev->curvol) fmi_unmute(myport); 95 return 0; 96 } 97 98 static inline int fmi_getsigstr(struct fmi_device *dev) 99 { 100 int val; 101 int res; 102 int myport = dev->port; 103 104 105 down(&lock); 106 val = dev->curvol ? 0x08 : 0x00; /* unmute/mute */ 107 outb(val, myport); 108 outb(val | 0x10, myport); 109 msleep(143); /* was schedule_timeout(HZ/7) */ 110 res = (int)inb(myport+1); 111 outb(val, myport); 112 113 up(&lock); 114 return (res & 2) ? 0 : 0xFFFF; 115 } 116 117 static int fmi_do_ioctl(struct inode *inode, struct file *file, 118 unsigned int cmd, void *arg) 119 { 120 struct video_device *dev = video_devdata(file); 121 struct fmi_device *fmi=dev->priv; 122 123 switch(cmd) 124 { 125 case VIDIOCGCAP: 126 { 127 struct video_capability *v = arg; 128 memset(v,0,sizeof(*v)); 129 strcpy(v->name, "SF16-FMx radio"); 130 v->type=VID_TYPE_TUNER; 131 v->channels=1; 132 v->audios=1; 133 return 0; 134 } 135 case VIDIOCGTUNER: 136 { 137 struct video_tuner *v = arg; 138 int mult; 139 140 if(v->tuner) /* Only 1 tuner */ 141 return -EINVAL; 142 strcpy(v->name, "FM"); 143 mult = (fmi->flags & VIDEO_TUNER_LOW) ? 1 : 1000; 144 v->rangelow = RSF16_MINFREQ/mult; 145 v->rangehigh = RSF16_MAXFREQ/mult; 146 v->flags=fmi->flags; 147 v->mode=VIDEO_MODE_AUTO; 148 v->signal = fmi_getsigstr(fmi); 149 return 0; 150 } 151 case VIDIOCSTUNER: 152 { 153 struct video_tuner *v = arg; 154 if(v->tuner!=0) 155 return -EINVAL; 156 fmi->flags = v->flags & VIDEO_TUNER_LOW; 157 /* Only 1 tuner so no setting needed ! */ 158 return 0; 159 } 160 case VIDIOCGFREQ: 161 { 162 unsigned long *freq = arg; 163 *freq = fmi->curfreq; 164 if (!(fmi->flags & VIDEO_TUNER_LOW)) 165 *freq /= 1000; 166 return 0; 167 } 168 case VIDIOCSFREQ: 169 { 170 unsigned long *freq = arg; 171 if (!(fmi->flags & VIDEO_TUNER_LOW)) 172 *freq *= 1000; 173 if (*freq < RSF16_MINFREQ || *freq > RSF16_MAXFREQ ) 174 return -EINVAL; 175 /*rounding in steps of 800 to match th freq 176 that will be used */ 177 fmi->curfreq = (*freq/800)*800; 178 fmi_setfreq(fmi); 179 return 0; 180 } 181 case VIDIOCGAUDIO: 182 { 183 struct video_audio *v = arg; 184 memset(v,0,sizeof(*v)); 185 v->flags=( (!fmi->curvol)*VIDEO_AUDIO_MUTE | VIDEO_AUDIO_MUTABLE); 186 strcpy(v->name, "Radio"); 187 v->mode=VIDEO_SOUND_STEREO; 188 return 0; 189 } 190 case VIDIOCSAUDIO: 191 { 192 struct video_audio *v = arg; 193 if(v->audio) 194 return -EINVAL; 195 fmi->curvol= v->flags&VIDEO_AUDIO_MUTE ? 0 : 1; 196 fmi->curvol ? 197 fmi_unmute(fmi->port) : fmi_mute(fmi->port); 198 return 0; 199 } 200 case VIDIOCGUNIT: 201 { 202 struct video_unit *v = arg; 203 v->video=VIDEO_NO_UNIT; 204 v->vbi=VIDEO_NO_UNIT; 205 v->radio=dev->minor; 206 v->audio=0; /* How do we find out this??? */ 207 v->teletext=VIDEO_NO_UNIT; 208 return 0; 209 } 210 default: 211 return -ENOIOCTLCMD; 212 } 213 } 214 215 static int fmi_ioctl(struct inode *inode, struct file *file, 216 unsigned int cmd, unsigned long arg) 217 { 218 return video_usercopy(inode, file, cmd, arg, fmi_do_ioctl); 219 } 220 221 static struct fmi_device fmi_unit; 222 223 static struct file_operations fmi_fops = { 224 .owner = THIS_MODULE, 225 .open = video_exclusive_open, 226 .release = video_exclusive_release, 227 .ioctl = fmi_ioctl, 228 .llseek = no_llseek, 229 }; 230 231 static struct video_device fmi_radio= 232 { 233 .owner = THIS_MODULE, 234 .name = "SF16FMx radio", 235 .type = VID_TYPE_TUNER, 236 .hardware = VID_HARDWARE_SF16MI, 237 .fops = &fmi_fops, 238 }; 239 240 /* ladis: this is my card. does any other types exist? */ 241 static struct isapnp_device_id id_table[] __devinitdata = { 242 { ISAPNP_ANY_ID, ISAPNP_ANY_ID, 243 ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0}, 244 { ISAPNP_CARD_END, }, 245 }; 246 247 MODULE_DEVICE_TABLE(isapnp, id_table); 248 249 static int isapnp_fmi_probe(void) 250 { 251 int i = 0; 252 253 while (id_table[i].card_vendor != 0 && dev == NULL) { 254 dev = pnp_find_dev(NULL, id_table[i].vendor, 255 id_table[i].function, NULL); 256 i++; 257 } 258 259 if (!dev) 260 return -ENODEV; 261 if (pnp_device_attach(dev) < 0) 262 return -EAGAIN; 263 if (pnp_activate_dev(dev) < 0) { 264 printk ("radio-sf16fmi: PnP configure failed (out of resources?)\n"); 265 pnp_device_detach(dev); 266 return -ENOMEM; 267 } 268 if (!pnp_port_valid(dev, 0)) { 269 pnp_device_detach(dev); 270 return -ENODEV; 271 } 272 273 i = pnp_port_start(dev, 0); 274 printk ("radio-sf16fmi: PnP reports card at %#x\n", i); 275 276 return i; 277 } 278 279 static int __init fmi_init(void) 280 { 281 if (io < 0) 282 io = isapnp_fmi_probe(); 283 if (io < 0) { 284 printk(KERN_ERR "radio-sf16fmi: No PnP card found.\n"); 285 return io; 286 } 287 if (!request_region(io, 2, "radio-sf16fmi")) { 288 printk(KERN_ERR "radio-sf16fmi: port 0x%x already in use\n", io); 289 return -EBUSY; 290 } 291 292 fmi_unit.port = io; 293 fmi_unit.curvol = 0; 294 fmi_unit.curfreq = 0; 295 fmi_unit.flags = VIDEO_TUNER_LOW; 296 fmi_radio.priv = &fmi_unit; 297 298 init_MUTEX(&lock); 299 300 if (video_register_device(&fmi_radio, VFL_TYPE_RADIO, radio_nr) == -1) { 301 release_region(io, 2); 302 return -EINVAL; 303 } 304 305 printk(KERN_INFO "SF16FMx radio card driver at 0x%x\n", io); 306 /* mute card - prevents noisy bootups */ 307 fmi_mute(io); 308 return 0; 309 } 310 311 MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood"); 312 MODULE_DESCRIPTION("A driver for the SF16MI radio."); 313 MODULE_LICENSE("GPL"); 314 315 module_param(io, int, 0); 316 MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)"); 317 module_param(radio_nr, int, 0); 318 319 static void __exit fmi_cleanup_module(void) 320 { 321 video_unregister_device(&fmi_radio); 322 release_region(io, 2); 323 if (dev) 324 pnp_device_detach(dev); 325 } 326 327 module_init(fmi_init); 328 module_exit(fmi_cleanup_module); 329