109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2fdcfd4e7SHans Verkuil /*
3fdcfd4e7SHans Verkuil  * Miro PCM20 radio driver for Linux radio support
48366fc39SKrzysztof Helt  * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl>
58366fc39SKrzysztof Helt  * Thanks to Norberto Pellici for the ACI device interface specification
68366fc39SKrzysztof Helt  * The API part is based on the radiotrack driver by M. Kirkwood
78366fc39SKrzysztof Helt  * This driver relies on the aci mixer provided by the snd-miro
88366fc39SKrzysztof Helt  * ALSA driver.
98366fc39SKrzysztof Helt  * Look there for further info...
10fdcfd4e7SHans Verkuil  *
11fdcfd4e7SHans Verkuil  * From the original miro RDS sources:
12fdcfd4e7SHans Verkuil  *
13fdcfd4e7SHans Verkuil  *  (c) 2001 Robert Siemer <Robert.Siemer@gmx.de>
14fdcfd4e7SHans Verkuil  *
15fdcfd4e7SHans Verkuil  *  Many thanks to Fred Seidel <seidel@metabox.de>, the
16fdcfd4e7SHans Verkuil  *  designer of the RDS decoder hardware. With his help
17fdcfd4e7SHans Verkuil  *  I was able to code this driver.
18fdcfd4e7SHans Verkuil  *  Thanks also to Norberto Pellicci, Dominic Mounteney
19fdcfd4e7SHans Verkuil  *  <DMounteney@pinnaclesys.com> and www.teleauskunft.de
20fdcfd4e7SHans Verkuil  *  for good hints on finding Fred. It was somewhat hard
21fdcfd4e7SHans Verkuil  *  to locate him here in Germany... [:
22fdcfd4e7SHans Verkuil  *
23fdcfd4e7SHans Verkuil  * This code has been reintroduced and converted to use
24fdcfd4e7SHans Verkuil  * the new V4L2 RDS API by:
25fdcfd4e7SHans Verkuil  *
26fdcfd4e7SHans Verkuil  * Hans Verkuil <hans.verkuil@cisco.com>
278366fc39SKrzysztof Helt  */
288366fc39SKrzysztof Helt 
298366fc39SKrzysztof Helt #include <linux/module.h>
308366fc39SKrzysztof Helt #include <linux/init.h>
317102076cSRandy Dunlap #include <linux/io.h>
32fdcfd4e7SHans Verkuil #include <linux/delay.h>
338366fc39SKrzysztof Helt #include <linux/videodev2.h>
34fdcfd4e7SHans Verkuil #include <linux/kthread.h>
358366fc39SKrzysztof Helt #include <media/v4l2-device.h>
368366fc39SKrzysztof Helt #include <media/v4l2-ioctl.h>
377f51a610SHans Verkuil #include <media/v4l2-ctrls.h>
38f7c096f7SHans Verkuil #include <media/v4l2-fh.h>
39f7c096f7SHans Verkuil #include <media/v4l2-event.h>
408366fc39SKrzysztof Helt #include <sound/aci.h>
418366fc39SKrzysztof Helt 
42fdcfd4e7SHans Verkuil #define RDS_DATASHIFT          2   /* Bit 2 */
43fdcfd4e7SHans Verkuil #define RDS_DATAMASK        (1 << RDS_DATASHIFT)
44fdcfd4e7SHans Verkuil #define RDS_BUSYMASK        0x10   /* Bit 4 */
45fdcfd4e7SHans Verkuil #define RDS_CLOCKMASK       0x08   /* Bit 3 */
46fdcfd4e7SHans Verkuil #define RDS_DATA(x)         (((x) >> RDS_DATASHIFT) & 1)
47fdcfd4e7SHans Verkuil 
48fdcfd4e7SHans Verkuil #define RDS_STATUS      0x01
49fdcfd4e7SHans Verkuil #define RDS_STATIONNAME 0x02
50fdcfd4e7SHans Verkuil #define RDS_TEXT        0x03
51fdcfd4e7SHans Verkuil #define RDS_ALTFREQ     0x04
52fdcfd4e7SHans Verkuil #define RDS_TIMEDATE    0x05
53fdcfd4e7SHans Verkuil #define RDS_PI_CODE     0x06
54fdcfd4e7SHans Verkuil #define RDS_PTYTATP     0x07
55fdcfd4e7SHans Verkuil #define RDS_RESET       0x08
56fdcfd4e7SHans Verkuil #define RDS_RXVALUE     0x09
57fdcfd4e7SHans Verkuil 
588366fc39SKrzysztof Helt static int radio_nr = -1;
598366fc39SKrzysztof Helt module_param(radio_nr, int, 0);
608366fc39SKrzysztof Helt MODULE_PARM_DESC(radio_nr, "Set radio device number (/dev/radioX).  Default: -1 (autodetect)");
618366fc39SKrzysztof Helt 
628366fc39SKrzysztof Helt struct pcm20 {
638366fc39SKrzysztof Helt 	struct v4l2_device v4l2_dev;
648366fc39SKrzysztof Helt 	struct video_device vdev;
657f51a610SHans Verkuil 	struct v4l2_ctrl_handler ctrl_handler;
66fdcfd4e7SHans Verkuil 	struct v4l2_ctrl *rds_pty;
67fdcfd4e7SHans Verkuil 	struct v4l2_ctrl *rds_ps_name;
68fdcfd4e7SHans Verkuil 	struct v4l2_ctrl *rds_radio_test;
69fdcfd4e7SHans Verkuil 	struct v4l2_ctrl *rds_ta;
70fdcfd4e7SHans Verkuil 	struct v4l2_ctrl *rds_tp;
71fdcfd4e7SHans Verkuil 	struct v4l2_ctrl *rds_ms;
72fdcfd4e7SHans Verkuil 	/* thread for periodic RDS status checking */
73fdcfd4e7SHans Verkuil 	struct task_struct *kthread;
748366fc39SKrzysztof Helt 	unsigned long freq;
75f5e7cc4aSHans Verkuil 	u32 audmode;
768366fc39SKrzysztof Helt 	struct snd_miro_aci *aci;
7732958fddSHans Verkuil 	struct mutex lock;
788366fc39SKrzysztof Helt };
798366fc39SKrzysztof Helt 
808366fc39SKrzysztof Helt static struct pcm20 pcm20_card = {
818366fc39SKrzysztof Helt 	.freq = 87 * 16000,
82f5e7cc4aSHans Verkuil 	.audmode = V4L2_TUNER_MODE_STEREO,
838366fc39SKrzysztof Helt };
848366fc39SKrzysztof Helt 
85fdcfd4e7SHans Verkuil 
rds_waitread(struct snd_miro_aci * aci)86fdcfd4e7SHans Verkuil static int rds_waitread(struct snd_miro_aci *aci)
87fdcfd4e7SHans Verkuil {
88fdcfd4e7SHans Verkuil 	u8 byte;
89fdcfd4e7SHans Verkuil 	int i = 2000;
90fdcfd4e7SHans Verkuil 
91fdcfd4e7SHans Verkuil 	do {
92fdcfd4e7SHans Verkuil 		byte = inb(aci->aci_port + ACI_REG_RDS);
93fdcfd4e7SHans Verkuil 		i--;
94fdcfd4e7SHans Verkuil 	} while ((byte & RDS_BUSYMASK) && i);
95fdcfd4e7SHans Verkuil 
96fdcfd4e7SHans Verkuil 	/*
97fdcfd4e7SHans Verkuil 	 * It's magic, but without this the data that you read later on
98fdcfd4e7SHans Verkuil 	 * is unreliable and full of bit errors. With this 1 usec delay
99fdcfd4e7SHans Verkuil 	 * everything is fine.
100fdcfd4e7SHans Verkuil 	 */
101fdcfd4e7SHans Verkuil 	udelay(1);
102fdcfd4e7SHans Verkuil 	return i ? byte : -1;
103fdcfd4e7SHans Verkuil }
104fdcfd4e7SHans Verkuil 
rds_rawwrite(struct snd_miro_aci * aci,u8 byte)105fdcfd4e7SHans Verkuil static int rds_rawwrite(struct snd_miro_aci *aci, u8 byte)
106fdcfd4e7SHans Verkuil {
107fdcfd4e7SHans Verkuil 	if (rds_waitread(aci) >= 0) {
108fdcfd4e7SHans Verkuil 		outb(byte, aci->aci_port + ACI_REG_RDS);
109fdcfd4e7SHans Verkuil 		return 0;
110fdcfd4e7SHans Verkuil 	}
111fdcfd4e7SHans Verkuil 	return -1;
112fdcfd4e7SHans Verkuil }
113fdcfd4e7SHans Verkuil 
rds_write(struct snd_miro_aci * aci,u8 byte)114fdcfd4e7SHans Verkuil static int rds_write(struct snd_miro_aci *aci, u8 byte)
115fdcfd4e7SHans Verkuil {
116fdcfd4e7SHans Verkuil 	u8 sendbuffer[8];
117fdcfd4e7SHans Verkuil 	int i;
118fdcfd4e7SHans Verkuil 
119fdcfd4e7SHans Verkuil 	for (i = 7; i >= 0; i--)
120fdcfd4e7SHans Verkuil 		sendbuffer[7 - i] = (byte & (1 << i)) ? RDS_DATAMASK : 0;
121fdcfd4e7SHans Verkuil 	sendbuffer[0] |= RDS_CLOCKMASK;
122fdcfd4e7SHans Verkuil 
123fdcfd4e7SHans Verkuil 	for (i = 0; i < 8; i++)
124fdcfd4e7SHans Verkuil 		rds_rawwrite(aci, sendbuffer[i]);
125fdcfd4e7SHans Verkuil 	return 0;
126fdcfd4e7SHans Verkuil }
127fdcfd4e7SHans Verkuil 
rds_readcycle_nowait(struct snd_miro_aci * aci)128fdcfd4e7SHans Verkuil static int rds_readcycle_nowait(struct snd_miro_aci *aci)
129fdcfd4e7SHans Verkuil {
130fdcfd4e7SHans Verkuil 	outb(0, aci->aci_port + ACI_REG_RDS);
131fdcfd4e7SHans Verkuil 	return rds_waitread(aci);
132fdcfd4e7SHans Verkuil }
133fdcfd4e7SHans Verkuil 
rds_readcycle(struct snd_miro_aci * aci)134fdcfd4e7SHans Verkuil static int rds_readcycle(struct snd_miro_aci *aci)
135fdcfd4e7SHans Verkuil {
136fdcfd4e7SHans Verkuil 	if (rds_rawwrite(aci, 0) < 0)
137fdcfd4e7SHans Verkuil 		return -1;
138fdcfd4e7SHans Verkuil 	return rds_waitread(aci);
139fdcfd4e7SHans Verkuil }
140fdcfd4e7SHans Verkuil 
rds_ack(struct snd_miro_aci * aci)141fdcfd4e7SHans Verkuil static int rds_ack(struct snd_miro_aci *aci)
142fdcfd4e7SHans Verkuil {
143fdcfd4e7SHans Verkuil 	int i = rds_readcycle(aci);
144fdcfd4e7SHans Verkuil 
145fdcfd4e7SHans Verkuil 	if (i < 0)
146fdcfd4e7SHans Verkuil 		return -1;
147fdcfd4e7SHans Verkuil 	if (i & RDS_DATAMASK)
148fdcfd4e7SHans Verkuil 		return 0;  /* ACK  */
149fdcfd4e7SHans Verkuil 	return 1;  /* NACK */
150fdcfd4e7SHans Verkuil }
151fdcfd4e7SHans Verkuil 
rds_cmd(struct snd_miro_aci * aci,u8 cmd,u8 databuffer[],u8 datasize)152fdcfd4e7SHans Verkuil static int rds_cmd(struct snd_miro_aci *aci, u8 cmd, u8 databuffer[], u8 datasize)
153fdcfd4e7SHans Verkuil {
154fdcfd4e7SHans Verkuil 	int i, j;
155fdcfd4e7SHans Verkuil 
156fdcfd4e7SHans Verkuil 	rds_write(aci, cmd);
157fdcfd4e7SHans Verkuil 
158fdcfd4e7SHans Verkuil 	/* RDS_RESET doesn't need further processing */
159fdcfd4e7SHans Verkuil 	if (cmd == RDS_RESET)
160fdcfd4e7SHans Verkuil 		return 0;
161fdcfd4e7SHans Verkuil 	if (rds_ack(aci))
162fdcfd4e7SHans Verkuil 		return -EIO;
163fdcfd4e7SHans Verkuil 	if (datasize == 0)
164fdcfd4e7SHans Verkuil 		return 0;
165fdcfd4e7SHans Verkuil 
166fdcfd4e7SHans Verkuil 	/* to be able to use rds_readcycle_nowait()
167fdcfd4e7SHans Verkuil 	   I have to waitread() here */
168fdcfd4e7SHans Verkuil 	if (rds_waitread(aci) < 0)
169fdcfd4e7SHans Verkuil 		return -1;
170fdcfd4e7SHans Verkuil 
171fdcfd4e7SHans Verkuil 	memset(databuffer, 0, datasize);
172fdcfd4e7SHans Verkuil 
173fdcfd4e7SHans Verkuil 	for (i = 0; i < 8 * datasize; i++) {
174fdcfd4e7SHans Verkuil 		j = rds_readcycle_nowait(aci);
175fdcfd4e7SHans Verkuil 		if (j < 0)
176fdcfd4e7SHans Verkuil 			return -EIO;
177fdcfd4e7SHans Verkuil 		databuffer[i / 8] |= RDS_DATA(j) << (7 - (i % 8));
178fdcfd4e7SHans Verkuil 	}
179fdcfd4e7SHans Verkuil 	return 0;
180fdcfd4e7SHans Verkuil }
181fdcfd4e7SHans Verkuil 
pcm20_setfreq(struct pcm20 * dev,unsigned long freq)1828366fc39SKrzysztof Helt static int pcm20_setfreq(struct pcm20 *dev, unsigned long freq)
1838366fc39SKrzysztof Helt {
1848366fc39SKrzysztof Helt 	unsigned char freql;
1858366fc39SKrzysztof Helt 	unsigned char freqh;
1868366fc39SKrzysztof Helt 	struct snd_miro_aci *aci = dev->aci;
1878366fc39SKrzysztof Helt 
1888366fc39SKrzysztof Helt 	freq /= 160;
1898366fc39SKrzysztof Helt 	if (!(aci->aci_version == 0x07 || aci->aci_version >= 0xb0))
1908366fc39SKrzysztof Helt 		freq /= 10;  /* I don't know exactly which version
1918366fc39SKrzysztof Helt 			      * needs this hack */
1928366fc39SKrzysztof Helt 	freql = freq & 0xff;
1938366fc39SKrzysztof Helt 	freqh = freq >> 8;
1948366fc39SKrzysztof Helt 
19527dcb00dSWei Yongjun 	rds_cmd(aci, RDS_RESET, NULL, 0);
1968366fc39SKrzysztof Helt 	return snd_aci_cmd(aci, ACI_WRITE_TUNE, freql, freqh);
1978366fc39SKrzysztof Helt }
1988366fc39SKrzysztof Helt 
vidioc_querycap(struct file * file,void * priv,struct v4l2_capability * v)1998366fc39SKrzysztof Helt static int vidioc_querycap(struct file *file, void *priv,
2008366fc39SKrzysztof Helt 				struct v4l2_capability *v)
2018366fc39SKrzysztof Helt {
202f122d9a8SHans Verkuil 	struct pcm20 *dev = video_drvdata(file);
203f122d9a8SHans Verkuil 
204c0decac1SMauro Carvalho Chehab 	strscpy(v->driver, "Miro PCM20", sizeof(v->driver));
205c0decac1SMauro Carvalho Chehab 	strscpy(v->card, "Miro PCM20", sizeof(v->card));
206f122d9a8SHans Verkuil 	snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", dev->v4l2_dev.name);
2078366fc39SKrzysztof Helt 	return 0;
2088366fc39SKrzysztof Helt }
2098366fc39SKrzysztof Helt 
sanitize(char * p,int size)210fdcfd4e7SHans Verkuil static bool sanitize(char *p, int size)
211fdcfd4e7SHans Verkuil {
212fdcfd4e7SHans Verkuil 	int i;
213fdcfd4e7SHans Verkuil 	bool ret = true;
214fdcfd4e7SHans Verkuil 
215fdcfd4e7SHans Verkuil 	for (i = 0; i < size; i++) {
2169c46c7faSMauro Carvalho Chehab 		if (p[i] < 32) {
217fdcfd4e7SHans Verkuil 			p[i] = ' ';
218fdcfd4e7SHans Verkuil 			ret = false;
219fdcfd4e7SHans Verkuil 		}
220fdcfd4e7SHans Verkuil 	}
221fdcfd4e7SHans Verkuil 	return ret;
222fdcfd4e7SHans Verkuil }
223fdcfd4e7SHans Verkuil 
vidioc_g_tuner(struct file * file,void * priv,struct v4l2_tuner * v)2248366fc39SKrzysztof Helt static int vidioc_g_tuner(struct file *file, void *priv,
2258366fc39SKrzysztof Helt 				struct v4l2_tuner *v)
2268366fc39SKrzysztof Helt {
227f5e7cc4aSHans Verkuil 	struct pcm20 *dev = video_drvdata(file);
2289bbc5820SHans Verkuil 	int res;
229fdcfd4e7SHans Verkuil 	u8 buf;
230f5e7cc4aSHans Verkuil 
231f5e7cc4aSHans Verkuil 	if (v->index)
2328366fc39SKrzysztof Helt 		return -EINVAL;
233c0decac1SMauro Carvalho Chehab 	strscpy(v->name, "FM", sizeof(v->name));
2348366fc39SKrzysztof Helt 	v->type = V4L2_TUNER_RADIO;
2358366fc39SKrzysztof Helt 	v->rangelow = 87*16000;
2368366fc39SKrzysztof Helt 	v->rangehigh = 108*16000;
2379bbc5820SHans Verkuil 	res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTATION, -1, -1);
2389bbc5820SHans Verkuil 	v->signal = (res & 0x80) ? 0 : 0xffff;
2399bbc5820SHans Verkuil 	/* Note: stereo detection does not work if the audio is muted,
2409bbc5820SHans Verkuil 	   it will default to mono in that case. */
2419bbc5820SHans Verkuil 	res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTEREO, -1, -1);
2429bbc5820SHans Verkuil 	v->rxsubchans = (res & 0x40) ? V4L2_TUNER_SUB_MONO :
2439bbc5820SHans Verkuil 					V4L2_TUNER_SUB_STEREO;
244fdcfd4e7SHans Verkuil 	v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
245fdcfd4e7SHans Verkuil 			V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_CONTROLS;
246f5e7cc4aSHans Verkuil 	v->audmode = dev->audmode;
247fdcfd4e7SHans Verkuil 	res = rds_cmd(dev->aci, RDS_RXVALUE, &buf, 1);
248fdcfd4e7SHans Verkuil 	if (res >= 0 && buf)
249fdcfd4e7SHans Verkuil 		v->rxsubchans |= V4L2_TUNER_SUB_RDS;
2508366fc39SKrzysztof Helt 	return 0;
2518366fc39SKrzysztof Helt }
2528366fc39SKrzysztof Helt 
vidioc_s_tuner(struct file * file,void * priv,const struct v4l2_tuner * v)2538366fc39SKrzysztof Helt static int vidioc_s_tuner(struct file *file, void *priv,
2542f73c7c5SHans Verkuil 				const struct v4l2_tuner *v)
2558366fc39SKrzysztof Helt {
256f5e7cc4aSHans Verkuil 	struct pcm20 *dev = video_drvdata(file);
257f5e7cc4aSHans Verkuil 
258f5e7cc4aSHans Verkuil 	if (v->index)
259f5e7cc4aSHans Verkuil 		return -EINVAL;
260f5e7cc4aSHans Verkuil 	if (v->audmode > V4L2_TUNER_MODE_STEREO)
2612f73c7c5SHans Verkuil 		dev->audmode = V4L2_TUNER_MODE_STEREO;
2622f73c7c5SHans Verkuil 	else
2632f73c7c5SHans Verkuil 		dev->audmode = v->audmode;
264f5e7cc4aSHans Verkuil 	snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO,
2652f73c7c5SHans Verkuil 			dev->audmode == V4L2_TUNER_MODE_MONO, -1);
266f5e7cc4aSHans Verkuil 	return 0;
2678366fc39SKrzysztof Helt }
2688366fc39SKrzysztof Helt 
vidioc_g_frequency(struct file * file,void * priv,struct v4l2_frequency * f)2698366fc39SKrzysztof Helt static int vidioc_g_frequency(struct file *file, void *priv,
2708366fc39SKrzysztof Helt 				struct v4l2_frequency *f)
2718366fc39SKrzysztof Helt {
2728366fc39SKrzysztof Helt 	struct pcm20 *dev = video_drvdata(file);
2738366fc39SKrzysztof Helt 
2748366fc39SKrzysztof Helt 	if (f->tuner != 0)
2758366fc39SKrzysztof Helt 		return -EINVAL;
2768366fc39SKrzysztof Helt 
2778366fc39SKrzysztof Helt 	f->type = V4L2_TUNER_RADIO;
2788366fc39SKrzysztof Helt 	f->frequency = dev->freq;
2798366fc39SKrzysztof Helt 	return 0;
2808366fc39SKrzysztof Helt }
2818366fc39SKrzysztof Helt 
2828366fc39SKrzysztof Helt 
vidioc_s_frequency(struct file * file,void * priv,const struct v4l2_frequency * f)2838366fc39SKrzysztof Helt static int vidioc_s_frequency(struct file *file, void *priv,
284b530a447SHans Verkuil 				const struct v4l2_frequency *f)
2858366fc39SKrzysztof Helt {
2868366fc39SKrzysztof Helt 	struct pcm20 *dev = video_drvdata(file);
2878366fc39SKrzysztof Helt 
2888366fc39SKrzysztof Helt 	if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
2898366fc39SKrzysztof Helt 		return -EINVAL;
2908366fc39SKrzysztof Helt 
291b530a447SHans Verkuil 	dev->freq = clamp_t(u32, f->frequency, 87 * 16000U, 108 * 16000U);
292f5e7cc4aSHans Verkuil 	pcm20_setfreq(dev, dev->freq);
2938366fc39SKrzysztof Helt 	return 0;
2948366fc39SKrzysztof Helt }
2958366fc39SKrzysztof Helt 
pcm20_s_ctrl(struct v4l2_ctrl * ctrl)2967f51a610SHans Verkuil static int pcm20_s_ctrl(struct v4l2_ctrl *ctrl)
2978366fc39SKrzysztof Helt {
2987f51a610SHans Verkuil 	struct pcm20 *dev = container_of(ctrl->handler, struct pcm20, ctrl_handler);
2998366fc39SKrzysztof Helt 
3008366fc39SKrzysztof Helt 	switch (ctrl->id) {
3018366fc39SKrzysztof Helt 	case V4L2_CID_AUDIO_MUTE:
302f5e7cc4aSHans Verkuil 		snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, ctrl->val, -1);
3038366fc39SKrzysztof Helt 		return 0;
3048366fc39SKrzysztof Helt 	}
3058366fc39SKrzysztof Helt 	return -EINVAL;
3068366fc39SKrzysztof Helt }
3078366fc39SKrzysztof Helt 
pcm20_thread(void * data)308fdcfd4e7SHans Verkuil static int pcm20_thread(void *data)
309fdcfd4e7SHans Verkuil {
310fdcfd4e7SHans Verkuil 	struct pcm20 *dev = data;
311fdcfd4e7SHans Verkuil 	const unsigned no_rds_start_counter = 5;
312fdcfd4e7SHans Verkuil 	const unsigned sleep_msecs = 2000;
313fdcfd4e7SHans Verkuil 	unsigned no_rds_counter = no_rds_start_counter;
314fdcfd4e7SHans Verkuil 
315fdcfd4e7SHans Verkuil 	for (;;) {
316fdcfd4e7SHans Verkuil 		char text_buffer[66];
317fdcfd4e7SHans Verkuil 		u8 buf;
318fdcfd4e7SHans Verkuil 		int res;
319fdcfd4e7SHans Verkuil 
320fdcfd4e7SHans Verkuil 		msleep_interruptible(sleep_msecs);
321fdcfd4e7SHans Verkuil 
322fdcfd4e7SHans Verkuil 		if (kthread_should_stop())
323fdcfd4e7SHans Verkuil 			break;
324fdcfd4e7SHans Verkuil 
325fdcfd4e7SHans Verkuil 		res = rds_cmd(dev->aci, RDS_RXVALUE, &buf, 1);
326fdcfd4e7SHans Verkuil 		if (res)
327fdcfd4e7SHans Verkuil 			continue;
328fdcfd4e7SHans Verkuil 		if (buf == 0) {
329fdcfd4e7SHans Verkuil 			if (no_rds_counter == 0)
330fdcfd4e7SHans Verkuil 				continue;
331fdcfd4e7SHans Verkuil 			no_rds_counter--;
332fdcfd4e7SHans Verkuil 			if (no_rds_counter)
333fdcfd4e7SHans Verkuil 				continue;
334fdcfd4e7SHans Verkuil 
335fdcfd4e7SHans Verkuil 			/*
336fdcfd4e7SHans Verkuil 			 * No RDS seen for no_rds_start_counter * sleep_msecs
337fdcfd4e7SHans Verkuil 			 * milliseconds, clear all RDS controls to their
338fdcfd4e7SHans Verkuil 			 * default values.
339fdcfd4e7SHans Verkuil 			 */
340fdcfd4e7SHans Verkuil 			v4l2_ctrl_s_ctrl_string(dev->rds_ps_name, "");
341fdcfd4e7SHans Verkuil 			v4l2_ctrl_s_ctrl(dev->rds_ms, 1);
342fdcfd4e7SHans Verkuil 			v4l2_ctrl_s_ctrl(dev->rds_ta, 0);
343fdcfd4e7SHans Verkuil 			v4l2_ctrl_s_ctrl(dev->rds_tp, 0);
344fdcfd4e7SHans Verkuil 			v4l2_ctrl_s_ctrl(dev->rds_pty, 0);
345fdcfd4e7SHans Verkuil 			v4l2_ctrl_s_ctrl_string(dev->rds_radio_test, "");
346fdcfd4e7SHans Verkuil 			continue;
347fdcfd4e7SHans Verkuil 		}
348fdcfd4e7SHans Verkuil 		no_rds_counter = no_rds_start_counter;
349fdcfd4e7SHans Verkuil 
350fdcfd4e7SHans Verkuil 		res = rds_cmd(dev->aci, RDS_STATUS, &buf, 1);
351fdcfd4e7SHans Verkuil 		if (res)
352fdcfd4e7SHans Verkuil 			continue;
353fdcfd4e7SHans Verkuil 		if ((buf >> 3) & 1) {
354fdcfd4e7SHans Verkuil 			res = rds_cmd(dev->aci, RDS_STATIONNAME, text_buffer, 8);
355fdcfd4e7SHans Verkuil 			text_buffer[8] = 0;
356fdcfd4e7SHans Verkuil 			if (!res && sanitize(text_buffer, 8))
357fdcfd4e7SHans Verkuil 				v4l2_ctrl_s_ctrl_string(dev->rds_ps_name, text_buffer);
358fdcfd4e7SHans Verkuil 		}
359fdcfd4e7SHans Verkuil 		if ((buf >> 6) & 1) {
360fdcfd4e7SHans Verkuil 			u8 pty;
361fdcfd4e7SHans Verkuil 
362fdcfd4e7SHans Verkuil 			res = rds_cmd(dev->aci, RDS_PTYTATP, &pty, 1);
363fdcfd4e7SHans Verkuil 			if (!res) {
364fdcfd4e7SHans Verkuil 				v4l2_ctrl_s_ctrl(dev->rds_ms, !!(pty & 0x01));
365fdcfd4e7SHans Verkuil 				v4l2_ctrl_s_ctrl(dev->rds_ta, !!(pty & 0x02));
366fdcfd4e7SHans Verkuil 				v4l2_ctrl_s_ctrl(dev->rds_tp, !!(pty & 0x80));
367fdcfd4e7SHans Verkuil 				v4l2_ctrl_s_ctrl(dev->rds_pty, (pty >> 2) & 0x1f);
368fdcfd4e7SHans Verkuil 			}
369fdcfd4e7SHans Verkuil 		}
370fdcfd4e7SHans Verkuil 		if ((buf >> 4) & 1) {
371fdcfd4e7SHans Verkuil 			res = rds_cmd(dev->aci, RDS_TEXT, text_buffer, 65);
372fdcfd4e7SHans Verkuil 			text_buffer[65] = 0;
373fdcfd4e7SHans Verkuil 			if (!res && sanitize(text_buffer + 1, 64))
374fdcfd4e7SHans Verkuil 				v4l2_ctrl_s_ctrl_string(dev->rds_radio_test, text_buffer + 1);
375fdcfd4e7SHans Verkuil 		}
376fdcfd4e7SHans Verkuil 	}
377fdcfd4e7SHans Verkuil 	return 0;
378fdcfd4e7SHans Verkuil }
379fdcfd4e7SHans Verkuil 
pcm20_open(struct file * file)380fdcfd4e7SHans Verkuil static int pcm20_open(struct file *file)
381fdcfd4e7SHans Verkuil {
382fdcfd4e7SHans Verkuil 	struct pcm20 *dev = video_drvdata(file);
383fdcfd4e7SHans Verkuil 	int res = v4l2_fh_open(file);
384fdcfd4e7SHans Verkuil 
385fdcfd4e7SHans Verkuil 	if (!res && v4l2_fh_is_singular_file(file) &&
386fdcfd4e7SHans Verkuil 	    IS_ERR_OR_NULL(dev->kthread)) {
387fdcfd4e7SHans Verkuil 		dev->kthread = kthread_run(pcm20_thread, dev, "%s",
388fdcfd4e7SHans Verkuil 					   dev->v4l2_dev.name);
389fdcfd4e7SHans Verkuil 		if (IS_ERR(dev->kthread)) {
390fdcfd4e7SHans Verkuil 			v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n");
391fdcfd4e7SHans Verkuil 			v4l2_fh_release(file);
392fdcfd4e7SHans Verkuil 			return PTR_ERR(dev->kthread);
393fdcfd4e7SHans Verkuil 		}
394fdcfd4e7SHans Verkuil 	}
395fdcfd4e7SHans Verkuil 	return res;
396fdcfd4e7SHans Verkuil }
397fdcfd4e7SHans Verkuil 
pcm20_release(struct file * file)398fdcfd4e7SHans Verkuil static int pcm20_release(struct file *file)
399fdcfd4e7SHans Verkuil {
400fdcfd4e7SHans Verkuil 	struct pcm20 *dev = video_drvdata(file);
401fdcfd4e7SHans Verkuil 
402fdcfd4e7SHans Verkuil 	if (v4l2_fh_is_singular_file(file) && !IS_ERR_OR_NULL(dev->kthread)) {
403fdcfd4e7SHans Verkuil 		kthread_stop(dev->kthread);
404fdcfd4e7SHans Verkuil 		dev->kthread = NULL;
405fdcfd4e7SHans Verkuil 	}
406fdcfd4e7SHans Verkuil 	return v4l2_fh_release(file);
407fdcfd4e7SHans Verkuil }
408fdcfd4e7SHans Verkuil 
409fdcfd4e7SHans Verkuil static const struct v4l2_file_operations pcm20_fops = {
410fdcfd4e7SHans Verkuil 	.owner		= THIS_MODULE,
411fdcfd4e7SHans Verkuil 	.open		= pcm20_open,
412fdcfd4e7SHans Verkuil 	.poll		= v4l2_ctrl_poll,
413fdcfd4e7SHans Verkuil 	.release	= pcm20_release,
414fdcfd4e7SHans Verkuil 	.unlocked_ioctl	= video_ioctl2,
415fdcfd4e7SHans Verkuil };
416fdcfd4e7SHans Verkuil 
4178366fc39SKrzysztof Helt static const struct v4l2_ioctl_ops pcm20_ioctl_ops = {
4188366fc39SKrzysztof Helt 	.vidioc_querycap    = vidioc_querycap,
4198366fc39SKrzysztof Helt 	.vidioc_g_tuner     = vidioc_g_tuner,
4208366fc39SKrzysztof Helt 	.vidioc_s_tuner     = vidioc_s_tuner,
4218366fc39SKrzysztof Helt 	.vidioc_g_frequency = vidioc_g_frequency,
4228366fc39SKrzysztof Helt 	.vidioc_s_frequency = vidioc_s_frequency,
423f7c096f7SHans Verkuil 	.vidioc_log_status  = v4l2_ctrl_log_status,
424f7c096f7SHans Verkuil 	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
425f7c096f7SHans Verkuil 	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
4267f51a610SHans Verkuil };
4277f51a610SHans Verkuil 
4287f51a610SHans Verkuil static const struct v4l2_ctrl_ops pcm20_ctrl_ops = {
4297f51a610SHans Verkuil 	.s_ctrl = pcm20_s_ctrl,
4308366fc39SKrzysztof Helt };
4318366fc39SKrzysztof Helt 
pcm20_init(void)4328366fc39SKrzysztof Helt static int __init pcm20_init(void)
4338366fc39SKrzysztof Helt {
4348366fc39SKrzysztof Helt 	struct pcm20 *dev = &pcm20_card;
4358366fc39SKrzysztof Helt 	struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
4367f51a610SHans Verkuil 	struct v4l2_ctrl_handler *hdl;
4378366fc39SKrzysztof Helt 	int res;
4388366fc39SKrzysztof Helt 
4398366fc39SKrzysztof Helt 	dev->aci = snd_aci_get_aci();
4408366fc39SKrzysztof Helt 	if (dev->aci == NULL) {
4418366fc39SKrzysztof Helt 		v4l2_err(v4l2_dev,
4428366fc39SKrzysztof Helt 			 "you must load the snd-miro driver first!\n");
4438366fc39SKrzysztof Helt 		return -ENODEV;
4448366fc39SKrzysztof Helt 	}
445c0decac1SMauro Carvalho Chehab 	strscpy(v4l2_dev->name, "radio-miropcm20", sizeof(v4l2_dev->name));
44632958fddSHans Verkuil 	mutex_init(&dev->lock);
4478366fc39SKrzysztof Helt 
4488366fc39SKrzysztof Helt 	res = v4l2_device_register(NULL, v4l2_dev);
4498366fc39SKrzysztof Helt 	if (res < 0) {
4508366fc39SKrzysztof Helt 		v4l2_err(v4l2_dev, "could not register v4l2_device\n");
4518366fc39SKrzysztof Helt 		return -EINVAL;
4528366fc39SKrzysztof Helt 	}
4538366fc39SKrzysztof Helt 
4547f51a610SHans Verkuil 	hdl = &dev->ctrl_handler;
455fdcfd4e7SHans Verkuil 	v4l2_ctrl_handler_init(hdl, 7);
4567f51a610SHans Verkuil 	v4l2_ctrl_new_std(hdl, &pcm20_ctrl_ops,
4577f51a610SHans Verkuil 			V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
458fdcfd4e7SHans Verkuil 	dev->rds_pty = v4l2_ctrl_new_std(hdl, NULL,
459fdcfd4e7SHans Verkuil 			V4L2_CID_RDS_RX_PTY, 0, 0x1f, 1, 0);
460fdcfd4e7SHans Verkuil 	dev->rds_ps_name = v4l2_ctrl_new_std(hdl, NULL,
461fdcfd4e7SHans Verkuil 			V4L2_CID_RDS_RX_PS_NAME, 0, 8, 8, 0);
462fdcfd4e7SHans Verkuil 	dev->rds_radio_test = v4l2_ctrl_new_std(hdl, NULL,
463fdcfd4e7SHans Verkuil 			V4L2_CID_RDS_RX_RADIO_TEXT, 0, 64, 64, 0);
464fdcfd4e7SHans Verkuil 	dev->rds_ta = v4l2_ctrl_new_std(hdl, NULL,
465fdcfd4e7SHans Verkuil 			V4L2_CID_RDS_RX_TRAFFIC_ANNOUNCEMENT, 0, 1, 1, 0);
466fdcfd4e7SHans Verkuil 	dev->rds_tp = v4l2_ctrl_new_std(hdl, NULL,
467fdcfd4e7SHans Verkuil 			V4L2_CID_RDS_RX_TRAFFIC_PROGRAM, 0, 1, 1, 0);
468fdcfd4e7SHans Verkuil 	dev->rds_ms = v4l2_ctrl_new_std(hdl, NULL,
469fdcfd4e7SHans Verkuil 			V4L2_CID_RDS_RX_MUSIC_SPEECH, 0, 1, 1, 1);
4707f51a610SHans Verkuil 	v4l2_dev->ctrl_handler = hdl;
4717f51a610SHans Verkuil 	if (hdl->error) {
4727f51a610SHans Verkuil 		res = hdl->error;
4737f51a610SHans Verkuil 		v4l2_err(v4l2_dev, "Could not register control\n");
4747f51a610SHans Verkuil 		goto err_hdl;
4757f51a610SHans Verkuil 	}
476c0decac1SMauro Carvalho Chehab 	strscpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
4778366fc39SKrzysztof Helt 	dev->vdev.v4l2_dev = v4l2_dev;
4788366fc39SKrzysztof Helt 	dev->vdev.fops = &pcm20_fops;
4798366fc39SKrzysztof Helt 	dev->vdev.ioctl_ops = &pcm20_ioctl_ops;
4808366fc39SKrzysztof Helt 	dev->vdev.release = video_device_release_empty;
48132958fddSHans Verkuil 	dev->vdev.lock = &dev->lock;
482e83ce300SHans Verkuil 	dev->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
483e83ce300SHans Verkuil 				V4L2_CAP_RDS_CAPTURE;
4848366fc39SKrzysztof Helt 	video_set_drvdata(&dev->vdev, dev);
485f5e7cc4aSHans Verkuil 	snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO,
486f5e7cc4aSHans Verkuil 			dev->audmode == V4L2_TUNER_MODE_MONO, -1);
487f5e7cc4aSHans Verkuil 	pcm20_setfreq(dev, dev->freq);
4888366fc39SKrzysztof Helt 
4898366fc39SKrzysztof Helt 	if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0)
4907f51a610SHans Verkuil 		goto err_hdl;
4918366fc39SKrzysztof Helt 
4928366fc39SKrzysztof Helt 	v4l2_info(v4l2_dev, "Mirosound PCM20 Radio tuner\n");
4938366fc39SKrzysztof Helt 	return 0;
4947f51a610SHans Verkuil err_hdl:
4957f51a610SHans Verkuil 	v4l2_ctrl_handler_free(hdl);
4968366fc39SKrzysztof Helt 	v4l2_device_unregister(v4l2_dev);
4978366fc39SKrzysztof Helt 	return -EINVAL;
4988366fc39SKrzysztof Helt }
4998366fc39SKrzysztof Helt 
5008366fc39SKrzysztof Helt MODULE_AUTHOR("Ruurd Reitsma, Krzysztof Helt");
5018366fc39SKrzysztof Helt MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card.");
5028366fc39SKrzysztof Helt MODULE_LICENSE("GPL");
5038366fc39SKrzysztof Helt 
pcm20_cleanup(void)5048366fc39SKrzysztof Helt static void __exit pcm20_cleanup(void)
5058366fc39SKrzysztof Helt {
5068366fc39SKrzysztof Helt 	struct pcm20 *dev = &pcm20_card;
5078366fc39SKrzysztof Helt 
5088366fc39SKrzysztof Helt 	video_unregister_device(&dev->vdev);
509f5e7cc4aSHans Verkuil 	snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, 1, -1);
5107f51a610SHans Verkuil 	v4l2_ctrl_handler_free(&dev->ctrl_handler);
5118366fc39SKrzysztof Helt 	v4l2_device_unregister(&dev->v4l2_dev);
5128366fc39SKrzysztof Helt }
5138366fc39SKrzysztof Helt 
5148366fc39SKrzysztof Helt module_init(pcm20_init);
5158366fc39SKrzysztof Helt module_exit(pcm20_cleanup);
516