18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * vivid-radio-common.c - common radio rx/tx support functions.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/errno.h>
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/delay.h>
118c2ecf20Sopenharmony_ci#include <linux/videodev2.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include "vivid-core.h"
148c2ecf20Sopenharmony_ci#include "vivid-ctrls.h"
158c2ecf20Sopenharmony_ci#include "vivid-radio-common.h"
168c2ecf20Sopenharmony_ci#include "vivid-rds-gen.h"
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci/*
198c2ecf20Sopenharmony_ci * These functions are shared between the vivid receiver and transmitter
208c2ecf20Sopenharmony_ci * since both use the same frequency bands.
218c2ecf20Sopenharmony_ci */
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ciconst struct v4l2_frequency_band vivid_radio_bands[TOT_BANDS] = {
248c2ecf20Sopenharmony_ci	/* Band FM */
258c2ecf20Sopenharmony_ci	{
268c2ecf20Sopenharmony_ci		.type = V4L2_TUNER_RADIO,
278c2ecf20Sopenharmony_ci		.index = 0,
288c2ecf20Sopenharmony_ci		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
298c2ecf20Sopenharmony_ci			      V4L2_TUNER_CAP_FREQ_BANDS,
308c2ecf20Sopenharmony_ci		.rangelow   = FM_FREQ_RANGE_LOW,
318c2ecf20Sopenharmony_ci		.rangehigh  = FM_FREQ_RANGE_HIGH,
328c2ecf20Sopenharmony_ci		.modulation = V4L2_BAND_MODULATION_FM,
338c2ecf20Sopenharmony_ci	},
348c2ecf20Sopenharmony_ci	/* Band AM */
358c2ecf20Sopenharmony_ci	{
368c2ecf20Sopenharmony_ci		.type = V4L2_TUNER_RADIO,
378c2ecf20Sopenharmony_ci		.index = 1,
388c2ecf20Sopenharmony_ci		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
398c2ecf20Sopenharmony_ci		.rangelow   = AM_FREQ_RANGE_LOW,
408c2ecf20Sopenharmony_ci		.rangehigh  = AM_FREQ_RANGE_HIGH,
418c2ecf20Sopenharmony_ci		.modulation = V4L2_BAND_MODULATION_AM,
428c2ecf20Sopenharmony_ci	},
438c2ecf20Sopenharmony_ci	/* Band SW */
448c2ecf20Sopenharmony_ci	{
458c2ecf20Sopenharmony_ci		.type = V4L2_TUNER_RADIO,
468c2ecf20Sopenharmony_ci		.index = 2,
478c2ecf20Sopenharmony_ci		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
488c2ecf20Sopenharmony_ci		.rangelow   = SW_FREQ_RANGE_LOW,
498c2ecf20Sopenharmony_ci		.rangehigh  = SW_FREQ_RANGE_HIGH,
508c2ecf20Sopenharmony_ci		.modulation = V4L2_BAND_MODULATION_AM,
518c2ecf20Sopenharmony_ci	},
528c2ecf20Sopenharmony_ci};
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci/*
558c2ecf20Sopenharmony_ci * Initialize the RDS generator. If we can loop, then the RDS generator
568c2ecf20Sopenharmony_ci * is set up with the values from the RDS TX controls, otherwise it
578c2ecf20Sopenharmony_ci * will fill in standard values using one of two alternates.
588c2ecf20Sopenharmony_ci */
598c2ecf20Sopenharmony_civoid vivid_radio_rds_init(struct vivid_dev *dev)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	struct vivid_rds_gen *rds = &dev->rds_gen;
628c2ecf20Sopenharmony_ci	bool alt = dev->radio_rx_rds_use_alternates;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	/* Do nothing, blocks will be filled by the transmitter */
658c2ecf20Sopenharmony_ci	if (dev->radio_rds_loop && !dev->radio_tx_rds_controls)
668c2ecf20Sopenharmony_ci		return;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	if (dev->radio_rds_loop) {
698c2ecf20Sopenharmony_ci		v4l2_ctrl_lock(dev->radio_tx_rds_pi);
708c2ecf20Sopenharmony_ci		rds->picode = dev->radio_tx_rds_pi->cur.val;
718c2ecf20Sopenharmony_ci		rds->pty = dev->radio_tx_rds_pty->cur.val;
728c2ecf20Sopenharmony_ci		rds->mono_stereo = dev->radio_tx_rds_mono_stereo->cur.val;
738c2ecf20Sopenharmony_ci		rds->art_head = dev->radio_tx_rds_art_head->cur.val;
748c2ecf20Sopenharmony_ci		rds->compressed = dev->radio_tx_rds_compressed->cur.val;
758c2ecf20Sopenharmony_ci		rds->dyn_pty = dev->radio_tx_rds_dyn_pty->cur.val;
768c2ecf20Sopenharmony_ci		rds->ta = dev->radio_tx_rds_ta->cur.val;
778c2ecf20Sopenharmony_ci		rds->tp = dev->radio_tx_rds_tp->cur.val;
788c2ecf20Sopenharmony_ci		rds->ms = dev->radio_tx_rds_ms->cur.val;
798c2ecf20Sopenharmony_ci		strscpy(rds->psname,
808c2ecf20Sopenharmony_ci			dev->radio_tx_rds_psname->p_cur.p_char,
818c2ecf20Sopenharmony_ci			sizeof(rds->psname));
828c2ecf20Sopenharmony_ci		strscpy(rds->radiotext,
838c2ecf20Sopenharmony_ci			dev->radio_tx_rds_radiotext->p_cur.p_char + alt * 64,
848c2ecf20Sopenharmony_ci			sizeof(rds->radiotext));
858c2ecf20Sopenharmony_ci		v4l2_ctrl_unlock(dev->radio_tx_rds_pi);
868c2ecf20Sopenharmony_ci	} else {
878c2ecf20Sopenharmony_ci		vivid_rds_gen_fill(rds, dev->radio_rx_freq, alt);
888c2ecf20Sopenharmony_ci	}
898c2ecf20Sopenharmony_ci	if (dev->radio_rx_rds_controls) {
908c2ecf20Sopenharmony_ci		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_pty, rds->pty);
918c2ecf20Sopenharmony_ci		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ta, rds->ta);
928c2ecf20Sopenharmony_ci		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_tp, rds->tp);
938c2ecf20Sopenharmony_ci		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ms, rds->ms);
948c2ecf20Sopenharmony_ci		v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_psname, rds->psname);
958c2ecf20Sopenharmony_ci		v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_radiotext, rds->radiotext);
968c2ecf20Sopenharmony_ci		if (!dev->radio_rds_loop)
978c2ecf20Sopenharmony_ci			dev->radio_rx_rds_use_alternates = !dev->radio_rx_rds_use_alternates;
988c2ecf20Sopenharmony_ci	}
998c2ecf20Sopenharmony_ci	vivid_rds_generate(rds);
1008c2ecf20Sopenharmony_ci}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci/*
1038c2ecf20Sopenharmony_ci * Calculate the emulated signal quality taking into account the frequency
1048c2ecf20Sopenharmony_ci * the transmitter is using.
1058c2ecf20Sopenharmony_ci */
1068c2ecf20Sopenharmony_cistatic void vivid_radio_calc_sig_qual(struct vivid_dev *dev)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	int mod = 16000;
1098c2ecf20Sopenharmony_ci	int delta = 800;
1108c2ecf20Sopenharmony_ci	int sig_qual, sig_qual_tx = mod;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	/*
1138c2ecf20Sopenharmony_ci	 * For SW and FM there is a channel every 1000 kHz, for AM there is one
1148c2ecf20Sopenharmony_ci	 * every 100 kHz.
1158c2ecf20Sopenharmony_ci	 */
1168c2ecf20Sopenharmony_ci	if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH) {
1178c2ecf20Sopenharmony_ci		mod /= 10;
1188c2ecf20Sopenharmony_ci		delta /= 10;
1198c2ecf20Sopenharmony_ci	}
1208c2ecf20Sopenharmony_ci	sig_qual = (dev->radio_rx_freq + delta) % mod - delta;
1218c2ecf20Sopenharmony_ci	if (dev->has_radio_tx)
1228c2ecf20Sopenharmony_ci		sig_qual_tx = dev->radio_rx_freq - dev->radio_tx_freq;
1238c2ecf20Sopenharmony_ci	if (abs(sig_qual_tx) <= abs(sig_qual)) {
1248c2ecf20Sopenharmony_ci		sig_qual = sig_qual_tx;
1258c2ecf20Sopenharmony_ci		/*
1268c2ecf20Sopenharmony_ci		 * Zero the internal rds buffer if we are going to loop
1278c2ecf20Sopenharmony_ci		 * rds blocks.
1288c2ecf20Sopenharmony_ci		 */
1298c2ecf20Sopenharmony_ci		if (!dev->radio_rds_loop && !dev->radio_tx_rds_controls)
1308c2ecf20Sopenharmony_ci			memset(dev->rds_gen.data, 0,
1318c2ecf20Sopenharmony_ci			       sizeof(dev->rds_gen.data));
1328c2ecf20Sopenharmony_ci		dev->radio_rds_loop = dev->radio_rx_freq >= FM_FREQ_RANGE_LOW;
1338c2ecf20Sopenharmony_ci	} else {
1348c2ecf20Sopenharmony_ci		dev->radio_rds_loop = false;
1358c2ecf20Sopenharmony_ci	}
1368c2ecf20Sopenharmony_ci	if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH)
1378c2ecf20Sopenharmony_ci		sig_qual *= 10;
1388c2ecf20Sopenharmony_ci	dev->radio_rx_sig_qual = sig_qual;
1398c2ecf20Sopenharmony_ci}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ciint vivid_radio_g_frequency(struct file *file, const unsigned *pfreq, struct v4l2_frequency *vf)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	if (vf->tuner != 0)
1448c2ecf20Sopenharmony_ci		return -EINVAL;
1458c2ecf20Sopenharmony_ci	vf->frequency = *pfreq;
1468c2ecf20Sopenharmony_ci	return 0;
1478c2ecf20Sopenharmony_ci}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ciint vivid_radio_s_frequency(struct file *file, unsigned *pfreq, const struct v4l2_frequency *vf)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	struct vivid_dev *dev = video_drvdata(file);
1528c2ecf20Sopenharmony_ci	unsigned freq;
1538c2ecf20Sopenharmony_ci	unsigned band;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	if (vf->tuner != 0)
1568c2ecf20Sopenharmony_ci		return -EINVAL;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	if (vf->frequency >= (FM_FREQ_RANGE_LOW + SW_FREQ_RANGE_HIGH) / 2)
1598c2ecf20Sopenharmony_ci		band = BAND_FM;
1608c2ecf20Sopenharmony_ci	else if (vf->frequency <= (AM_FREQ_RANGE_HIGH + SW_FREQ_RANGE_LOW) / 2)
1618c2ecf20Sopenharmony_ci		band = BAND_AM;
1628c2ecf20Sopenharmony_ci	else
1638c2ecf20Sopenharmony_ci		band = BAND_SW;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	freq = clamp_t(u32, vf->frequency, vivid_radio_bands[band].rangelow,
1668c2ecf20Sopenharmony_ci					   vivid_radio_bands[band].rangehigh);
1678c2ecf20Sopenharmony_ci	*pfreq = freq;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	/*
1708c2ecf20Sopenharmony_ci	 * For both receiver and transmitter recalculate the signal quality
1718c2ecf20Sopenharmony_ci	 * (since that depends on both frequencies) and re-init the rds
1728c2ecf20Sopenharmony_ci	 * generator.
1738c2ecf20Sopenharmony_ci	 */
1748c2ecf20Sopenharmony_ci	vivid_radio_calc_sig_qual(dev);
1758c2ecf20Sopenharmony_ci	vivid_radio_rds_init(dev);
1768c2ecf20Sopenharmony_ci	return 0;
1778c2ecf20Sopenharmony_ci}
178