18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * vivid-radio-rx.c - radio receiver 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#include <linux/v4l2-dv-timings.h> 138c2ecf20Sopenharmony_ci#include <linux/sched/signal.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <media/v4l2-common.h> 168c2ecf20Sopenharmony_ci#include <media/v4l2-event.h> 178c2ecf20Sopenharmony_ci#include <media/v4l2-dv-timings.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include "vivid-core.h" 208c2ecf20Sopenharmony_ci#include "vivid-ctrls.h" 218c2ecf20Sopenharmony_ci#include "vivid-radio-common.h" 228c2ecf20Sopenharmony_ci#include "vivid-rds-gen.h" 238c2ecf20Sopenharmony_ci#include "vivid-radio-rx.h" 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cissize_t vivid_radio_rx_read(struct file *file, char __user *buf, 268c2ecf20Sopenharmony_ci size_t size, loff_t *offset) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci struct vivid_dev *dev = video_drvdata(file); 298c2ecf20Sopenharmony_ci struct v4l2_rds_data *data = dev->rds_gen.data; 308c2ecf20Sopenharmony_ci bool use_alternates; 318c2ecf20Sopenharmony_ci ktime_t timestamp; 328c2ecf20Sopenharmony_ci unsigned blk; 338c2ecf20Sopenharmony_ci int perc; 348c2ecf20Sopenharmony_ci int i; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci if (dev->radio_rx_rds_controls) 378c2ecf20Sopenharmony_ci return -EINVAL; 388c2ecf20Sopenharmony_ci if (size < sizeof(*data)) 398c2ecf20Sopenharmony_ci return 0; 408c2ecf20Sopenharmony_ci size = sizeof(*data) * (size / sizeof(*data)); 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci if (mutex_lock_interruptible(&dev->mutex)) 438c2ecf20Sopenharmony_ci return -ERESTARTSYS; 448c2ecf20Sopenharmony_ci if (dev->radio_rx_rds_owner && 458c2ecf20Sopenharmony_ci file->private_data != dev->radio_rx_rds_owner) { 468c2ecf20Sopenharmony_ci mutex_unlock(&dev->mutex); 478c2ecf20Sopenharmony_ci return -EBUSY; 488c2ecf20Sopenharmony_ci } 498c2ecf20Sopenharmony_ci if (dev->radio_rx_rds_owner == NULL) { 508c2ecf20Sopenharmony_ci vivid_radio_rds_init(dev); 518c2ecf20Sopenharmony_ci dev->radio_rx_rds_owner = file->private_data; 528c2ecf20Sopenharmony_ci } 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ciretry: 558c2ecf20Sopenharmony_ci timestamp = ktime_sub(ktime_get(), dev->radio_rds_init_time); 568c2ecf20Sopenharmony_ci blk = ktime_divns(timestamp, VIVID_RDS_NSEC_PER_BLK); 578c2ecf20Sopenharmony_ci use_alternates = (blk % VIVID_RDS_GEN_BLOCKS) & 1; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci if (dev->radio_rx_rds_last_block == 0 || 608c2ecf20Sopenharmony_ci dev->radio_rx_rds_use_alternates != use_alternates) { 618c2ecf20Sopenharmony_ci dev->radio_rx_rds_use_alternates = use_alternates; 628c2ecf20Sopenharmony_ci /* Re-init the RDS generator */ 638c2ecf20Sopenharmony_ci vivid_radio_rds_init(dev); 648c2ecf20Sopenharmony_ci } 658c2ecf20Sopenharmony_ci if (blk >= dev->radio_rx_rds_last_block + VIVID_RDS_GEN_BLOCKS) 668c2ecf20Sopenharmony_ci dev->radio_rx_rds_last_block = blk - VIVID_RDS_GEN_BLOCKS + 1; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci /* 698c2ecf20Sopenharmony_ci * No data is available if there hasn't been time to get new data, 708c2ecf20Sopenharmony_ci * or if the RDS receiver has been disabled, or if we use the data 718c2ecf20Sopenharmony_ci * from the RDS transmitter and that RDS transmitter has been disabled, 728c2ecf20Sopenharmony_ci * or if the signal quality is too weak. 738c2ecf20Sopenharmony_ci */ 748c2ecf20Sopenharmony_ci if (blk == dev->radio_rx_rds_last_block || !dev->radio_rx_rds_enabled || 758c2ecf20Sopenharmony_ci (dev->radio_rds_loop && !(dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) || 768c2ecf20Sopenharmony_ci abs(dev->radio_rx_sig_qual) > 200) { 778c2ecf20Sopenharmony_ci mutex_unlock(&dev->mutex); 788c2ecf20Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 798c2ecf20Sopenharmony_ci return -EWOULDBLOCK; 808c2ecf20Sopenharmony_ci if (msleep_interruptible(20) && signal_pending(current)) 818c2ecf20Sopenharmony_ci return -EINTR; 828c2ecf20Sopenharmony_ci if (mutex_lock_interruptible(&dev->mutex)) 838c2ecf20Sopenharmony_ci return -ERESTARTSYS; 848c2ecf20Sopenharmony_ci goto retry; 858c2ecf20Sopenharmony_ci } 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* abs(dev->radio_rx_sig_qual) <= 200, map that to a 0-50% range */ 888c2ecf20Sopenharmony_ci perc = abs(dev->radio_rx_sig_qual) / 4; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci for (i = 0; i < size && blk > dev->radio_rx_rds_last_block; 918c2ecf20Sopenharmony_ci dev->radio_rx_rds_last_block++) { 928c2ecf20Sopenharmony_ci unsigned data_blk = dev->radio_rx_rds_last_block % VIVID_RDS_GEN_BLOCKS; 938c2ecf20Sopenharmony_ci struct v4l2_rds_data rds = data[data_blk]; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci if (data_blk == 0 && dev->radio_rds_loop) 968c2ecf20Sopenharmony_ci vivid_radio_rds_init(dev); 978c2ecf20Sopenharmony_ci if (perc && prandom_u32_max(100) < perc) { 988c2ecf20Sopenharmony_ci switch (prandom_u32_max(4)) { 998c2ecf20Sopenharmony_ci case 0: 1008c2ecf20Sopenharmony_ci rds.block |= V4L2_RDS_BLOCK_CORRECTED; 1018c2ecf20Sopenharmony_ci break; 1028c2ecf20Sopenharmony_ci case 1: 1038c2ecf20Sopenharmony_ci rds.block |= V4L2_RDS_BLOCK_INVALID; 1048c2ecf20Sopenharmony_ci break; 1058c2ecf20Sopenharmony_ci case 2: 1068c2ecf20Sopenharmony_ci rds.block |= V4L2_RDS_BLOCK_ERROR; 1078c2ecf20Sopenharmony_ci rds.lsb = prandom_u32_max(256); 1088c2ecf20Sopenharmony_ci rds.msb = prandom_u32_max(256); 1098c2ecf20Sopenharmony_ci break; 1108c2ecf20Sopenharmony_ci case 3: /* Skip block altogether */ 1118c2ecf20Sopenharmony_ci if (i) 1128c2ecf20Sopenharmony_ci continue; 1138c2ecf20Sopenharmony_ci /* 1148c2ecf20Sopenharmony_ci * Must make sure at least one block is 1158c2ecf20Sopenharmony_ci * returned, otherwise the application 1168c2ecf20Sopenharmony_ci * might think that end-of-file occurred. 1178c2ecf20Sopenharmony_ci */ 1188c2ecf20Sopenharmony_ci break; 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci if (copy_to_user(buf + i, &rds, sizeof(rds))) { 1228c2ecf20Sopenharmony_ci i = -EFAULT; 1238c2ecf20Sopenharmony_ci break; 1248c2ecf20Sopenharmony_ci } 1258c2ecf20Sopenharmony_ci i += sizeof(rds); 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci mutex_unlock(&dev->mutex); 1288c2ecf20Sopenharmony_ci return i; 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci__poll_t vivid_radio_rx_poll(struct file *file, struct poll_table_struct *wait) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci return EPOLLIN | EPOLLRDNORM | v4l2_ctrl_poll(file, wait); 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ciint vivid_radio_rx_enum_freq_bands(struct file *file, void *fh, struct v4l2_frequency_band *band) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci if (band->tuner != 0) 1398c2ecf20Sopenharmony_ci return -EINVAL; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (band->index >= TOT_BANDS) 1428c2ecf20Sopenharmony_ci return -EINVAL; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci *band = vivid_radio_bands[band->index]; 1458c2ecf20Sopenharmony_ci return 0; 1468c2ecf20Sopenharmony_ci} 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ciint vivid_radio_rx_s_hw_freq_seek(struct file *file, void *fh, const struct v4l2_hw_freq_seek *a) 1498c2ecf20Sopenharmony_ci{ 1508c2ecf20Sopenharmony_ci struct vivid_dev *dev = video_drvdata(file); 1518c2ecf20Sopenharmony_ci unsigned low, high; 1528c2ecf20Sopenharmony_ci unsigned freq; 1538c2ecf20Sopenharmony_ci unsigned spacing; 1548c2ecf20Sopenharmony_ci unsigned band; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci if (a->tuner) 1578c2ecf20Sopenharmony_ci return -EINVAL; 1588c2ecf20Sopenharmony_ci if (a->wrap_around && dev->radio_rx_hw_seek_mode == VIVID_HW_SEEK_BOUNDED) 1598c2ecf20Sopenharmony_ci return -EINVAL; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci if (!a->wrap_around && dev->radio_rx_hw_seek_mode == VIVID_HW_SEEK_WRAP) 1628c2ecf20Sopenharmony_ci return -EINVAL; 1638c2ecf20Sopenharmony_ci if (!a->rangelow ^ !a->rangehigh) 1648c2ecf20Sopenharmony_ci return -EINVAL; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 1678c2ecf20Sopenharmony_ci return -EWOULDBLOCK; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci if (a->rangelow) { 1708c2ecf20Sopenharmony_ci for (band = 0; band < TOT_BANDS; band++) 1718c2ecf20Sopenharmony_ci if (a->rangelow >= vivid_radio_bands[band].rangelow && 1728c2ecf20Sopenharmony_ci a->rangehigh <= vivid_radio_bands[band].rangehigh) 1738c2ecf20Sopenharmony_ci break; 1748c2ecf20Sopenharmony_ci if (band == TOT_BANDS) 1758c2ecf20Sopenharmony_ci return -EINVAL; 1768c2ecf20Sopenharmony_ci if (!dev->radio_rx_hw_seek_prog_lim && 1778c2ecf20Sopenharmony_ci (a->rangelow != vivid_radio_bands[band].rangelow || 1788c2ecf20Sopenharmony_ci a->rangehigh != vivid_radio_bands[band].rangehigh)) 1798c2ecf20Sopenharmony_ci return -EINVAL; 1808c2ecf20Sopenharmony_ci low = a->rangelow; 1818c2ecf20Sopenharmony_ci high = a->rangehigh; 1828c2ecf20Sopenharmony_ci } else { 1838c2ecf20Sopenharmony_ci for (band = 0; band < TOT_BANDS; band++) 1848c2ecf20Sopenharmony_ci if (dev->radio_rx_freq >= vivid_radio_bands[band].rangelow && 1858c2ecf20Sopenharmony_ci dev->radio_rx_freq <= vivid_radio_bands[band].rangehigh) 1868c2ecf20Sopenharmony_ci break; 1878c2ecf20Sopenharmony_ci if (band == TOT_BANDS) 1888c2ecf20Sopenharmony_ci return -EINVAL; 1898c2ecf20Sopenharmony_ci low = vivid_radio_bands[band].rangelow; 1908c2ecf20Sopenharmony_ci high = vivid_radio_bands[band].rangehigh; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci spacing = band == BAND_AM ? 1600 : 16000; 1938c2ecf20Sopenharmony_ci freq = clamp(dev->radio_rx_freq, low, high); 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci if (a->seek_upward) { 1968c2ecf20Sopenharmony_ci freq = spacing * (freq / spacing) + spacing; 1978c2ecf20Sopenharmony_ci if (freq > high) { 1988c2ecf20Sopenharmony_ci if (!a->wrap_around) 1998c2ecf20Sopenharmony_ci return -ENODATA; 2008c2ecf20Sopenharmony_ci freq = spacing * (low / spacing) + spacing; 2018c2ecf20Sopenharmony_ci if (freq >= dev->radio_rx_freq) 2028c2ecf20Sopenharmony_ci return -ENODATA; 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci } else { 2058c2ecf20Sopenharmony_ci freq = spacing * ((freq + spacing - 1) / spacing) - spacing; 2068c2ecf20Sopenharmony_ci if (freq < low) { 2078c2ecf20Sopenharmony_ci if (!a->wrap_around) 2088c2ecf20Sopenharmony_ci return -ENODATA; 2098c2ecf20Sopenharmony_ci freq = spacing * ((high + spacing - 1) / spacing) - spacing; 2108c2ecf20Sopenharmony_ci if (freq <= dev->radio_rx_freq) 2118c2ecf20Sopenharmony_ci return -ENODATA; 2128c2ecf20Sopenharmony_ci } 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci return 0; 2158c2ecf20Sopenharmony_ci} 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ciint vivid_radio_rx_g_tuner(struct file *file, void *fh, struct v4l2_tuner *vt) 2188c2ecf20Sopenharmony_ci{ 2198c2ecf20Sopenharmony_ci struct vivid_dev *dev = video_drvdata(file); 2208c2ecf20Sopenharmony_ci int delta = 800; 2218c2ecf20Sopenharmony_ci int sig_qual; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci if (vt->index > 0) 2248c2ecf20Sopenharmony_ci return -EINVAL; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci strscpy(vt->name, "AM/FM/SW Receiver", sizeof(vt->name)); 2278c2ecf20Sopenharmony_ci vt->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 2288c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS | V4L2_TUNER_CAP_RDS | 2298c2ecf20Sopenharmony_ci (dev->radio_rx_rds_controls ? 2308c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_RDS_CONTROLS : 2318c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_RDS_BLOCK_IO) | 2328c2ecf20Sopenharmony_ci (dev->radio_rx_hw_seek_prog_lim ? 2338c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_PROG_LIM : 0); 2348c2ecf20Sopenharmony_ci switch (dev->radio_rx_hw_seek_mode) { 2358c2ecf20Sopenharmony_ci case VIVID_HW_SEEK_BOUNDED: 2368c2ecf20Sopenharmony_ci vt->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED; 2378c2ecf20Sopenharmony_ci break; 2388c2ecf20Sopenharmony_ci case VIVID_HW_SEEK_WRAP: 2398c2ecf20Sopenharmony_ci vt->capability |= V4L2_TUNER_CAP_HWSEEK_WRAP; 2408c2ecf20Sopenharmony_ci break; 2418c2ecf20Sopenharmony_ci case VIVID_HW_SEEK_BOTH: 2428c2ecf20Sopenharmony_ci vt->capability |= V4L2_TUNER_CAP_HWSEEK_WRAP | 2438c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED; 2448c2ecf20Sopenharmony_ci break; 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci vt->rangelow = AM_FREQ_RANGE_LOW; 2478c2ecf20Sopenharmony_ci vt->rangehigh = FM_FREQ_RANGE_HIGH; 2488c2ecf20Sopenharmony_ci sig_qual = dev->radio_rx_sig_qual; 2498c2ecf20Sopenharmony_ci vt->signal = abs(sig_qual) > delta ? 0 : 2508c2ecf20Sopenharmony_ci 0xffff - ((unsigned)abs(sig_qual) * 0xffff) / delta; 2518c2ecf20Sopenharmony_ci vt->afc = sig_qual > delta ? 0 : sig_qual; 2528c2ecf20Sopenharmony_ci if (abs(sig_qual) > delta) 2538c2ecf20Sopenharmony_ci vt->rxsubchans = 0; 2548c2ecf20Sopenharmony_ci else if (dev->radio_rx_freq < FM_FREQ_RANGE_LOW || vt->signal < 0x8000) 2558c2ecf20Sopenharmony_ci vt->rxsubchans = V4L2_TUNER_SUB_MONO; 2568c2ecf20Sopenharmony_ci else if (dev->radio_rds_loop && !(dev->radio_tx_subchans & V4L2_TUNER_SUB_STEREO)) 2578c2ecf20Sopenharmony_ci vt->rxsubchans = V4L2_TUNER_SUB_MONO; 2588c2ecf20Sopenharmony_ci else 2598c2ecf20Sopenharmony_ci vt->rxsubchans = V4L2_TUNER_SUB_STEREO; 2608c2ecf20Sopenharmony_ci if (dev->radio_rx_rds_enabled && 2618c2ecf20Sopenharmony_ci (!dev->radio_rds_loop || (dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) && 2628c2ecf20Sopenharmony_ci dev->radio_rx_freq >= FM_FREQ_RANGE_LOW && vt->signal >= 0xc000) 2638c2ecf20Sopenharmony_ci vt->rxsubchans |= V4L2_TUNER_SUB_RDS; 2648c2ecf20Sopenharmony_ci if (dev->radio_rx_rds_controls) 2658c2ecf20Sopenharmony_ci vivid_radio_rds_init(dev); 2668c2ecf20Sopenharmony_ci vt->audmode = dev->radio_rx_audmode; 2678c2ecf20Sopenharmony_ci return 0; 2688c2ecf20Sopenharmony_ci} 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ciint vivid_radio_rx_s_tuner(struct file *file, void *fh, const struct v4l2_tuner *vt) 2718c2ecf20Sopenharmony_ci{ 2728c2ecf20Sopenharmony_ci struct vivid_dev *dev = video_drvdata(file); 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci if (vt->index) 2758c2ecf20Sopenharmony_ci return -EINVAL; 2768c2ecf20Sopenharmony_ci dev->radio_rx_audmode = vt->audmode >= V4L2_TUNER_MODE_STEREO; 2778c2ecf20Sopenharmony_ci return 0; 2788c2ecf20Sopenharmony_ci} 279