xref: /openbmc/linux/drivers/media/test-drivers/vivid/vivid-radio-rx.c (revision 7ae9fb1b7ecbb5d85d07857943f677fd1a559b18)
1dacca5f0SHans Verkuil // SPDX-License-Identifier: GPL-2.0-only
2dacca5f0SHans Verkuil /*
3dacca5f0SHans Verkuil  * vivid-radio-rx.c - radio receiver support functions.
4dacca5f0SHans Verkuil  *
5dacca5f0SHans Verkuil  * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
6dacca5f0SHans Verkuil  */
7dacca5f0SHans Verkuil 
8dacca5f0SHans Verkuil #include <linux/errno.h>
9dacca5f0SHans Verkuil #include <linux/kernel.h>
10dacca5f0SHans Verkuil #include <linux/delay.h>
11dacca5f0SHans Verkuil #include <linux/videodev2.h>
12dacca5f0SHans Verkuil #include <linux/v4l2-dv-timings.h>
13dacca5f0SHans Verkuil #include <linux/sched/signal.h>
14dacca5f0SHans Verkuil 
15dacca5f0SHans Verkuil #include <media/v4l2-common.h>
16dacca5f0SHans Verkuil #include <media/v4l2-event.h>
17dacca5f0SHans Verkuil #include <media/v4l2-dv-timings.h>
18dacca5f0SHans Verkuil 
19dacca5f0SHans Verkuil #include "vivid-core.h"
20dacca5f0SHans Verkuil #include "vivid-ctrls.h"
21dacca5f0SHans Verkuil #include "vivid-radio-common.h"
22dacca5f0SHans Verkuil #include "vivid-rds-gen.h"
23dacca5f0SHans Verkuil #include "vivid-radio-rx.h"
24dacca5f0SHans Verkuil 
vivid_radio_rx_read(struct file * file,char __user * buf,size_t size,loff_t * offset)25dacca5f0SHans Verkuil ssize_t vivid_radio_rx_read(struct file *file, char __user *buf,
26dacca5f0SHans Verkuil 			 size_t size, loff_t *offset)
27dacca5f0SHans Verkuil {
28dacca5f0SHans Verkuil 	struct vivid_dev *dev = video_drvdata(file);
29dacca5f0SHans Verkuil 	struct v4l2_rds_data *data = dev->rds_gen.data;
30dacca5f0SHans Verkuil 	bool use_alternates;
31dacca5f0SHans Verkuil 	ktime_t timestamp;
32dacca5f0SHans Verkuil 	unsigned blk;
33dacca5f0SHans Verkuil 	int perc;
34dacca5f0SHans Verkuil 	int i;
35dacca5f0SHans Verkuil 
36dacca5f0SHans Verkuil 	if (dev->radio_rx_rds_controls)
37dacca5f0SHans Verkuil 		return -EINVAL;
38dacca5f0SHans Verkuil 	if (size < sizeof(*data))
39dacca5f0SHans Verkuil 		return 0;
40dacca5f0SHans Verkuil 	size = sizeof(*data) * (size / sizeof(*data));
41dacca5f0SHans Verkuil 
42dacca5f0SHans Verkuil 	if (mutex_lock_interruptible(&dev->mutex))
43dacca5f0SHans Verkuil 		return -ERESTARTSYS;
44dacca5f0SHans Verkuil 	if (dev->radio_rx_rds_owner &&
45dacca5f0SHans Verkuil 	    file->private_data != dev->radio_rx_rds_owner) {
46dacca5f0SHans Verkuil 		mutex_unlock(&dev->mutex);
47dacca5f0SHans Verkuil 		return -EBUSY;
48dacca5f0SHans Verkuil 	}
49dacca5f0SHans Verkuil 	if (dev->radio_rx_rds_owner == NULL) {
50dacca5f0SHans Verkuil 		vivid_radio_rds_init(dev);
51dacca5f0SHans Verkuil 		dev->radio_rx_rds_owner = file->private_data;
52dacca5f0SHans Verkuil 	}
53dacca5f0SHans Verkuil 
54dacca5f0SHans Verkuil retry:
55dacca5f0SHans Verkuil 	timestamp = ktime_sub(ktime_get(), dev->radio_rds_init_time);
56dacca5f0SHans Verkuil 	blk = ktime_divns(timestamp, VIVID_RDS_NSEC_PER_BLK);
57dacca5f0SHans Verkuil 	use_alternates = (blk % VIVID_RDS_GEN_BLOCKS) & 1;
58dacca5f0SHans Verkuil 
59dacca5f0SHans Verkuil 	if (dev->radio_rx_rds_last_block == 0 ||
60dacca5f0SHans Verkuil 	    dev->radio_rx_rds_use_alternates != use_alternates) {
61dacca5f0SHans Verkuil 		dev->radio_rx_rds_use_alternates = use_alternates;
62dacca5f0SHans Verkuil 		/* Re-init the RDS generator */
63dacca5f0SHans Verkuil 		vivid_radio_rds_init(dev);
64dacca5f0SHans Verkuil 	}
65dacca5f0SHans Verkuil 	if (blk >= dev->radio_rx_rds_last_block + VIVID_RDS_GEN_BLOCKS)
66dacca5f0SHans Verkuil 		dev->radio_rx_rds_last_block = blk - VIVID_RDS_GEN_BLOCKS + 1;
67dacca5f0SHans Verkuil 
68dacca5f0SHans Verkuil 	/*
69dacca5f0SHans Verkuil 	 * No data is available if there hasn't been time to get new data,
70dacca5f0SHans Verkuil 	 * or if the RDS receiver has been disabled, or if we use the data
71dacca5f0SHans Verkuil 	 * from the RDS transmitter and that RDS transmitter has been disabled,
72dacca5f0SHans Verkuil 	 * or if the signal quality is too weak.
73dacca5f0SHans Verkuil 	 */
74dacca5f0SHans Verkuil 	if (blk == dev->radio_rx_rds_last_block || !dev->radio_rx_rds_enabled ||
75dacca5f0SHans Verkuil 	    (dev->radio_rds_loop && !(dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) ||
76dacca5f0SHans Verkuil 	    abs(dev->radio_rx_sig_qual) > 200) {
77dacca5f0SHans Verkuil 		mutex_unlock(&dev->mutex);
78dacca5f0SHans Verkuil 		if (file->f_flags & O_NONBLOCK)
79dacca5f0SHans Verkuil 			return -EWOULDBLOCK;
80dacca5f0SHans Verkuil 		if (msleep_interruptible(20) && signal_pending(current))
81dacca5f0SHans Verkuil 			return -EINTR;
82dacca5f0SHans Verkuil 		if (mutex_lock_interruptible(&dev->mutex))
83dacca5f0SHans Verkuil 			return -ERESTARTSYS;
84dacca5f0SHans Verkuil 		goto retry;
85dacca5f0SHans Verkuil 	}
86dacca5f0SHans Verkuil 
87dacca5f0SHans Verkuil 	/* abs(dev->radio_rx_sig_qual) <= 200, map that to a 0-50% range */
88dacca5f0SHans Verkuil 	perc = abs(dev->radio_rx_sig_qual) / 4;
89dacca5f0SHans Verkuil 
90dacca5f0SHans Verkuil 	for (i = 0; i < size && blk > dev->radio_rx_rds_last_block;
91dacca5f0SHans Verkuil 			dev->radio_rx_rds_last_block++) {
92dacca5f0SHans Verkuil 		unsigned data_blk = dev->radio_rx_rds_last_block % VIVID_RDS_GEN_BLOCKS;
93dacca5f0SHans Verkuil 		struct v4l2_rds_data rds = data[data_blk];
94dacca5f0SHans Verkuil 
95dacca5f0SHans Verkuil 		if (data_blk == 0 && dev->radio_rds_loop)
96dacca5f0SHans Verkuil 			vivid_radio_rds_init(dev);
97*8032bf12SJason A. Donenfeld 		if (perc && get_random_u32_below(100) < perc) {
98*8032bf12SJason A. Donenfeld 			switch (get_random_u32_below(4)) {
99dacca5f0SHans Verkuil 			case 0:
100dacca5f0SHans Verkuil 				rds.block |= V4L2_RDS_BLOCK_CORRECTED;
101dacca5f0SHans Verkuil 				break;
102dacca5f0SHans Verkuil 			case 1:
103dacca5f0SHans Verkuil 				rds.block |= V4L2_RDS_BLOCK_INVALID;
104dacca5f0SHans Verkuil 				break;
105dacca5f0SHans Verkuil 			case 2:
106dacca5f0SHans Verkuil 				rds.block |= V4L2_RDS_BLOCK_ERROR;
1077e3cf084SJason A. Donenfeld 				rds.lsb = get_random_u8();
1087e3cf084SJason A. Donenfeld 				rds.msb = get_random_u8();
109dacca5f0SHans Verkuil 				break;
110dacca5f0SHans Verkuil 			case 3: /* Skip block altogether */
111dacca5f0SHans Verkuil 				if (i)
112dacca5f0SHans Verkuil 					continue;
113dacca5f0SHans Verkuil 				/*
114dacca5f0SHans Verkuil 				 * Must make sure at least one block is
115dacca5f0SHans Verkuil 				 * returned, otherwise the application
116dacca5f0SHans Verkuil 				 * might think that end-of-file occurred.
117dacca5f0SHans Verkuil 				 */
118dacca5f0SHans Verkuil 				break;
119dacca5f0SHans Verkuil 			}
120dacca5f0SHans Verkuil 		}
121dacca5f0SHans Verkuil 		if (copy_to_user(buf + i, &rds, sizeof(rds))) {
122dacca5f0SHans Verkuil 			i = -EFAULT;
123dacca5f0SHans Verkuil 			break;
124dacca5f0SHans Verkuil 		}
125dacca5f0SHans Verkuil 		i += sizeof(rds);
126dacca5f0SHans Verkuil 	}
127dacca5f0SHans Verkuil 	mutex_unlock(&dev->mutex);
128dacca5f0SHans Verkuil 	return i;
129dacca5f0SHans Verkuil }
130dacca5f0SHans Verkuil 
vivid_radio_rx_poll(struct file * file,struct poll_table_struct * wait)131dacca5f0SHans Verkuil __poll_t vivid_radio_rx_poll(struct file *file, struct poll_table_struct *wait)
132dacca5f0SHans Verkuil {
133dacca5f0SHans Verkuil 	return EPOLLIN | EPOLLRDNORM | v4l2_ctrl_poll(file, wait);
134dacca5f0SHans Verkuil }
135dacca5f0SHans Verkuil 
vivid_radio_rx_enum_freq_bands(struct file * file,void * fh,struct v4l2_frequency_band * band)136dacca5f0SHans Verkuil int vivid_radio_rx_enum_freq_bands(struct file *file, void *fh, struct v4l2_frequency_band *band)
137dacca5f0SHans Verkuil {
138dacca5f0SHans Verkuil 	if (band->tuner != 0)
139dacca5f0SHans Verkuil 		return -EINVAL;
140dacca5f0SHans Verkuil 
141dacca5f0SHans Verkuil 	if (band->index >= TOT_BANDS)
142dacca5f0SHans Verkuil 		return -EINVAL;
143dacca5f0SHans Verkuil 
144dacca5f0SHans Verkuil 	*band = vivid_radio_bands[band->index];
145dacca5f0SHans Verkuil 	return 0;
146dacca5f0SHans Verkuil }
147dacca5f0SHans Verkuil 
vivid_radio_rx_s_hw_freq_seek(struct file * file,void * fh,const struct v4l2_hw_freq_seek * a)148dacca5f0SHans Verkuil int vivid_radio_rx_s_hw_freq_seek(struct file *file, void *fh, const struct v4l2_hw_freq_seek *a)
149dacca5f0SHans Verkuil {
150dacca5f0SHans Verkuil 	struct vivid_dev *dev = video_drvdata(file);
151dacca5f0SHans Verkuil 	unsigned low, high;
152dacca5f0SHans Verkuil 	unsigned freq;
153dacca5f0SHans Verkuil 	unsigned spacing;
154dacca5f0SHans Verkuil 	unsigned band;
155dacca5f0SHans Verkuil 
156dacca5f0SHans Verkuil 	if (a->tuner)
157dacca5f0SHans Verkuil 		return -EINVAL;
158dacca5f0SHans Verkuil 	if (a->wrap_around && dev->radio_rx_hw_seek_mode == VIVID_HW_SEEK_BOUNDED)
159dacca5f0SHans Verkuil 		return -EINVAL;
160dacca5f0SHans Verkuil 
161dacca5f0SHans Verkuil 	if (!a->wrap_around && dev->radio_rx_hw_seek_mode == VIVID_HW_SEEK_WRAP)
162dacca5f0SHans Verkuil 		return -EINVAL;
163dacca5f0SHans Verkuil 	if (!a->rangelow ^ !a->rangehigh)
164dacca5f0SHans Verkuil 		return -EINVAL;
165dacca5f0SHans Verkuil 
166dacca5f0SHans Verkuil 	if (file->f_flags & O_NONBLOCK)
167dacca5f0SHans Verkuil 		return -EWOULDBLOCK;
168dacca5f0SHans Verkuil 
169dacca5f0SHans Verkuil 	if (a->rangelow) {
170dacca5f0SHans Verkuil 		for (band = 0; band < TOT_BANDS; band++)
171dacca5f0SHans Verkuil 			if (a->rangelow >= vivid_radio_bands[band].rangelow &&
172dacca5f0SHans Verkuil 			    a->rangehigh <= vivid_radio_bands[band].rangehigh)
173dacca5f0SHans Verkuil 				break;
174dacca5f0SHans Verkuil 		if (band == TOT_BANDS)
175dacca5f0SHans Verkuil 			return -EINVAL;
176dacca5f0SHans Verkuil 		if (!dev->radio_rx_hw_seek_prog_lim &&
177dacca5f0SHans Verkuil 		    (a->rangelow != vivid_radio_bands[band].rangelow ||
178dacca5f0SHans Verkuil 		     a->rangehigh != vivid_radio_bands[band].rangehigh))
179dacca5f0SHans Verkuil 			return -EINVAL;
180dacca5f0SHans Verkuil 		low = a->rangelow;
181dacca5f0SHans Verkuil 		high = a->rangehigh;
182dacca5f0SHans Verkuil 	} else {
183dacca5f0SHans Verkuil 		for (band = 0; band < TOT_BANDS; band++)
184dacca5f0SHans Verkuil 			if (dev->radio_rx_freq >= vivid_radio_bands[band].rangelow &&
185dacca5f0SHans Verkuil 			    dev->radio_rx_freq <= vivid_radio_bands[band].rangehigh)
186dacca5f0SHans Verkuil 				break;
187dacca5f0SHans Verkuil 		if (band == TOT_BANDS)
188dacca5f0SHans Verkuil 			return -EINVAL;
189dacca5f0SHans Verkuil 		low = vivid_radio_bands[band].rangelow;
190dacca5f0SHans Verkuil 		high = vivid_radio_bands[band].rangehigh;
191dacca5f0SHans Verkuil 	}
192dacca5f0SHans Verkuil 	spacing = band == BAND_AM ? 1600 : 16000;
193dacca5f0SHans Verkuil 	freq = clamp(dev->radio_rx_freq, low, high);
194dacca5f0SHans Verkuil 
195dacca5f0SHans Verkuil 	if (a->seek_upward) {
196dacca5f0SHans Verkuil 		freq = spacing * (freq / spacing) + spacing;
197dacca5f0SHans Verkuil 		if (freq > high) {
198dacca5f0SHans Verkuil 			if (!a->wrap_around)
199dacca5f0SHans Verkuil 				return -ENODATA;
200dacca5f0SHans Verkuil 			freq = spacing * (low / spacing) + spacing;
201dacca5f0SHans Verkuil 			if (freq >= dev->radio_rx_freq)
202dacca5f0SHans Verkuil 				return -ENODATA;
203dacca5f0SHans Verkuil 		}
204dacca5f0SHans Verkuil 	} else {
205dacca5f0SHans Verkuil 		freq = spacing * ((freq + spacing - 1) / spacing) - spacing;
206dacca5f0SHans Verkuil 		if (freq < low) {
207dacca5f0SHans Verkuil 			if (!a->wrap_around)
208dacca5f0SHans Verkuil 				return -ENODATA;
209dacca5f0SHans Verkuil 			freq = spacing * ((high + spacing - 1) / spacing) - spacing;
210dacca5f0SHans Verkuil 			if (freq <= dev->radio_rx_freq)
211dacca5f0SHans Verkuil 				return -ENODATA;
212dacca5f0SHans Verkuil 		}
213dacca5f0SHans Verkuil 	}
214dacca5f0SHans Verkuil 	return 0;
215dacca5f0SHans Verkuil }
216dacca5f0SHans Verkuil 
vivid_radio_rx_g_tuner(struct file * file,void * fh,struct v4l2_tuner * vt)217dacca5f0SHans Verkuil int vivid_radio_rx_g_tuner(struct file *file, void *fh, struct v4l2_tuner *vt)
218dacca5f0SHans Verkuil {
219dacca5f0SHans Verkuil 	struct vivid_dev *dev = video_drvdata(file);
220dacca5f0SHans Verkuil 	int delta = 800;
221dacca5f0SHans Verkuil 	int sig_qual;
222dacca5f0SHans Verkuil 
223dacca5f0SHans Verkuil 	if (vt->index > 0)
224dacca5f0SHans Verkuil 		return -EINVAL;
225dacca5f0SHans Verkuil 
226dacca5f0SHans Verkuil 	strscpy(vt->name, "AM/FM/SW Receiver", sizeof(vt->name));
227dacca5f0SHans Verkuil 	vt->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
228dacca5f0SHans Verkuil 			 V4L2_TUNER_CAP_FREQ_BANDS | V4L2_TUNER_CAP_RDS |
229dacca5f0SHans Verkuil 			 (dev->radio_rx_rds_controls ?
230dacca5f0SHans Verkuil 				V4L2_TUNER_CAP_RDS_CONTROLS :
231dacca5f0SHans Verkuil 				V4L2_TUNER_CAP_RDS_BLOCK_IO) |
232dacca5f0SHans Verkuil 			 (dev->radio_rx_hw_seek_prog_lim ?
233dacca5f0SHans Verkuil 				V4L2_TUNER_CAP_HWSEEK_PROG_LIM : 0);
234dacca5f0SHans Verkuil 	switch (dev->radio_rx_hw_seek_mode) {
235dacca5f0SHans Verkuil 	case VIVID_HW_SEEK_BOUNDED:
236dacca5f0SHans Verkuil 		vt->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED;
237dacca5f0SHans Verkuil 		break;
238dacca5f0SHans Verkuil 	case VIVID_HW_SEEK_WRAP:
239dacca5f0SHans Verkuil 		vt->capability |= V4L2_TUNER_CAP_HWSEEK_WRAP;
240dacca5f0SHans Verkuil 		break;
241dacca5f0SHans Verkuil 	case VIVID_HW_SEEK_BOTH:
242dacca5f0SHans Verkuil 		vt->capability |= V4L2_TUNER_CAP_HWSEEK_WRAP |
243dacca5f0SHans Verkuil 				  V4L2_TUNER_CAP_HWSEEK_BOUNDED;
244dacca5f0SHans Verkuil 		break;
245dacca5f0SHans Verkuil 	}
246dacca5f0SHans Verkuil 	vt->rangelow = AM_FREQ_RANGE_LOW;
247dacca5f0SHans Verkuil 	vt->rangehigh = FM_FREQ_RANGE_HIGH;
248dacca5f0SHans Verkuil 	sig_qual = dev->radio_rx_sig_qual;
249dacca5f0SHans Verkuil 	vt->signal = abs(sig_qual) > delta ? 0 :
250dacca5f0SHans Verkuil 		     0xffff - ((unsigned)abs(sig_qual) * 0xffff) / delta;
251dacca5f0SHans Verkuil 	vt->afc = sig_qual > delta ? 0 : sig_qual;
252dacca5f0SHans Verkuil 	if (abs(sig_qual) > delta)
253dacca5f0SHans Verkuil 		vt->rxsubchans = 0;
254dacca5f0SHans Verkuil 	else if (dev->radio_rx_freq < FM_FREQ_RANGE_LOW || vt->signal < 0x8000)
255dacca5f0SHans Verkuil 		vt->rxsubchans = V4L2_TUNER_SUB_MONO;
256dacca5f0SHans Verkuil 	else if (dev->radio_rds_loop && !(dev->radio_tx_subchans & V4L2_TUNER_SUB_STEREO))
257dacca5f0SHans Verkuil 		vt->rxsubchans = V4L2_TUNER_SUB_MONO;
258dacca5f0SHans Verkuil 	else
259dacca5f0SHans Verkuil 		vt->rxsubchans = V4L2_TUNER_SUB_STEREO;
260dacca5f0SHans Verkuil 	if (dev->radio_rx_rds_enabled &&
261dacca5f0SHans Verkuil 	    (!dev->radio_rds_loop || (dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) &&
262dacca5f0SHans Verkuil 	    dev->radio_rx_freq >= FM_FREQ_RANGE_LOW && vt->signal >= 0xc000)
263dacca5f0SHans Verkuil 		vt->rxsubchans |= V4L2_TUNER_SUB_RDS;
264dacca5f0SHans Verkuil 	if (dev->radio_rx_rds_controls)
265dacca5f0SHans Verkuil 		vivid_radio_rds_init(dev);
266dacca5f0SHans Verkuil 	vt->audmode = dev->radio_rx_audmode;
267dacca5f0SHans Verkuil 	return 0;
268dacca5f0SHans Verkuil }
269dacca5f0SHans Verkuil 
vivid_radio_rx_s_tuner(struct file * file,void * fh,const struct v4l2_tuner * vt)270dacca5f0SHans Verkuil int vivid_radio_rx_s_tuner(struct file *file, void *fh, const struct v4l2_tuner *vt)
271dacca5f0SHans Verkuil {
272dacca5f0SHans Verkuil 	struct vivid_dev *dev = video_drvdata(file);
273dacca5f0SHans Verkuil 
274dacca5f0SHans Verkuil 	if (vt->index)
275dacca5f0SHans Verkuil 		return -EINVAL;
276dacca5f0SHans Verkuil 	dev->radio_rx_audmode = vt->audmode >= V4L2_TUNER_MODE_STEREO;
277dacca5f0SHans Verkuil 	return 0;
278dacca5f0SHans Verkuil }
279