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