File: /usr/src/linux/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 */
27
28 #include <linux/module.h> /* Modules */
29 #include <linux/init.h> /* Initdata */
30 #include <linux/ioport.h> /* check_region, request_region */
31 #include <linux/delay.h> /* udelay */
32 #include <asm/io.h> /* outb, outb_p */
33 #include <asm/uaccess.h> /* copy to/from user */
34 #include <linux/videodev.h> /* kernel radio structs */
35 #include <linux/config.h> /* CONFIG_RADIO_ZOLTRIX_PORT */
36
37 #ifndef CONFIG_RADIO_ZOLTRIX_PORT
38 #define CONFIG_RADIO_ZOLTRIX_PORT -1
39 #endif
40
41 static int io = CONFIG_RADIO_ZOLTRIX_PORT;
42 static int radio_nr = -1;
43 static int users = 0;
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_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
220 {
221 struct zol_device *zol = dev->priv;
222
223 switch (cmd) {
224 case VIDIOCGCAP:
225 {
226 struct video_capability v;
227 v.type = VID_TYPE_TUNER;
228 v.channels = 1 + zol->stereo;
229 v.audios = 1;
230 /* No we don't do pictures */
231 v.maxwidth = 0;
232 v.maxheight = 0;
233 v.minwidth = 0;
234 v.minheight = 0;
235 strcpy(v.name, "Zoltrix Radio");
236 if (copy_to_user(arg, &v, sizeof(v)))
237 return -EFAULT;
238 return 0;
239 }
240 case VIDIOCGTUNER:
241 {
242 struct video_tuner v;
243 if (copy_from_user(&v, arg, sizeof(v)))
244 return -EFAULT;
245 if (v.tuner)
246 return -EINVAL;
247 strcpy(v.name, "FM");
248 v.rangelow = (int) (88.0 * 16000);
249 v.rangehigh = (int) (108.0 * 16000);
250 v.flags = zol_is_stereo(zol)
251 ? VIDEO_TUNER_STEREO_ON : 0;
252 v.flags |= VIDEO_TUNER_LOW;
253 v.mode = VIDEO_MODE_AUTO;
254 v.signal = 0xFFFF * zol_getsigstr(zol);
255 if (copy_to_user(arg, &v, sizeof(v)))
256 return -EFAULT;
257 return 0;
258 }
259 case VIDIOCSTUNER:
260 {
261 struct video_tuner v;
262 if (copy_from_user(&v, arg, sizeof(v)))
263 return -EFAULT;
264 if (v.tuner != 0)
265 return -EINVAL;
266 /* Only 1 tuner so no setting needed ! */
267 return 0;
268 }
269 case VIDIOCGFREQ:
270 if (copy_to_user(arg, &zol->curfreq, sizeof(zol->curfreq)))
271 return -EFAULT;
272 return 0;
273 case VIDIOCSFREQ:
274 if (copy_from_user(&zol->curfreq, arg, sizeof(zol->curfreq)))
275 return -EFAULT;
276 zol_setfreq(zol, zol->curfreq);
277 return 0;
278 case VIDIOCGAUDIO:
279 {
280 struct video_audio v;
281 memset(&v, 0, sizeof(v));
282 v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME;
283 v.mode != zol_is_stereo(zol)
284 ? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO;
285 v.volume = zol->curvol * 4096;
286 v.step = 4096;
287 strcpy(v.name, "Zoltrix Radio");
288 if (copy_to_user(arg, &v, sizeof(v)))
289 return -EFAULT;
290 return 0;
291 }
292 case VIDIOCSAUDIO:
293 {
294 struct video_audio v;
295 if (copy_from_user(&v, arg, sizeof(v)))
296 return -EFAULT;
297 if (v.audio)
298 return -EINVAL;
299
300 if (v.flags & VIDEO_AUDIO_MUTE)
301 zol_mute(zol);
302 else
303 {
304 zol_unmute(zol);
305 zol_setvol(zol, v.volume / 4096);
306 }
307
308 if (v.mode & VIDEO_SOUND_STEREO)
309 {
310 zol->stereo = 1;
311 zol_setfreq(zol, zol->curfreq);
312 }
313 if (v.mode & VIDEO_SOUND_MONO)
314 {
315 zol->stereo = 0;
316 zol_setfreq(zol, zol->curfreq);
317 }
318
319 return 0;
320 }
321 default:
322 return -ENOIOCTLCMD;
323 }
324 }
325
326 static int zol_open(struct video_device *dev, int flags)
327 {
328 if (users)
329 return -EBUSY;
330 users++;
331 return 0;
332 }
333
334 static void zol_close(struct video_device *dev)
335 {
336 users--;
337 }
338
339 static struct zol_device zoltrix_unit;
340
341 static struct video_device zoltrix_radio =
342 {
343 owner: THIS_MODULE,
344 name: "Zoltrix Radio Plus",
345 type: VID_TYPE_TUNER,
346 hardware: VID_HARDWARE_ZOLTRIX,
347 open: zol_open,
348 close: zol_close,
349 ioctl: zol_ioctl,
350 };
351
352 static int __init zoltrix_init(void)
353 {
354 if (io == -1) {
355 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
356 return -EINVAL;
357 }
358 if ((io != 0x20c) && (io != 0x30c)) {
359 printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n");
360 return -ENXIO;
361 }
362
363 zoltrix_radio.priv = &zoltrix_unit;
364 if (!request_region(io, 2, "zoltrix")) {
365 printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io);
366 return -EBUSY;
367 }
368
369 if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) == -1)
370 {
371 release_region(io, 2);
372 return -EINVAL;
373 }
374 printk(KERN_INFO "Zoltrix Radio Plus card driver.\n");
375
376 init_MUTEX(&zoltrix_unit.lock);
377
378 /* mute card - prevents noisy bootups */
379
380 /* this ensures that the volume is all the way down */
381
382 outb(0, io);
383 outb(0, io);
384 sleep_delay();
385 sleep_delay();
386 inb(io + 3);
387
388 zoltrix_unit.curvol = 0;
389 zoltrix_unit.stereo = 1;
390
391 return 0;
392 }
393
394 MODULE_AUTHOR("C.van Schaik");
395 MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
396 MODULE_PARM(io, "i");
397 MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
398 MODULE_PARM(radio_nr, "i");
399
400 EXPORT_NO_SYMBOLS;
401
402 static void __exit zoltrix_cleanup_module(void)
403 {
404 video_unregister_device(&zoltrix_radio);
405 release_region(io, 2);
406 }
407
408 module_init(zoltrix_init);
409 module_exit(zoltrix_cleanup_module);
410
411