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