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