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
sleep_delay(void)58 static void sleep_delay(void)
59 {
60 /* Sleep nicely for +/- 10 mS */
61 schedule();
62 }
63
zol_setvol(struct zol_device * dev,int vol)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
zol_mute(struct zol_device * dev)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
zol_unmute(struct zol_device * dev)96 static void zol_unmute(struct zol_device *dev)
97 {
98 dev->muted = 0;
99 zol_setvol(dev, dev->curvol);
100 }
101
zol_setfreq(struct zol_device * dev,unsigned long freq)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
zol_getsigstr(struct zol_device * dev)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
zol_is_stereo(struct zol_device * dev)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
zol_ioctl(struct video_device * dev,unsigned int cmd,void * arg)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
zol_open(struct video_device * dev,int flags)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
zol_close(struct video_device * dev)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
zoltrix_init(void)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
zoltrix_cleanup_module(void)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
414