18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * The Virtual DVB test driver serves as a reference DVB driver and helps
48c2ecf20Sopenharmony_ci * validate the existing APIs in the media subsystem. It can also aid
58c2ecf20Sopenharmony_ci * developers working on userspace applications.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * The vidtv tuner should support common TV standards such as
88c2ecf20Sopenharmony_ci * DVB-T/T2/S/S2, ISDB-T and ATSC when completed.
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci * Copyright (C) 2020 Daniel W. S. Almeida
118c2ecf20Sopenharmony_ci */
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/errno.h>
148c2ecf20Sopenharmony_ci#include <linux/i2c.h>
158c2ecf20Sopenharmony_ci#include <linux/module.h>
168c2ecf20Sopenharmony_ci#include <linux/printk.h>
178c2ecf20Sopenharmony_ci#include <linux/ratelimit.h>
188c2ecf20Sopenharmony_ci#include <linux/slab.h>
198c2ecf20Sopenharmony_ci#include <linux/types.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <media/dvb_frontend.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#include "vidtv_tuner.h"
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistruct vidtv_tuner_cnr_to_qual_s {
268c2ecf20Sopenharmony_ci	/* attempt to use the same values as libdvbv5 */
278c2ecf20Sopenharmony_ci	u32 modulation;
288c2ecf20Sopenharmony_ci	u32 fec;
298c2ecf20Sopenharmony_ci	u32 cnr_ok;
308c2ecf20Sopenharmony_ci	u32 cnr_good;
318c2ecf20Sopenharmony_ci};
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_c_cnr_2_qual[] = {
348c2ecf20Sopenharmony_ci	/* from libdvbv5 source code, in milli db */
358c2ecf20Sopenharmony_ci	{ QAM_256, FEC_NONE,  34000, 38000},
368c2ecf20Sopenharmony_ci	{ QAM_64,  FEC_NONE,  30000, 34000},
378c2ecf20Sopenharmony_ci};
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s_cnr_2_qual[] = {
408c2ecf20Sopenharmony_ci	/* from libdvbv5 source code, in milli db */
418c2ecf20Sopenharmony_ci	{ QPSK, FEC_1_2,  7000, 10000},
428c2ecf20Sopenharmony_ci	{ QPSK, FEC_2_3,  9000, 12000},
438c2ecf20Sopenharmony_ci	{ QPSK, FEC_3_4, 10000, 13000},
448c2ecf20Sopenharmony_ci	{ QPSK, FEC_5_6, 11000, 14000},
458c2ecf20Sopenharmony_ci	{ QPSK, FEC_7_8, 12000, 15000},
468c2ecf20Sopenharmony_ci};
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s2_cnr_2_qual[] = {
498c2ecf20Sopenharmony_ci	/* from libdvbv5 source code, in milli db */
508c2ecf20Sopenharmony_ci	{ QPSK,  FEC_1_2,   9000,  12000},
518c2ecf20Sopenharmony_ci	{ QPSK,  FEC_2_3,  11000,  14000},
528c2ecf20Sopenharmony_ci	{ QPSK,  FEC_3_4,  12000,  15000},
538c2ecf20Sopenharmony_ci	{ QPSK,  FEC_5_6,  12000,  15000},
548c2ecf20Sopenharmony_ci	{ QPSK,  FEC_8_9,  13000,  16000},
558c2ecf20Sopenharmony_ci	{ QPSK,  FEC_9_10, 13500,  16500},
568c2ecf20Sopenharmony_ci	{ PSK_8, FEC_2_3,  14500,  17500},
578c2ecf20Sopenharmony_ci	{ PSK_8, FEC_3_4,  16000,  19000},
588c2ecf20Sopenharmony_ci	{ PSK_8, FEC_5_6,  17500,  20500},
598c2ecf20Sopenharmony_ci	{ PSK_8, FEC_8_9,  19000,  22000},
608c2ecf20Sopenharmony_ci};
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistatic const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_t_cnr_2_qual[] = {
638c2ecf20Sopenharmony_ci	/* from libdvbv5 source code, in milli db*/
648c2ecf20Sopenharmony_ci	{   QPSK, FEC_1_2,  4100,  5900},
658c2ecf20Sopenharmony_ci	{   QPSK, FEC_2_3,  6100,  9600},
668c2ecf20Sopenharmony_ci	{   QPSK, FEC_3_4,  7200, 12400},
678c2ecf20Sopenharmony_ci	{   QPSK, FEC_5_6,  8500, 15600},
688c2ecf20Sopenharmony_ci	{   QPSK, FEC_7_8,  9200, 17500},
698c2ecf20Sopenharmony_ci	{ QAM_16, FEC_1_2,  9800, 11800},
708c2ecf20Sopenharmony_ci	{ QAM_16, FEC_2_3, 12100, 15300},
718c2ecf20Sopenharmony_ci	{ QAM_16, FEC_3_4, 13400, 18100},
728c2ecf20Sopenharmony_ci	{ QAM_16, FEC_5_6, 14800, 21300},
738c2ecf20Sopenharmony_ci	{ QAM_16, FEC_7_8, 15700, 23600},
748c2ecf20Sopenharmony_ci	{ QAM_64, FEC_1_2, 14000, 16000},
758c2ecf20Sopenharmony_ci	{ QAM_64, FEC_2_3, 19900, 25400},
768c2ecf20Sopenharmony_ci	{ QAM_64, FEC_3_4, 24900, 27900},
778c2ecf20Sopenharmony_ci	{ QAM_64, FEC_5_6, 21300, 23300},
788c2ecf20Sopenharmony_ci	{ QAM_64, FEC_7_8, 22000, 24000},
798c2ecf20Sopenharmony_ci};
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci/**
828c2ecf20Sopenharmony_ci * struct vidtv_tuner_hardware_state - Simulate the tuner hardware status
838c2ecf20Sopenharmony_ci * @asleep: whether the tuner is asleep, i.e whether _sleep() or _suspend() was
848c2ecf20Sopenharmony_ci * called.
858c2ecf20Sopenharmony_ci * @lock_status: Whether the tuner has managed to lock on the requested
868c2ecf20Sopenharmony_ci * frequency.
878c2ecf20Sopenharmony_ci * @if_frequency: The tuner's intermediate frequency. Hardcoded for the purposes
888c2ecf20Sopenharmony_ci * of simulation.
898c2ecf20Sopenharmony_ci * @tuned_frequency: The actual tuned frequency.
908c2ecf20Sopenharmony_ci * @bandwidth: The actual bandwidth.
918c2ecf20Sopenharmony_ci *
928c2ecf20Sopenharmony_ci * This structure is meant to simulate the status of the tuner hardware, as if
938c2ecf20Sopenharmony_ci * we had a physical tuner hardware.
948c2ecf20Sopenharmony_ci */
958c2ecf20Sopenharmony_cistruct vidtv_tuner_hardware_state {
968c2ecf20Sopenharmony_ci	bool asleep;
978c2ecf20Sopenharmony_ci	u32 lock_status;
988c2ecf20Sopenharmony_ci	u32 if_frequency;
998c2ecf20Sopenharmony_ci	u32 tuned_frequency;
1008c2ecf20Sopenharmony_ci	u32 bandwidth;
1018c2ecf20Sopenharmony_ci};
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci/**
1048c2ecf20Sopenharmony_ci * struct vidtv_tuner_dev - The tuner struct
1058c2ecf20Sopenharmony_ci * @fe: A pointer to the dvb_frontend structure allocated by vidtv_demod
1068c2ecf20Sopenharmony_ci * @hw_state: A struct to simulate the tuner's hardware state as if we had a
1078c2ecf20Sopenharmony_ci * physical tuner hardware.
1088c2ecf20Sopenharmony_ci * @config: The configuration used to start the tuner module, usually filled
1098c2ecf20Sopenharmony_ci * by a bridge driver. For vidtv, this is filled by vidtv_bridge before the
1108c2ecf20Sopenharmony_ci * tuner module is probed.
1118c2ecf20Sopenharmony_ci */
1128c2ecf20Sopenharmony_cistruct vidtv_tuner_dev {
1138c2ecf20Sopenharmony_ci	struct dvb_frontend *fe;
1148c2ecf20Sopenharmony_ci	struct vidtv_tuner_hardware_state hw_state;
1158c2ecf20Sopenharmony_ci	struct vidtv_tuner_config config;
1168c2ecf20Sopenharmony_ci};
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_cistatic struct vidtv_tuner_dev*
1198c2ecf20Sopenharmony_cividtv_tuner_get_dev(struct dvb_frontend *fe)
1208c2ecf20Sopenharmony_ci{
1218c2ecf20Sopenharmony_ci	return i2c_get_clientdata(fe->tuner_priv);
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_cistatic int vidtv_tuner_check_frequency_shift(struct dvb_frontend *fe)
1258c2ecf20Sopenharmony_ci{
1268c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
1278c2ecf20Sopenharmony_ci	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
1288c2ecf20Sopenharmony_ci	struct vidtv_tuner_config config  = tuner_dev->config;
1298c2ecf20Sopenharmony_ci	u32 *valid_freqs = NULL;
1308c2ecf20Sopenharmony_ci	u32 array_sz = 0;
1318c2ecf20Sopenharmony_ci	u32 i;
1328c2ecf20Sopenharmony_ci	u32 shift;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	switch (c->delivery_system) {
1358c2ecf20Sopenharmony_ci	case SYS_DVBT:
1368c2ecf20Sopenharmony_ci	case SYS_DVBT2:
1378c2ecf20Sopenharmony_ci		valid_freqs = config.vidtv_valid_dvb_t_freqs;
1388c2ecf20Sopenharmony_ci		array_sz    = ARRAY_SIZE(config.vidtv_valid_dvb_t_freqs);
1398c2ecf20Sopenharmony_ci		break;
1408c2ecf20Sopenharmony_ci	case SYS_DVBS:
1418c2ecf20Sopenharmony_ci	case SYS_DVBS2:
1428c2ecf20Sopenharmony_ci		valid_freqs = config.vidtv_valid_dvb_s_freqs;
1438c2ecf20Sopenharmony_ci		array_sz    = ARRAY_SIZE(config.vidtv_valid_dvb_s_freqs);
1448c2ecf20Sopenharmony_ci		break;
1458c2ecf20Sopenharmony_ci	case SYS_DVBC_ANNEX_A:
1468c2ecf20Sopenharmony_ci		valid_freqs = config.vidtv_valid_dvb_c_freqs;
1478c2ecf20Sopenharmony_ci		array_sz    = ARRAY_SIZE(config.vidtv_valid_dvb_c_freqs);
1488c2ecf20Sopenharmony_ci		break;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	default:
1518c2ecf20Sopenharmony_ci		dev_warn(fe->dvb->device,
1528c2ecf20Sopenharmony_ci			 "%s: unsupported delivery system: %u\n",
1538c2ecf20Sopenharmony_ci			 __func__,
1548c2ecf20Sopenharmony_ci			 c->delivery_system);
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci		return -EINVAL;
1578c2ecf20Sopenharmony_ci	}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	for (i = 0; i < array_sz; i++) {
1608c2ecf20Sopenharmony_ci		if (!valid_freqs[i])
1618c2ecf20Sopenharmony_ci			break;
1628c2ecf20Sopenharmony_ci		shift = abs(c->frequency - valid_freqs[i]);
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci		if (!shift)
1658c2ecf20Sopenharmony_ci			return 0;
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci		/*
1688c2ecf20Sopenharmony_ci		 * This will provide a value from 0 to 100 that would
1698c2ecf20Sopenharmony_ci		 * indicate how far is the tuned frequency from the
1708c2ecf20Sopenharmony_ci		 * right one.
1718c2ecf20Sopenharmony_ci		 */
1728c2ecf20Sopenharmony_ci		if (shift < config.max_frequency_shift_hz)
1738c2ecf20Sopenharmony_ci			return shift * 100 / config.max_frequency_shift_hz;
1748c2ecf20Sopenharmony_ci	}
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	return -EINVAL;
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_cistatic int
1808c2ecf20Sopenharmony_cividtv_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength)
1818c2ecf20Sopenharmony_ci{
1828c2ecf20Sopenharmony_ci	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
1838c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
1848c2ecf20Sopenharmony_ci	const struct vidtv_tuner_cnr_to_qual_s *cnr2qual = NULL;
1858c2ecf20Sopenharmony_ci	struct device *dev = fe->dvb->device;
1868c2ecf20Sopenharmony_ci	u32 array_size = 0;
1878c2ecf20Sopenharmony_ci	s32 shift;
1888c2ecf20Sopenharmony_ci	u32 i;
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	shift = vidtv_tuner_check_frequency_shift(fe);
1918c2ecf20Sopenharmony_ci	if (shift < 0) {
1928c2ecf20Sopenharmony_ci		tuner_dev->hw_state.lock_status = 0;
1938c2ecf20Sopenharmony_ci		*strength = 0;
1948c2ecf20Sopenharmony_ci		return 0;
1958c2ecf20Sopenharmony_ci	}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	switch (c->delivery_system) {
1988c2ecf20Sopenharmony_ci	case SYS_DVBT:
1998c2ecf20Sopenharmony_ci	case SYS_DVBT2:
2008c2ecf20Sopenharmony_ci		cnr2qual   = vidtv_tuner_t_cnr_2_qual;
2018c2ecf20Sopenharmony_ci		array_size = ARRAY_SIZE(vidtv_tuner_t_cnr_2_qual);
2028c2ecf20Sopenharmony_ci		break;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	case SYS_DVBS:
2058c2ecf20Sopenharmony_ci		cnr2qual   = vidtv_tuner_s_cnr_2_qual;
2068c2ecf20Sopenharmony_ci		array_size = ARRAY_SIZE(vidtv_tuner_s_cnr_2_qual);
2078c2ecf20Sopenharmony_ci		break;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	case SYS_DVBS2:
2108c2ecf20Sopenharmony_ci		cnr2qual   = vidtv_tuner_s2_cnr_2_qual;
2118c2ecf20Sopenharmony_ci		array_size = ARRAY_SIZE(vidtv_tuner_s2_cnr_2_qual);
2128c2ecf20Sopenharmony_ci		break;
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	case SYS_DVBC_ANNEX_A:
2158c2ecf20Sopenharmony_ci		cnr2qual   = vidtv_tuner_c_cnr_2_qual;
2168c2ecf20Sopenharmony_ci		array_size = ARRAY_SIZE(vidtv_tuner_c_cnr_2_qual);
2178c2ecf20Sopenharmony_ci		break;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	default:
2208c2ecf20Sopenharmony_ci		dev_warn_ratelimited(dev,
2218c2ecf20Sopenharmony_ci				     "%s: unsupported delivery system: %u\n",
2228c2ecf20Sopenharmony_ci				     __func__,
2238c2ecf20Sopenharmony_ci				     c->delivery_system);
2248c2ecf20Sopenharmony_ci		return -EINVAL;
2258c2ecf20Sopenharmony_ci	}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	for (i = 0; i < array_size; i++) {
2288c2ecf20Sopenharmony_ci		if (cnr2qual[i].modulation != c->modulation ||
2298c2ecf20Sopenharmony_ci		    cnr2qual[i].fec != c->fec_inner)
2308c2ecf20Sopenharmony_ci			continue;
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci		if (!shift) {
2338c2ecf20Sopenharmony_ci			*strength = cnr2qual[i].cnr_good;
2348c2ecf20Sopenharmony_ci			return 0;
2358c2ecf20Sopenharmony_ci		}
2368c2ecf20Sopenharmony_ci		/*
2378c2ecf20Sopenharmony_ci		 * Channel tuned at wrong frequency. Simulate that the
2388c2ecf20Sopenharmony_ci		 * Carrier S/N ratio is not too good.
2398c2ecf20Sopenharmony_ci		 */
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci		*strength = cnr2qual[i].cnr_ok -
2428c2ecf20Sopenharmony_ci			    (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok);
2438c2ecf20Sopenharmony_ci		return 0;
2448c2ecf20Sopenharmony_ci	}
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	/*
2478c2ecf20Sopenharmony_ci	 * do a linear interpolation between 34dB and 10dB if we can't
2488c2ecf20Sopenharmony_ci	 * match against the table
2498c2ecf20Sopenharmony_ci	 */
2508c2ecf20Sopenharmony_ci	*strength = 34000 - 24000 * shift / 100;
2518c2ecf20Sopenharmony_ci	return 0;
2528c2ecf20Sopenharmony_ci}
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_cistatic int vidtv_tuner_init(struct dvb_frontend *fe)
2558c2ecf20Sopenharmony_ci{
2568c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
2578c2ecf20Sopenharmony_ci	struct vidtv_tuner_config config  = tuner_dev->config;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	msleep_interruptible(config.mock_power_up_delay_msec);
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	tuner_dev->hw_state.asleep = false;
2628c2ecf20Sopenharmony_ci	tuner_dev->hw_state.if_frequency = 5000;
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	return 0;
2658c2ecf20Sopenharmony_ci}
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_cistatic int vidtv_tuner_sleep(struct dvb_frontend *fe)
2688c2ecf20Sopenharmony_ci{
2698c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	tuner_dev->hw_state.asleep = true;
2728c2ecf20Sopenharmony_ci	return 0;
2738c2ecf20Sopenharmony_ci}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_cistatic int vidtv_tuner_suspend(struct dvb_frontend *fe)
2768c2ecf20Sopenharmony_ci{
2778c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	tuner_dev->hw_state.asleep = true;
2808c2ecf20Sopenharmony_ci	return 0;
2818c2ecf20Sopenharmony_ci}
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_cistatic int vidtv_tuner_resume(struct dvb_frontend *fe)
2848c2ecf20Sopenharmony_ci{
2858c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	tuner_dev->hw_state.asleep = false;
2888c2ecf20Sopenharmony_ci	return 0;
2898c2ecf20Sopenharmony_ci}
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_cistatic int vidtv_tuner_set_params(struct dvb_frontend *fe)
2928c2ecf20Sopenharmony_ci{
2938c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
2948c2ecf20Sopenharmony_ci	struct vidtv_tuner_config config  = tuner_dev->config;
2958c2ecf20Sopenharmony_ci	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
2968c2ecf20Sopenharmony_ci	s32 shift;
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci	u32 min_freq = fe->ops.tuner_ops.info.frequency_min_hz;
2998c2ecf20Sopenharmony_ci	u32 max_freq = fe->ops.tuner_ops.info.frequency_max_hz;
3008c2ecf20Sopenharmony_ci	u32 min_bw = fe->ops.tuner_ops.info.bandwidth_min;
3018c2ecf20Sopenharmony_ci	u32 max_bw = fe->ops.tuner_ops.info.bandwidth_max;
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_ci	if (c->frequency < min_freq  || c->frequency > max_freq  ||
3048c2ecf20Sopenharmony_ci	    c->bandwidth_hz < min_bw || c->bandwidth_hz > max_bw) {
3058c2ecf20Sopenharmony_ci		tuner_dev->hw_state.lock_status = 0;
3068c2ecf20Sopenharmony_ci		return -EINVAL;
3078c2ecf20Sopenharmony_ci	}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	tuner_dev->hw_state.tuned_frequency = c->frequency;
3108c2ecf20Sopenharmony_ci	tuner_dev->hw_state.bandwidth = c->bandwidth_hz;
3118c2ecf20Sopenharmony_ci	tuner_dev->hw_state.lock_status = TUNER_STATUS_LOCKED;
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	msleep_interruptible(config.mock_tune_delay_msec);
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_ci	shift = vidtv_tuner_check_frequency_shift(fe);
3168c2ecf20Sopenharmony_ci	if (shift < 0) {
3178c2ecf20Sopenharmony_ci		tuner_dev->hw_state.lock_status = 0;
3188c2ecf20Sopenharmony_ci		return shift;
3198c2ecf20Sopenharmony_ci	}
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	return 0;
3228c2ecf20Sopenharmony_ci}
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_cistatic int vidtv_tuner_set_config(struct dvb_frontend *fe,
3258c2ecf20Sopenharmony_ci				  void *priv_cfg)
3268c2ecf20Sopenharmony_ci{
3278c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	memcpy(&tuner_dev->config, priv_cfg, sizeof(tuner_dev->config));
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	return 0;
3328c2ecf20Sopenharmony_ci}
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_cistatic int vidtv_tuner_get_frequency(struct dvb_frontend *fe,
3358c2ecf20Sopenharmony_ci				     u32 *frequency)
3368c2ecf20Sopenharmony_ci{
3378c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	*frequency = tuner_dev->hw_state.tuned_frequency;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	return 0;
3428c2ecf20Sopenharmony_ci}
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_cistatic int vidtv_tuner_get_bandwidth(struct dvb_frontend *fe,
3458c2ecf20Sopenharmony_ci				     u32 *bandwidth)
3468c2ecf20Sopenharmony_ci{
3478c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci	*bandwidth = tuner_dev->hw_state.bandwidth;
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_ci	return 0;
3528c2ecf20Sopenharmony_ci}
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_cistatic int vidtv_tuner_get_if_frequency(struct dvb_frontend *fe,
3558c2ecf20Sopenharmony_ci					u32 *frequency)
3568c2ecf20Sopenharmony_ci{
3578c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	*frequency = tuner_dev->hw_state.if_frequency;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	return 0;
3628c2ecf20Sopenharmony_ci}
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_cistatic int vidtv_tuner_get_status(struct dvb_frontend *fe, u32 *status)
3658c2ecf20Sopenharmony_ci{
3668c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	*status = tuner_dev->hw_state.lock_status;
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci	return 0;
3718c2ecf20Sopenharmony_ci}
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_cistatic const struct dvb_tuner_ops vidtv_tuner_ops = {
3748c2ecf20Sopenharmony_ci	.init             = vidtv_tuner_init,
3758c2ecf20Sopenharmony_ci	.sleep            = vidtv_tuner_sleep,
3768c2ecf20Sopenharmony_ci	.suspend          = vidtv_tuner_suspend,
3778c2ecf20Sopenharmony_ci	.resume           = vidtv_tuner_resume,
3788c2ecf20Sopenharmony_ci	.set_params       = vidtv_tuner_set_params,
3798c2ecf20Sopenharmony_ci	.set_config       = vidtv_tuner_set_config,
3808c2ecf20Sopenharmony_ci	.get_bandwidth    = vidtv_tuner_get_bandwidth,
3818c2ecf20Sopenharmony_ci	.get_frequency    = vidtv_tuner_get_frequency,
3828c2ecf20Sopenharmony_ci	.get_if_frequency = vidtv_tuner_get_if_frequency,
3838c2ecf20Sopenharmony_ci	.get_status       = vidtv_tuner_get_status,
3848c2ecf20Sopenharmony_ci	.get_rf_strength  = vidtv_tuner_get_signal_strength
3858c2ecf20Sopenharmony_ci};
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_cistatic const struct i2c_device_id vidtv_tuner_i2c_id_table[] = {
3888c2ecf20Sopenharmony_ci	{"dvb_vidtv_tuner", 0},
3898c2ecf20Sopenharmony_ci	{}
3908c2ecf20Sopenharmony_ci};
3918c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, vidtv_tuner_i2c_id_table);
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_cistatic int vidtv_tuner_i2c_probe(struct i2c_client *client,
3948c2ecf20Sopenharmony_ci				 const struct i2c_device_id *id)
3958c2ecf20Sopenharmony_ci{
3968c2ecf20Sopenharmony_ci	struct vidtv_tuner_config *config = client->dev.platform_data;
3978c2ecf20Sopenharmony_ci	struct dvb_frontend *fe           = config->fe;
3988c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = NULL;
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_ci	tuner_dev = kzalloc(sizeof(*tuner_dev), GFP_KERNEL);
4018c2ecf20Sopenharmony_ci	if (!tuner_dev)
4028c2ecf20Sopenharmony_ci		return -ENOMEM;
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_ci	tuner_dev->fe = config->fe;
4058c2ecf20Sopenharmony_ci	i2c_set_clientdata(client, tuner_dev);
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_ci	memcpy(&fe->ops.tuner_ops,
4088c2ecf20Sopenharmony_ci	       &vidtv_tuner_ops,
4098c2ecf20Sopenharmony_ci	       sizeof(struct dvb_tuner_ops));
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_ci	memcpy(&tuner_dev->config, config, sizeof(tuner_dev->config));
4128c2ecf20Sopenharmony_ci	fe->tuner_priv = client;
4138c2ecf20Sopenharmony_ci
4148c2ecf20Sopenharmony_ci	return 0;
4158c2ecf20Sopenharmony_ci}
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_cistatic int vidtv_tuner_i2c_remove(struct i2c_client *client)
4188c2ecf20Sopenharmony_ci{
4198c2ecf20Sopenharmony_ci	struct vidtv_tuner_dev *tuner_dev = i2c_get_clientdata(client);
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci	kfree(tuner_dev);
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_ci	return 0;
4248c2ecf20Sopenharmony_ci}
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_cistatic struct i2c_driver vidtv_tuner_i2c_driver = {
4278c2ecf20Sopenharmony_ci	.driver = {
4288c2ecf20Sopenharmony_ci		.name                = "dvb_vidtv_tuner",
4298c2ecf20Sopenharmony_ci		.suppress_bind_attrs = true,
4308c2ecf20Sopenharmony_ci	},
4318c2ecf20Sopenharmony_ci	.probe    = vidtv_tuner_i2c_probe,
4328c2ecf20Sopenharmony_ci	.remove   = vidtv_tuner_i2c_remove,
4338c2ecf20Sopenharmony_ci	.id_table = vidtv_tuner_i2c_id_table,
4348c2ecf20Sopenharmony_ci};
4358c2ecf20Sopenharmony_cimodule_i2c_driver(vidtv_tuner_i2c_driver);
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Virtual DVB Tuner");
4388c2ecf20Sopenharmony_ciMODULE_AUTHOR("Daniel W. S. Almeida");
4398c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
440