ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[linux-2.6.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 inconsistency 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
45 struct zol_device {
46         int port;
47         int curvol;
48         unsigned long curfreq;
49         int muted;
50         unsigned int stereo;
51         struct semaphore lock;
52 };
53
54
55 /* local things */
56
57 static void sleep_delay(void)
58 {
59         /* Sleep nicely for +/- 10 mS */
60         schedule();
61 }
62
63 static int zol_setvol(struct zol_device *dev, int vol)
64 {
65         dev->curvol = vol;
66         if (dev->muted)
67                 return 0;
68
69         down(&dev->lock);
70         if (vol == 0) {
71                 outb(0, io);
72                 outb(0, io);
73                 inb(io + 3);    /* Zoltrix needs to be read to confirm */
74                 up(&dev->lock);
75                 return 0;
76         }
77
78         outb(dev->curvol-1, io);
79         sleep_delay();
80         inb(io + 2);
81         up(&dev->lock);
82         return 0;
83 }
84
85 static void zol_mute(struct zol_device *dev)
86 {
87         dev->muted = 1;
88         down(&dev->lock);
89         outb(0, io);
90         outb(0, io);
91         inb(io + 3);            /* Zoltrix needs to be read to confirm */
92         up(&dev->lock);
93 }
94
95 static void zol_unmute(struct zol_device *dev)
96 {
97         dev->muted = 0;
98         zol_setvol(dev, dev->curvol);
99 }
100
101 static int zol_setfreq(struct zol_device *dev, unsigned long freq)
102 {
103         /* tunes the radio to the desired frequency */
104         unsigned long long bitmask, f, m;
105         unsigned int stereo = dev->stereo;
106         int i;
107
108         if (freq == 0)
109                 return 1;
110         m = (freq / 160 - 8800) * 2;
111         f = (unsigned long long) m + 0x4d1c;
112
113         bitmask = 0xc480402c10080000ull;
114         i = 45;
115
116         down(&dev->lock);
117         
118         outb(0, io);
119         outb(0, io);
120         inb(io + 3);            /* Zoltrix needs to be read to confirm */
121
122         outb(0x40, io);
123         outb(0xc0, io);
124
125         bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31));
126         while (i--) {
127                 if ((bitmask & 0x8000000000000000ull) != 0) {
128                         outb(0x80, io);
129                         udelay(50);
130                         outb(0x00, io);
131                         udelay(50);
132                         outb(0x80, io);
133                         udelay(50);
134                 } else {
135                         outb(0xc0, io);
136                         udelay(50);
137                         outb(0x40, io);
138                         udelay(50);
139                         outb(0xc0, io);
140                         udelay(50);
141                 }
142                 bitmask *= 2;
143         }
144         /* termination sequence */
145         outb(0x80, io);
146         outb(0xc0, io);
147         outb(0x40, io);
148         udelay(1000);
149         inb(io+2);
150
151         udelay(1000);
152         
153         if (dev->muted)
154         {
155                 outb(0, io);
156                 outb(0, io);
157                 inb(io + 3);
158                 udelay(1000);
159         }
160         
161         up(&dev->lock);
162         
163         if(!dev->muted)
164         {
165                 zol_setvol(dev, dev->curvol);
166         }
167         return 0;
168 }
169
170 /* Get signal strength */
171
172 int zol_getsigstr(struct zol_device *dev)
173 {
174         int a, b;
175
176         down(&dev->lock);
177         outb(0x00, io);         /* This stuff I found to do nothing */
178         outb(dev->curvol, io);
179         sleep_delay();
180         sleep_delay();
181
182         a = inb(io);
183         sleep_delay();
184         b = inb(io);
185
186         up(&dev->lock);
187         
188         if (a != b)
189                 return (0);
190
191         if ((a == 0xcf) || (a == 0xdf)  /* I found this out by playing */
192                 || (a == 0xef))       /* with a binary scanner on the card io */
193                 return (1);
194         return (0);
195 }
196
197 int zol_is_stereo (struct zol_device *dev)
198 {
199         int x1, x2;
200
201         down(&dev->lock);
202         
203         outb(0x00, io);
204         outb(dev->curvol, io);
205         sleep_delay();
206         sleep_delay();
207
208         x1 = inb(io);
209         sleep_delay();
210         x2 = inb(io);
211
212         up(&dev->lock);
213         
214         if ((x1 == x2) && (x1 == 0xcf))
215                 return 1;
216         return 0;
217 }
218
219 static int zol_do_ioctl(struct inode *inode, struct file *file,
220                         unsigned int cmd, void *arg)
221 {
222         struct video_device *dev = video_devdata(file);
223         struct zol_device *zol = dev->priv;
224
225         switch (cmd) {
226         case VIDIOCGCAP:
227                 {
228                         struct video_capability *v = arg;
229
230                         memset(v,0,sizeof(*v));
231                         v->type = VID_TYPE_TUNER;
232                         v->channels = 1 + zol->stereo;
233                         v->audios = 1;
234                         strcpy(v->name, "Zoltrix Radio");
235                         return 0;
236                 }
237         case VIDIOCGTUNER:
238                 {
239                         struct video_tuner *v = arg;
240                         if (v->tuner)   
241                                 return -EINVAL;
242                         strcpy(v->name, "FM");
243                         v->rangelow = (int) (88.0 * 16000);
244                         v->rangehigh = (int) (108.0 * 16000);
245                         v->flags = zol_is_stereo(zol)
246                                         ? VIDEO_TUNER_STEREO_ON : 0;
247                         v->flags |= VIDEO_TUNER_LOW;
248                         v->mode = VIDEO_MODE_AUTO;
249                         v->signal = 0xFFFF * zol_getsigstr(zol);
250                         return 0;
251                 }
252         case VIDIOCSTUNER:
253                 {
254                         struct video_tuner *v = arg;
255                         if (v->tuner != 0)
256                                 return -EINVAL;
257                         /* Only 1 tuner so no setting needed ! */
258                         return 0;
259                 }
260         case VIDIOCGFREQ:
261         {
262                 unsigned long *freq = arg;
263                 *freq = zol->curfreq;
264                 return 0;
265         }
266         case VIDIOCSFREQ:
267         {
268                 unsigned long *freq = arg;
269                 zol->curfreq = *freq;
270                 zol_setfreq(zol, zol->curfreq);
271                 return 0;
272         }
273         case VIDIOCGAUDIO:
274                 {
275                         struct video_audio *v = arg;
276                         memset(&v, 0, sizeof(*v));
277                         v->flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME;
278                         v->mode |= zol_is_stereo(zol)
279                                 ? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO;
280                         v->volume = zol->curvol * 4096;
281                         v->step = 4096;
282                         strcpy(v->name, "Zoltrix Radio");
283                         return 0;
284                 }
285         case VIDIOCSAUDIO:
286                 {
287                         struct video_audio *v = arg;
288                         if (v->audio)
289                                 return -EINVAL;
290
291                         if (v->flags & VIDEO_AUDIO_MUTE)
292                                 zol_mute(zol);
293                         else {
294                                 zol_unmute(zol);
295                                 zol_setvol(zol, v->volume / 4096);
296                         }
297
298                         if (v->mode & VIDEO_SOUND_STEREO) {
299                                 zol->stereo = 1;
300                                 zol_setfreq(zol, zol->curfreq);
301                         }
302                         if (v->mode & VIDEO_SOUND_MONO) {
303                                 zol->stereo = 0;
304                                 zol_setfreq(zol, zol->curfreq);
305                         }
306                         return 0;
307                 }
308         default:
309                 return -ENOIOCTLCMD;
310         }
311 }
312
313 static int zol_ioctl(struct inode *inode, struct file *file,
314                      unsigned int cmd, unsigned long arg)
315 {
316         return video_usercopy(inode, file, cmd, arg, zol_do_ioctl);
317 }
318
319 static struct zol_device zoltrix_unit;
320
321 static struct file_operations zoltrix_fops =
322 {
323         .owner          = THIS_MODULE,
324         .open           = video_exclusive_open,
325         .release        = video_exclusive_release,
326         .ioctl          = zol_ioctl,
327         .llseek         = no_llseek,
328 };
329
330 static struct video_device zoltrix_radio =
331 {
332         .owner          = THIS_MODULE,
333         .name           = "Zoltrix Radio Plus",
334         .type           = VID_TYPE_TUNER,
335         .hardware       = VID_HARDWARE_ZOLTRIX,
336         .fops           = &zoltrix_fops,
337 };
338
339 static int __init zoltrix_init(void)
340 {
341         if (io == -1) {
342                 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
343                 return -EINVAL;
344         }
345         if ((io != 0x20c) && (io != 0x30c)) {
346                 printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n");
347                 return -ENXIO;
348         }
349
350         zoltrix_radio.priv = &zoltrix_unit;
351         if (!request_region(io, 2, "zoltrix")) {
352                 printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io);
353                 return -EBUSY;
354         }
355
356         if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) == -1)
357         {
358                 release_region(io, 2);
359                 return -EINVAL;
360         }
361         printk(KERN_INFO "Zoltrix Radio Plus card driver.\n");
362
363         init_MUTEX(&zoltrix_unit.lock);
364         
365         /* mute card - prevents noisy bootups */
366
367         /* this ensures that the volume is all the way down  */
368
369         outb(0, io);
370         outb(0, io);
371         sleep_delay();
372         sleep_delay();
373         inb(io + 3);
374
375         zoltrix_unit.curvol = 0;
376         zoltrix_unit.stereo = 1;
377
378         return 0;
379 }
380
381 MODULE_AUTHOR("C.van Schaik");
382 MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
383 MODULE_LICENSE("GPL");
384
385 MODULE_PARM(io, "i");
386 MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
387 MODULE_PARM(radio_nr, "i");
388
389 static void __exit zoltrix_cleanup_module(void)
390 {
391         video_unregister_device(&zoltrix_radio);
392         release_region(io, 2);
393 }
394
395 module_init(zoltrix_init);
396 module_exit(zoltrix_cleanup_module);
397