2 * Miro PCM20 radio driver for Linux radio support
3 * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl>
4 * Thanks to Norberto Pellici for the ACI device interface specification
5 * The API part is based on the radiotrack driver by M. Kirkwood
6 * This driver relies on the aci mixer provided by the snd-miro
8 * Look there for further info...
10 * From the original miro RDS sources:
12 * (c) 2001 Robert Siemer <Robert.Siemer@gmx.de>
14 * Many thanks to Fred Seidel <seidel@metabox.de>, the
15 * designer of the RDS decoder hardware. With his help
16 * I was able to code this driver.
17 * Thanks also to Norberto Pellicci, Dominic Mounteney
18 * <DMounteney@pinnaclesys.com> and www.teleauskunft.de
19 * for good hints on finding Fred. It was somewhat hard
20 * to locate him here in Germany... [:
22 * This code has been reintroduced and converted to use
23 * the new V4L2 RDS API by:
25 * Hans Verkuil <hans.verkuil@cisco.com>
28 #include <linux/module.h>
29 #include <linux/init.h>
31 #include <linux/delay.h>
32 #include <linux/videodev2.h>
33 #include <linux/kthread.h>
34 #include <media/v4l2-device.h>
35 #include <media/v4l2-ioctl.h>
36 #include <media/v4l2-ctrls.h>
37 #include <media/v4l2-fh.h>
38 #include <media/v4l2-event.h>
39 #include <sound/aci.h>
41 #define RDS_DATASHIFT 2 /* Bit 2 */
42 #define RDS_DATAMASK (1 << RDS_DATASHIFT)
43 #define RDS_BUSYMASK 0x10 /* Bit 4 */
44 #define RDS_CLOCKMASK 0x08 /* Bit 3 */
45 #define RDS_DATA(x) (((x) >> RDS_DATASHIFT) & 1)
47 #define RDS_STATUS 0x01
48 #define RDS_STATIONNAME 0x02
50 #define RDS_ALTFREQ 0x04
51 #define RDS_TIMEDATE 0x05
52 #define RDS_PI_CODE 0x06
53 #define RDS_PTYTATP 0x07
54 #define RDS_RESET 0x08
55 #define RDS_RXVALUE 0x09
57 static int radio_nr
= -1;
58 module_param(radio_nr
, int, 0);
59 MODULE_PARM_DESC(radio_nr
, "Set radio device number (/dev/radioX). Default: -1 (autodetect)");
62 struct v4l2_device v4l2_dev
;
63 struct video_device vdev
;
64 struct v4l2_ctrl_handler ctrl_handler
;
65 struct v4l2_ctrl
*rds_pty
;
66 struct v4l2_ctrl
*rds_ps_name
;
67 struct v4l2_ctrl
*rds_radio_test
;
68 struct v4l2_ctrl
*rds_ta
;
69 struct v4l2_ctrl
*rds_tp
;
70 struct v4l2_ctrl
*rds_ms
;
71 /* thread for periodic RDS status checking */
72 struct task_struct
*kthread
;
75 struct snd_miro_aci
*aci
;
79 static struct pcm20 pcm20_card
= {
81 .audmode
= V4L2_TUNER_MODE_STEREO
,
85 static int rds_waitread(struct snd_miro_aci
*aci
)
91 byte
= inb(aci
->aci_port
+ ACI_REG_RDS
);
93 } while ((byte
& RDS_BUSYMASK
) && i
);
96 * It's magic, but without this the data that you read later on
97 * is unreliable and full of bit errors. With this 1 usec delay
101 return i
? byte
: -1;
104 static int rds_rawwrite(struct snd_miro_aci
*aci
, u8 byte
)
106 if (rds_waitread(aci
) >= 0) {
107 outb(byte
, aci
->aci_port
+ ACI_REG_RDS
);
113 static int rds_write(struct snd_miro_aci
*aci
, u8 byte
)
118 for (i
= 7; i
>= 0; i
--)
119 sendbuffer
[7 - i
] = (byte
& (1 << i
)) ? RDS_DATAMASK
: 0;
120 sendbuffer
[0] |= RDS_CLOCKMASK
;
122 for (i
= 0; i
< 8; i
++)
123 rds_rawwrite(aci
, sendbuffer
[i
]);
127 static int rds_readcycle_nowait(struct snd_miro_aci
*aci
)
129 outb(0, aci
->aci_port
+ ACI_REG_RDS
);
130 return rds_waitread(aci
);
133 static int rds_readcycle(struct snd_miro_aci
*aci
)
135 if (rds_rawwrite(aci
, 0) < 0)
137 return rds_waitread(aci
);
140 static int rds_ack(struct snd_miro_aci
*aci
)
142 int i
= rds_readcycle(aci
);
146 if (i
& RDS_DATAMASK
)
151 static int rds_cmd(struct snd_miro_aci
*aci
, u8 cmd
, u8 databuffer
[], u8 datasize
)
157 /* RDS_RESET doesn't need further processing */
158 if (cmd
== RDS_RESET
)
165 /* to be able to use rds_readcycle_nowait()
166 I have to waitread() here */
167 if (rds_waitread(aci
) < 0)
170 memset(databuffer
, 0, datasize
);
172 for (i
= 0; i
< 8 * datasize
; i
++) {
173 j
= rds_readcycle_nowait(aci
);
176 databuffer
[i
/ 8] |= RDS_DATA(j
) << (7 - (i
% 8));
181 static int pcm20_setfreq(struct pcm20
*dev
, unsigned long freq
)
185 struct snd_miro_aci
*aci
= dev
->aci
;
188 if (!(aci
->aci_version
== 0x07 || aci
->aci_version
>= 0xb0))
189 freq
/= 10; /* I don't know exactly which version
194 rds_cmd(aci
, RDS_RESET
, NULL
, 0);
195 return snd_aci_cmd(aci
, ACI_WRITE_TUNE
, freql
, freqh
);
198 static int vidioc_querycap(struct file
*file
, void *priv
,
199 struct v4l2_capability
*v
)
201 struct pcm20
*dev
= video_drvdata(file
);
203 strscpy(v
->driver
, "Miro PCM20", sizeof(v
->driver
));
204 strscpy(v
->card
, "Miro PCM20", sizeof(v
->card
));
205 snprintf(v
->bus_info
, sizeof(v
->bus_info
), "ISA:%s", dev
->v4l2_dev
.name
);
206 v
->device_caps
= V4L2_CAP_TUNER
| V4L2_CAP_RADIO
| V4L2_CAP_RDS_CAPTURE
;
207 v
->capabilities
= v
->device_caps
| V4L2_CAP_DEVICE_CAPS
;
211 static bool sanitize(char *p
, int size
)
216 for (i
= 0; i
< size
; i
++) {
225 static int vidioc_g_tuner(struct file
*file
, void *priv
,
226 struct v4l2_tuner
*v
)
228 struct pcm20
*dev
= video_drvdata(file
);
234 strscpy(v
->name
, "FM", sizeof(v
->name
));
235 v
->type
= V4L2_TUNER_RADIO
;
236 v
->rangelow
= 87*16000;
237 v
->rangehigh
= 108*16000;
238 res
= snd_aci_cmd(dev
->aci
, ACI_READ_TUNERSTATION
, -1, -1);
239 v
->signal
= (res
& 0x80) ? 0 : 0xffff;
240 /* Note: stereo detection does not work if the audio is muted,
241 it will default to mono in that case. */
242 res
= snd_aci_cmd(dev
->aci
, ACI_READ_TUNERSTEREO
, -1, -1);
243 v
->rxsubchans
= (res
& 0x40) ? V4L2_TUNER_SUB_MONO
:
244 V4L2_TUNER_SUB_STEREO
;
245 v
->capability
= V4L2_TUNER_CAP_LOW
| V4L2_TUNER_CAP_STEREO
|
246 V4L2_TUNER_CAP_RDS
| V4L2_TUNER_CAP_RDS_CONTROLS
;
247 v
->audmode
= dev
->audmode
;
248 res
= rds_cmd(dev
->aci
, RDS_RXVALUE
, &buf
, 1);
250 v
->rxsubchans
|= V4L2_TUNER_SUB_RDS
;
254 static int vidioc_s_tuner(struct file
*file
, void *priv
,
255 const struct v4l2_tuner
*v
)
257 struct pcm20
*dev
= video_drvdata(file
);
261 if (v
->audmode
> V4L2_TUNER_MODE_STEREO
)
262 dev
->audmode
= V4L2_TUNER_MODE_STEREO
;
264 dev
->audmode
= v
->audmode
;
265 snd_aci_cmd(dev
->aci
, ACI_SET_TUNERMONO
,
266 dev
->audmode
== V4L2_TUNER_MODE_MONO
, -1);
270 static int vidioc_g_frequency(struct file
*file
, void *priv
,
271 struct v4l2_frequency
*f
)
273 struct pcm20
*dev
= video_drvdata(file
);
278 f
->type
= V4L2_TUNER_RADIO
;
279 f
->frequency
= dev
->freq
;
284 static int vidioc_s_frequency(struct file
*file
, void *priv
,
285 const struct v4l2_frequency
*f
)
287 struct pcm20
*dev
= video_drvdata(file
);
289 if (f
->tuner
!= 0 || f
->type
!= V4L2_TUNER_RADIO
)
292 dev
->freq
= clamp_t(u32
, f
->frequency
, 87 * 16000U, 108 * 16000U);
293 pcm20_setfreq(dev
, dev
->freq
);
297 static int pcm20_s_ctrl(struct v4l2_ctrl
*ctrl
)
299 struct pcm20
*dev
= container_of(ctrl
->handler
, struct pcm20
, ctrl_handler
);
302 case V4L2_CID_AUDIO_MUTE
:
303 snd_aci_cmd(dev
->aci
, ACI_SET_TUNERMUTE
, ctrl
->val
, -1);
309 static int pcm20_thread(void *data
)
311 struct pcm20
*dev
= data
;
312 const unsigned no_rds_start_counter
= 5;
313 const unsigned sleep_msecs
= 2000;
314 unsigned no_rds_counter
= no_rds_start_counter
;
317 char text_buffer
[66];
321 msleep_interruptible(sleep_msecs
);
323 if (kthread_should_stop())
326 res
= rds_cmd(dev
->aci
, RDS_RXVALUE
, &buf
, 1);
330 if (no_rds_counter
== 0)
337 * No RDS seen for no_rds_start_counter * sleep_msecs
338 * milliseconds, clear all RDS controls to their
341 v4l2_ctrl_s_ctrl_string(dev
->rds_ps_name
, "");
342 v4l2_ctrl_s_ctrl(dev
->rds_ms
, 1);
343 v4l2_ctrl_s_ctrl(dev
->rds_ta
, 0);
344 v4l2_ctrl_s_ctrl(dev
->rds_tp
, 0);
345 v4l2_ctrl_s_ctrl(dev
->rds_pty
, 0);
346 v4l2_ctrl_s_ctrl_string(dev
->rds_radio_test
, "");
349 no_rds_counter
= no_rds_start_counter
;
351 res
= rds_cmd(dev
->aci
, RDS_STATUS
, &buf
, 1);
354 if ((buf
>> 3) & 1) {
355 res
= rds_cmd(dev
->aci
, RDS_STATIONNAME
, text_buffer
, 8);
357 if (!res
&& sanitize(text_buffer
, 8))
358 v4l2_ctrl_s_ctrl_string(dev
->rds_ps_name
, text_buffer
);
360 if ((buf
>> 6) & 1) {
363 res
= rds_cmd(dev
->aci
, RDS_PTYTATP
, &pty
, 1);
365 v4l2_ctrl_s_ctrl(dev
->rds_ms
, !!(pty
& 0x01));
366 v4l2_ctrl_s_ctrl(dev
->rds_ta
, !!(pty
& 0x02));
367 v4l2_ctrl_s_ctrl(dev
->rds_tp
, !!(pty
& 0x80));
368 v4l2_ctrl_s_ctrl(dev
->rds_pty
, (pty
>> 2) & 0x1f);
371 if ((buf
>> 4) & 1) {
372 res
= rds_cmd(dev
->aci
, RDS_TEXT
, text_buffer
, 65);
374 if (!res
&& sanitize(text_buffer
+ 1, 64))
375 v4l2_ctrl_s_ctrl_string(dev
->rds_radio_test
, text_buffer
+ 1);
381 static int pcm20_open(struct file
*file
)
383 struct pcm20
*dev
= video_drvdata(file
);
384 int res
= v4l2_fh_open(file
);
386 if (!res
&& v4l2_fh_is_singular_file(file
) &&
387 IS_ERR_OR_NULL(dev
->kthread
)) {
388 dev
->kthread
= kthread_run(pcm20_thread
, dev
, "%s",
390 if (IS_ERR(dev
->kthread
)) {
391 v4l2_err(&dev
->v4l2_dev
, "kernel_thread() failed\n");
392 v4l2_fh_release(file
);
393 return PTR_ERR(dev
->kthread
);
399 static int pcm20_release(struct file
*file
)
401 struct pcm20
*dev
= video_drvdata(file
);
403 if (v4l2_fh_is_singular_file(file
) && !IS_ERR_OR_NULL(dev
->kthread
)) {
404 kthread_stop(dev
->kthread
);
407 return v4l2_fh_release(file
);
410 static const struct v4l2_file_operations pcm20_fops
= {
411 .owner
= THIS_MODULE
,
413 .poll
= v4l2_ctrl_poll
,
414 .release
= pcm20_release
,
415 .unlocked_ioctl
= video_ioctl2
,
418 static const struct v4l2_ioctl_ops pcm20_ioctl_ops
= {
419 .vidioc_querycap
= vidioc_querycap
,
420 .vidioc_g_tuner
= vidioc_g_tuner
,
421 .vidioc_s_tuner
= vidioc_s_tuner
,
422 .vidioc_g_frequency
= vidioc_g_frequency
,
423 .vidioc_s_frequency
= vidioc_s_frequency
,
424 .vidioc_log_status
= v4l2_ctrl_log_status
,
425 .vidioc_subscribe_event
= v4l2_ctrl_subscribe_event
,
426 .vidioc_unsubscribe_event
= v4l2_event_unsubscribe
,
429 static const struct v4l2_ctrl_ops pcm20_ctrl_ops
= {
430 .s_ctrl
= pcm20_s_ctrl
,
433 static int __init
pcm20_init(void)
435 struct pcm20
*dev
= &pcm20_card
;
436 struct v4l2_device
*v4l2_dev
= &dev
->v4l2_dev
;
437 struct v4l2_ctrl_handler
*hdl
;
440 dev
->aci
= snd_aci_get_aci();
441 if (dev
->aci
== NULL
) {
443 "you must load the snd-miro driver first!\n");
446 strscpy(v4l2_dev
->name
, "radio-miropcm20", sizeof(v4l2_dev
->name
));
447 mutex_init(&dev
->lock
);
449 res
= v4l2_device_register(NULL
, v4l2_dev
);
451 v4l2_err(v4l2_dev
, "could not register v4l2_device\n");
455 hdl
= &dev
->ctrl_handler
;
456 v4l2_ctrl_handler_init(hdl
, 7);
457 v4l2_ctrl_new_std(hdl
, &pcm20_ctrl_ops
,
458 V4L2_CID_AUDIO_MUTE
, 0, 1, 1, 1);
459 dev
->rds_pty
= v4l2_ctrl_new_std(hdl
, NULL
,
460 V4L2_CID_RDS_RX_PTY
, 0, 0x1f, 1, 0);
461 dev
->rds_ps_name
= v4l2_ctrl_new_std(hdl
, NULL
,
462 V4L2_CID_RDS_RX_PS_NAME
, 0, 8, 8, 0);
463 dev
->rds_radio_test
= v4l2_ctrl_new_std(hdl
, NULL
,
464 V4L2_CID_RDS_RX_RADIO_TEXT
, 0, 64, 64, 0);
465 dev
->rds_ta
= v4l2_ctrl_new_std(hdl
, NULL
,
466 V4L2_CID_RDS_RX_TRAFFIC_ANNOUNCEMENT
, 0, 1, 1, 0);
467 dev
->rds_tp
= v4l2_ctrl_new_std(hdl
, NULL
,
468 V4L2_CID_RDS_RX_TRAFFIC_PROGRAM
, 0, 1, 1, 0);
469 dev
->rds_ms
= v4l2_ctrl_new_std(hdl
, NULL
,
470 V4L2_CID_RDS_RX_MUSIC_SPEECH
, 0, 1, 1, 1);
471 v4l2_dev
->ctrl_handler
= hdl
;
474 v4l2_err(v4l2_dev
, "Could not register control\n");
477 strscpy(dev
->vdev
.name
, v4l2_dev
->name
, sizeof(dev
->vdev
.name
));
478 dev
->vdev
.v4l2_dev
= v4l2_dev
;
479 dev
->vdev
.fops
= &pcm20_fops
;
480 dev
->vdev
.ioctl_ops
= &pcm20_ioctl_ops
;
481 dev
->vdev
.release
= video_device_release_empty
;
482 dev
->vdev
.lock
= &dev
->lock
;
483 video_set_drvdata(&dev
->vdev
, dev
);
484 snd_aci_cmd(dev
->aci
, ACI_SET_TUNERMONO
,
485 dev
->audmode
== V4L2_TUNER_MODE_MONO
, -1);
486 pcm20_setfreq(dev
, dev
->freq
);
488 if (video_register_device(&dev
->vdev
, VFL_TYPE_RADIO
, radio_nr
) < 0)
491 v4l2_info(v4l2_dev
, "Mirosound PCM20 Radio tuner\n");
494 v4l2_ctrl_handler_free(hdl
);
495 v4l2_device_unregister(v4l2_dev
);
499 MODULE_AUTHOR("Ruurd Reitsma, Krzysztof Helt");
500 MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card.");
501 MODULE_LICENSE("GPL");
503 static void __exit
pcm20_cleanup(void)
505 struct pcm20
*dev
= &pcm20_card
;
507 video_unregister_device(&dev
->vdev
);
508 snd_aci_cmd(dev
->aci
, ACI_SET_TUNERMUTE
, 1, -1);
509 v4l2_ctrl_handler_free(&dev
->ctrl_handler
);
510 v4l2_device_unregister(&dev
->v4l2_dev
);
513 module_init(pcm20_init
);
514 module_exit(pcm20_cleanup
);