1 // SPDX-License-Identifier: GPL-2.0-only
3 * Miro PCM20 radio driver for Linux radio support
4 * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl>
5 * Thanks to Norberto Pellici for the ACI device interface specification
6 * The API part is based on the radiotrack driver by M. Kirkwood
7 * This driver relies on the aci mixer provided by the snd-miro
9 * Look there for further info...
11 * From the original miro RDS sources:
13 * (c) 2001 Robert Siemer <Robert.Siemer@gmx.de>
15 * Many thanks to Fred Seidel <seidel@metabox.de>, the
16 * designer of the RDS decoder hardware. With his help
17 * I was able to code this driver.
18 * Thanks also to Norberto Pellicci, Dominic Mounteney
19 * <DMounteney@pinnaclesys.com> and www.teleauskunft.de
20 * for good hints on finding Fred. It was somewhat hard
21 * to locate him here in Germany... [:
23 * This code has been reintroduced and converted to use
24 * the new V4L2 RDS API by:
26 * Hans Verkuil <hans.verkuil@cisco.com>
29 #include <linux/module.h>
30 #include <linux/init.h>
32 #include <linux/delay.h>
33 #include <linux/videodev2.h>
34 #include <linux/kthread.h>
35 #include <media/v4l2-device.h>
36 #include <media/v4l2-ioctl.h>
37 #include <media/v4l2-ctrls.h>
38 #include <media/v4l2-fh.h>
39 #include <media/v4l2-event.h>
40 #include <sound/aci.h>
42 #define RDS_DATASHIFT 2 /* Bit 2 */
43 #define RDS_DATAMASK (1 << RDS_DATASHIFT)
44 #define RDS_BUSYMASK 0x10 /* Bit 4 */
45 #define RDS_CLOCKMASK 0x08 /* Bit 3 */
46 #define RDS_DATA(x) (((x) >> RDS_DATASHIFT) & 1)
48 #define RDS_STATUS 0x01
49 #define RDS_STATIONNAME 0x02
51 #define RDS_ALTFREQ 0x04
52 #define RDS_TIMEDATE 0x05
53 #define RDS_PI_CODE 0x06
54 #define RDS_PTYTATP 0x07
55 #define RDS_RESET 0x08
56 #define RDS_RXVALUE 0x09
58 static int radio_nr
= -1;
59 module_param(radio_nr
, int, 0);
60 MODULE_PARM_DESC(radio_nr
, "Set radio device number (/dev/radioX). Default: -1 (autodetect)");
63 struct v4l2_device v4l2_dev
;
64 struct video_device vdev
;
65 struct v4l2_ctrl_handler ctrl_handler
;
66 struct v4l2_ctrl
*rds_pty
;
67 struct v4l2_ctrl
*rds_ps_name
;
68 struct v4l2_ctrl
*rds_radio_test
;
69 struct v4l2_ctrl
*rds_ta
;
70 struct v4l2_ctrl
*rds_tp
;
71 struct v4l2_ctrl
*rds_ms
;
72 /* thread for periodic RDS status checking */
73 struct task_struct
*kthread
;
76 struct snd_miro_aci
*aci
;
80 static struct pcm20 pcm20_card
= {
82 .audmode
= V4L2_TUNER_MODE_STEREO
,
86 static int rds_waitread(struct snd_miro_aci
*aci
)
92 byte
= inb(aci
->aci_port
+ ACI_REG_RDS
);
94 } while ((byte
& RDS_BUSYMASK
) && i
);
97 * It's magic, but without this the data that you read later on
98 * is unreliable and full of bit errors. With this 1 usec delay
102 return i
? byte
: -1;
105 static int rds_rawwrite(struct snd_miro_aci
*aci
, u8 byte
)
107 if (rds_waitread(aci
) >= 0) {
108 outb(byte
, aci
->aci_port
+ ACI_REG_RDS
);
114 static int rds_write(struct snd_miro_aci
*aci
, u8 byte
)
119 for (i
= 7; i
>= 0; i
--)
120 sendbuffer
[7 - i
] = (byte
& (1 << i
)) ? RDS_DATAMASK
: 0;
121 sendbuffer
[0] |= RDS_CLOCKMASK
;
123 for (i
= 0; i
< 8; i
++)
124 rds_rawwrite(aci
, sendbuffer
[i
]);
128 static int rds_readcycle_nowait(struct snd_miro_aci
*aci
)
130 outb(0, aci
->aci_port
+ ACI_REG_RDS
);
131 return rds_waitread(aci
);
134 static int rds_readcycle(struct snd_miro_aci
*aci
)
136 if (rds_rawwrite(aci
, 0) < 0)
138 return rds_waitread(aci
);
141 static int rds_ack(struct snd_miro_aci
*aci
)
143 int i
= rds_readcycle(aci
);
147 if (i
& RDS_DATAMASK
)
152 static int rds_cmd(struct snd_miro_aci
*aci
, u8 cmd
, u8 databuffer
[], u8 datasize
)
158 /* RDS_RESET doesn't need further processing */
159 if (cmd
== RDS_RESET
)
166 /* to be able to use rds_readcycle_nowait()
167 I have to waitread() here */
168 if (rds_waitread(aci
) < 0)
171 memset(databuffer
, 0, datasize
);
173 for (i
= 0; i
< 8 * datasize
; i
++) {
174 j
= rds_readcycle_nowait(aci
);
177 databuffer
[i
/ 8] |= RDS_DATA(j
) << (7 - (i
% 8));
182 static int pcm20_setfreq(struct pcm20
*dev
, unsigned long freq
)
186 struct snd_miro_aci
*aci
= dev
->aci
;
189 if (!(aci
->aci_version
== 0x07 || aci
->aci_version
>= 0xb0))
190 freq
/= 10; /* I don't know exactly which version
195 rds_cmd(aci
, RDS_RESET
, NULL
, 0);
196 return snd_aci_cmd(aci
, ACI_WRITE_TUNE
, freql
, freqh
);
199 static int vidioc_querycap(struct file
*file
, void *priv
,
200 struct v4l2_capability
*v
)
202 struct pcm20
*dev
= video_drvdata(file
);
204 strscpy(v
->driver
, "Miro PCM20", sizeof(v
->driver
));
205 strscpy(v
->card
, "Miro PCM20", sizeof(v
->card
));
206 snprintf(v
->bus_info
, sizeof(v
->bus_info
), "ISA:%s", dev
->v4l2_dev
.name
);
210 static bool sanitize(char *p
, int size
)
215 for (i
= 0; i
< size
; i
++) {
224 static int vidioc_g_tuner(struct file
*file
, void *priv
,
225 struct v4l2_tuner
*v
)
227 struct pcm20
*dev
= video_drvdata(file
);
233 strscpy(v
->name
, "FM", sizeof(v
->name
));
234 v
->type
= V4L2_TUNER_RADIO
;
235 v
->rangelow
= 87*16000;
236 v
->rangehigh
= 108*16000;
237 res
= snd_aci_cmd(dev
->aci
, ACI_READ_TUNERSTATION
, -1, -1);
238 v
->signal
= (res
& 0x80) ? 0 : 0xffff;
239 /* Note: stereo detection does not work if the audio is muted,
240 it will default to mono in that case. */
241 res
= snd_aci_cmd(dev
->aci
, ACI_READ_TUNERSTEREO
, -1, -1);
242 v
->rxsubchans
= (res
& 0x40) ? V4L2_TUNER_SUB_MONO
:
243 V4L2_TUNER_SUB_STEREO
;
244 v
->capability
= V4L2_TUNER_CAP_LOW
| V4L2_TUNER_CAP_STEREO
|
245 V4L2_TUNER_CAP_RDS
| V4L2_TUNER_CAP_RDS_CONTROLS
;
246 v
->audmode
= dev
->audmode
;
247 res
= rds_cmd(dev
->aci
, RDS_RXVALUE
, &buf
, 1);
249 v
->rxsubchans
|= V4L2_TUNER_SUB_RDS
;
253 static int vidioc_s_tuner(struct file
*file
, void *priv
,
254 const struct v4l2_tuner
*v
)
256 struct pcm20
*dev
= video_drvdata(file
);
260 if (v
->audmode
> V4L2_TUNER_MODE_STEREO
)
261 dev
->audmode
= V4L2_TUNER_MODE_STEREO
;
263 dev
->audmode
= v
->audmode
;
264 snd_aci_cmd(dev
->aci
, ACI_SET_TUNERMONO
,
265 dev
->audmode
== V4L2_TUNER_MODE_MONO
, -1);
269 static int vidioc_g_frequency(struct file
*file
, void *priv
,
270 struct v4l2_frequency
*f
)
272 struct pcm20
*dev
= video_drvdata(file
);
277 f
->type
= V4L2_TUNER_RADIO
;
278 f
->frequency
= dev
->freq
;
283 static int vidioc_s_frequency(struct file
*file
, void *priv
,
284 const struct v4l2_frequency
*f
)
286 struct pcm20
*dev
= video_drvdata(file
);
288 if (f
->tuner
!= 0 || f
->type
!= V4L2_TUNER_RADIO
)
291 dev
->freq
= clamp_t(u32
, f
->frequency
, 87 * 16000U, 108 * 16000U);
292 pcm20_setfreq(dev
, dev
->freq
);
296 static int pcm20_s_ctrl(struct v4l2_ctrl
*ctrl
)
298 struct pcm20
*dev
= container_of(ctrl
->handler
, struct pcm20
, ctrl_handler
);
301 case V4L2_CID_AUDIO_MUTE
:
302 snd_aci_cmd(dev
->aci
, ACI_SET_TUNERMUTE
, ctrl
->val
, -1);
308 static int pcm20_thread(void *data
)
310 struct pcm20
*dev
= data
;
311 const unsigned no_rds_start_counter
= 5;
312 const unsigned sleep_msecs
= 2000;
313 unsigned no_rds_counter
= no_rds_start_counter
;
316 char text_buffer
[66];
320 msleep_interruptible(sleep_msecs
);
322 if (kthread_should_stop())
325 res
= rds_cmd(dev
->aci
, RDS_RXVALUE
, &buf
, 1);
329 if (no_rds_counter
== 0)
336 * No RDS seen for no_rds_start_counter * sleep_msecs
337 * milliseconds, clear all RDS controls to their
340 v4l2_ctrl_s_ctrl_string(dev
->rds_ps_name
, "");
341 v4l2_ctrl_s_ctrl(dev
->rds_ms
, 1);
342 v4l2_ctrl_s_ctrl(dev
->rds_ta
, 0);
343 v4l2_ctrl_s_ctrl(dev
->rds_tp
, 0);
344 v4l2_ctrl_s_ctrl(dev
->rds_pty
, 0);
345 v4l2_ctrl_s_ctrl_string(dev
->rds_radio_test
, "");
348 no_rds_counter
= no_rds_start_counter
;
350 res
= rds_cmd(dev
->aci
, RDS_STATUS
, &buf
, 1);
353 if ((buf
>> 3) & 1) {
354 res
= rds_cmd(dev
->aci
, RDS_STATIONNAME
, text_buffer
, 8);
356 if (!res
&& sanitize(text_buffer
, 8))
357 v4l2_ctrl_s_ctrl_string(dev
->rds_ps_name
, text_buffer
);
359 if ((buf
>> 6) & 1) {
362 res
= rds_cmd(dev
->aci
, RDS_PTYTATP
, &pty
, 1);
364 v4l2_ctrl_s_ctrl(dev
->rds_ms
, !!(pty
& 0x01));
365 v4l2_ctrl_s_ctrl(dev
->rds_ta
, !!(pty
& 0x02));
366 v4l2_ctrl_s_ctrl(dev
->rds_tp
, !!(pty
& 0x80));
367 v4l2_ctrl_s_ctrl(dev
->rds_pty
, (pty
>> 2) & 0x1f);
370 if ((buf
>> 4) & 1) {
371 res
= rds_cmd(dev
->aci
, RDS_TEXT
, text_buffer
, 65);
373 if (!res
&& sanitize(text_buffer
+ 1, 64))
374 v4l2_ctrl_s_ctrl_string(dev
->rds_radio_test
, text_buffer
+ 1);
380 static int pcm20_open(struct file
*file
)
382 struct pcm20
*dev
= video_drvdata(file
);
383 int res
= v4l2_fh_open(file
);
385 if (!res
&& v4l2_fh_is_singular_file(file
) &&
386 IS_ERR_OR_NULL(dev
->kthread
)) {
387 dev
->kthread
= kthread_run(pcm20_thread
, dev
, "%s",
389 if (IS_ERR(dev
->kthread
)) {
390 v4l2_err(&dev
->v4l2_dev
, "kernel_thread() failed\n");
391 v4l2_fh_release(file
);
392 return PTR_ERR(dev
->kthread
);
398 static int pcm20_release(struct file
*file
)
400 struct pcm20
*dev
= video_drvdata(file
);
402 if (v4l2_fh_is_singular_file(file
) && !IS_ERR_OR_NULL(dev
->kthread
)) {
403 kthread_stop(dev
->kthread
);
406 return v4l2_fh_release(file
);
409 static const struct v4l2_file_operations pcm20_fops
= {
410 .owner
= THIS_MODULE
,
412 .poll
= v4l2_ctrl_poll
,
413 .release
= pcm20_release
,
414 .unlocked_ioctl
= video_ioctl2
,
417 static const struct v4l2_ioctl_ops pcm20_ioctl_ops
= {
418 .vidioc_querycap
= vidioc_querycap
,
419 .vidioc_g_tuner
= vidioc_g_tuner
,
420 .vidioc_s_tuner
= vidioc_s_tuner
,
421 .vidioc_g_frequency
= vidioc_g_frequency
,
422 .vidioc_s_frequency
= vidioc_s_frequency
,
423 .vidioc_log_status
= v4l2_ctrl_log_status
,
424 .vidioc_subscribe_event
= v4l2_ctrl_subscribe_event
,
425 .vidioc_unsubscribe_event
= v4l2_event_unsubscribe
,
428 static const struct v4l2_ctrl_ops pcm20_ctrl_ops
= {
429 .s_ctrl
= pcm20_s_ctrl
,
432 static int __init
pcm20_init(void)
434 struct pcm20
*dev
= &pcm20_card
;
435 struct v4l2_device
*v4l2_dev
= &dev
->v4l2_dev
;
436 struct v4l2_ctrl_handler
*hdl
;
439 dev
->aci
= snd_aci_get_aci();
440 if (dev
->aci
== NULL
) {
442 "you must load the snd-miro driver first!\n");
445 strscpy(v4l2_dev
->name
, "radio-miropcm20", sizeof(v4l2_dev
->name
));
446 mutex_init(&dev
->lock
);
448 res
= v4l2_device_register(NULL
, v4l2_dev
);
450 v4l2_err(v4l2_dev
, "could not register v4l2_device\n");
454 hdl
= &dev
->ctrl_handler
;
455 v4l2_ctrl_handler_init(hdl
, 7);
456 v4l2_ctrl_new_std(hdl
, &pcm20_ctrl_ops
,
457 V4L2_CID_AUDIO_MUTE
, 0, 1, 1, 1);
458 dev
->rds_pty
= v4l2_ctrl_new_std(hdl
, NULL
,
459 V4L2_CID_RDS_RX_PTY
, 0, 0x1f, 1, 0);
460 dev
->rds_ps_name
= v4l2_ctrl_new_std(hdl
, NULL
,
461 V4L2_CID_RDS_RX_PS_NAME
, 0, 8, 8, 0);
462 dev
->rds_radio_test
= v4l2_ctrl_new_std(hdl
, NULL
,
463 V4L2_CID_RDS_RX_RADIO_TEXT
, 0, 64, 64, 0);
464 dev
->rds_ta
= v4l2_ctrl_new_std(hdl
, NULL
,
465 V4L2_CID_RDS_RX_TRAFFIC_ANNOUNCEMENT
, 0, 1, 1, 0);
466 dev
->rds_tp
= v4l2_ctrl_new_std(hdl
, NULL
,
467 V4L2_CID_RDS_RX_TRAFFIC_PROGRAM
, 0, 1, 1, 0);
468 dev
->rds_ms
= v4l2_ctrl_new_std(hdl
, NULL
,
469 V4L2_CID_RDS_RX_MUSIC_SPEECH
, 0, 1, 1, 1);
470 v4l2_dev
->ctrl_handler
= hdl
;
473 v4l2_err(v4l2_dev
, "Could not register control\n");
476 strscpy(dev
->vdev
.name
, v4l2_dev
->name
, sizeof(dev
->vdev
.name
));
477 dev
->vdev
.v4l2_dev
= v4l2_dev
;
478 dev
->vdev
.fops
= &pcm20_fops
;
479 dev
->vdev
.ioctl_ops
= &pcm20_ioctl_ops
;
480 dev
->vdev
.release
= video_device_release_empty
;
481 dev
->vdev
.lock
= &dev
->lock
;
482 dev
->vdev
.device_caps
= V4L2_CAP_TUNER
| V4L2_CAP_RADIO
|
483 V4L2_CAP_RDS_CAPTURE
;
484 video_set_drvdata(&dev
->vdev
, dev
);
485 snd_aci_cmd(dev
->aci
, ACI_SET_TUNERMONO
,
486 dev
->audmode
== V4L2_TUNER_MODE_MONO
, -1);
487 pcm20_setfreq(dev
, dev
->freq
);
489 if (video_register_device(&dev
->vdev
, VFL_TYPE_RADIO
, radio_nr
) < 0)
492 v4l2_info(v4l2_dev
, "Mirosound PCM20 Radio tuner\n");
495 v4l2_ctrl_handler_free(hdl
);
496 v4l2_device_unregister(v4l2_dev
);
500 MODULE_AUTHOR("Ruurd Reitsma, Krzysztof Helt");
501 MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card.");
502 MODULE_LICENSE("GPL");
504 static void __exit
pcm20_cleanup(void)
506 struct pcm20
*dev
= &pcm20_card
;
508 video_unregister_device(&dev
->vdev
);
509 snd_aci_cmd(dev
->aci
, ACI_SET_TUNERMUTE
, 1, -1);
510 v4l2_ctrl_handler_free(&dev
->ctrl_handler
);
511 v4l2_device_unregister(&dev
->v4l2_dev
);
514 module_init(pcm20_init
);
515 module_exit(pcm20_cleanup
);