162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * vivid-radio-rx.c - radio receiver support functions.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/errno.h>
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/delay.h>
1162306a36Sopenharmony_ci#include <linux/videodev2.h>
1262306a36Sopenharmony_ci#include <linux/v4l2-dv-timings.h>
1362306a36Sopenharmony_ci#include <linux/sched/signal.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <media/v4l2-common.h>
1662306a36Sopenharmony_ci#include <media/v4l2-event.h>
1762306a36Sopenharmony_ci#include <media/v4l2-dv-timings.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include "vivid-core.h"
2062306a36Sopenharmony_ci#include "vivid-ctrls.h"
2162306a36Sopenharmony_ci#include "vivid-radio-common.h"
2262306a36Sopenharmony_ci#include "vivid-rds-gen.h"
2362306a36Sopenharmony_ci#include "vivid-radio-rx.h"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cissize_t vivid_radio_rx_read(struct file *file, char __user *buf,
2662306a36Sopenharmony_ci			 size_t size, loff_t *offset)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	struct vivid_dev *dev = video_drvdata(file);
2962306a36Sopenharmony_ci	struct v4l2_rds_data *data = dev->rds_gen.data;
3062306a36Sopenharmony_ci	bool use_alternates;
3162306a36Sopenharmony_ci	ktime_t timestamp;
3262306a36Sopenharmony_ci	unsigned blk;
3362306a36Sopenharmony_ci	int perc;
3462306a36Sopenharmony_ci	int i;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	if (dev->radio_rx_rds_controls)
3762306a36Sopenharmony_ci		return -EINVAL;
3862306a36Sopenharmony_ci	if (size < sizeof(*data))
3962306a36Sopenharmony_ci		return 0;
4062306a36Sopenharmony_ci	size = sizeof(*data) * (size / sizeof(*data));
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	if (mutex_lock_interruptible(&dev->mutex))
4362306a36Sopenharmony_ci		return -ERESTARTSYS;
4462306a36Sopenharmony_ci	if (dev->radio_rx_rds_owner &&
4562306a36Sopenharmony_ci	    file->private_data != dev->radio_rx_rds_owner) {
4662306a36Sopenharmony_ci		mutex_unlock(&dev->mutex);
4762306a36Sopenharmony_ci		return -EBUSY;
4862306a36Sopenharmony_ci	}
4962306a36Sopenharmony_ci	if (dev->radio_rx_rds_owner == NULL) {
5062306a36Sopenharmony_ci		vivid_radio_rds_init(dev);
5162306a36Sopenharmony_ci		dev->radio_rx_rds_owner = file->private_data;
5262306a36Sopenharmony_ci	}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ciretry:
5562306a36Sopenharmony_ci	timestamp = ktime_sub(ktime_get(), dev->radio_rds_init_time);
5662306a36Sopenharmony_ci	blk = ktime_divns(timestamp, VIVID_RDS_NSEC_PER_BLK);
5762306a36Sopenharmony_ci	use_alternates = (blk % VIVID_RDS_GEN_BLOCKS) & 1;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	if (dev->radio_rx_rds_last_block == 0 ||
6062306a36Sopenharmony_ci	    dev->radio_rx_rds_use_alternates != use_alternates) {
6162306a36Sopenharmony_ci		dev->radio_rx_rds_use_alternates = use_alternates;
6262306a36Sopenharmony_ci		/* Re-init the RDS generator */
6362306a36Sopenharmony_ci		vivid_radio_rds_init(dev);
6462306a36Sopenharmony_ci	}
6562306a36Sopenharmony_ci	if (blk >= dev->radio_rx_rds_last_block + VIVID_RDS_GEN_BLOCKS)
6662306a36Sopenharmony_ci		dev->radio_rx_rds_last_block = blk - VIVID_RDS_GEN_BLOCKS + 1;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	/*
6962306a36Sopenharmony_ci	 * No data is available if there hasn't been time to get new data,
7062306a36Sopenharmony_ci	 * or if the RDS receiver has been disabled, or if we use the data
7162306a36Sopenharmony_ci	 * from the RDS transmitter and that RDS transmitter has been disabled,
7262306a36Sopenharmony_ci	 * or if the signal quality is too weak.
7362306a36Sopenharmony_ci	 */
7462306a36Sopenharmony_ci	if (blk == dev->radio_rx_rds_last_block || !dev->radio_rx_rds_enabled ||
7562306a36Sopenharmony_ci	    (dev->radio_rds_loop && !(dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) ||
7662306a36Sopenharmony_ci	    abs(dev->radio_rx_sig_qual) > 200) {
7762306a36Sopenharmony_ci		mutex_unlock(&dev->mutex);
7862306a36Sopenharmony_ci		if (file->f_flags & O_NONBLOCK)
7962306a36Sopenharmony_ci			return -EWOULDBLOCK;
8062306a36Sopenharmony_ci		if (msleep_interruptible(20) && signal_pending(current))
8162306a36Sopenharmony_ci			return -EINTR;
8262306a36Sopenharmony_ci		if (mutex_lock_interruptible(&dev->mutex))
8362306a36Sopenharmony_ci			return -ERESTARTSYS;
8462306a36Sopenharmony_ci		goto retry;
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	/* abs(dev->radio_rx_sig_qual) <= 200, map that to a 0-50% range */
8862306a36Sopenharmony_ci	perc = abs(dev->radio_rx_sig_qual) / 4;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	for (i = 0; i < size && blk > dev->radio_rx_rds_last_block;
9162306a36Sopenharmony_ci			dev->radio_rx_rds_last_block++) {
9262306a36Sopenharmony_ci		unsigned data_blk = dev->radio_rx_rds_last_block % VIVID_RDS_GEN_BLOCKS;
9362306a36Sopenharmony_ci		struct v4l2_rds_data rds = data[data_blk];
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci		if (data_blk == 0 && dev->radio_rds_loop)
9662306a36Sopenharmony_ci			vivid_radio_rds_init(dev);
9762306a36Sopenharmony_ci		if (perc && get_random_u32_below(100) < perc) {
9862306a36Sopenharmony_ci			switch (get_random_u32_below(4)) {
9962306a36Sopenharmony_ci			case 0:
10062306a36Sopenharmony_ci				rds.block |= V4L2_RDS_BLOCK_CORRECTED;
10162306a36Sopenharmony_ci				break;
10262306a36Sopenharmony_ci			case 1:
10362306a36Sopenharmony_ci				rds.block |= V4L2_RDS_BLOCK_INVALID;
10462306a36Sopenharmony_ci				break;
10562306a36Sopenharmony_ci			case 2:
10662306a36Sopenharmony_ci				rds.block |= V4L2_RDS_BLOCK_ERROR;
10762306a36Sopenharmony_ci				rds.lsb = get_random_u8();
10862306a36Sopenharmony_ci				rds.msb = get_random_u8();
10962306a36Sopenharmony_ci				break;
11062306a36Sopenharmony_ci			case 3: /* Skip block altogether */
11162306a36Sopenharmony_ci				if (i)
11262306a36Sopenharmony_ci					continue;
11362306a36Sopenharmony_ci				/*
11462306a36Sopenharmony_ci				 * Must make sure at least one block is
11562306a36Sopenharmony_ci				 * returned, otherwise the application
11662306a36Sopenharmony_ci				 * might think that end-of-file occurred.
11762306a36Sopenharmony_ci				 */
11862306a36Sopenharmony_ci				break;
11962306a36Sopenharmony_ci			}
12062306a36Sopenharmony_ci		}
12162306a36Sopenharmony_ci		if (copy_to_user(buf + i, &rds, sizeof(rds))) {
12262306a36Sopenharmony_ci			i = -EFAULT;
12362306a36Sopenharmony_ci			break;
12462306a36Sopenharmony_ci		}
12562306a36Sopenharmony_ci		i += sizeof(rds);
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci	mutex_unlock(&dev->mutex);
12862306a36Sopenharmony_ci	return i;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci__poll_t vivid_radio_rx_poll(struct file *file, struct poll_table_struct *wait)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	return EPOLLIN | EPOLLRDNORM | v4l2_ctrl_poll(file, wait);
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ciint vivid_radio_rx_enum_freq_bands(struct file *file, void *fh, struct v4l2_frequency_band *band)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	if (band->tuner != 0)
13962306a36Sopenharmony_ci		return -EINVAL;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	if (band->index >= TOT_BANDS)
14262306a36Sopenharmony_ci		return -EINVAL;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	*band = vivid_radio_bands[band->index];
14562306a36Sopenharmony_ci	return 0;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ciint vivid_radio_rx_s_hw_freq_seek(struct file *file, void *fh, const struct v4l2_hw_freq_seek *a)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	struct vivid_dev *dev = video_drvdata(file);
15162306a36Sopenharmony_ci	unsigned low, high;
15262306a36Sopenharmony_ci	unsigned freq;
15362306a36Sopenharmony_ci	unsigned spacing;
15462306a36Sopenharmony_ci	unsigned band;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	if (a->tuner)
15762306a36Sopenharmony_ci		return -EINVAL;
15862306a36Sopenharmony_ci	if (a->wrap_around && dev->radio_rx_hw_seek_mode == VIVID_HW_SEEK_BOUNDED)
15962306a36Sopenharmony_ci		return -EINVAL;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	if (!a->wrap_around && dev->radio_rx_hw_seek_mode == VIVID_HW_SEEK_WRAP)
16262306a36Sopenharmony_ci		return -EINVAL;
16362306a36Sopenharmony_ci	if (!a->rangelow ^ !a->rangehigh)
16462306a36Sopenharmony_ci		return -EINVAL;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (file->f_flags & O_NONBLOCK)
16762306a36Sopenharmony_ci		return -EWOULDBLOCK;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	if (a->rangelow) {
17062306a36Sopenharmony_ci		for (band = 0; band < TOT_BANDS; band++)
17162306a36Sopenharmony_ci			if (a->rangelow >= vivid_radio_bands[band].rangelow &&
17262306a36Sopenharmony_ci			    a->rangehigh <= vivid_radio_bands[band].rangehigh)
17362306a36Sopenharmony_ci				break;
17462306a36Sopenharmony_ci		if (band == TOT_BANDS)
17562306a36Sopenharmony_ci			return -EINVAL;
17662306a36Sopenharmony_ci		if (!dev->radio_rx_hw_seek_prog_lim &&
17762306a36Sopenharmony_ci		    (a->rangelow != vivid_radio_bands[band].rangelow ||
17862306a36Sopenharmony_ci		     a->rangehigh != vivid_radio_bands[band].rangehigh))
17962306a36Sopenharmony_ci			return -EINVAL;
18062306a36Sopenharmony_ci		low = a->rangelow;
18162306a36Sopenharmony_ci		high = a->rangehigh;
18262306a36Sopenharmony_ci	} else {
18362306a36Sopenharmony_ci		for (band = 0; band < TOT_BANDS; band++)
18462306a36Sopenharmony_ci			if (dev->radio_rx_freq >= vivid_radio_bands[band].rangelow &&
18562306a36Sopenharmony_ci			    dev->radio_rx_freq <= vivid_radio_bands[band].rangehigh)
18662306a36Sopenharmony_ci				break;
18762306a36Sopenharmony_ci		if (band == TOT_BANDS)
18862306a36Sopenharmony_ci			return -EINVAL;
18962306a36Sopenharmony_ci		low = vivid_radio_bands[band].rangelow;
19062306a36Sopenharmony_ci		high = vivid_radio_bands[band].rangehigh;
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci	spacing = band == BAND_AM ? 1600 : 16000;
19362306a36Sopenharmony_ci	freq = clamp(dev->radio_rx_freq, low, high);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (a->seek_upward) {
19662306a36Sopenharmony_ci		freq = spacing * (freq / spacing) + spacing;
19762306a36Sopenharmony_ci		if (freq > high) {
19862306a36Sopenharmony_ci			if (!a->wrap_around)
19962306a36Sopenharmony_ci				return -ENODATA;
20062306a36Sopenharmony_ci			freq = spacing * (low / spacing) + spacing;
20162306a36Sopenharmony_ci			if (freq >= dev->radio_rx_freq)
20262306a36Sopenharmony_ci				return -ENODATA;
20362306a36Sopenharmony_ci		}
20462306a36Sopenharmony_ci	} else {
20562306a36Sopenharmony_ci		freq = spacing * ((freq + spacing - 1) / spacing) - spacing;
20662306a36Sopenharmony_ci		if (freq < low) {
20762306a36Sopenharmony_ci			if (!a->wrap_around)
20862306a36Sopenharmony_ci				return -ENODATA;
20962306a36Sopenharmony_ci			freq = spacing * ((high + spacing - 1) / spacing) - spacing;
21062306a36Sopenharmony_ci			if (freq <= dev->radio_rx_freq)
21162306a36Sopenharmony_ci				return -ENODATA;
21262306a36Sopenharmony_ci		}
21362306a36Sopenharmony_ci	}
21462306a36Sopenharmony_ci	return 0;
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ciint vivid_radio_rx_g_tuner(struct file *file, void *fh, struct v4l2_tuner *vt)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	struct vivid_dev *dev = video_drvdata(file);
22062306a36Sopenharmony_ci	int delta = 800;
22162306a36Sopenharmony_ci	int sig_qual;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	if (vt->index > 0)
22462306a36Sopenharmony_ci		return -EINVAL;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	strscpy(vt->name, "AM/FM/SW Receiver", sizeof(vt->name));
22762306a36Sopenharmony_ci	vt->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
22862306a36Sopenharmony_ci			 V4L2_TUNER_CAP_FREQ_BANDS | V4L2_TUNER_CAP_RDS |
22962306a36Sopenharmony_ci			 (dev->radio_rx_rds_controls ?
23062306a36Sopenharmony_ci				V4L2_TUNER_CAP_RDS_CONTROLS :
23162306a36Sopenharmony_ci				V4L2_TUNER_CAP_RDS_BLOCK_IO) |
23262306a36Sopenharmony_ci			 (dev->radio_rx_hw_seek_prog_lim ?
23362306a36Sopenharmony_ci				V4L2_TUNER_CAP_HWSEEK_PROG_LIM : 0);
23462306a36Sopenharmony_ci	switch (dev->radio_rx_hw_seek_mode) {
23562306a36Sopenharmony_ci	case VIVID_HW_SEEK_BOUNDED:
23662306a36Sopenharmony_ci		vt->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED;
23762306a36Sopenharmony_ci		break;
23862306a36Sopenharmony_ci	case VIVID_HW_SEEK_WRAP:
23962306a36Sopenharmony_ci		vt->capability |= V4L2_TUNER_CAP_HWSEEK_WRAP;
24062306a36Sopenharmony_ci		break;
24162306a36Sopenharmony_ci	case VIVID_HW_SEEK_BOTH:
24262306a36Sopenharmony_ci		vt->capability |= V4L2_TUNER_CAP_HWSEEK_WRAP |
24362306a36Sopenharmony_ci				  V4L2_TUNER_CAP_HWSEEK_BOUNDED;
24462306a36Sopenharmony_ci		break;
24562306a36Sopenharmony_ci	}
24662306a36Sopenharmony_ci	vt->rangelow = AM_FREQ_RANGE_LOW;
24762306a36Sopenharmony_ci	vt->rangehigh = FM_FREQ_RANGE_HIGH;
24862306a36Sopenharmony_ci	sig_qual = dev->radio_rx_sig_qual;
24962306a36Sopenharmony_ci	vt->signal = abs(sig_qual) > delta ? 0 :
25062306a36Sopenharmony_ci		     0xffff - ((unsigned)abs(sig_qual) * 0xffff) / delta;
25162306a36Sopenharmony_ci	vt->afc = sig_qual > delta ? 0 : sig_qual;
25262306a36Sopenharmony_ci	if (abs(sig_qual) > delta)
25362306a36Sopenharmony_ci		vt->rxsubchans = 0;
25462306a36Sopenharmony_ci	else if (dev->radio_rx_freq < FM_FREQ_RANGE_LOW || vt->signal < 0x8000)
25562306a36Sopenharmony_ci		vt->rxsubchans = V4L2_TUNER_SUB_MONO;
25662306a36Sopenharmony_ci	else if (dev->radio_rds_loop && !(dev->radio_tx_subchans & V4L2_TUNER_SUB_STEREO))
25762306a36Sopenharmony_ci		vt->rxsubchans = V4L2_TUNER_SUB_MONO;
25862306a36Sopenharmony_ci	else
25962306a36Sopenharmony_ci		vt->rxsubchans = V4L2_TUNER_SUB_STEREO;
26062306a36Sopenharmony_ci	if (dev->radio_rx_rds_enabled &&
26162306a36Sopenharmony_ci	    (!dev->radio_rds_loop || (dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) &&
26262306a36Sopenharmony_ci	    dev->radio_rx_freq >= FM_FREQ_RANGE_LOW && vt->signal >= 0xc000)
26362306a36Sopenharmony_ci		vt->rxsubchans |= V4L2_TUNER_SUB_RDS;
26462306a36Sopenharmony_ci	if (dev->radio_rx_rds_controls)
26562306a36Sopenharmony_ci		vivid_radio_rds_init(dev);
26662306a36Sopenharmony_ci	vt->audmode = dev->radio_rx_audmode;
26762306a36Sopenharmony_ci	return 0;
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ciint vivid_radio_rx_s_tuner(struct file *file, void *fh, const struct v4l2_tuner *vt)
27162306a36Sopenharmony_ci{
27262306a36Sopenharmony_ci	struct vivid_dev *dev = video_drvdata(file);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	if (vt->index)
27562306a36Sopenharmony_ci		return -EINVAL;
27662306a36Sopenharmony_ci	dev->radio_rx_audmode = vt->audmode >= V4L2_TUNER_MODE_STEREO;
27762306a36Sopenharmony_ci	return 0;
27862306a36Sopenharmony_ci}
279