import of ftp.dlink.com/GPL/DSMG-600_reB/ppclinux.tar.gz
[linux-2.4.21-pre4.git] / drivers / media / radio / radio-zoltrix.c
1 /* zoltrix radio plus driver for Linux radio support
2  * (c) 1998 C. van Schaik <carl@leg.uct.ac.za>
3  *
4  * BUGS  
5  *  Due to the inconsistancy in reading from the signal flags
6  *  it is difficult to get an accurate tuned signal.
7  *
8  *  It seems that the card is not linear to 0 volume. It cuts off
9  *  at a low volume, and it is not possible (at least I have not found)
10  *  to get fine volume control over the low volume range.
11  *
12  *  Some code derived from code by Romolo Manfredini
13  *                                 romolo@bicnet.it
14  *
15  * 1999-05-06 - (C. van Schaik)
16  *            - Make signal strength and stereo scans
17  *              kinder to cpu while in delay
18  * 1999-01-05 - (C. van Schaik)
19  *            - Changed tuning to 1/160Mhz accuracy
20  *            - Added stereo support
21  *              (card defaults to stereo)
22  *              (can explicitly force mono on the card)
23  *              (can detect if station is in stereo)
24  *            - Added unmute function
25  *            - Reworked ioctl functions
26  * 2002-07-15 - Fix Stereo typo
27  */
28
29 #include <linux/module.h>       /* Modules                        */
30 #include <linux/init.h>         /* Initdata                       */
31 #include <linux/ioport.h>       /* check_region, request_region   */
32 #include <linux/delay.h>        /* udelay                 */
33 #include <asm/io.h>             /* outb, outb_p                   */
34 #include <asm/uaccess.h>        /* copy to/from user              */
35 #include <linux/videodev.h>     /* kernel radio structs           */
36 #include <linux/config.h>       /* CONFIG_RADIO_ZOLTRIX_PORT      */
37
38 #ifndef CONFIG_RADIO_ZOLTRIX_PORT
39 #define CONFIG_RADIO_ZOLTRIX_PORT -1
40 #endif
41
42 static int io = CONFIG_RADIO_ZOLTRIX_PORT;
43 static int radio_nr = -1;
44 static int users = 0;
45
46 struct zol_device {
47         int port;
48         int curvol;
49         unsigned long curfreq;
50         int muted;
51         unsigned int stereo;
52         struct semaphore lock;
53 };
54
55
56 /* local things */
57
58 static void sleep_delay(void)
59 {
60         /* Sleep nicely for +/- 10 mS */
61         schedule();
62 }
63
64 static int zol_setvol(struct zol_device *dev, int vol)
65 {
66         dev->curvol = vol;
67         if (dev->muted)
68                 return 0;
69
70         down(&dev->lock);
71         if (vol == 0) {
72                 outb(0, io);
73                 outb(0, io);
74                 inb(io + 3);    /* Zoltrix needs to be read to confirm */
75                 up(&dev->lock);
76                 return 0;
77         }
78
79         outb(dev->curvol-1, io);
80         sleep_delay();
81         inb(io + 2);
82         up(&dev->lock);
83         return 0;
84 }
85
86 static void zol_mute(struct zol_device *dev)
87 {
88         dev->muted = 1;
89         down(&dev->lock);
90         outb(0, io);
91         outb(0, io);
92         inb(io + 3);            /* Zoltrix needs to be read to confirm */
93         up(&dev->lock);
94 }
95
96 static void zol_unmute(struct zol_device *dev)
97 {
98         dev->muted = 0;
99         zol_setvol(dev, dev->curvol);
100 }
101
102 static int zol_setfreq(struct zol_device *dev, unsigned long freq)
103 {
104         /* tunes the radio to the desired frequency */
105         unsigned long long bitmask, f, m;
106         unsigned int stereo = dev->stereo;
107         int i;
108
109         if (freq == 0)
110                 return 1;
111         m = (freq / 160 - 8800) * 2;
112         f = (unsigned long long) m + 0x4d1c;
113
114         bitmask = 0xc480402c10080000ull;
115         i = 45;
116
117         down(&dev->lock);
118         
119         outb(0, io);
120         outb(0, io);
121         inb(io + 3);            /* Zoltrix needs to be read to confirm */
122
123         outb(0x40, io);
124         outb(0xc0, io);
125
126         bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31));
127         while (i--) {
128                 if ((bitmask & 0x8000000000000000ull) != 0) {
129                         outb(0x80, io);
130                         udelay(50);
131                         outb(0x00, io);
132                         udelay(50);
133                         outb(0x80, io);
134                         udelay(50);
135                 } else {
136                         outb(0xc0, io);
137                         udelay(50);
138                         outb(0x40, io);
139                         udelay(50);
140                         outb(0xc0, io);
141                         udelay(50);
142                 }
143                 bitmask *= 2;
144         }
145         /* termination sequence */
146         outb(0x80, io);
147         outb(0xc0, io);
148         outb(0x40, io);
149         udelay(1000);
150         inb(io+2);
151
152         udelay(1000);
153         
154         if (dev->muted)
155         {
156                 outb(0, io);
157                 outb(0, io);
158                 inb(io + 3);
159                 udelay(1000);
160         }
161         
162         up(&dev->lock);
163         
164         if(!dev->muted)
165         {
166                 zol_setvol(dev, dev->curvol);
167         }
168         return 0;
169 }
170
171 /* Get signal strength */
172
173 int zol_getsigstr(struct zol_device *dev)
174 {
175         int a, b;
176
177         down(&dev->lock);
178         outb(0x00, io);         /* This stuff I found to do nothing */
179         outb(dev->curvol, io);
180         sleep_delay();
181         sleep_delay();
182
183         a = inb(io);
184         sleep_delay();
185         b = inb(io);
186
187         up(&dev->lock);
188         
189         if (a != b)
190                 return (0);
191
192         if ((a == 0xcf) || (a == 0xdf)  /* I found this out by playing */
193                 || (a == 0xef))       /* with a binary scanner on the card io */
194                 return (1);
195         return (0);
196 }
197
198 int zol_is_stereo (struct zol_device *dev)
199 {
200         int x1, x2;
201
202         down(&dev->lock);
203         
204         outb(0x00, io);
205         outb(dev->curvol, io);
206         sleep_delay();
207         sleep_delay();
208
209         x1 = inb(io);
210         sleep_delay();
211         x2 = inb(io);
212
213         up(&dev->lock);
214         
215         if ((x1 == x2) && (x1 == 0xcf))
216                 return 1;
217         return 0;
218 }
219
220 static int zol_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
221 {
222         struct zol_device *zol = dev->priv;
223
224         switch (cmd) {
225         case VIDIOCGCAP:
226                 {
227                         struct video_capability v;
228                         v.type = VID_TYPE_TUNER;
229                         v.channels = 1 + zol->stereo;
230                         v.audios = 1;
231                         /* No we don't do pictures */
232                         v.maxwidth = 0;
233                         v.maxheight = 0;
234                         v.minwidth = 0;
235                         v.minheight = 0;
236                         strcpy(v.name, "Zoltrix Radio");
237                         if (copy_to_user(arg, &v, sizeof(v)))
238                                 return -EFAULT;
239                         return 0;
240                 }
241         case VIDIOCGTUNER:
242                 {
243                         struct video_tuner v;
244                         if (copy_from_user(&v, arg, sizeof(v)))
245                                 return -EFAULT;
246                         if (v.tuner)    
247                                 return -EINVAL;
248                         strcpy(v.name, "FM");
249                         v.rangelow = (int) (88.0 * 16000);
250                         v.rangehigh = (int) (108.0 * 16000);
251                         v.flags = zol_is_stereo(zol)
252                                         ? VIDEO_TUNER_STEREO_ON : 0;
253                         v.flags |= VIDEO_TUNER_LOW;
254                         v.mode = VIDEO_MODE_AUTO;
255                         v.signal = 0xFFFF * zol_getsigstr(zol);
256                         if (copy_to_user(arg, &v, sizeof(v)))
257                                 return -EFAULT;
258                         return 0;
259                 }
260         case VIDIOCSTUNER:
261                 {
262                         struct video_tuner v;
263                         if (copy_from_user(&v, arg, sizeof(v)))
264                                 return -EFAULT;
265                         if (v.tuner != 0)
266                                 return -EINVAL;
267                         /* Only 1 tuner so no setting needed ! */
268                         return 0;
269                 }
270         case VIDIOCGFREQ:
271                 if (copy_to_user(arg, &zol->curfreq, sizeof(zol->curfreq)))
272                         return -EFAULT;
273                 return 0;
274         case VIDIOCSFREQ:
275                 if (copy_from_user(&zol->curfreq, arg, sizeof(zol->curfreq)))
276                         return -EFAULT;
277                 zol_setfreq(zol, zol->curfreq);
278                 return 0;
279         case VIDIOCGAUDIO:
280                 {
281                         struct video_audio v;
282                         memset(&v, 0, sizeof(v));
283                         v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME;
284                         v.mode |= zol_is_stereo(zol)
285                                 ? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO;
286                         v.volume = zol->curvol * 4096;
287                         v.step = 4096;
288                         strcpy(v.name, "Zoltrix Radio");
289                         if (copy_to_user(arg, &v, sizeof(v)))
290                                 return -EFAULT;
291                         return 0;
292                 }
293         case VIDIOCSAUDIO:
294                 {
295                         struct video_audio v;
296                         if (copy_from_user(&v, arg, sizeof(v)))
297                                 return -EFAULT;
298                         if (v.audio)
299                                 return -EINVAL;
300
301                         if (v.flags & VIDEO_AUDIO_MUTE)
302                                 zol_mute(zol);
303                         else
304                         {
305                                 zol_unmute(zol);
306                                 zol_setvol(zol, v.volume / 4096);
307                         }
308
309                         if (v.mode & VIDEO_SOUND_STEREO)
310                         {
311                                 zol->stereo = 1;
312                                 zol_setfreq(zol, zol->curfreq);
313                         }
314                         if (v.mode & VIDEO_SOUND_MONO)
315                         {
316                                 zol->stereo = 0;
317                                 zol_setfreq(zol, zol->curfreq);
318                         }
319
320                         return 0;
321                 }
322         default:
323                 return -ENOIOCTLCMD;
324         }
325 }
326
327 static int zol_open(struct video_device *dev, int flags)
328 {
329         if (users)
330                 return -EBUSY;
331         users++;
332         return 0;
333 }
334
335 static void zol_close(struct video_device *dev)
336 {
337         users--;
338 }
339
340 static struct zol_device zoltrix_unit;
341
342 static struct video_device zoltrix_radio =
343 {
344         owner:          THIS_MODULE,
345         name:           "Zoltrix Radio Plus",
346         type:           VID_TYPE_TUNER,
347         hardware:       VID_HARDWARE_ZOLTRIX,
348         open:           zol_open,
349         close:          zol_close,
350         ioctl:          zol_ioctl,
351 };
352
353 static int __init zoltrix_init(void)
354 {
355         if (io == -1) {
356                 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
357                 return -EINVAL;
358         }
359         if ((io != 0x20c) && (io != 0x30c)) {
360                 printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n");
361                 return -ENXIO;
362         }
363
364         zoltrix_radio.priv = &zoltrix_unit;
365         if (!request_region(io, 2, "zoltrix")) {
366                 printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io);
367                 return -EBUSY;
368         }
369
370         if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) == -1)
371         {
372                 release_region(io, 2);
373                 return -EINVAL;
374         }
375         printk(KERN_INFO "Zoltrix Radio Plus card driver.\n");
376
377         init_MUTEX(&zoltrix_unit.lock);
378         
379         /* mute card - prevents noisy bootups */
380
381         /* this ensures that the volume is all the way down  */
382
383         outb(0, io);
384         outb(0, io);
385         sleep_delay();
386         sleep_delay();
387         inb(io + 3);
388
389         zoltrix_unit.curvol = 0;
390         zoltrix_unit.stereo = 1;
391
392         return 0;
393 }
394
395 MODULE_AUTHOR("C.van Schaik");
396 MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
397 MODULE_LICENSE("GPL");
398
399 MODULE_PARM(io, "i");
400 MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
401 MODULE_PARM(radio_nr, "i");
402
403 EXPORT_NO_SYMBOLS;
404
405 static void __exit zoltrix_cleanup_module(void)
406 {
407         video_unregister_device(&zoltrix_radio);
408         release_region(io, 2);
409 }
410
411 module_init(zoltrix_init);
412 module_exit(zoltrix_cleanup_module);
413