1dacca5f0SHans Verkuil // SPDX-License-Identifier: GPL-2.0-only
2dacca5f0SHans Verkuil /*
3dacca5f0SHans Verkuil  * vivid-radio-common.c - common radio rx/tx 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 
13dacca5f0SHans Verkuil #include "vivid-core.h"
14dacca5f0SHans Verkuil #include "vivid-ctrls.h"
15dacca5f0SHans Verkuil #include "vivid-radio-common.h"
16dacca5f0SHans Verkuil #include "vivid-rds-gen.h"
17dacca5f0SHans Verkuil 
18dacca5f0SHans Verkuil /*
19dacca5f0SHans Verkuil  * These functions are shared between the vivid receiver and transmitter
20dacca5f0SHans Verkuil  * since both use the same frequency bands.
21dacca5f0SHans Verkuil  */
22dacca5f0SHans Verkuil 
23dacca5f0SHans Verkuil const struct v4l2_frequency_band vivid_radio_bands[TOT_BANDS] = {
24dacca5f0SHans Verkuil 	/* Band FM */
25dacca5f0SHans Verkuil 	{
26dacca5f0SHans Verkuil 		.type = V4L2_TUNER_RADIO,
27dacca5f0SHans Verkuil 		.index = 0,
28dacca5f0SHans Verkuil 		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
29dacca5f0SHans Verkuil 			      V4L2_TUNER_CAP_FREQ_BANDS,
30dacca5f0SHans Verkuil 		.rangelow   = FM_FREQ_RANGE_LOW,
31dacca5f0SHans Verkuil 		.rangehigh  = FM_FREQ_RANGE_HIGH,
32dacca5f0SHans Verkuil 		.modulation = V4L2_BAND_MODULATION_FM,
33dacca5f0SHans Verkuil 	},
34dacca5f0SHans Verkuil 	/* Band AM */
35dacca5f0SHans Verkuil 	{
36dacca5f0SHans Verkuil 		.type = V4L2_TUNER_RADIO,
37dacca5f0SHans Verkuil 		.index = 1,
38dacca5f0SHans Verkuil 		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
39dacca5f0SHans Verkuil 		.rangelow   = AM_FREQ_RANGE_LOW,
40dacca5f0SHans Verkuil 		.rangehigh  = AM_FREQ_RANGE_HIGH,
41dacca5f0SHans Verkuil 		.modulation = V4L2_BAND_MODULATION_AM,
42dacca5f0SHans Verkuil 	},
43dacca5f0SHans Verkuil 	/* Band SW */
44dacca5f0SHans Verkuil 	{
45dacca5f0SHans Verkuil 		.type = V4L2_TUNER_RADIO,
46dacca5f0SHans Verkuil 		.index = 2,
47dacca5f0SHans Verkuil 		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
48dacca5f0SHans Verkuil 		.rangelow   = SW_FREQ_RANGE_LOW,
49dacca5f0SHans Verkuil 		.rangehigh  = SW_FREQ_RANGE_HIGH,
50dacca5f0SHans Verkuil 		.modulation = V4L2_BAND_MODULATION_AM,
51dacca5f0SHans Verkuil 	},
52dacca5f0SHans Verkuil };
53dacca5f0SHans Verkuil 
54dacca5f0SHans Verkuil /*
55dacca5f0SHans Verkuil  * Initialize the RDS generator. If we can loop, then the RDS generator
56dacca5f0SHans Verkuil  * is set up with the values from the RDS TX controls, otherwise it
57dacca5f0SHans Verkuil  * will fill in standard values using one of two alternates.
58dacca5f0SHans Verkuil  */
vivid_radio_rds_init(struct vivid_dev * dev)59dacca5f0SHans Verkuil void vivid_radio_rds_init(struct vivid_dev *dev)
60dacca5f0SHans Verkuil {
61dacca5f0SHans Verkuil 	struct vivid_rds_gen *rds = &dev->rds_gen;
62dacca5f0SHans Verkuil 	bool alt = dev->radio_rx_rds_use_alternates;
63dacca5f0SHans Verkuil 
64dacca5f0SHans Verkuil 	/* Do nothing, blocks will be filled by the transmitter */
65dacca5f0SHans Verkuil 	if (dev->radio_rds_loop && !dev->radio_tx_rds_controls)
66dacca5f0SHans Verkuil 		return;
67dacca5f0SHans Verkuil 
68dacca5f0SHans Verkuil 	if (dev->radio_rds_loop) {
69dacca5f0SHans Verkuil 		v4l2_ctrl_lock(dev->radio_tx_rds_pi);
70dacca5f0SHans Verkuil 		rds->picode = dev->radio_tx_rds_pi->cur.val;
71dacca5f0SHans Verkuil 		rds->pty = dev->radio_tx_rds_pty->cur.val;
72dacca5f0SHans Verkuil 		rds->mono_stereo = dev->radio_tx_rds_mono_stereo->cur.val;
73dacca5f0SHans Verkuil 		rds->art_head = dev->radio_tx_rds_art_head->cur.val;
74dacca5f0SHans Verkuil 		rds->compressed = dev->radio_tx_rds_compressed->cur.val;
75dacca5f0SHans Verkuil 		rds->dyn_pty = dev->radio_tx_rds_dyn_pty->cur.val;
76dacca5f0SHans Verkuil 		rds->ta = dev->radio_tx_rds_ta->cur.val;
77dacca5f0SHans Verkuil 		rds->tp = dev->radio_tx_rds_tp->cur.val;
78dacca5f0SHans Verkuil 		rds->ms = dev->radio_tx_rds_ms->cur.val;
79dacca5f0SHans Verkuil 		strscpy(rds->psname,
80dacca5f0SHans Verkuil 			dev->radio_tx_rds_psname->p_cur.p_char,
81dacca5f0SHans Verkuil 			sizeof(rds->psname));
82dacca5f0SHans Verkuil 		strscpy(rds->radiotext,
83dacca5f0SHans Verkuil 			dev->radio_tx_rds_radiotext->p_cur.p_char + alt * 64,
84dacca5f0SHans Verkuil 			sizeof(rds->radiotext));
85dacca5f0SHans Verkuil 		v4l2_ctrl_unlock(dev->radio_tx_rds_pi);
86dacca5f0SHans Verkuil 	} else {
87dacca5f0SHans Verkuil 		vivid_rds_gen_fill(rds, dev->radio_rx_freq, alt);
88dacca5f0SHans Verkuil 	}
89dacca5f0SHans Verkuil 	if (dev->radio_rx_rds_controls) {
90dacca5f0SHans Verkuil 		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_pty, rds->pty);
91dacca5f0SHans Verkuil 		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ta, rds->ta);
92dacca5f0SHans Verkuil 		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_tp, rds->tp);
93dacca5f0SHans Verkuil 		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ms, rds->ms);
94dacca5f0SHans Verkuil 		v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_psname, rds->psname);
95dacca5f0SHans Verkuil 		v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_radiotext, rds->radiotext);
96dacca5f0SHans Verkuil 		if (!dev->radio_rds_loop)
97dacca5f0SHans Verkuil 			dev->radio_rx_rds_use_alternates = !dev->radio_rx_rds_use_alternates;
98dacca5f0SHans Verkuil 	}
99dacca5f0SHans Verkuil 	vivid_rds_generate(rds);
100dacca5f0SHans Verkuil }
101dacca5f0SHans Verkuil 
102dacca5f0SHans Verkuil /*
103dacca5f0SHans Verkuil  * Calculate the emulated signal quality taking into account the frequency
104dacca5f0SHans Verkuil  * the transmitter is using.
105dacca5f0SHans Verkuil  */
vivid_radio_calc_sig_qual(struct vivid_dev * dev)106dacca5f0SHans Verkuil static void vivid_radio_calc_sig_qual(struct vivid_dev *dev)
107dacca5f0SHans Verkuil {
108dacca5f0SHans Verkuil 	int mod = 16000;
109dacca5f0SHans Verkuil 	int delta = 800;
110dacca5f0SHans Verkuil 	int sig_qual, sig_qual_tx = mod;
111dacca5f0SHans Verkuil 
112dacca5f0SHans Verkuil 	/*
113dacca5f0SHans Verkuil 	 * For SW and FM there is a channel every 1000 kHz, for AM there is one
114dacca5f0SHans Verkuil 	 * every 100 kHz.
115dacca5f0SHans Verkuil 	 */
116dacca5f0SHans Verkuil 	if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH) {
117dacca5f0SHans Verkuil 		mod /= 10;
118dacca5f0SHans Verkuil 		delta /= 10;
119dacca5f0SHans Verkuil 	}
120dacca5f0SHans Verkuil 	sig_qual = (dev->radio_rx_freq + delta) % mod - delta;
121dacca5f0SHans Verkuil 	if (dev->has_radio_tx)
122dacca5f0SHans Verkuil 		sig_qual_tx = dev->radio_rx_freq - dev->radio_tx_freq;
123dacca5f0SHans Verkuil 	if (abs(sig_qual_tx) <= abs(sig_qual)) {
124dacca5f0SHans Verkuil 		sig_qual = sig_qual_tx;
125dacca5f0SHans Verkuil 		/*
126dacca5f0SHans Verkuil 		 * Zero the internal rds buffer if we are going to loop
127dacca5f0SHans Verkuil 		 * rds blocks.
128dacca5f0SHans Verkuil 		 */
129dacca5f0SHans Verkuil 		if (!dev->radio_rds_loop && !dev->radio_tx_rds_controls)
130dacca5f0SHans Verkuil 			memset(dev->rds_gen.data, 0,
131dacca5f0SHans Verkuil 			       sizeof(dev->rds_gen.data));
132dacca5f0SHans Verkuil 		dev->radio_rds_loop = dev->radio_rx_freq >= FM_FREQ_RANGE_LOW;
133dacca5f0SHans Verkuil 	} else {
134dacca5f0SHans Verkuil 		dev->radio_rds_loop = false;
135dacca5f0SHans Verkuil 	}
136dacca5f0SHans Verkuil 	if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH)
137dacca5f0SHans Verkuil 		sig_qual *= 10;
138dacca5f0SHans Verkuil 	dev->radio_rx_sig_qual = sig_qual;
139dacca5f0SHans Verkuil }
140dacca5f0SHans Verkuil 
vivid_radio_g_frequency(struct file * file,const unsigned * pfreq,struct v4l2_frequency * vf)141dacca5f0SHans Verkuil int vivid_radio_g_frequency(struct file *file, const unsigned *pfreq, struct v4l2_frequency *vf)
142dacca5f0SHans Verkuil {
143dacca5f0SHans Verkuil 	if (vf->tuner != 0)
144dacca5f0SHans Verkuil 		return -EINVAL;
145dacca5f0SHans Verkuil 	vf->frequency = *pfreq;
146dacca5f0SHans Verkuil 	return 0;
147dacca5f0SHans Verkuil }
148dacca5f0SHans Verkuil 
vivid_radio_s_frequency(struct file * file,unsigned * pfreq,const struct v4l2_frequency * vf)149dacca5f0SHans Verkuil int vivid_radio_s_frequency(struct file *file, unsigned *pfreq, const struct v4l2_frequency *vf)
150dacca5f0SHans Verkuil {
151dacca5f0SHans Verkuil 	struct vivid_dev *dev = video_drvdata(file);
152dacca5f0SHans Verkuil 	unsigned freq;
153dacca5f0SHans Verkuil 	unsigned band;
154dacca5f0SHans Verkuil 
155dacca5f0SHans Verkuil 	if (vf->tuner != 0)
156dacca5f0SHans Verkuil 		return -EINVAL;
157dacca5f0SHans Verkuil 
158dacca5f0SHans Verkuil 	if (vf->frequency >= (FM_FREQ_RANGE_LOW + SW_FREQ_RANGE_HIGH) / 2)
159dacca5f0SHans Verkuil 		band = BAND_FM;
160dacca5f0SHans Verkuil 	else if (vf->frequency <= (AM_FREQ_RANGE_HIGH + SW_FREQ_RANGE_LOW) / 2)
161dacca5f0SHans Verkuil 		band = BAND_AM;
162dacca5f0SHans Verkuil 	else
163dacca5f0SHans Verkuil 		band = BAND_SW;
164dacca5f0SHans Verkuil 
165dacca5f0SHans Verkuil 	freq = clamp_t(u32, vf->frequency, vivid_radio_bands[band].rangelow,
166dacca5f0SHans Verkuil 					   vivid_radio_bands[band].rangehigh);
167dacca5f0SHans Verkuil 	*pfreq = freq;
168dacca5f0SHans Verkuil 
169dacca5f0SHans Verkuil 	/*
170dacca5f0SHans Verkuil 	 * For both receiver and transmitter recalculate the signal quality
171dacca5f0SHans Verkuil 	 * (since that depends on both frequencies) and re-init the rds
172dacca5f0SHans Verkuil 	 * generator.
173dacca5f0SHans Verkuil 	 */
174dacca5f0SHans Verkuil 	vivid_radio_calc_sig_qual(dev);
175dacca5f0SHans Verkuil 	vivid_radio_rds_init(dev);
176dacca5f0SHans Verkuil 	return 0;
177dacca5f0SHans Verkuil }
178