Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* zoltrix radio plus driver for Linux radio support |
2 | * (c) 1998 C. van Schaik <carl@leg.uct.ac.za> | |
3 | * | |
4286c6f6 | 4 | * BUGS |
1da177e4 LT |
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 | |
4286c6f6 | 17 | * kinder to cpu while in delay |
1da177e4 LT |
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 | |
2ab65299 MCC |
27 | * |
28 | * 2006-07-24 - Converted to V4L2 API | |
29 | * by Mauro Carvalho Chehab <mchehab@infradead.org> | |
1da177e4 LT |
30 | */ |
31 | ||
32 | #include <linux/module.h> /* Modules */ | |
33 | #include <linux/init.h> /* Initdata */ | |
fb911ee8 | 34 | #include <linux/ioport.h> /* request_region */ |
1da177e4 LT |
35 | #include <linux/delay.h> /* udelay, msleep */ |
36 | #include <asm/io.h> /* outb, outb_p */ | |
37 | #include <asm/uaccess.h> /* copy to/from user */ | |
2ab65299 | 38 | #include <linux/videodev2.h> /* kernel radio structs */ |
5e87efa3 | 39 | #include <media/v4l2-common.h> |
35ea11ff | 40 | #include <media/v4l2-ioctl.h> |
1da177e4 | 41 | |
2ab65299 MCC |
42 | #include <linux/version.h> /* for KERNEL_VERSION MACRO */ |
43 | #define RADIO_VERSION KERNEL_VERSION(0,0,2) | |
44 | ||
45 | static struct v4l2_queryctrl radio_qctrl[] = { | |
46 | { | |
47 | .id = V4L2_CID_AUDIO_MUTE, | |
48 | .name = "Mute", | |
49 | .minimum = 0, | |
50 | .maximum = 1, | |
51 | .default_value = 1, | |
52 | .type = V4L2_CTRL_TYPE_BOOLEAN, | |
53 | },{ | |
54 | .id = V4L2_CID_AUDIO_VOLUME, | |
55 | .name = "Volume", | |
56 | .minimum = 0, | |
57 | .maximum = 65535, | |
58 | .step = 4096, | |
59 | .default_value = 0xff, | |
60 | .type = V4L2_CTRL_TYPE_INTEGER, | |
61 | } | |
62 | }; | |
63 | ||
1da177e4 LT |
64 | #ifndef CONFIG_RADIO_ZOLTRIX_PORT |
65 | #define CONFIG_RADIO_ZOLTRIX_PORT -1 | |
66 | #endif | |
67 | ||
68 | static int io = CONFIG_RADIO_ZOLTRIX_PORT; | |
69 | static int radio_nr = -1; | |
70 | ||
71 | struct zol_device { | |
3ca685aa | 72 | unsigned long in_use; |
1da177e4 LT |
73 | int port; |
74 | int curvol; | |
75 | unsigned long curfreq; | |
76 | int muted; | |
77 | unsigned int stereo; | |
3593cab5 | 78 | struct mutex lock; |
1da177e4 LT |
79 | }; |
80 | ||
81 | static int zol_setvol(struct zol_device *dev, int vol) | |
82 | { | |
83 | dev->curvol = vol; | |
84 | if (dev->muted) | |
85 | return 0; | |
86 | ||
3593cab5 | 87 | mutex_lock(&dev->lock); |
1da177e4 LT |
88 | if (vol == 0) { |
89 | outb(0, io); | |
90 | outb(0, io); | |
91 | inb(io + 3); /* Zoltrix needs to be read to confirm */ | |
3593cab5 | 92 | mutex_unlock(&dev->lock); |
1da177e4 LT |
93 | return 0; |
94 | } | |
95 | ||
96 | outb(dev->curvol-1, io); | |
97 | msleep(10); | |
98 | inb(io + 2); | |
3593cab5 | 99 | mutex_unlock(&dev->lock); |
1da177e4 LT |
100 | return 0; |
101 | } | |
102 | ||
103 | static void zol_mute(struct zol_device *dev) | |
104 | { | |
105 | dev->muted = 1; | |
3593cab5 | 106 | mutex_lock(&dev->lock); |
1da177e4 LT |
107 | outb(0, io); |
108 | outb(0, io); | |
109 | inb(io + 3); /* Zoltrix needs to be read to confirm */ | |
3593cab5 | 110 | mutex_unlock(&dev->lock); |
1da177e4 LT |
111 | } |
112 | ||
113 | static void zol_unmute(struct zol_device *dev) | |
114 | { | |
115 | dev->muted = 0; | |
116 | zol_setvol(dev, dev->curvol); | |
117 | } | |
118 | ||
119 | static int zol_setfreq(struct zol_device *dev, unsigned long freq) | |
120 | { | |
121 | /* tunes the radio to the desired frequency */ | |
122 | unsigned long long bitmask, f, m; | |
123 | unsigned int stereo = dev->stereo; | |
124 | int i; | |
125 | ||
126 | if (freq == 0) | |
127 | return 1; | |
128 | m = (freq / 160 - 8800) * 2; | |
129 | f = (unsigned long long) m + 0x4d1c; | |
130 | ||
131 | bitmask = 0xc480402c10080000ull; | |
132 | i = 45; | |
133 | ||
3593cab5 | 134 | mutex_lock(&dev->lock); |
4286c6f6 | 135 | |
1da177e4 LT |
136 | outb(0, io); |
137 | outb(0, io); | |
138 | inb(io + 3); /* Zoltrix needs to be read to confirm */ | |
139 | ||
140 | outb(0x40, io); | |
141 | outb(0xc0, io); | |
142 | ||
143 | bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31)); | |
144 | while (i--) { | |
145 | if ((bitmask & 0x8000000000000000ull) != 0) { | |
146 | outb(0x80, io); | |
147 | udelay(50); | |
148 | outb(0x00, io); | |
149 | udelay(50); | |
150 | outb(0x80, io); | |
151 | udelay(50); | |
152 | } else { | |
153 | outb(0xc0, io); | |
154 | udelay(50); | |
155 | outb(0x40, io); | |
156 | udelay(50); | |
157 | outb(0xc0, io); | |
158 | udelay(50); | |
159 | } | |
160 | bitmask *= 2; | |
161 | } | |
162 | /* termination sequence */ | |
163 | outb(0x80, io); | |
164 | outb(0xc0, io); | |
165 | outb(0x40, io); | |
166 | udelay(1000); | |
167 | inb(io+2); | |
168 | ||
4286c6f6 MCC |
169 | udelay(1000); |
170 | ||
1da177e4 LT |
171 | if (dev->muted) |
172 | { | |
173 | outb(0, io); | |
174 | outb(0, io); | |
175 | inb(io + 3); | |
176 | udelay(1000); | |
177 | } | |
4286c6f6 | 178 | |
3593cab5 | 179 | mutex_unlock(&dev->lock); |
4286c6f6 | 180 | |
1da177e4 LT |
181 | if(!dev->muted) |
182 | { | |
4286c6f6 | 183 | zol_setvol(dev, dev->curvol); |
1da177e4 LT |
184 | } |
185 | return 0; | |
186 | } | |
187 | ||
188 | /* Get signal strength */ | |
189 | ||
190 | static int zol_getsigstr(struct zol_device *dev) | |
191 | { | |
192 | int a, b; | |
193 | ||
3593cab5 | 194 | mutex_lock(&dev->lock); |
1da177e4 LT |
195 | outb(0x00, io); /* This stuff I found to do nothing */ |
196 | outb(dev->curvol, io); | |
197 | msleep(20); | |
198 | ||
199 | a = inb(io); | |
200 | msleep(10); | |
201 | b = inb(io); | |
202 | ||
3593cab5 | 203 | mutex_unlock(&dev->lock); |
4286c6f6 | 204 | |
1da177e4 LT |
205 | if (a != b) |
206 | return (0); | |
207 | ||
4286c6f6 | 208 | if ((a == 0xcf) || (a == 0xdf) /* I found this out by playing */ |
1da177e4 LT |
209 | || (a == 0xef)) /* with a binary scanner on the card io */ |
210 | return (1); | |
4286c6f6 | 211 | return (0); |
1da177e4 LT |
212 | } |
213 | ||
214 | static int zol_is_stereo (struct zol_device *dev) | |
215 | { | |
216 | int x1, x2; | |
217 | ||
3593cab5 | 218 | mutex_lock(&dev->lock); |
4286c6f6 | 219 | |
1da177e4 LT |
220 | outb(0x00, io); |
221 | outb(dev->curvol, io); | |
222 | msleep(20); | |
223 | ||
224 | x1 = inb(io); | |
225 | msleep(10); | |
226 | x2 = inb(io); | |
227 | ||
3593cab5 | 228 | mutex_unlock(&dev->lock); |
4286c6f6 | 229 | |
1da177e4 LT |
230 | if ((x1 == x2) && (x1 == 0xcf)) |
231 | return 1; | |
232 | return 0; | |
233 | } | |
234 | ||
a1314b1a DL |
235 | static int vidioc_querycap(struct file *file, void *priv, |
236 | struct v4l2_capability *v) | |
237 | { | |
238 | strlcpy(v->driver, "radio-zoltrix", sizeof(v->driver)); | |
239 | strlcpy(v->card, "Zoltrix Radio", sizeof(v->card)); | |
240 | sprintf(v->bus_info, "ISA"); | |
241 | v->version = RADIO_VERSION; | |
242 | v->capabilities = V4L2_CAP_TUNER; | |
243 | return 0; | |
244 | } | |
245 | ||
246 | static int vidioc_g_tuner(struct file *file, void *priv, | |
247 | struct v4l2_tuner *v) | |
1da177e4 | 248 | { |
c170ecf4 | 249 | struct zol_device *zol = video_drvdata(file); |
1da177e4 | 250 | |
a1314b1a DL |
251 | if (v->index > 0) |
252 | return -EINVAL; | |
2ab65299 | 253 | |
a1314b1a DL |
254 | strcpy(v->name, "FM"); |
255 | v->type = V4L2_TUNER_RADIO; | |
256 | v->rangelow = (88*16000); | |
257 | v->rangehigh = (108*16000); | |
258 | v->rxsubchans = V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO; | |
259 | v->capability = V4L2_TUNER_CAP_LOW; | |
260 | if (zol_is_stereo(zol)) | |
261 | v->audmode = V4L2_TUNER_MODE_STEREO; | |
262 | else | |
263 | v->audmode = V4L2_TUNER_MODE_MONO; | |
264 | v->signal = 0xFFFF*zol_getsigstr(zol); | |
265 | return 0; | |
266 | } | |
2ab65299 | 267 | |
a1314b1a DL |
268 | static int vidioc_s_tuner(struct file *file, void *priv, |
269 | struct v4l2_tuner *v) | |
270 | { | |
271 | if (v->index > 0) | |
272 | return -EINVAL; | |
273 | return 0; | |
274 | } | |
2ab65299 | 275 | |
a1314b1a DL |
276 | static int vidioc_s_frequency(struct file *file, void *priv, |
277 | struct v4l2_frequency *f) | |
278 | { | |
c170ecf4 | 279 | struct zol_device *zol = video_drvdata(file); |
2ab65299 | 280 | |
a1314b1a DL |
281 | zol->curfreq = f->frequency; |
282 | zol_setfreq(zol, zol->curfreq); | |
283 | return 0; | |
284 | } | |
2ab65299 | 285 | |
a1314b1a DL |
286 | static int vidioc_g_frequency(struct file *file, void *priv, |
287 | struct v4l2_frequency *f) | |
288 | { | |
c170ecf4 | 289 | struct zol_device *zol = video_drvdata(file); |
a1314b1a DL |
290 | |
291 | f->type = V4L2_TUNER_RADIO; | |
292 | f->frequency = zol->curfreq; | |
293 | return 0; | |
294 | } | |
1da177e4 | 295 | |
a1314b1a DL |
296 | static int vidioc_queryctrl(struct file *file, void *priv, |
297 | struct v4l2_queryctrl *qc) | |
298 | { | |
299 | int i; | |
1da177e4 | 300 | |
a1314b1a DL |
301 | for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { |
302 | if (qc->id && qc->id == radio_qctrl[i].id) { | |
303 | memcpy(qc, &(radio_qctrl[i]), | |
304 | sizeof(*qc)); | |
2ab65299 MCC |
305 | return 0; |
306 | } | |
a1314b1a DL |
307 | } |
308 | return -EINVAL; | |
309 | } | |
310 | ||
311 | static int vidioc_g_ctrl(struct file *file, void *priv, | |
312 | struct v4l2_control *ctrl) | |
313 | { | |
c170ecf4 | 314 | struct zol_device *zol = video_drvdata(file); |
a1314b1a DL |
315 | |
316 | switch (ctrl->id) { | |
317 | case V4L2_CID_AUDIO_MUTE: | |
318 | ctrl->value = zol->muted; | |
319 | return 0; | |
320 | case V4L2_CID_AUDIO_VOLUME: | |
321 | ctrl->value = zol->curvol * 4096; | |
322 | return 0; | |
323 | } | |
324 | return -EINVAL; | |
325 | } | |
326 | ||
327 | static int vidioc_s_ctrl(struct file *file, void *priv, | |
328 | struct v4l2_control *ctrl) | |
329 | { | |
c170ecf4 | 330 | struct zol_device *zol = video_drvdata(file); |
a1314b1a DL |
331 | |
332 | switch (ctrl->id) { | |
333 | case V4L2_CID_AUDIO_MUTE: | |
334 | if (ctrl->value) | |
335 | zol_mute(zol); | |
336 | else { | |
337 | zol_unmute(zol); | |
338 | zol_setvol(zol,zol->curvol); | |
2ab65299 | 339 | } |
a1314b1a DL |
340 | return 0; |
341 | case V4L2_CID_AUDIO_VOLUME: | |
342 | zol_setvol(zol,ctrl->value/4096); | |
343 | return 0; | |
344 | } | |
345 | zol->stereo = 1; | |
346 | zol_setfreq(zol, zol->curfreq); | |
2ab65299 MCC |
347 | #if 0 |
348 | /* FIXME: Implement stereo/mono switch on V4L2 */ | |
1da177e4 LT |
349 | if (v->mode & VIDEO_SOUND_STEREO) { |
350 | zol->stereo = 1; | |
351 | zol_setfreq(zol, zol->curfreq); | |
352 | } | |
353 | if (v->mode & VIDEO_SOUND_MONO) { | |
354 | zol->stereo = 0; | |
355 | zol_setfreq(zol, zol->curfreq); | |
356 | } | |
2ab65299 | 357 | #endif |
a1314b1a DL |
358 | return -EINVAL; |
359 | } | |
2ab65299 | 360 | |
a1314b1a DL |
361 | static int vidioc_g_audio(struct file *file, void *priv, |
362 | struct v4l2_audio *a) | |
363 | { | |
364 | if (a->index > 1) | |
365 | return -EINVAL; | |
366 | ||
367 | strcpy(a->name, "Radio"); | |
368 | a->capability = V4L2_AUDCAP_STEREO; | |
369 | return 0; | |
370 | } | |
371 | ||
372 | static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) | |
373 | { | |
374 | *i = 0; | |
375 | return 0; | |
1da177e4 LT |
376 | } |
377 | ||
a1314b1a | 378 | static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) |
1da177e4 | 379 | { |
a1314b1a DL |
380 | if (i != 0) |
381 | return -EINVAL; | |
382 | return 0; | |
383 | } | |
384 | ||
385 | static int vidioc_s_audio(struct file *file, void *priv, | |
386 | struct v4l2_audio *a) | |
387 | { | |
388 | if (a->index != 0) | |
389 | return -EINVAL; | |
390 | return 0; | |
1da177e4 LT |
391 | } |
392 | ||
393 | static struct zol_device zoltrix_unit; | |
394 | ||
3ca685aa HV |
395 | static int zoltrix_exclusive_open(struct inode *inode, struct file *file) |
396 | { | |
397 | return test_and_set_bit(0, &zoltrix_unit.in_use) ? -EBUSY : 0; | |
398 | } | |
399 | ||
400 | static int zoltrix_exclusive_release(struct inode *inode, struct file *file) | |
401 | { | |
402 | clear_bit(0, &zoltrix_unit.in_use); | |
403 | return 0; | |
404 | } | |
405 | ||
fa027c2a | 406 | static const struct file_operations zoltrix_fops = |
1da177e4 LT |
407 | { |
408 | .owner = THIS_MODULE, | |
3ca685aa HV |
409 | .open = zoltrix_exclusive_open, |
410 | .release = zoltrix_exclusive_release, | |
a1314b1a | 411 | .ioctl = video_ioctl2, |
078ff795 | 412 | #ifdef CONFIG_COMPAT |
0d0fbf81 | 413 | .compat_ioctl = v4l_compat_ioctl32, |
078ff795 | 414 | #endif |
1da177e4 LT |
415 | .llseek = no_llseek, |
416 | }; | |
417 | ||
a399810c | 418 | static const struct v4l2_ioctl_ops zoltrix_ioctl_ops = { |
a1314b1a DL |
419 | .vidioc_querycap = vidioc_querycap, |
420 | .vidioc_g_tuner = vidioc_g_tuner, | |
421 | .vidioc_s_tuner = vidioc_s_tuner, | |
422 | .vidioc_g_audio = vidioc_g_audio, | |
423 | .vidioc_s_audio = vidioc_s_audio, | |
424 | .vidioc_g_input = vidioc_g_input, | |
425 | .vidioc_s_input = vidioc_s_input, | |
426 | .vidioc_g_frequency = vidioc_g_frequency, | |
427 | .vidioc_s_frequency = vidioc_s_frequency, | |
428 | .vidioc_queryctrl = vidioc_queryctrl, | |
429 | .vidioc_g_ctrl = vidioc_g_ctrl, | |
430 | .vidioc_s_ctrl = vidioc_s_ctrl, | |
1da177e4 LT |
431 | }; |
432 | ||
a399810c | 433 | static struct video_device zoltrix_radio = { |
a399810c | 434 | .name = "Zoltrix Radio Plus", |
a399810c HV |
435 | .fops = &zoltrix_fops, |
436 | .ioctl_ops = &zoltrix_ioctl_ops, | |
aa5e90af | 437 | .release = video_device_release_empty, |
a399810c HV |
438 | }; |
439 | ||
1da177e4 LT |
440 | static int __init zoltrix_init(void) |
441 | { | |
442 | if (io == -1) { | |
443 | printk(KERN_ERR "You must set an I/O address with io=0x???\n"); | |
444 | return -EINVAL; | |
445 | } | |
446 | if ((io != 0x20c) && (io != 0x30c)) { | |
447 | printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n"); | |
448 | return -ENXIO; | |
449 | } | |
450 | ||
601e9444 | 451 | video_set_drvdata(&zoltrix_radio, &zoltrix_unit); |
1da177e4 LT |
452 | if (!request_region(io, 2, "zoltrix")) { |
453 | printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io); | |
454 | return -EBUSY; | |
455 | } | |
456 | ||
cba99ae8 | 457 | if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) < 0) { |
1da177e4 LT |
458 | release_region(io, 2); |
459 | return -EINVAL; | |
460 | } | |
461 | printk(KERN_INFO "Zoltrix Radio Plus card driver.\n"); | |
462 | ||
3593cab5 | 463 | mutex_init(&zoltrix_unit.lock); |
4286c6f6 | 464 | |
1da177e4 LT |
465 | /* mute card - prevents noisy bootups */ |
466 | ||
467 | /* this ensures that the volume is all the way down */ | |
468 | ||
469 | outb(0, io); | |
470 | outb(0, io); | |
471 | msleep(20); | |
472 | inb(io + 3); | |
473 | ||
474 | zoltrix_unit.curvol = 0; | |
475 | zoltrix_unit.stereo = 1; | |
476 | ||
477 | return 0; | |
478 | } | |
479 | ||
480 | MODULE_AUTHOR("C.van Schaik"); | |
481 | MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus."); | |
482 | MODULE_LICENSE("GPL"); | |
483 | ||
484 | module_param(io, int, 0); | |
485 | MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)"); | |
486 | module_param(radio_nr, int, 0); | |
487 | ||
488 | static void __exit zoltrix_cleanup_module(void) | |
489 | { | |
490 | video_unregister_device(&zoltrix_radio); | |
491 | release_region(io, 2); | |
492 | } | |
493 | ||
494 | module_init(zoltrix_init); | |
495 | module_exit(zoltrix_cleanup_module); | |
496 |