18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * FM Driver for Connectivity chip of Texas Instruments. 48c2ecf20Sopenharmony_ci * This file provides interfaces to V4L2 subsystem. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * This module registers with V4L2 subsystem as Radio 78c2ecf20Sopenharmony_ci * data system interface (/dev/radio). During the registration, 88c2ecf20Sopenharmony_ci * it will expose two set of function pointers. 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * 1) File operation related API (open, close, read, write, poll...etc). 118c2ecf20Sopenharmony_ci * 2) Set of V4L2 IOCTL complaint API. 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * Copyright (C) 2011 Texas Instruments 148c2ecf20Sopenharmony_ci * Author: Raja Mani <raja_mani@ti.com> 158c2ecf20Sopenharmony_ci * Author: Manjunatha Halli <manjunatha_halli@ti.com> 168c2ecf20Sopenharmony_ci */ 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include <linux/export.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include "fmdrv.h" 218c2ecf20Sopenharmony_ci#include "fmdrv_v4l2.h" 228c2ecf20Sopenharmony_ci#include "fmdrv_common.h" 238c2ecf20Sopenharmony_ci#include "fmdrv_rx.h" 248c2ecf20Sopenharmony_ci#include "fmdrv_tx.h" 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic struct video_device gradio_dev; 278c2ecf20Sopenharmony_cistatic u8 radio_disconnected; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci/* -- V4L2 RADIO (/dev/radioX) device file operation interfaces --- */ 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci/* Read RX RDS data */ 328c2ecf20Sopenharmony_cistatic ssize_t fm_v4l2_fops_read(struct file *file, char __user * buf, 338c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci u8 rds_mode; 368c2ecf20Sopenharmony_ci int ret; 378c2ecf20Sopenharmony_ci struct fmdev *fmdev; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci fmdev = video_drvdata(file); 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci if (!radio_disconnected) { 428c2ecf20Sopenharmony_ci fmerr("FM device is already disconnected\n"); 438c2ecf20Sopenharmony_ci return -EIO; 448c2ecf20Sopenharmony_ci } 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci if (mutex_lock_interruptible(&fmdev->mutex)) 478c2ecf20Sopenharmony_ci return -ERESTARTSYS; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci /* Turn on RDS mode if it is disabled */ 508c2ecf20Sopenharmony_ci ret = fm_rx_get_rds_mode(fmdev, &rds_mode); 518c2ecf20Sopenharmony_ci if (ret < 0) { 528c2ecf20Sopenharmony_ci fmerr("Unable to read current rds mode\n"); 538c2ecf20Sopenharmony_ci goto read_unlock; 548c2ecf20Sopenharmony_ci } 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci if (rds_mode == FM_RDS_DISABLE) { 578c2ecf20Sopenharmony_ci ret = fmc_set_rds_mode(fmdev, FM_RDS_ENABLE); 588c2ecf20Sopenharmony_ci if (ret < 0) { 598c2ecf20Sopenharmony_ci fmerr("Failed to enable rds mode\n"); 608c2ecf20Sopenharmony_ci goto read_unlock; 618c2ecf20Sopenharmony_ci } 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci /* Copy RDS data from internal buffer to user buffer */ 658c2ecf20Sopenharmony_ci ret = fmc_transfer_rds_from_internal_buff(fmdev, file, buf, count); 668c2ecf20Sopenharmony_ciread_unlock: 678c2ecf20Sopenharmony_ci mutex_unlock(&fmdev->mutex); 688c2ecf20Sopenharmony_ci return ret; 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci/* Write TX RDS data */ 728c2ecf20Sopenharmony_cistatic ssize_t fm_v4l2_fops_write(struct file *file, const char __user * buf, 738c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci struct tx_rds rds; 768c2ecf20Sopenharmony_ci int ret; 778c2ecf20Sopenharmony_ci struct fmdev *fmdev; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci ret = copy_from_user(&rds, buf, sizeof(rds)); 808c2ecf20Sopenharmony_ci rds.text[sizeof(rds.text) - 1] = '\0'; 818c2ecf20Sopenharmony_ci fmdbg("(%d)type: %d, text %s, af %d\n", 828c2ecf20Sopenharmony_ci ret, rds.text_type, rds.text, rds.af_freq); 838c2ecf20Sopenharmony_ci if (ret) 848c2ecf20Sopenharmony_ci return -EFAULT; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci fmdev = video_drvdata(file); 878c2ecf20Sopenharmony_ci if (mutex_lock_interruptible(&fmdev->mutex)) 888c2ecf20Sopenharmony_ci return -ERESTARTSYS; 898c2ecf20Sopenharmony_ci fm_tx_set_radio_text(fmdev, rds.text, rds.text_type); 908c2ecf20Sopenharmony_ci fm_tx_set_af(fmdev, rds.af_freq); 918c2ecf20Sopenharmony_ci mutex_unlock(&fmdev->mutex); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci return sizeof(rds); 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic __poll_t fm_v4l2_fops_poll(struct file *file, struct poll_table_struct *pts) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci int ret; 998c2ecf20Sopenharmony_ci struct fmdev *fmdev; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci fmdev = video_drvdata(file); 1028c2ecf20Sopenharmony_ci mutex_lock(&fmdev->mutex); 1038c2ecf20Sopenharmony_ci ret = fmc_is_rds_data_available(fmdev, file, pts); 1048c2ecf20Sopenharmony_ci mutex_unlock(&fmdev->mutex); 1058c2ecf20Sopenharmony_ci if (ret < 0) 1068c2ecf20Sopenharmony_ci return EPOLLIN | EPOLLRDNORM; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci return 0; 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci/* 1128c2ecf20Sopenharmony_ci * Handle open request for "/dev/radioX" device. 1138c2ecf20Sopenharmony_ci * Start with FM RX mode as default. 1148c2ecf20Sopenharmony_ci */ 1158c2ecf20Sopenharmony_cistatic int fm_v4l2_fops_open(struct file *file) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci int ret; 1188c2ecf20Sopenharmony_ci struct fmdev *fmdev = NULL; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* Don't allow multiple open */ 1218c2ecf20Sopenharmony_ci if (radio_disconnected) { 1228c2ecf20Sopenharmony_ci fmerr("FM device is already opened\n"); 1238c2ecf20Sopenharmony_ci return -EBUSY; 1248c2ecf20Sopenharmony_ci } 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci fmdev = video_drvdata(file); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci if (mutex_lock_interruptible(&fmdev->mutex)) 1298c2ecf20Sopenharmony_ci return -ERESTARTSYS; 1308c2ecf20Sopenharmony_ci ret = fmc_prepare(fmdev); 1318c2ecf20Sopenharmony_ci if (ret < 0) { 1328c2ecf20Sopenharmony_ci fmerr("Unable to prepare FM CORE\n"); 1338c2ecf20Sopenharmony_ci goto open_unlock; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci fmdbg("Load FM RX firmware..\n"); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci ret = fmc_set_mode(fmdev, FM_MODE_RX); 1398c2ecf20Sopenharmony_ci if (ret < 0) { 1408c2ecf20Sopenharmony_ci fmerr("Unable to load FM RX firmware\n"); 1418c2ecf20Sopenharmony_ci goto open_unlock; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci radio_disconnected = 1; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ciopen_unlock: 1468c2ecf20Sopenharmony_ci mutex_unlock(&fmdev->mutex); 1478c2ecf20Sopenharmony_ci return ret; 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_cistatic int fm_v4l2_fops_release(struct file *file) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci int ret; 1538c2ecf20Sopenharmony_ci struct fmdev *fmdev; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci fmdev = video_drvdata(file); 1568c2ecf20Sopenharmony_ci if (!radio_disconnected) { 1578c2ecf20Sopenharmony_ci fmdbg("FM device is already closed\n"); 1588c2ecf20Sopenharmony_ci return 0; 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci mutex_lock(&fmdev->mutex); 1628c2ecf20Sopenharmony_ci ret = fmc_set_mode(fmdev, FM_MODE_OFF); 1638c2ecf20Sopenharmony_ci if (ret < 0) { 1648c2ecf20Sopenharmony_ci fmerr("Unable to turn off the chip\n"); 1658c2ecf20Sopenharmony_ci goto release_unlock; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci ret = fmc_release(fmdev); 1698c2ecf20Sopenharmony_ci if (ret < 0) { 1708c2ecf20Sopenharmony_ci fmerr("FM CORE release failed\n"); 1718c2ecf20Sopenharmony_ci goto release_unlock; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci radio_disconnected = 0; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cirelease_unlock: 1768c2ecf20Sopenharmony_ci mutex_unlock(&fmdev->mutex); 1778c2ecf20Sopenharmony_ci return ret; 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci/* V4L2 RADIO (/dev/radioX) device IOCTL interfaces */ 1818c2ecf20Sopenharmony_cistatic int fm_v4l2_vidioc_querycap(struct file *file, void *priv, 1828c2ecf20Sopenharmony_ci struct v4l2_capability *capability) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci strscpy(capability->driver, FM_DRV_NAME, sizeof(capability->driver)); 1858c2ecf20Sopenharmony_ci strscpy(capability->card, FM_DRV_CARD_SHORT_NAME, 1868c2ecf20Sopenharmony_ci sizeof(capability->card)); 1878c2ecf20Sopenharmony_ci sprintf(capability->bus_info, "UART"); 1888c2ecf20Sopenharmony_ci return 0; 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic int fm_g_volatile_ctrl(struct v4l2_ctrl *ctrl) 1928c2ecf20Sopenharmony_ci{ 1938c2ecf20Sopenharmony_ci struct fmdev *fmdev = container_of(ctrl->handler, 1948c2ecf20Sopenharmony_ci struct fmdev, ctrl_handler); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci switch (ctrl->id) { 1978c2ecf20Sopenharmony_ci case V4L2_CID_TUNE_ANTENNA_CAPACITOR: 1988c2ecf20Sopenharmony_ci ctrl->val = fm_tx_get_tune_cap_val(fmdev); 1998c2ecf20Sopenharmony_ci break; 2008c2ecf20Sopenharmony_ci default: 2018c2ecf20Sopenharmony_ci fmwarn("%s: Unknown IOCTL: %d\n", __func__, ctrl->id); 2028c2ecf20Sopenharmony_ci break; 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci return 0; 2068c2ecf20Sopenharmony_ci} 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_cistatic int fm_v4l2_s_ctrl(struct v4l2_ctrl *ctrl) 2098c2ecf20Sopenharmony_ci{ 2108c2ecf20Sopenharmony_ci struct fmdev *fmdev = container_of(ctrl->handler, 2118c2ecf20Sopenharmony_ci struct fmdev, ctrl_handler); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci switch (ctrl->id) { 2148c2ecf20Sopenharmony_ci case V4L2_CID_AUDIO_VOLUME: /* set volume */ 2158c2ecf20Sopenharmony_ci return fm_rx_set_volume(fmdev, (u16)ctrl->val); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: /* set mute */ 2188c2ecf20Sopenharmony_ci return fmc_set_mute_mode(fmdev, (u8)ctrl->val); 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci case V4L2_CID_TUNE_POWER_LEVEL: 2218c2ecf20Sopenharmony_ci /* set TX power level - ext control */ 2228c2ecf20Sopenharmony_ci return fm_tx_set_pwr_lvl(fmdev, (u8)ctrl->val); 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci case V4L2_CID_TUNE_PREEMPHASIS: 2258c2ecf20Sopenharmony_ci return fm_tx_set_preemph_filter(fmdev, (u8) ctrl->val); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci default: 2288c2ecf20Sopenharmony_ci return -EINVAL; 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic int fm_v4l2_vidioc_g_audio(struct file *file, void *priv, 2338c2ecf20Sopenharmony_ci struct v4l2_audio *audio) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci memset(audio, 0, sizeof(*audio)); 2368c2ecf20Sopenharmony_ci strscpy(audio->name, "Radio", sizeof(audio->name)); 2378c2ecf20Sopenharmony_ci audio->capability = V4L2_AUDCAP_STEREO; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci return 0; 2408c2ecf20Sopenharmony_ci} 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_cistatic int fm_v4l2_vidioc_s_audio(struct file *file, void *priv, 2438c2ecf20Sopenharmony_ci const struct v4l2_audio *audio) 2448c2ecf20Sopenharmony_ci{ 2458c2ecf20Sopenharmony_ci if (audio->index != 0) 2468c2ecf20Sopenharmony_ci return -EINVAL; 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci return 0; 2498c2ecf20Sopenharmony_ci} 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci/* Get tuner attributes. If current mode is NOT RX, return error */ 2528c2ecf20Sopenharmony_cistatic int fm_v4l2_vidioc_g_tuner(struct file *file, void *priv, 2538c2ecf20Sopenharmony_ci struct v4l2_tuner *tuner) 2548c2ecf20Sopenharmony_ci{ 2558c2ecf20Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 2568c2ecf20Sopenharmony_ci u32 bottom_freq; 2578c2ecf20Sopenharmony_ci u32 top_freq; 2588c2ecf20Sopenharmony_ci u16 stereo_mono_mode; 2598c2ecf20Sopenharmony_ci u16 rssilvl; 2608c2ecf20Sopenharmony_ci int ret; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci if (tuner->index != 0) 2638c2ecf20Sopenharmony_ci return -EINVAL; 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci if (fmdev->curr_fmmode != FM_MODE_RX) 2668c2ecf20Sopenharmony_ci return -EPERM; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci ret = fm_rx_get_band_freq_range(fmdev, &bottom_freq, &top_freq); 2698c2ecf20Sopenharmony_ci if (ret != 0) 2708c2ecf20Sopenharmony_ci return ret; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci ret = fm_rx_get_stereo_mono(fmdev, &stereo_mono_mode); 2738c2ecf20Sopenharmony_ci if (ret != 0) 2748c2ecf20Sopenharmony_ci return ret; 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci ret = fm_rx_get_rssi_level(fmdev, &rssilvl); 2778c2ecf20Sopenharmony_ci if (ret != 0) 2788c2ecf20Sopenharmony_ci return ret; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci strscpy(tuner->name, "FM", sizeof(tuner->name)); 2818c2ecf20Sopenharmony_ci tuner->type = V4L2_TUNER_RADIO; 2828c2ecf20Sopenharmony_ci /* Store rangelow and rangehigh freq in unit of 62.5 Hz */ 2838c2ecf20Sopenharmony_ci tuner->rangelow = bottom_freq * 16; 2848c2ecf20Sopenharmony_ci tuner->rangehigh = top_freq * 16; 2858c2ecf20Sopenharmony_ci tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO | 2868c2ecf20Sopenharmony_ci ((fmdev->rx.rds.flag == FM_RDS_ENABLE) ? V4L2_TUNER_SUB_RDS : 0); 2878c2ecf20Sopenharmony_ci tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | 2888c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_LOW | 2898c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 2908c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_WRAP; 2918c2ecf20Sopenharmony_ci tuner->audmode = (stereo_mono_mode ? 2928c2ecf20Sopenharmony_ci V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO); 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci /* 2958c2ecf20Sopenharmony_ci * Actual rssi value lies in between -128 to +127. 2968c2ecf20Sopenharmony_ci * Convert this range from 0 to 255 by adding +128 2978c2ecf20Sopenharmony_ci */ 2988c2ecf20Sopenharmony_ci rssilvl += 128; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci /* 3018c2ecf20Sopenharmony_ci * Return signal strength value should be within 0 to 65535. 3028c2ecf20Sopenharmony_ci * Find out correct signal radio by multiplying (65535/255) = 257 3038c2ecf20Sopenharmony_ci */ 3048c2ecf20Sopenharmony_ci tuner->signal = rssilvl * 257; 3058c2ecf20Sopenharmony_ci tuner->afc = 0; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci return ret; 3088c2ecf20Sopenharmony_ci} 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci/* 3118c2ecf20Sopenharmony_ci * Set tuner attributes. If current mode is NOT RX, set to RX. 3128c2ecf20Sopenharmony_ci * Currently, we set only audio mode (mono/stereo) and RDS state (on/off). 3138c2ecf20Sopenharmony_ci * Should we set other tuner attributes, too? 3148c2ecf20Sopenharmony_ci */ 3158c2ecf20Sopenharmony_cistatic int fm_v4l2_vidioc_s_tuner(struct file *file, void *priv, 3168c2ecf20Sopenharmony_ci const struct v4l2_tuner *tuner) 3178c2ecf20Sopenharmony_ci{ 3188c2ecf20Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 3198c2ecf20Sopenharmony_ci u16 aud_mode; 3208c2ecf20Sopenharmony_ci u8 rds_mode; 3218c2ecf20Sopenharmony_ci int ret; 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci if (tuner->index != 0) 3248c2ecf20Sopenharmony_ci return -EINVAL; 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci aud_mode = (tuner->audmode == V4L2_TUNER_MODE_STEREO) ? 3278c2ecf20Sopenharmony_ci FM_STEREO_MODE : FM_MONO_MODE; 3288c2ecf20Sopenharmony_ci rds_mode = (tuner->rxsubchans & V4L2_TUNER_SUB_RDS) ? 3298c2ecf20Sopenharmony_ci FM_RDS_ENABLE : FM_RDS_DISABLE; 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci if (fmdev->curr_fmmode != FM_MODE_RX) { 3328c2ecf20Sopenharmony_ci ret = fmc_set_mode(fmdev, FM_MODE_RX); 3338c2ecf20Sopenharmony_ci if (ret < 0) { 3348c2ecf20Sopenharmony_ci fmerr("Failed to set RX mode\n"); 3358c2ecf20Sopenharmony_ci return ret; 3368c2ecf20Sopenharmony_ci } 3378c2ecf20Sopenharmony_ci } 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci ret = fmc_set_stereo_mono(fmdev, aud_mode); 3408c2ecf20Sopenharmony_ci if (ret < 0) { 3418c2ecf20Sopenharmony_ci fmerr("Failed to set RX stereo/mono mode\n"); 3428c2ecf20Sopenharmony_ci return ret; 3438c2ecf20Sopenharmony_ci } 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci ret = fmc_set_rds_mode(fmdev, rds_mode); 3468c2ecf20Sopenharmony_ci if (ret < 0) 3478c2ecf20Sopenharmony_ci fmerr("Failed to set RX RDS mode\n"); 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci return ret; 3508c2ecf20Sopenharmony_ci} 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ci/* Get tuner or modulator radio frequency */ 3538c2ecf20Sopenharmony_cistatic int fm_v4l2_vidioc_g_freq(struct file *file, void *priv, 3548c2ecf20Sopenharmony_ci struct v4l2_frequency *freq) 3558c2ecf20Sopenharmony_ci{ 3568c2ecf20Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 3578c2ecf20Sopenharmony_ci int ret; 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci ret = fmc_get_freq(fmdev, &freq->frequency); 3608c2ecf20Sopenharmony_ci if (ret < 0) { 3618c2ecf20Sopenharmony_ci fmerr("Failed to get frequency\n"); 3628c2ecf20Sopenharmony_ci return ret; 3638c2ecf20Sopenharmony_ci } 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci /* Frequency unit of 62.5 Hz*/ 3668c2ecf20Sopenharmony_ci freq->frequency = (u32) freq->frequency * 16; 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci return 0; 3698c2ecf20Sopenharmony_ci} 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci/* Set tuner or modulator radio frequency */ 3728c2ecf20Sopenharmony_cistatic int fm_v4l2_vidioc_s_freq(struct file *file, void *priv, 3738c2ecf20Sopenharmony_ci const struct v4l2_frequency *freq) 3748c2ecf20Sopenharmony_ci{ 3758c2ecf20Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci /* 3788c2ecf20Sopenharmony_ci * As V4L2_TUNER_CAP_LOW is set 1 user sends the frequency 3798c2ecf20Sopenharmony_ci * in units of 62.5 Hz. 3808c2ecf20Sopenharmony_ci */ 3818c2ecf20Sopenharmony_ci return fmc_set_freq(fmdev, freq->frequency / 16); 3828c2ecf20Sopenharmony_ci} 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci/* Set hardware frequency seek. If current mode is NOT RX, set it RX. */ 3858c2ecf20Sopenharmony_cistatic int fm_v4l2_vidioc_s_hw_freq_seek(struct file *file, void *priv, 3868c2ecf20Sopenharmony_ci const struct v4l2_hw_freq_seek *seek) 3878c2ecf20Sopenharmony_ci{ 3888c2ecf20Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 3898c2ecf20Sopenharmony_ci int ret; 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 3928c2ecf20Sopenharmony_ci return -EWOULDBLOCK; 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_ci if (fmdev->curr_fmmode != FM_MODE_RX) { 3958c2ecf20Sopenharmony_ci ret = fmc_set_mode(fmdev, FM_MODE_RX); 3968c2ecf20Sopenharmony_ci if (ret != 0) { 3978c2ecf20Sopenharmony_ci fmerr("Failed to set RX mode\n"); 3988c2ecf20Sopenharmony_ci return ret; 3998c2ecf20Sopenharmony_ci } 4008c2ecf20Sopenharmony_ci } 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci ret = fm_rx_seek(fmdev, seek->seek_upward, seek->wrap_around, 4038c2ecf20Sopenharmony_ci seek->spacing); 4048c2ecf20Sopenharmony_ci if (ret < 0) 4058c2ecf20Sopenharmony_ci fmerr("RX seek failed - %d\n", ret); 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci return ret; 4088c2ecf20Sopenharmony_ci} 4098c2ecf20Sopenharmony_ci/* Get modulator attributes. If mode is not TX, return no attributes. */ 4108c2ecf20Sopenharmony_cistatic int fm_v4l2_vidioc_g_modulator(struct file *file, void *priv, 4118c2ecf20Sopenharmony_ci struct v4l2_modulator *mod) 4128c2ecf20Sopenharmony_ci{ 4138c2ecf20Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ci if (mod->index != 0) 4168c2ecf20Sopenharmony_ci return -EINVAL; 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci if (fmdev->curr_fmmode != FM_MODE_TX) 4198c2ecf20Sopenharmony_ci return -EPERM; 4208c2ecf20Sopenharmony_ci 4218c2ecf20Sopenharmony_ci mod->txsubchans = ((fmdev->tx_data.aud_mode == FM_STEREO_MODE) ? 4228c2ecf20Sopenharmony_ci V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO) | 4238c2ecf20Sopenharmony_ci ((fmdev->tx_data.rds.flag == FM_RDS_ENABLE) ? 4248c2ecf20Sopenharmony_ci V4L2_TUNER_SUB_RDS : 0); 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci mod->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | 4278c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_LOW; 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci return 0; 4308c2ecf20Sopenharmony_ci} 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci/* Set modulator attributes. If mode is not TX, set to TX. */ 4338c2ecf20Sopenharmony_cistatic int fm_v4l2_vidioc_s_modulator(struct file *file, void *priv, 4348c2ecf20Sopenharmony_ci const struct v4l2_modulator *mod) 4358c2ecf20Sopenharmony_ci{ 4368c2ecf20Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 4378c2ecf20Sopenharmony_ci u8 rds_mode; 4388c2ecf20Sopenharmony_ci u16 aud_mode; 4398c2ecf20Sopenharmony_ci int ret; 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci if (mod->index != 0) 4428c2ecf20Sopenharmony_ci return -EINVAL; 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci if (fmdev->curr_fmmode != FM_MODE_TX) { 4458c2ecf20Sopenharmony_ci ret = fmc_set_mode(fmdev, FM_MODE_TX); 4468c2ecf20Sopenharmony_ci if (ret != 0) { 4478c2ecf20Sopenharmony_ci fmerr("Failed to set TX mode\n"); 4488c2ecf20Sopenharmony_ci return ret; 4498c2ecf20Sopenharmony_ci } 4508c2ecf20Sopenharmony_ci } 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci aud_mode = (mod->txsubchans & V4L2_TUNER_SUB_STEREO) ? 4538c2ecf20Sopenharmony_ci FM_STEREO_MODE : FM_MONO_MODE; 4548c2ecf20Sopenharmony_ci rds_mode = (mod->txsubchans & V4L2_TUNER_SUB_RDS) ? 4558c2ecf20Sopenharmony_ci FM_RDS_ENABLE : FM_RDS_DISABLE; 4568c2ecf20Sopenharmony_ci ret = fm_tx_set_stereo_mono(fmdev, aud_mode); 4578c2ecf20Sopenharmony_ci if (ret < 0) { 4588c2ecf20Sopenharmony_ci fmerr("Failed to set mono/stereo mode for TX\n"); 4598c2ecf20Sopenharmony_ci return ret; 4608c2ecf20Sopenharmony_ci } 4618c2ecf20Sopenharmony_ci ret = fm_tx_set_rds_mode(fmdev, rds_mode); 4628c2ecf20Sopenharmony_ci if (ret < 0) 4638c2ecf20Sopenharmony_ci fmerr("Failed to set rds mode for TX\n"); 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci return ret; 4668c2ecf20Sopenharmony_ci} 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations fm_drv_fops = { 4698c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 4708c2ecf20Sopenharmony_ci .read = fm_v4l2_fops_read, 4718c2ecf20Sopenharmony_ci .write = fm_v4l2_fops_write, 4728c2ecf20Sopenharmony_ci .poll = fm_v4l2_fops_poll, 4738c2ecf20Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 4748c2ecf20Sopenharmony_ci .open = fm_v4l2_fops_open, 4758c2ecf20Sopenharmony_ci .release = fm_v4l2_fops_release, 4768c2ecf20Sopenharmony_ci}; 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops fm_ctrl_ops = { 4798c2ecf20Sopenharmony_ci .s_ctrl = fm_v4l2_s_ctrl, 4808c2ecf20Sopenharmony_ci .g_volatile_ctrl = fm_g_volatile_ctrl, 4818c2ecf20Sopenharmony_ci}; 4828c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops fm_drv_ioctl_ops = { 4838c2ecf20Sopenharmony_ci .vidioc_querycap = fm_v4l2_vidioc_querycap, 4848c2ecf20Sopenharmony_ci .vidioc_g_audio = fm_v4l2_vidioc_g_audio, 4858c2ecf20Sopenharmony_ci .vidioc_s_audio = fm_v4l2_vidioc_s_audio, 4868c2ecf20Sopenharmony_ci .vidioc_g_tuner = fm_v4l2_vidioc_g_tuner, 4878c2ecf20Sopenharmony_ci .vidioc_s_tuner = fm_v4l2_vidioc_s_tuner, 4888c2ecf20Sopenharmony_ci .vidioc_g_frequency = fm_v4l2_vidioc_g_freq, 4898c2ecf20Sopenharmony_ci .vidioc_s_frequency = fm_v4l2_vidioc_s_freq, 4908c2ecf20Sopenharmony_ci .vidioc_s_hw_freq_seek = fm_v4l2_vidioc_s_hw_freq_seek, 4918c2ecf20Sopenharmony_ci .vidioc_g_modulator = fm_v4l2_vidioc_g_modulator, 4928c2ecf20Sopenharmony_ci .vidioc_s_modulator = fm_v4l2_vidioc_s_modulator 4938c2ecf20Sopenharmony_ci}; 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci/* V4L2 RADIO device parent structure */ 4968c2ecf20Sopenharmony_cistatic const struct video_device fm_viddev_template = { 4978c2ecf20Sopenharmony_ci .fops = &fm_drv_fops, 4988c2ecf20Sopenharmony_ci .ioctl_ops = &fm_drv_ioctl_ops, 4998c2ecf20Sopenharmony_ci .name = FM_DRV_NAME, 5008c2ecf20Sopenharmony_ci .release = video_device_release_empty, 5018c2ecf20Sopenharmony_ci /* 5028c2ecf20Sopenharmony_ci * To ensure both the tuner and modulator ioctls are accessible we 5038c2ecf20Sopenharmony_ci * set the vfl_dir to M2M to indicate this. 5048c2ecf20Sopenharmony_ci * 5058c2ecf20Sopenharmony_ci * It is not really a mem2mem device of course, but it can both receive 5068c2ecf20Sopenharmony_ci * and transmit using the same radio device. It's the only radio driver 5078c2ecf20Sopenharmony_ci * that does this and it should really be split in two radio devices, 5088c2ecf20Sopenharmony_ci * but that would affect applications using this driver. 5098c2ecf20Sopenharmony_ci */ 5108c2ecf20Sopenharmony_ci .vfl_dir = VFL_DIR_M2M, 5118c2ecf20Sopenharmony_ci .device_caps = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_TUNER | V4L2_CAP_RADIO | 5128c2ecf20Sopenharmony_ci V4L2_CAP_MODULATOR | V4L2_CAP_AUDIO | 5138c2ecf20Sopenharmony_ci V4L2_CAP_READWRITE | V4L2_CAP_RDS_CAPTURE, 5148c2ecf20Sopenharmony_ci}; 5158c2ecf20Sopenharmony_ci 5168c2ecf20Sopenharmony_ciint fm_v4l2_init_video_device(struct fmdev *fmdev, int radio_nr) 5178c2ecf20Sopenharmony_ci{ 5188c2ecf20Sopenharmony_ci struct v4l2_ctrl *ctrl; 5198c2ecf20Sopenharmony_ci int ret; 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci strscpy(fmdev->v4l2_dev.name, FM_DRV_NAME, 5228c2ecf20Sopenharmony_ci sizeof(fmdev->v4l2_dev.name)); 5238c2ecf20Sopenharmony_ci ret = v4l2_device_register(NULL, &fmdev->v4l2_dev); 5248c2ecf20Sopenharmony_ci if (ret < 0) 5258c2ecf20Sopenharmony_ci return ret; 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_ci /* Init mutex for core locking */ 5288c2ecf20Sopenharmony_ci mutex_init(&fmdev->mutex); 5298c2ecf20Sopenharmony_ci 5308c2ecf20Sopenharmony_ci /* Setup FM driver's V4L2 properties */ 5318c2ecf20Sopenharmony_ci gradio_dev = fm_viddev_template; 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci video_set_drvdata(&gradio_dev, fmdev); 5348c2ecf20Sopenharmony_ci 5358c2ecf20Sopenharmony_ci gradio_dev.lock = &fmdev->mutex; 5368c2ecf20Sopenharmony_ci gradio_dev.v4l2_dev = &fmdev->v4l2_dev; 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_ci /* Register with V4L2 subsystem as RADIO device */ 5398c2ecf20Sopenharmony_ci if (video_register_device(&gradio_dev, VFL_TYPE_RADIO, radio_nr)) { 5408c2ecf20Sopenharmony_ci v4l2_device_unregister(&fmdev->v4l2_dev); 5418c2ecf20Sopenharmony_ci fmerr("Could not register video device\n"); 5428c2ecf20Sopenharmony_ci return -ENOMEM; 5438c2ecf20Sopenharmony_ci } 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci fmdev->radio_dev = &gradio_dev; 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_ci /* Register to v4l2 ctrl handler framework */ 5488c2ecf20Sopenharmony_ci fmdev->radio_dev->ctrl_handler = &fmdev->ctrl_handler; 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci ret = v4l2_ctrl_handler_init(&fmdev->ctrl_handler, 5); 5518c2ecf20Sopenharmony_ci if (ret < 0) { 5528c2ecf20Sopenharmony_ci fmerr("(fmdev): Can't init ctrl handler\n"); 5538c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(&fmdev->ctrl_handler); 5548c2ecf20Sopenharmony_ci video_unregister_device(fmdev->radio_dev); 5558c2ecf20Sopenharmony_ci v4l2_device_unregister(&fmdev->v4l2_dev); 5568c2ecf20Sopenharmony_ci return -EBUSY; 5578c2ecf20Sopenharmony_ci } 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci /* 5608c2ecf20Sopenharmony_ci * Following controls are handled by V4L2 control framework. 5618c2ecf20Sopenharmony_ci * Added in ascending ID order. 5628c2ecf20Sopenharmony_ci */ 5638c2ecf20Sopenharmony_ci v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops, 5648c2ecf20Sopenharmony_ci V4L2_CID_AUDIO_VOLUME, FM_RX_VOLUME_MIN, 5658c2ecf20Sopenharmony_ci FM_RX_VOLUME_MAX, 1, FM_RX_VOLUME_MAX); 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_ci v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops, 5688c2ecf20Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci v4l2_ctrl_new_std_menu(&fmdev->ctrl_handler, &fm_ctrl_ops, 5718c2ecf20Sopenharmony_ci V4L2_CID_TUNE_PREEMPHASIS, V4L2_PREEMPHASIS_75_uS, 5728c2ecf20Sopenharmony_ci 0, V4L2_PREEMPHASIS_75_uS); 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_ci v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops, 5758c2ecf20Sopenharmony_ci V4L2_CID_TUNE_POWER_LEVEL, FM_PWR_LVL_LOW, 5768c2ecf20Sopenharmony_ci FM_PWR_LVL_HIGH, 1, FM_PWR_LVL_HIGH); 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci ctrl = v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops, 5798c2ecf20Sopenharmony_ci V4L2_CID_TUNE_ANTENNA_CAPACITOR, 0, 5808c2ecf20Sopenharmony_ci 255, 1, 255); 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_ci if (ctrl) 5838c2ecf20Sopenharmony_ci ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci return 0; 5868c2ecf20Sopenharmony_ci} 5878c2ecf20Sopenharmony_ci 5888c2ecf20Sopenharmony_civoid *fm_v4l2_deinit_video_device(void) 5898c2ecf20Sopenharmony_ci{ 5908c2ecf20Sopenharmony_ci struct fmdev *fmdev; 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ci fmdev = video_get_drvdata(&gradio_dev); 5948c2ecf20Sopenharmony_ci 5958c2ecf20Sopenharmony_ci /* Unregister to v4l2 ctrl handler framework*/ 5968c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(&fmdev->ctrl_handler); 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_ci /* Unregister RADIO device from V4L2 subsystem */ 5998c2ecf20Sopenharmony_ci video_unregister_device(&gradio_dev); 6008c2ecf20Sopenharmony_ci 6018c2ecf20Sopenharmony_ci v4l2_device_unregister(&fmdev->v4l2_dev); 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci return fmdev; 6048c2ecf20Sopenharmony_ci} 605