162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * FM Driver for Connectivity chip of Texas Instruments. 462306a36Sopenharmony_ci * This file provides interfaces to V4L2 subsystem. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * This module registers with V4L2 subsystem as Radio 762306a36Sopenharmony_ci * data system interface (/dev/radio). During the registration, 862306a36Sopenharmony_ci * it will expose two set of function pointers. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * 1) File operation related API (open, close, read, write, poll...etc). 1162306a36Sopenharmony_ci * 2) Set of V4L2 IOCTL complaint API. 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * Copyright (C) 2011 Texas Instruments 1462306a36Sopenharmony_ci * Author: Raja Mani <raja_mani@ti.com> 1562306a36Sopenharmony_ci * Author: Manjunatha Halli <manjunatha_halli@ti.com> 1662306a36Sopenharmony_ci */ 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <linux/export.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include "fmdrv.h" 2162306a36Sopenharmony_ci#include "fmdrv_v4l2.h" 2262306a36Sopenharmony_ci#include "fmdrv_common.h" 2362306a36Sopenharmony_ci#include "fmdrv_rx.h" 2462306a36Sopenharmony_ci#include "fmdrv_tx.h" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic struct video_device gradio_dev; 2762306a36Sopenharmony_cistatic u8 radio_disconnected; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* -- V4L2 RADIO (/dev/radioX) device file operation interfaces --- */ 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/* Read RX RDS data */ 3262306a36Sopenharmony_cistatic ssize_t fm_v4l2_fops_read(struct file *file, char __user * buf, 3362306a36Sopenharmony_ci size_t count, loff_t *ppos) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci u8 rds_mode; 3662306a36Sopenharmony_ci int ret; 3762306a36Sopenharmony_ci struct fmdev *fmdev; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci fmdev = video_drvdata(file); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci if (!radio_disconnected) { 4262306a36Sopenharmony_ci fmerr("FM device is already disconnected\n"); 4362306a36Sopenharmony_ci return -EIO; 4462306a36Sopenharmony_ci } 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci if (mutex_lock_interruptible(&fmdev->mutex)) 4762306a36Sopenharmony_ci return -ERESTARTSYS; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci /* Turn on RDS mode if it is disabled */ 5062306a36Sopenharmony_ci ret = fm_rx_get_rds_mode(fmdev, &rds_mode); 5162306a36Sopenharmony_ci if (ret < 0) { 5262306a36Sopenharmony_ci fmerr("Unable to read current rds mode\n"); 5362306a36Sopenharmony_ci goto read_unlock; 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci if (rds_mode == FM_RDS_DISABLE) { 5762306a36Sopenharmony_ci ret = fmc_set_rds_mode(fmdev, FM_RDS_ENABLE); 5862306a36Sopenharmony_ci if (ret < 0) { 5962306a36Sopenharmony_ci fmerr("Failed to enable rds mode\n"); 6062306a36Sopenharmony_ci goto read_unlock; 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci /* Copy RDS data from internal buffer to user buffer */ 6562306a36Sopenharmony_ci ret = fmc_transfer_rds_from_internal_buff(fmdev, file, buf, count); 6662306a36Sopenharmony_ciread_unlock: 6762306a36Sopenharmony_ci mutex_unlock(&fmdev->mutex); 6862306a36Sopenharmony_ci return ret; 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci/* Write TX RDS data */ 7262306a36Sopenharmony_cistatic ssize_t fm_v4l2_fops_write(struct file *file, const char __user * buf, 7362306a36Sopenharmony_ci size_t count, loff_t *ppos) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci struct tx_rds rds; 7662306a36Sopenharmony_ci int ret; 7762306a36Sopenharmony_ci struct fmdev *fmdev; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci ret = copy_from_user(&rds, buf, sizeof(rds)); 8062306a36Sopenharmony_ci rds.text[sizeof(rds.text) - 1] = '\0'; 8162306a36Sopenharmony_ci fmdbg("(%d)type: %d, text %s, af %d\n", 8262306a36Sopenharmony_ci ret, rds.text_type, rds.text, rds.af_freq); 8362306a36Sopenharmony_ci if (ret) 8462306a36Sopenharmony_ci return -EFAULT; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci fmdev = video_drvdata(file); 8762306a36Sopenharmony_ci if (mutex_lock_interruptible(&fmdev->mutex)) 8862306a36Sopenharmony_ci return -ERESTARTSYS; 8962306a36Sopenharmony_ci fm_tx_set_radio_text(fmdev, rds.text, rds.text_type); 9062306a36Sopenharmony_ci fm_tx_set_af(fmdev, rds.af_freq); 9162306a36Sopenharmony_ci mutex_unlock(&fmdev->mutex); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci return sizeof(rds); 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic __poll_t fm_v4l2_fops_poll(struct file *file, struct poll_table_struct *pts) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci int ret; 9962306a36Sopenharmony_ci struct fmdev *fmdev; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci fmdev = video_drvdata(file); 10262306a36Sopenharmony_ci mutex_lock(&fmdev->mutex); 10362306a36Sopenharmony_ci ret = fmc_is_rds_data_available(fmdev, file, pts); 10462306a36Sopenharmony_ci mutex_unlock(&fmdev->mutex); 10562306a36Sopenharmony_ci if (ret < 0) 10662306a36Sopenharmony_ci return EPOLLIN | EPOLLRDNORM; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci return 0; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci/* 11262306a36Sopenharmony_ci * Handle open request for "/dev/radioX" device. 11362306a36Sopenharmony_ci * Start with FM RX mode as default. 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_cistatic int fm_v4l2_fops_open(struct file *file) 11662306a36Sopenharmony_ci{ 11762306a36Sopenharmony_ci int ret; 11862306a36Sopenharmony_ci struct fmdev *fmdev = NULL; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci /* Don't allow multiple open */ 12162306a36Sopenharmony_ci if (radio_disconnected) { 12262306a36Sopenharmony_ci fmerr("FM device is already opened\n"); 12362306a36Sopenharmony_ci return -EBUSY; 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci fmdev = video_drvdata(file); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (mutex_lock_interruptible(&fmdev->mutex)) 12962306a36Sopenharmony_ci return -ERESTARTSYS; 13062306a36Sopenharmony_ci ret = fmc_prepare(fmdev); 13162306a36Sopenharmony_ci if (ret < 0) { 13262306a36Sopenharmony_ci fmerr("Unable to prepare FM CORE\n"); 13362306a36Sopenharmony_ci goto open_unlock; 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci fmdbg("Load FM RX firmware..\n"); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci ret = fmc_set_mode(fmdev, FM_MODE_RX); 13962306a36Sopenharmony_ci if (ret < 0) { 14062306a36Sopenharmony_ci fmerr("Unable to load FM RX firmware\n"); 14162306a36Sopenharmony_ci goto open_unlock; 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci radio_disconnected = 1; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ciopen_unlock: 14662306a36Sopenharmony_ci mutex_unlock(&fmdev->mutex); 14762306a36Sopenharmony_ci return ret; 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic int fm_v4l2_fops_release(struct file *file) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci int ret; 15362306a36Sopenharmony_ci struct fmdev *fmdev; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci fmdev = video_drvdata(file); 15662306a36Sopenharmony_ci if (!radio_disconnected) { 15762306a36Sopenharmony_ci fmdbg("FM device is already closed\n"); 15862306a36Sopenharmony_ci return 0; 15962306a36Sopenharmony_ci } 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci mutex_lock(&fmdev->mutex); 16262306a36Sopenharmony_ci ret = fmc_set_mode(fmdev, FM_MODE_OFF); 16362306a36Sopenharmony_ci if (ret < 0) { 16462306a36Sopenharmony_ci fmerr("Unable to turn off the chip\n"); 16562306a36Sopenharmony_ci goto release_unlock; 16662306a36Sopenharmony_ci } 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci ret = fmc_release(fmdev); 16962306a36Sopenharmony_ci if (ret < 0) { 17062306a36Sopenharmony_ci fmerr("FM CORE release failed\n"); 17162306a36Sopenharmony_ci goto release_unlock; 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci radio_disconnected = 0; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cirelease_unlock: 17662306a36Sopenharmony_ci mutex_unlock(&fmdev->mutex); 17762306a36Sopenharmony_ci return ret; 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci/* V4L2 RADIO (/dev/radioX) device IOCTL interfaces */ 18162306a36Sopenharmony_cistatic int fm_v4l2_vidioc_querycap(struct file *file, void *priv, 18262306a36Sopenharmony_ci struct v4l2_capability *capability) 18362306a36Sopenharmony_ci{ 18462306a36Sopenharmony_ci strscpy(capability->driver, FM_DRV_NAME, sizeof(capability->driver)); 18562306a36Sopenharmony_ci strscpy(capability->card, FM_DRV_CARD_SHORT_NAME, 18662306a36Sopenharmony_ci sizeof(capability->card)); 18762306a36Sopenharmony_ci sprintf(capability->bus_info, "UART"); 18862306a36Sopenharmony_ci return 0; 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic int fm_g_volatile_ctrl(struct v4l2_ctrl *ctrl) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci struct fmdev *fmdev = container_of(ctrl->handler, 19462306a36Sopenharmony_ci struct fmdev, ctrl_handler); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci switch (ctrl->id) { 19762306a36Sopenharmony_ci case V4L2_CID_TUNE_ANTENNA_CAPACITOR: 19862306a36Sopenharmony_ci ctrl->val = fm_tx_get_tune_cap_val(fmdev); 19962306a36Sopenharmony_ci break; 20062306a36Sopenharmony_ci default: 20162306a36Sopenharmony_ci fmwarn("%s: Unknown IOCTL: %d\n", __func__, ctrl->id); 20262306a36Sopenharmony_ci break; 20362306a36Sopenharmony_ci } 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci return 0; 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic int fm_v4l2_s_ctrl(struct v4l2_ctrl *ctrl) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci struct fmdev *fmdev = container_of(ctrl->handler, 21162306a36Sopenharmony_ci struct fmdev, ctrl_handler); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci switch (ctrl->id) { 21462306a36Sopenharmony_ci case V4L2_CID_AUDIO_VOLUME: /* set volume */ 21562306a36Sopenharmony_ci return fm_rx_set_volume(fmdev, (u16)ctrl->val); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: /* set mute */ 21862306a36Sopenharmony_ci return fmc_set_mute_mode(fmdev, (u8)ctrl->val); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci case V4L2_CID_TUNE_POWER_LEVEL: 22162306a36Sopenharmony_ci /* set TX power level - ext control */ 22262306a36Sopenharmony_ci return fm_tx_set_pwr_lvl(fmdev, (u8)ctrl->val); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci case V4L2_CID_TUNE_PREEMPHASIS: 22562306a36Sopenharmony_ci return fm_tx_set_preemph_filter(fmdev, (u8) ctrl->val); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci default: 22862306a36Sopenharmony_ci return -EINVAL; 22962306a36Sopenharmony_ci } 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic int fm_v4l2_vidioc_g_audio(struct file *file, void *priv, 23362306a36Sopenharmony_ci struct v4l2_audio *audio) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci memset(audio, 0, sizeof(*audio)); 23662306a36Sopenharmony_ci strscpy(audio->name, "Radio", sizeof(audio->name)); 23762306a36Sopenharmony_ci audio->capability = V4L2_AUDCAP_STEREO; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci return 0; 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cistatic int fm_v4l2_vidioc_s_audio(struct file *file, void *priv, 24362306a36Sopenharmony_ci const struct v4l2_audio *audio) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci if (audio->index != 0) 24662306a36Sopenharmony_ci return -EINVAL; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci return 0; 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci/* Get tuner attributes. If current mode is NOT RX, return error */ 25262306a36Sopenharmony_cistatic int fm_v4l2_vidioc_g_tuner(struct file *file, void *priv, 25362306a36Sopenharmony_ci struct v4l2_tuner *tuner) 25462306a36Sopenharmony_ci{ 25562306a36Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 25662306a36Sopenharmony_ci u32 bottom_freq; 25762306a36Sopenharmony_ci u32 top_freq; 25862306a36Sopenharmony_ci u16 stereo_mono_mode; 25962306a36Sopenharmony_ci u16 rssilvl; 26062306a36Sopenharmony_ci int ret; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci if (tuner->index != 0) 26362306a36Sopenharmony_ci return -EINVAL; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci if (fmdev->curr_fmmode != FM_MODE_RX) 26662306a36Sopenharmony_ci return -EPERM; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci ret = fm_rx_get_band_freq_range(fmdev, &bottom_freq, &top_freq); 26962306a36Sopenharmony_ci if (ret != 0) 27062306a36Sopenharmony_ci return ret; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci ret = fm_rx_get_stereo_mono(fmdev, &stereo_mono_mode); 27362306a36Sopenharmony_ci if (ret != 0) 27462306a36Sopenharmony_ci return ret; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci ret = fm_rx_get_rssi_level(fmdev, &rssilvl); 27762306a36Sopenharmony_ci if (ret != 0) 27862306a36Sopenharmony_ci return ret; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci strscpy(tuner->name, "FM", sizeof(tuner->name)); 28162306a36Sopenharmony_ci tuner->type = V4L2_TUNER_RADIO; 28262306a36Sopenharmony_ci /* Store rangelow and rangehigh freq in unit of 62.5 Hz */ 28362306a36Sopenharmony_ci tuner->rangelow = bottom_freq * 16; 28462306a36Sopenharmony_ci tuner->rangehigh = top_freq * 16; 28562306a36Sopenharmony_ci tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO | 28662306a36Sopenharmony_ci ((fmdev->rx.rds.flag == FM_RDS_ENABLE) ? V4L2_TUNER_SUB_RDS : 0); 28762306a36Sopenharmony_ci tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | 28862306a36Sopenharmony_ci V4L2_TUNER_CAP_LOW | 28962306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 29062306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_WRAP; 29162306a36Sopenharmony_ci tuner->audmode = (stereo_mono_mode ? 29262306a36Sopenharmony_ci V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci /* 29562306a36Sopenharmony_ci * Actual rssi value lies in between -128 to +127. 29662306a36Sopenharmony_ci * Convert this range from 0 to 255 by adding +128 29762306a36Sopenharmony_ci */ 29862306a36Sopenharmony_ci rssilvl += 128; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci /* 30162306a36Sopenharmony_ci * Return signal strength value should be within 0 to 65535. 30262306a36Sopenharmony_ci * Find out correct signal radio by multiplying (65535/255) = 257 30362306a36Sopenharmony_ci */ 30462306a36Sopenharmony_ci tuner->signal = rssilvl * 257; 30562306a36Sopenharmony_ci tuner->afc = 0; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci return ret; 30862306a36Sopenharmony_ci} 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci/* 31162306a36Sopenharmony_ci * Set tuner attributes. If current mode is NOT RX, set to RX. 31262306a36Sopenharmony_ci * Currently, we set only audio mode (mono/stereo) and RDS state (on/off). 31362306a36Sopenharmony_ci * Should we set other tuner attributes, too? 31462306a36Sopenharmony_ci */ 31562306a36Sopenharmony_cistatic int fm_v4l2_vidioc_s_tuner(struct file *file, void *priv, 31662306a36Sopenharmony_ci const struct v4l2_tuner *tuner) 31762306a36Sopenharmony_ci{ 31862306a36Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 31962306a36Sopenharmony_ci u16 aud_mode; 32062306a36Sopenharmony_ci u8 rds_mode; 32162306a36Sopenharmony_ci int ret; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci if (tuner->index != 0) 32462306a36Sopenharmony_ci return -EINVAL; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci aud_mode = (tuner->audmode == V4L2_TUNER_MODE_STEREO) ? 32762306a36Sopenharmony_ci FM_STEREO_MODE : FM_MONO_MODE; 32862306a36Sopenharmony_ci rds_mode = (tuner->rxsubchans & V4L2_TUNER_SUB_RDS) ? 32962306a36Sopenharmony_ci FM_RDS_ENABLE : FM_RDS_DISABLE; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci if (fmdev->curr_fmmode != FM_MODE_RX) { 33262306a36Sopenharmony_ci ret = fmc_set_mode(fmdev, FM_MODE_RX); 33362306a36Sopenharmony_ci if (ret < 0) { 33462306a36Sopenharmony_ci fmerr("Failed to set RX mode\n"); 33562306a36Sopenharmony_ci return ret; 33662306a36Sopenharmony_ci } 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci ret = fmc_set_stereo_mono(fmdev, aud_mode); 34062306a36Sopenharmony_ci if (ret < 0) { 34162306a36Sopenharmony_ci fmerr("Failed to set RX stereo/mono mode\n"); 34262306a36Sopenharmony_ci return ret; 34362306a36Sopenharmony_ci } 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci ret = fmc_set_rds_mode(fmdev, rds_mode); 34662306a36Sopenharmony_ci if (ret < 0) 34762306a36Sopenharmony_ci fmerr("Failed to set RX RDS mode\n"); 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci return ret; 35062306a36Sopenharmony_ci} 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci/* Get tuner or modulator radio frequency */ 35362306a36Sopenharmony_cistatic int fm_v4l2_vidioc_g_freq(struct file *file, void *priv, 35462306a36Sopenharmony_ci struct v4l2_frequency *freq) 35562306a36Sopenharmony_ci{ 35662306a36Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 35762306a36Sopenharmony_ci int ret; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci ret = fmc_get_freq(fmdev, &freq->frequency); 36062306a36Sopenharmony_ci if (ret < 0) { 36162306a36Sopenharmony_ci fmerr("Failed to get frequency\n"); 36262306a36Sopenharmony_ci return ret; 36362306a36Sopenharmony_ci } 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci /* Frequency unit of 62.5 Hz*/ 36662306a36Sopenharmony_ci freq->frequency = (u32) freq->frequency * 16; 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci return 0; 36962306a36Sopenharmony_ci} 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci/* Set tuner or modulator radio frequency */ 37262306a36Sopenharmony_cistatic int fm_v4l2_vidioc_s_freq(struct file *file, void *priv, 37362306a36Sopenharmony_ci const struct v4l2_frequency *freq) 37462306a36Sopenharmony_ci{ 37562306a36Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci /* 37862306a36Sopenharmony_ci * As V4L2_TUNER_CAP_LOW is set 1 user sends the frequency 37962306a36Sopenharmony_ci * in units of 62.5 Hz. 38062306a36Sopenharmony_ci */ 38162306a36Sopenharmony_ci return fmc_set_freq(fmdev, freq->frequency / 16); 38262306a36Sopenharmony_ci} 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci/* Set hardware frequency seek. If current mode is NOT RX, set it RX. */ 38562306a36Sopenharmony_cistatic int fm_v4l2_vidioc_s_hw_freq_seek(struct file *file, void *priv, 38662306a36Sopenharmony_ci const struct v4l2_hw_freq_seek *seek) 38762306a36Sopenharmony_ci{ 38862306a36Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 38962306a36Sopenharmony_ci int ret; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 39262306a36Sopenharmony_ci return -EWOULDBLOCK; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci if (fmdev->curr_fmmode != FM_MODE_RX) { 39562306a36Sopenharmony_ci ret = fmc_set_mode(fmdev, FM_MODE_RX); 39662306a36Sopenharmony_ci if (ret != 0) { 39762306a36Sopenharmony_ci fmerr("Failed to set RX mode\n"); 39862306a36Sopenharmony_ci return ret; 39962306a36Sopenharmony_ci } 40062306a36Sopenharmony_ci } 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci ret = fm_rx_seek(fmdev, seek->seek_upward, seek->wrap_around, 40362306a36Sopenharmony_ci seek->spacing); 40462306a36Sopenharmony_ci if (ret < 0) 40562306a36Sopenharmony_ci fmerr("RX seek failed - %d\n", ret); 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci return ret; 40862306a36Sopenharmony_ci} 40962306a36Sopenharmony_ci/* Get modulator attributes. If mode is not TX, return no attributes. */ 41062306a36Sopenharmony_cistatic int fm_v4l2_vidioc_g_modulator(struct file *file, void *priv, 41162306a36Sopenharmony_ci struct v4l2_modulator *mod) 41262306a36Sopenharmony_ci{ 41362306a36Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci if (mod->index != 0) 41662306a36Sopenharmony_ci return -EINVAL; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci if (fmdev->curr_fmmode != FM_MODE_TX) 41962306a36Sopenharmony_ci return -EPERM; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci mod->txsubchans = ((fmdev->tx_data.aud_mode == FM_STEREO_MODE) ? 42262306a36Sopenharmony_ci V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO) | 42362306a36Sopenharmony_ci ((fmdev->tx_data.rds.flag == FM_RDS_ENABLE) ? 42462306a36Sopenharmony_ci V4L2_TUNER_SUB_RDS : 0); 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci mod->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | 42762306a36Sopenharmony_ci V4L2_TUNER_CAP_LOW; 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci return 0; 43062306a36Sopenharmony_ci} 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci/* Set modulator attributes. If mode is not TX, set to TX. */ 43362306a36Sopenharmony_cistatic int fm_v4l2_vidioc_s_modulator(struct file *file, void *priv, 43462306a36Sopenharmony_ci const struct v4l2_modulator *mod) 43562306a36Sopenharmony_ci{ 43662306a36Sopenharmony_ci struct fmdev *fmdev = video_drvdata(file); 43762306a36Sopenharmony_ci u8 rds_mode; 43862306a36Sopenharmony_ci u16 aud_mode; 43962306a36Sopenharmony_ci int ret; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci if (mod->index != 0) 44262306a36Sopenharmony_ci return -EINVAL; 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci if (fmdev->curr_fmmode != FM_MODE_TX) { 44562306a36Sopenharmony_ci ret = fmc_set_mode(fmdev, FM_MODE_TX); 44662306a36Sopenharmony_ci if (ret != 0) { 44762306a36Sopenharmony_ci fmerr("Failed to set TX mode\n"); 44862306a36Sopenharmony_ci return ret; 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci } 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci aud_mode = (mod->txsubchans & V4L2_TUNER_SUB_STEREO) ? 45362306a36Sopenharmony_ci FM_STEREO_MODE : FM_MONO_MODE; 45462306a36Sopenharmony_ci rds_mode = (mod->txsubchans & V4L2_TUNER_SUB_RDS) ? 45562306a36Sopenharmony_ci FM_RDS_ENABLE : FM_RDS_DISABLE; 45662306a36Sopenharmony_ci ret = fm_tx_set_stereo_mono(fmdev, aud_mode); 45762306a36Sopenharmony_ci if (ret < 0) { 45862306a36Sopenharmony_ci fmerr("Failed to set mono/stereo mode for TX\n"); 45962306a36Sopenharmony_ci return ret; 46062306a36Sopenharmony_ci } 46162306a36Sopenharmony_ci ret = fm_tx_set_rds_mode(fmdev, rds_mode); 46262306a36Sopenharmony_ci if (ret < 0) 46362306a36Sopenharmony_ci fmerr("Failed to set rds mode for TX\n"); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci return ret; 46662306a36Sopenharmony_ci} 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_cistatic const struct v4l2_file_operations fm_drv_fops = { 46962306a36Sopenharmony_ci .owner = THIS_MODULE, 47062306a36Sopenharmony_ci .read = fm_v4l2_fops_read, 47162306a36Sopenharmony_ci .write = fm_v4l2_fops_write, 47262306a36Sopenharmony_ci .poll = fm_v4l2_fops_poll, 47362306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 47462306a36Sopenharmony_ci .open = fm_v4l2_fops_open, 47562306a36Sopenharmony_ci .release = fm_v4l2_fops_release, 47662306a36Sopenharmony_ci}; 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops fm_ctrl_ops = { 47962306a36Sopenharmony_ci .s_ctrl = fm_v4l2_s_ctrl, 48062306a36Sopenharmony_ci .g_volatile_ctrl = fm_g_volatile_ctrl, 48162306a36Sopenharmony_ci}; 48262306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops fm_drv_ioctl_ops = { 48362306a36Sopenharmony_ci .vidioc_querycap = fm_v4l2_vidioc_querycap, 48462306a36Sopenharmony_ci .vidioc_g_audio = fm_v4l2_vidioc_g_audio, 48562306a36Sopenharmony_ci .vidioc_s_audio = fm_v4l2_vidioc_s_audio, 48662306a36Sopenharmony_ci .vidioc_g_tuner = fm_v4l2_vidioc_g_tuner, 48762306a36Sopenharmony_ci .vidioc_s_tuner = fm_v4l2_vidioc_s_tuner, 48862306a36Sopenharmony_ci .vidioc_g_frequency = fm_v4l2_vidioc_g_freq, 48962306a36Sopenharmony_ci .vidioc_s_frequency = fm_v4l2_vidioc_s_freq, 49062306a36Sopenharmony_ci .vidioc_s_hw_freq_seek = fm_v4l2_vidioc_s_hw_freq_seek, 49162306a36Sopenharmony_ci .vidioc_g_modulator = fm_v4l2_vidioc_g_modulator, 49262306a36Sopenharmony_ci .vidioc_s_modulator = fm_v4l2_vidioc_s_modulator 49362306a36Sopenharmony_ci}; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci/* V4L2 RADIO device parent structure */ 49662306a36Sopenharmony_cistatic const struct video_device fm_viddev_template = { 49762306a36Sopenharmony_ci .fops = &fm_drv_fops, 49862306a36Sopenharmony_ci .ioctl_ops = &fm_drv_ioctl_ops, 49962306a36Sopenharmony_ci .name = FM_DRV_NAME, 50062306a36Sopenharmony_ci .release = video_device_release_empty, 50162306a36Sopenharmony_ci /* 50262306a36Sopenharmony_ci * To ensure both the tuner and modulator ioctls are accessible we 50362306a36Sopenharmony_ci * set the vfl_dir to M2M to indicate this. 50462306a36Sopenharmony_ci * 50562306a36Sopenharmony_ci * It is not really a mem2mem device of course, but it can both receive 50662306a36Sopenharmony_ci * and transmit using the same radio device. It's the only radio driver 50762306a36Sopenharmony_ci * that does this and it should really be split in two radio devices, 50862306a36Sopenharmony_ci * but that would affect applications using this driver. 50962306a36Sopenharmony_ci */ 51062306a36Sopenharmony_ci .vfl_dir = VFL_DIR_M2M, 51162306a36Sopenharmony_ci .device_caps = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_TUNER | V4L2_CAP_RADIO | 51262306a36Sopenharmony_ci V4L2_CAP_MODULATOR | V4L2_CAP_AUDIO | 51362306a36Sopenharmony_ci V4L2_CAP_READWRITE | V4L2_CAP_RDS_CAPTURE, 51462306a36Sopenharmony_ci}; 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ciint fm_v4l2_init_video_device(struct fmdev *fmdev, int radio_nr) 51762306a36Sopenharmony_ci{ 51862306a36Sopenharmony_ci struct v4l2_ctrl *ctrl; 51962306a36Sopenharmony_ci int ret; 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci strscpy(fmdev->v4l2_dev.name, FM_DRV_NAME, 52262306a36Sopenharmony_ci sizeof(fmdev->v4l2_dev.name)); 52362306a36Sopenharmony_ci ret = v4l2_device_register(NULL, &fmdev->v4l2_dev); 52462306a36Sopenharmony_ci if (ret < 0) 52562306a36Sopenharmony_ci return ret; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci /* Init mutex for core locking */ 52862306a36Sopenharmony_ci mutex_init(&fmdev->mutex); 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci /* Setup FM driver's V4L2 properties */ 53162306a36Sopenharmony_ci gradio_dev = fm_viddev_template; 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci video_set_drvdata(&gradio_dev, fmdev); 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci gradio_dev.lock = &fmdev->mutex; 53662306a36Sopenharmony_ci gradio_dev.v4l2_dev = &fmdev->v4l2_dev; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci /* Register with V4L2 subsystem as RADIO device */ 53962306a36Sopenharmony_ci if (video_register_device(&gradio_dev, VFL_TYPE_RADIO, radio_nr)) { 54062306a36Sopenharmony_ci v4l2_device_unregister(&fmdev->v4l2_dev); 54162306a36Sopenharmony_ci fmerr("Could not register video device\n"); 54262306a36Sopenharmony_ci return -ENOMEM; 54362306a36Sopenharmony_ci } 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci fmdev->radio_dev = &gradio_dev; 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci /* Register to v4l2 ctrl handler framework */ 54862306a36Sopenharmony_ci fmdev->radio_dev->ctrl_handler = &fmdev->ctrl_handler; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci ret = v4l2_ctrl_handler_init(&fmdev->ctrl_handler, 5); 55162306a36Sopenharmony_ci if (ret < 0) { 55262306a36Sopenharmony_ci fmerr("(fmdev): Can't init ctrl handler\n"); 55362306a36Sopenharmony_ci v4l2_ctrl_handler_free(&fmdev->ctrl_handler); 55462306a36Sopenharmony_ci video_unregister_device(fmdev->radio_dev); 55562306a36Sopenharmony_ci v4l2_device_unregister(&fmdev->v4l2_dev); 55662306a36Sopenharmony_ci return -EBUSY; 55762306a36Sopenharmony_ci } 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci /* 56062306a36Sopenharmony_ci * Following controls are handled by V4L2 control framework. 56162306a36Sopenharmony_ci * Added in ascending ID order. 56262306a36Sopenharmony_ci */ 56362306a36Sopenharmony_ci v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops, 56462306a36Sopenharmony_ci V4L2_CID_AUDIO_VOLUME, FM_RX_VOLUME_MIN, 56562306a36Sopenharmony_ci FM_RX_VOLUME_MAX, 1, FM_RX_VOLUME_MAX); 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops, 56862306a36Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci v4l2_ctrl_new_std_menu(&fmdev->ctrl_handler, &fm_ctrl_ops, 57162306a36Sopenharmony_ci V4L2_CID_TUNE_PREEMPHASIS, V4L2_PREEMPHASIS_75_uS, 57262306a36Sopenharmony_ci 0, V4L2_PREEMPHASIS_75_uS); 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops, 57562306a36Sopenharmony_ci V4L2_CID_TUNE_POWER_LEVEL, FM_PWR_LVL_LOW, 57662306a36Sopenharmony_ci FM_PWR_LVL_HIGH, 1, FM_PWR_LVL_HIGH); 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci ctrl = v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops, 57962306a36Sopenharmony_ci V4L2_CID_TUNE_ANTENNA_CAPACITOR, 0, 58062306a36Sopenharmony_ci 255, 1, 255); 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci if (ctrl) 58362306a36Sopenharmony_ci ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci return 0; 58662306a36Sopenharmony_ci} 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_civoid *fm_v4l2_deinit_video_device(void) 58962306a36Sopenharmony_ci{ 59062306a36Sopenharmony_ci struct fmdev *fmdev; 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci fmdev = video_get_drvdata(&gradio_dev); 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci /* Unregister to v4l2 ctrl handler framework*/ 59662306a36Sopenharmony_ci v4l2_ctrl_handler_free(&fmdev->ctrl_handler); 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci /* Unregister RADIO device from V4L2 subsystem */ 59962306a36Sopenharmony_ci video_unregister_device(&gradio_dev); 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci v4l2_device_unregister(&fmdev->v4l2_dev); 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_ci return fmdev; 60462306a36Sopenharmony_ci} 605