v2.4.8 -> v2.4.8.1
[opensuse:kernel.git] / drivers / media / radio / radio-sf16fmi.c
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 int users = 0;
40 static struct pci_dev *dev = NULL;
41 static struct semaphore lock;
42
43 /* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */
44 /* It is only useful to give freq in intervall of 800 (=0.05Mhz),
45  * other bits will be truncated, e.g 92.7400016 -> 92.7, but 
46  * 92.7400017 -> 92.75
47  */
48 #define RSF16_ENCODE(x) ((x)/800+214)
49 #define RSF16_MINFREQ 87*16000
50 #define RSF16_MAXFREQ 108*16000
51
52 static void outbits(int bits, unsigned int data, int port)
53 {
54         while(bits--) {
55                 if(data & 1) {
56                         outb(5, port);
57                         udelay(6);
58                         outb(7, port);
59                         udelay(6);
60                 } else {
61                         outb(1, port);
62                         udelay(6);
63                         outb(3, port);
64                         udelay(6);
65                 }
66                 data>>=1;
67         }
68 }
69
70 static inline void fmi_mute(int port)
71 {
72         down(&lock);
73         outb(0x00, port);
74         up(&lock);
75 }
76
77 static inline void fmi_unmute(int port)
78 {
79         down(&lock);
80         outb(0x08, port);
81         up(&lock);
82 }
83
84 static inline int fmi_setfreq(struct fmi_device *dev)
85 {
86         int myport = dev->port;
87         unsigned long freq = dev->curfreq;
88         int i;
89         
90         down(&lock);
91         
92         outbits(16, RSF16_ENCODE(freq), myport);
93         outbits(8, 0xC0, myport);
94         for(i=0; i< 100; i++)
95         {
96                 udelay(1400);
97                 if(current->need_resched)
98                         schedule();
99         }
100 /* If this becomes allowed use it ...   
101         current->state = TASK_UNINTERRUPTIBLE;
102         schedule_timeout(HZ/7);
103 */      
104
105         up(&lock);
106         if (dev->curvol) fmi_unmute(myport);
107         return 0;
108 }
109
110 static inline int fmi_getsigstr(struct fmi_device *dev)
111 {
112         int val;
113         int res;
114         int myport = dev->port;
115         int i;
116         
117         down(&lock);
118         val = dev->curvol ? 0x08 : 0x00;        /* unmute/mute */
119         outb(val, myport);
120         outb(val | 0x10, myport);
121         for(i=0; i< 100; i++)
122         {
123                 udelay(1400);
124                 if(current->need_resched)
125                         schedule();
126         }
127 /* If this becomes allowed use it ...   
128         current->state = TASK_UNINTERRUPTIBLE;
129         schedule_timeout(HZ/7);
130 */      
131         res = (int)inb(myport+1);
132         outb(val, myport);
133         
134         up(&lock);
135         return (res & 2) ? 0 : 0xFFFF;
136 }
137
138 static int fmi_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
139 {
140         struct fmi_device *fmi=dev->priv;
141         
142         switch(cmd)
143         {
144                 case VIDIOCGCAP:
145                 {
146                         struct video_capability v;
147                         strcpy(v.name, "SF16-FMx radio");
148                         v.type=VID_TYPE_TUNER;
149                         v.channels=1;
150                         v.audios=1;
151                         /* No we don't do pictures */
152                         v.maxwidth=0;
153                         v.maxheight=0;
154                         v.minwidth=0;
155                         v.minheight=0;
156                         if(copy_to_user(arg,&v,sizeof(v)))
157                                 return -EFAULT;
158                         return 0;
159                 }
160                 case VIDIOCGTUNER:
161                 {
162                         struct video_tuner v;
163                         int mult;
164
165                         if(copy_from_user(&v, arg,sizeof(v))!=0)
166                                 return -EFAULT;
167                         if(v.tuner)     /* Only 1 tuner */
168                                 return -EINVAL;
169                         strcpy(v.name, "FM");
170                         mult = (fmi->flags & VIDEO_TUNER_LOW) ? 1 : 1000;
171                         v.rangelow = RSF16_MINFREQ/mult;
172                         v.rangehigh = RSF16_MAXFREQ/mult;
173                         v.flags=fmi->flags;
174                         v.mode=VIDEO_MODE_AUTO;
175                         v.signal = fmi_getsigstr(fmi);
176                         if(copy_to_user(arg,&v, sizeof(v)))
177                                 return -EFAULT;
178                         return 0;
179                 }
180                 case VIDIOCSTUNER:
181                 {
182                         struct video_tuner v;
183                         if(copy_from_user(&v, arg, sizeof(v)))
184                                 return -EFAULT;
185                         if(v.tuner!=0)
186                                 return -EINVAL;
187                         fmi->flags = v.flags & VIDEO_TUNER_LOW;
188                         /* Only 1 tuner so no setting needed ! */
189                         return 0;
190                 }
191                 case VIDIOCGFREQ:
192                 {
193                         unsigned long tmp = fmi->curfreq;
194                         if (!(fmi->flags & VIDEO_TUNER_LOW))
195                                 tmp /= 1000;
196                         if(copy_to_user(arg, &tmp, sizeof(tmp)))
197                                 return -EFAULT;
198                         return 0;
199                 }
200                 case VIDIOCSFREQ:
201                 {
202                         unsigned long tmp;
203                         if(copy_from_user(&tmp, arg, sizeof(tmp)))
204                                 return -EFAULT;
205                         if (!(fmi->flags & VIDEO_TUNER_LOW))
206                                 tmp *= 1000;
207                         if ( tmp<RSF16_MINFREQ || tmp>RSF16_MAXFREQ )
208                           return -EINVAL;
209                         /*rounding in steps of 800 to match th freq
210                           that will be used */
211                         fmi->curfreq = (tmp/800)*800; 
212                         fmi_setfreq(fmi);
213                         return 0;
214                 }
215                 case VIDIOCGAUDIO:
216                 {       
217                         struct video_audio v;
218                         v.audio=0;
219                         v.volume=0;
220                         v.bass=0;
221                         v.treble=0;
222                         v.flags=( (!fmi->curvol)*VIDEO_AUDIO_MUTE | VIDEO_AUDIO_MUTABLE);
223                         strcpy(v.name, "Radio");
224                         v.mode=VIDEO_SOUND_STEREO;
225                         v.balance=0;
226                         v.step=0; /* No volume, just (un)mute */
227                         if(copy_to_user(arg,&v, sizeof(v)))
228                                 return -EFAULT;
229                         return 0;                       
230                 }
231                 case VIDIOCSAUDIO:
232                 {
233                         struct video_audio v;
234                         if(copy_from_user(&v, arg, sizeof(v)))
235                                 return -EFAULT;
236                         if(v.audio)
237                                 return -EINVAL;
238                         fmi->curvol= v.flags&VIDEO_AUDIO_MUTE ? 0 : 1;
239                         fmi->curvol ? 
240                           fmi_unmute(fmi->port) : fmi_mute(fmi->port);
241                         return 0;
242                 }
243                 case VIDIOCGUNIT:
244                 {
245                         struct video_unit v;
246                         v.video=VIDEO_NO_UNIT;
247                         v.vbi=VIDEO_NO_UNIT;
248                         v.radio=dev->minor;
249                         v.audio=0; /* How do we find out this??? */
250                         v.teletext=VIDEO_NO_UNIT;
251                         if(copy_to_user(arg, &v, sizeof(v)))
252                                 return -EFAULT;
253                         return 0;                       
254                 }
255                 default:
256                         return -ENOIOCTLCMD;
257         }
258 }
259
260 static int fmi_open(struct video_device *dev, int flags)
261 {
262         if(users)
263                 return -EBUSY;
264         users++;
265         return 0;
266 }
267
268 static void fmi_close(struct video_device *dev)
269 {
270         users--;
271 }
272
273 static struct fmi_device fmi_unit;
274
275 static struct video_device fmi_radio=
276 {
277         owner:          THIS_MODULE,
278         name:           "SF16FMx radio",
279         type:           VID_TYPE_TUNER,
280         hardware:       VID_HARDWARE_SF16MI,
281         open:           fmi_open,
282         close:          fmi_close,
283         ioctl:          fmi_ioctl,
284 };
285
286 /* ladis: this is my card. does any other types exist? */
287 static struct isapnp_device_id id_table[] __devinitdata = {
288         {       ISAPNP_ANY_ID, ISAPNP_ANY_ID,
289                 ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0},
290         {       ISAPNP_CARD_END, },
291 };
292
293 MODULE_DEVICE_TABLE(isapnp, id_table);
294
295 static int isapnp_fmi_probe(void)
296 {
297         int i = 0;
298
299         while (id_table[i].card_vendor != 0 && dev == NULL) {
300                 dev = isapnp_find_dev(NULL, id_table[i].vendor,
301                                       id_table[i].function, NULL);
302         }
303
304         if (!dev)
305                 return -ENODEV;
306         if (dev->prepare(dev) < 0)
307                 return -EAGAIN;
308         if (!(dev->resource[0].flags & IORESOURCE_IO))
309                 return -ENODEV;
310         if (dev->activate(dev) < 0) {
311                 printk ("radio-sf16fmi: ISAPnP configure failed (out of resources?)\n");
312                 return -ENOMEM;
313         }
314
315         i = dev->resource[0].start;
316         printk ("radio-sf16fmi: ISAPnP reports card at %#x\n", i);
317
318         return i;
319 }
320
321 static int __init fmi_init(void)
322 {
323         if (io < 0)
324                 io = isapnp_fmi_probe();
325         if (io < 0) {
326                 printk(KERN_ERR "radio-sf16fmi: No PnP card found.");
327                 return io;
328         }
329         if (!request_region(io, 2, "radio-sf16fmi")) {
330                 printk(KERN_ERR "radio-sf16fmi: port 0x%x already in use\n", io);
331                 return -EBUSY;
332         }
333
334         fmi_unit.port = io;
335         fmi_unit.curvol = 0;
336         fmi_unit.curfreq = 0;
337         fmi_unit.flags = VIDEO_TUNER_LOW;
338         fmi_radio.priv = &fmi_unit;
339         
340         init_MUTEX(&lock);
341         
342         if (video_register_device(&fmi_radio, VFL_TYPE_RADIO, radio_nr) == -1) {
343                 release_region(io, 2);
344                 return -EINVAL;
345         }
346                 
347         printk(KERN_INFO "SF16FMx radio card driver at 0x%x\n", io);
348         /* mute card - prevents noisy bootups */
349         fmi_mute(io);
350         return 0;
351 }
352
353 MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood");
354 MODULE_DESCRIPTION("A driver for the SF16MI radio.");
355 MODULE_PARM(io, "i");
356 MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)");
357 MODULE_PARM(radio_nr, "i");
358
359 EXPORT_NO_SYMBOLS;
360
361 static void __exit fmi_cleanup_module(void)
362 {
363         video_unregister_device(&fmi_radio);
364         release_region(io, 2);
365         if (dev)
366                 dev->deactivate(dev);
367 }
368
369 module_init(fmi_init);
370 module_exit(fmi_cleanup_module);
371
372 #ifndef MODULE
373 static int __init fmi_setup_io(char *str)
374 {
375         get_option(&str, &io);
376         return 1;
377 }
378
379 __setup("sf16fm=", fmi_setup_io);
380 #endif