178 lines
5.1 KiB
C
178 lines
5.1 KiB
C
// 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;
|
|
strlcpy(rds->psname,
|
|
dev->radio_tx_rds_psname->p_cur.p_char,
|
|
sizeof(rds->psname));
|
|
strlcpy(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;
|
|
}
|