162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * vivid-radio-common.c - common radio rx/tx 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 1362306a36Sopenharmony_ci#include "vivid-core.h" 1462306a36Sopenharmony_ci#include "vivid-ctrls.h" 1562306a36Sopenharmony_ci#include "vivid-radio-common.h" 1662306a36Sopenharmony_ci#include "vivid-rds-gen.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci/* 1962306a36Sopenharmony_ci * These functions are shared between the vivid receiver and transmitter 2062306a36Sopenharmony_ci * since both use the same frequency bands. 2162306a36Sopenharmony_ci */ 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ciconst struct v4l2_frequency_band vivid_radio_bands[TOT_BANDS] = { 2462306a36Sopenharmony_ci /* Band FM */ 2562306a36Sopenharmony_ci { 2662306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 2762306a36Sopenharmony_ci .index = 0, 2862306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 2962306a36Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS, 3062306a36Sopenharmony_ci .rangelow = FM_FREQ_RANGE_LOW, 3162306a36Sopenharmony_ci .rangehigh = FM_FREQ_RANGE_HIGH, 3262306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 3362306a36Sopenharmony_ci }, 3462306a36Sopenharmony_ci /* Band AM */ 3562306a36Sopenharmony_ci { 3662306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 3762306a36Sopenharmony_ci .index = 1, 3862306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, 3962306a36Sopenharmony_ci .rangelow = AM_FREQ_RANGE_LOW, 4062306a36Sopenharmony_ci .rangehigh = AM_FREQ_RANGE_HIGH, 4162306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_AM, 4262306a36Sopenharmony_ci }, 4362306a36Sopenharmony_ci /* Band SW */ 4462306a36Sopenharmony_ci { 4562306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 4662306a36Sopenharmony_ci .index = 2, 4762306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, 4862306a36Sopenharmony_ci .rangelow = SW_FREQ_RANGE_LOW, 4962306a36Sopenharmony_ci .rangehigh = SW_FREQ_RANGE_HIGH, 5062306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_AM, 5162306a36Sopenharmony_ci }, 5262306a36Sopenharmony_ci}; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci/* 5562306a36Sopenharmony_ci * Initialize the RDS generator. If we can loop, then the RDS generator 5662306a36Sopenharmony_ci * is set up with the values from the RDS TX controls, otherwise it 5762306a36Sopenharmony_ci * will fill in standard values using one of two alternates. 5862306a36Sopenharmony_ci */ 5962306a36Sopenharmony_civoid vivid_radio_rds_init(struct vivid_dev *dev) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci struct vivid_rds_gen *rds = &dev->rds_gen; 6262306a36Sopenharmony_ci bool alt = dev->radio_rx_rds_use_alternates; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci /* Do nothing, blocks will be filled by the transmitter */ 6562306a36Sopenharmony_ci if (dev->radio_rds_loop && !dev->radio_tx_rds_controls) 6662306a36Sopenharmony_ci return; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci if (dev->radio_rds_loop) { 6962306a36Sopenharmony_ci v4l2_ctrl_lock(dev->radio_tx_rds_pi); 7062306a36Sopenharmony_ci rds->picode = dev->radio_tx_rds_pi->cur.val; 7162306a36Sopenharmony_ci rds->pty = dev->radio_tx_rds_pty->cur.val; 7262306a36Sopenharmony_ci rds->mono_stereo = dev->radio_tx_rds_mono_stereo->cur.val; 7362306a36Sopenharmony_ci rds->art_head = dev->radio_tx_rds_art_head->cur.val; 7462306a36Sopenharmony_ci rds->compressed = dev->radio_tx_rds_compressed->cur.val; 7562306a36Sopenharmony_ci rds->dyn_pty = dev->radio_tx_rds_dyn_pty->cur.val; 7662306a36Sopenharmony_ci rds->ta = dev->radio_tx_rds_ta->cur.val; 7762306a36Sopenharmony_ci rds->tp = dev->radio_tx_rds_tp->cur.val; 7862306a36Sopenharmony_ci rds->ms = dev->radio_tx_rds_ms->cur.val; 7962306a36Sopenharmony_ci strscpy(rds->psname, 8062306a36Sopenharmony_ci dev->radio_tx_rds_psname->p_cur.p_char, 8162306a36Sopenharmony_ci sizeof(rds->psname)); 8262306a36Sopenharmony_ci strscpy(rds->radiotext, 8362306a36Sopenharmony_ci dev->radio_tx_rds_radiotext->p_cur.p_char + alt * 64, 8462306a36Sopenharmony_ci sizeof(rds->radiotext)); 8562306a36Sopenharmony_ci v4l2_ctrl_unlock(dev->radio_tx_rds_pi); 8662306a36Sopenharmony_ci } else { 8762306a36Sopenharmony_ci vivid_rds_gen_fill(rds, dev->radio_rx_freq, alt); 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci if (dev->radio_rx_rds_controls) { 9062306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->radio_rx_rds_pty, rds->pty); 9162306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ta, rds->ta); 9262306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->radio_rx_rds_tp, rds->tp); 9362306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ms, rds->ms); 9462306a36Sopenharmony_ci v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_psname, rds->psname); 9562306a36Sopenharmony_ci v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_radiotext, rds->radiotext); 9662306a36Sopenharmony_ci if (!dev->radio_rds_loop) 9762306a36Sopenharmony_ci dev->radio_rx_rds_use_alternates = !dev->radio_rx_rds_use_alternates; 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci vivid_rds_generate(rds); 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci/* 10362306a36Sopenharmony_ci * Calculate the emulated signal quality taking into account the frequency 10462306a36Sopenharmony_ci * the transmitter is using. 10562306a36Sopenharmony_ci */ 10662306a36Sopenharmony_cistatic void vivid_radio_calc_sig_qual(struct vivid_dev *dev) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci int mod = 16000; 10962306a36Sopenharmony_ci int delta = 800; 11062306a36Sopenharmony_ci int sig_qual, sig_qual_tx = mod; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci /* 11362306a36Sopenharmony_ci * For SW and FM there is a channel every 1000 kHz, for AM there is one 11462306a36Sopenharmony_ci * every 100 kHz. 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_ci if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH) { 11762306a36Sopenharmony_ci mod /= 10; 11862306a36Sopenharmony_ci delta /= 10; 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci sig_qual = (dev->radio_rx_freq + delta) % mod - delta; 12162306a36Sopenharmony_ci if (dev->has_radio_tx) 12262306a36Sopenharmony_ci sig_qual_tx = dev->radio_rx_freq - dev->radio_tx_freq; 12362306a36Sopenharmony_ci if (abs(sig_qual_tx) <= abs(sig_qual)) { 12462306a36Sopenharmony_ci sig_qual = sig_qual_tx; 12562306a36Sopenharmony_ci /* 12662306a36Sopenharmony_ci * Zero the internal rds buffer if we are going to loop 12762306a36Sopenharmony_ci * rds blocks. 12862306a36Sopenharmony_ci */ 12962306a36Sopenharmony_ci if (!dev->radio_rds_loop && !dev->radio_tx_rds_controls) 13062306a36Sopenharmony_ci memset(dev->rds_gen.data, 0, 13162306a36Sopenharmony_ci sizeof(dev->rds_gen.data)); 13262306a36Sopenharmony_ci dev->radio_rds_loop = dev->radio_rx_freq >= FM_FREQ_RANGE_LOW; 13362306a36Sopenharmony_ci } else { 13462306a36Sopenharmony_ci dev->radio_rds_loop = false; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH) 13762306a36Sopenharmony_ci sig_qual *= 10; 13862306a36Sopenharmony_ci dev->radio_rx_sig_qual = sig_qual; 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ciint vivid_radio_g_frequency(struct file *file, const unsigned *pfreq, struct v4l2_frequency *vf) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci if (vf->tuner != 0) 14462306a36Sopenharmony_ci return -EINVAL; 14562306a36Sopenharmony_ci vf->frequency = *pfreq; 14662306a36Sopenharmony_ci return 0; 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ciint vivid_radio_s_frequency(struct file *file, unsigned *pfreq, const struct v4l2_frequency *vf) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci struct vivid_dev *dev = video_drvdata(file); 15262306a36Sopenharmony_ci unsigned freq; 15362306a36Sopenharmony_ci unsigned band; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (vf->tuner != 0) 15662306a36Sopenharmony_ci return -EINVAL; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci if (vf->frequency >= (FM_FREQ_RANGE_LOW + SW_FREQ_RANGE_HIGH) / 2) 15962306a36Sopenharmony_ci band = BAND_FM; 16062306a36Sopenharmony_ci else if (vf->frequency <= (AM_FREQ_RANGE_HIGH + SW_FREQ_RANGE_LOW) / 2) 16162306a36Sopenharmony_ci band = BAND_AM; 16262306a36Sopenharmony_ci else 16362306a36Sopenharmony_ci band = BAND_SW; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci freq = clamp_t(u32, vf->frequency, vivid_radio_bands[band].rangelow, 16662306a36Sopenharmony_ci vivid_radio_bands[band].rangehigh); 16762306a36Sopenharmony_ci *pfreq = freq; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci /* 17062306a36Sopenharmony_ci * For both receiver and transmitter recalculate the signal quality 17162306a36Sopenharmony_ci * (since that depends on both frequencies) and re-init the rds 17262306a36Sopenharmony_ci * generator. 17362306a36Sopenharmony_ci */ 17462306a36Sopenharmony_ci vivid_radio_calc_sig_qual(dev); 17562306a36Sopenharmony_ci vivid_radio_rds_init(dev); 17662306a36Sopenharmony_ci return 0; 17762306a36Sopenharmony_ci} 178