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