| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * vivid-radio-common.c - common radio rx/tx support functions. |
| * |
| * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. |
| */ |
| |
| #include <linux/errno.h> |
| #include <linux/kernel.h> |
| #include <linux/delay.h> |
| #include <linux/videodev2.h> |
| |
| #include "vivid-core.h" |
| #include "vivid-ctrls.h" |
| #include "vivid-radio-common.h" |
| #include "vivid-rds-gen.h" |
| |
| /* |
| * These functions are shared between the vivid receiver and transmitter |
| * since both use the same frequency bands. |
| */ |
| |
| const struct v4l2_frequency_band vivid_radio_bands[TOT_BANDS] = { |
| /* Band FM */ |
| { |
| .type = V4L2_TUNER_RADIO, |
| .index = 0, |
| .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | |
| V4L2_TUNER_CAP_FREQ_BANDS, |
| .rangelow = FM_FREQ_RANGE_LOW, |
| .rangehigh = FM_FREQ_RANGE_HIGH, |
| .modulation = V4L2_BAND_MODULATION_FM, |
| }, |
| /* Band AM */ |
| { |
| .type = V4L2_TUNER_RADIO, |
| .index = 1, |
| .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, |
| .rangelow = AM_FREQ_RANGE_LOW, |
| .rangehigh = AM_FREQ_RANGE_HIGH, |
| .modulation = V4L2_BAND_MODULATION_AM, |
| }, |
| /* Band SW */ |
| { |
| .type = V4L2_TUNER_RADIO, |
| .index = 2, |
| .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, |
| .rangelow = SW_FREQ_RANGE_LOW, |
| .rangehigh = SW_FREQ_RANGE_HIGH, |
| .modulation = V4L2_BAND_MODULATION_AM, |
| }, |
| }; |
| |
| /* |
| * Initialize the RDS generator. If we can loop, then the RDS generator |
| * is set up with the values from the RDS TX controls, otherwise it |
| * will fill in standard values using one of two alternates. |
| */ |
| void vivid_radio_rds_init(struct vivid_dev *dev) |
| { |
| struct vivid_rds_gen *rds = &dev->rds_gen; |
| bool alt = dev->radio_rx_rds_use_alternates; |
| |
| /* Do nothing, blocks will be filled by the transmitter */ |
| if (dev->radio_rds_loop && !dev->radio_tx_rds_controls) |
| return; |
| |
| if (dev->radio_rds_loop) { |
| v4l2_ctrl_lock(dev->radio_tx_rds_pi); |
| rds->picode = dev->radio_tx_rds_pi->cur.val; |
| rds->pty = dev->radio_tx_rds_pty->cur.val; |
| rds->mono_stereo = dev->radio_tx_rds_mono_stereo->cur.val; |
| rds->art_head = dev->radio_tx_rds_art_head->cur.val; |
| rds->compressed = dev->radio_tx_rds_compressed->cur.val; |
| rds->dyn_pty = dev->radio_tx_rds_dyn_pty->cur.val; |
| rds->ta = dev->radio_tx_rds_ta->cur.val; |
| rds->tp = dev->radio_tx_rds_tp->cur.val; |
| rds->ms = dev->radio_tx_rds_ms->cur.val; |
| strscpy(rds->psname, |
| dev->radio_tx_rds_psname->p_cur.p_char, |
| sizeof(rds->psname)); |
| strscpy(rds->radiotext, |
| dev->radio_tx_rds_radiotext->p_cur.p_char + alt * 64, |
| sizeof(rds->radiotext)); |
| v4l2_ctrl_unlock(dev->radio_tx_rds_pi); |
| } else { |
| vivid_rds_gen_fill(rds, dev->radio_rx_freq, alt); |
| } |
| if (dev->radio_rx_rds_controls) { |
| v4l2_ctrl_s_ctrl(dev->radio_rx_rds_pty, rds->pty); |
| v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ta, rds->ta); |
| v4l2_ctrl_s_ctrl(dev->radio_rx_rds_tp, rds->tp); |
| v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ms, rds->ms); |
| v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_psname, rds->psname); |
| v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_radiotext, rds->radiotext); |
| if (!dev->radio_rds_loop) |
| dev->radio_rx_rds_use_alternates = !dev->radio_rx_rds_use_alternates; |
| } |
| vivid_rds_generate(rds); |
| } |
| |
| /* |
| * Calculate the emulated signal quality taking into account the frequency |
| * the transmitter is using. |
| */ |
| static void vivid_radio_calc_sig_qual(struct vivid_dev *dev) |
| { |
| int mod = 16000; |
| int delta = 800; |
| int sig_qual, sig_qual_tx = mod; |
| |
| /* |
| * For SW and FM there is a channel every 1000 kHz, for AM there is one |
| * every 100 kHz. |
| */ |
| if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH) { |
| mod /= 10; |
| delta /= 10; |
| } |
| sig_qual = (dev->radio_rx_freq + delta) % mod - delta; |
| if (dev->has_radio_tx) |
| sig_qual_tx = dev->radio_rx_freq - dev->radio_tx_freq; |
| if (abs(sig_qual_tx) <= abs(sig_qual)) { |
| sig_qual = sig_qual_tx; |
| /* |
| * Zero the internal rds buffer if we are going to loop |
| * rds blocks. |
| */ |
| if (!dev->radio_rds_loop && !dev->radio_tx_rds_controls) |
| memset(dev->rds_gen.data, 0, |
| sizeof(dev->rds_gen.data)); |
| dev->radio_rds_loop = dev->radio_rx_freq >= FM_FREQ_RANGE_LOW; |
| } else { |
| dev->radio_rds_loop = false; |
| } |
| if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH) |
| sig_qual *= 10; |
| dev->radio_rx_sig_qual = sig_qual; |
| } |
| |
| int vivid_radio_g_frequency(struct file *file, const unsigned *pfreq, struct v4l2_frequency *vf) |
| { |
| if (vf->tuner != 0) |
| return -EINVAL; |
| vf->frequency = *pfreq; |
| return 0; |
| } |
| |
| int vivid_radio_s_frequency(struct file *file, unsigned *pfreq, const struct v4l2_frequency *vf) |
| { |
| struct vivid_dev *dev = video_drvdata(file); |
| unsigned freq; |
| unsigned band; |
| |
| if (vf->tuner != 0) |
| return -EINVAL; |
| |
| if (vf->frequency >= (FM_FREQ_RANGE_LOW + SW_FREQ_RANGE_HIGH) / 2) |
| band = BAND_FM; |
| else if (vf->frequency <= (AM_FREQ_RANGE_HIGH + SW_FREQ_RANGE_LOW) / 2) |
| band = BAND_AM; |
| else |
| band = BAND_SW; |
| |
| freq = clamp_t(u32, vf->frequency, vivid_radio_bands[band].rangelow, |
| vivid_radio_bands[band].rangehigh); |
| *pfreq = freq; |
| |
| /* |
| * For both receiver and transmitter recalculate the signal quality |
| * (since that depends on both frequencies) and re-init the rds |
| * generator. |
| */ |
| vivid_radio_calc_sig_qual(dev); |
| vivid_radio_rds_init(dev); |
| return 0; |
| } |