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