xref: /linux/drivers/media/test-drivers/vidtv/vidtv_tuner.c (revision cc4cbd4b4f97a968203530ef73598ad2eaf70dee)
124fb190eSDaniel W. S. Almeida // SPDX-License-Identifier: GPL-2.0
224fb190eSDaniel W. S. Almeida /*
324fb190eSDaniel W. S. Almeida  * The Virtual DVB test driver serves as a reference DVB driver and helps
424fb190eSDaniel W. S. Almeida  * validate the existing APIs in the media subsystem. It can also aid
524fb190eSDaniel W. S. Almeida  * developers working on userspace applications.
624fb190eSDaniel W. S. Almeida  *
724fb190eSDaniel W. S. Almeida  * The vidtv tuner should support common TV standards such as
824fb190eSDaniel W. S. Almeida  * DVB-T/T2/S/S2, ISDB-T and ATSC when completed.
924fb190eSDaniel W. S. Almeida  *
1024fb190eSDaniel W. S. Almeida  * Copyright (C) 2020 Daniel W. S. Almeida
1124fb190eSDaniel W. S. Almeida  */
1224fb190eSDaniel W. S. Almeida 
1324fb190eSDaniel W. S. Almeida #include <linux/errno.h>
1424fb190eSDaniel W. S. Almeida #include <linux/i2c.h>
1524fb190eSDaniel W. S. Almeida #include <linux/module.h>
1624fb190eSDaniel W. S. Almeida #include <linux/printk.h>
1724fb190eSDaniel W. S. Almeida #include <linux/ratelimit.h>
188922e393SMauro Carvalho Chehab #include <linux/slab.h>
198922e393SMauro Carvalho Chehab #include <linux/types.h>
208922e393SMauro Carvalho Chehab 
218922e393SMauro Carvalho Chehab #include <media/dvb_frontend.h>
2224fb190eSDaniel W. S. Almeida 
2324fb190eSDaniel W. S. Almeida #include "vidtv_tuner.h"
2424fb190eSDaniel W. S. Almeida 
2524fb190eSDaniel W. S. Almeida struct vidtv_tuner_cnr_to_qual_s {
2624fb190eSDaniel W. S. Almeida 	/* attempt to use the same values as libdvbv5 */
2724fb190eSDaniel W. S. Almeida 	u32 modulation;
2824fb190eSDaniel W. S. Almeida 	u32 fec;
2924fb190eSDaniel W. S. Almeida 	u32 cnr_ok;
3024fb190eSDaniel W. S. Almeida 	u32 cnr_good;
3124fb190eSDaniel W. S. Almeida };
3224fb190eSDaniel W. S. Almeida 
3324fb190eSDaniel W. S. Almeida static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_c_cnr_2_qual[] = {
3424fb190eSDaniel W. S. Almeida 	/* from libdvbv5 source code, in milli db */
3524fb190eSDaniel W. S. Almeida 	{ QAM_256, FEC_NONE,  34000, 38000},
3624fb190eSDaniel W. S. Almeida 	{ QAM_64,  FEC_NONE,  30000, 34000},
3724fb190eSDaniel W. S. Almeida };
3824fb190eSDaniel W. S. Almeida 
3924fb190eSDaniel W. S. Almeida static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s_cnr_2_qual[] = {
4024fb190eSDaniel W. S. Almeida 	/* from libdvbv5 source code, in milli db */
4124fb190eSDaniel W. S. Almeida 	{ QPSK, FEC_1_2,  7000, 10000},
4224fb190eSDaniel W. S. Almeida 	{ QPSK, FEC_2_3,  9000, 12000},
4324fb190eSDaniel W. S. Almeida 	{ QPSK, FEC_3_4, 10000, 13000},
4424fb190eSDaniel W. S. Almeida 	{ QPSK, FEC_5_6, 11000, 14000},
4524fb190eSDaniel W. S. Almeida 	{ QPSK, FEC_7_8, 12000, 15000},
4624fb190eSDaniel W. S. Almeida };
4724fb190eSDaniel W. S. Almeida 
4824fb190eSDaniel W. S. Almeida static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s2_cnr_2_qual[] = {
4924fb190eSDaniel W. S. Almeida 	/* from libdvbv5 source code, in milli db */
5024fb190eSDaniel W. S. Almeida 	{ QPSK,  FEC_1_2,   9000,  12000},
5124fb190eSDaniel W. S. Almeida 	{ QPSK,  FEC_2_3,  11000,  14000},
5224fb190eSDaniel W. S. Almeida 	{ QPSK,  FEC_3_4,  12000,  15000},
5324fb190eSDaniel W. S. Almeida 	{ QPSK,  FEC_5_6,  12000,  15000},
5424fb190eSDaniel W. S. Almeida 	{ QPSK,  FEC_8_9,  13000,  16000},
5524fb190eSDaniel W. S. Almeida 	{ QPSK,  FEC_9_10, 13500,  16500},
5624fb190eSDaniel W. S. Almeida 	{ PSK_8, FEC_2_3,  14500,  17500},
5724fb190eSDaniel W. S. Almeida 	{ PSK_8, FEC_3_4,  16000,  19000},
5824fb190eSDaniel W. S. Almeida 	{ PSK_8, FEC_5_6,  17500,  20500},
5924fb190eSDaniel W. S. Almeida 	{ PSK_8, FEC_8_9,  19000,  22000},
6024fb190eSDaniel W. S. Almeida };
6124fb190eSDaniel W. S. Almeida 
6224fb190eSDaniel W. S. Almeida static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_t_cnr_2_qual[] = {
6324fb190eSDaniel W. S. Almeida 	/* from libdvbv5 source code, in milli db*/
6424fb190eSDaniel W. S. Almeida 	{   QPSK, FEC_1_2,  4100,  5900},
6524fb190eSDaniel W. S. Almeida 	{   QPSK, FEC_2_3,  6100,  9600},
6624fb190eSDaniel W. S. Almeida 	{   QPSK, FEC_3_4,  7200, 12400},
6724fb190eSDaniel W. S. Almeida 	{   QPSK, FEC_5_6,  8500, 15600},
6824fb190eSDaniel W. S. Almeida 	{   QPSK, FEC_7_8,  9200, 17500},
6924fb190eSDaniel W. S. Almeida 	{ QAM_16, FEC_1_2,  9800, 11800},
7024fb190eSDaniel W. S. Almeida 	{ QAM_16, FEC_2_3, 12100, 15300},
7124fb190eSDaniel W. S. Almeida 	{ QAM_16, FEC_3_4, 13400, 18100},
7224fb190eSDaniel W. S. Almeida 	{ QAM_16, FEC_5_6, 14800, 21300},
7324fb190eSDaniel W. S. Almeida 	{ QAM_16, FEC_7_8, 15700, 23600},
7424fb190eSDaniel W. S. Almeida 	{ QAM_64, FEC_1_2, 14000, 16000},
7524fb190eSDaniel W. S. Almeida 	{ QAM_64, FEC_2_3, 19900, 25400},
7624fb190eSDaniel W. S. Almeida 	{ QAM_64, FEC_3_4, 24900, 27900},
7724fb190eSDaniel W. S. Almeida 	{ QAM_64, FEC_5_6, 21300, 23300},
7824fb190eSDaniel W. S. Almeida 	{ QAM_64, FEC_7_8, 22000, 24000},
7924fb190eSDaniel W. S. Almeida };
8024fb190eSDaniel W. S. Almeida 
8124fb190eSDaniel W. S. Almeida /**
8224fb190eSDaniel W. S. Almeida  * struct vidtv_tuner_hardware_state - Simulate the tuner hardware status
8324fb190eSDaniel W. S. Almeida  * @asleep: whether the tuner is asleep, i.e whether _sleep() or _suspend() was
8424fb190eSDaniel W. S. Almeida  * called.
8524fb190eSDaniel W. S. Almeida  * @lock_status: Whether the tuner has managed to lock on the requested
8624fb190eSDaniel W. S. Almeida  * frequency.
8724fb190eSDaniel W. S. Almeida  * @if_frequency: The tuner's intermediate frequency. Hardcoded for the purposes
8824fb190eSDaniel W. S. Almeida  * of simulation.
8924fb190eSDaniel W. S. Almeida  * @tuned_frequency: The actual tuned frequency.
9024fb190eSDaniel W. S. Almeida  * @bandwidth: The actual bandwidth.
9124fb190eSDaniel W. S. Almeida  *
9224fb190eSDaniel W. S. Almeida  * This structure is meant to simulate the status of the tuner hardware, as if
9324fb190eSDaniel W. S. Almeida  * we had a physical tuner hardware.
9424fb190eSDaniel W. S. Almeida  */
9524fb190eSDaniel W. S. Almeida struct vidtv_tuner_hardware_state {
9624fb190eSDaniel W. S. Almeida 	bool asleep;
9724fb190eSDaniel W. S. Almeida 	u32 lock_status;
9824fb190eSDaniel W. S. Almeida 	u32 if_frequency;
9924fb190eSDaniel W. S. Almeida 	u32 tuned_frequency;
10024fb190eSDaniel W. S. Almeida 	u32 bandwidth;
10124fb190eSDaniel W. S. Almeida };
10224fb190eSDaniel W. S. Almeida 
10324fb190eSDaniel W. S. Almeida /**
10424fb190eSDaniel W. S. Almeida  * struct vidtv_tuner_dev - The tuner struct
10524fb190eSDaniel W. S. Almeida  * @fe: A pointer to the dvb_frontend structure allocated by vidtv_demod
10624fb190eSDaniel W. S. Almeida  * @hw_state: A struct to simulate the tuner's hardware state as if we had a
10724fb190eSDaniel W. S. Almeida  * physical tuner hardware.
10824fb190eSDaniel W. S. Almeida  * @config: The configuration used to start the tuner module, usually filled
10924fb190eSDaniel W. S. Almeida  * by a bridge driver. For vidtv, this is filled by vidtv_bridge before the
11024fb190eSDaniel W. S. Almeida  * tuner module is probed.
11124fb190eSDaniel W. S. Almeida  */
11224fb190eSDaniel W. S. Almeida struct vidtv_tuner_dev {
11324fb190eSDaniel W. S. Almeida 	struct dvb_frontend *fe;
11424fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_hardware_state hw_state;
11524fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_config config;
11624fb190eSDaniel W. S. Almeida };
11724fb190eSDaniel W. S. Almeida 
11824fb190eSDaniel W. S. Almeida static struct vidtv_tuner_dev*
11924fb190eSDaniel W. S. Almeida vidtv_tuner_get_dev(struct dvb_frontend *fe)
12024fb190eSDaniel W. S. Almeida {
12124fb190eSDaniel W. S. Almeida 	return i2c_get_clientdata(fe->tuner_priv);
12224fb190eSDaniel W. S. Almeida }
12324fb190eSDaniel W. S. Almeida 
124f3ea9da2SMauro Carvalho Chehab static int vidtv_tuner_check_frequency_shift(struct dvb_frontend *fe)
12524fb190eSDaniel W. S. Almeida {
12624fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
12724fb190eSDaniel W. S. Almeida 	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
12824fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_config config  = tuner_dev->config;
12924fb190eSDaniel W. S. Almeida 	u32 *valid_freqs = NULL;
13024fb190eSDaniel W. S. Almeida 	u32 array_sz = 0;
13124fb190eSDaniel W. S. Almeida 	u32 i;
13224fb190eSDaniel W. S. Almeida 	u32 shift;
13324fb190eSDaniel W. S. Almeida 
13424fb190eSDaniel W. S. Almeida 	switch (c->delivery_system) {
13524fb190eSDaniel W. S. Almeida 	case SYS_DVBT:
13624fb190eSDaniel W. S. Almeida 	case SYS_DVBT2:
13724fb190eSDaniel W. S. Almeida 		valid_freqs = config.vidtv_valid_dvb_t_freqs;
13824fb190eSDaniel W. S. Almeida 		array_sz    = ARRAY_SIZE(config.vidtv_valid_dvb_t_freqs);
13924fb190eSDaniel W. S. Almeida 		break;
14024fb190eSDaniel W. S. Almeida 	case SYS_DVBS:
14124fb190eSDaniel W. S. Almeida 	case SYS_DVBS2:
14224fb190eSDaniel W. S. Almeida 		valid_freqs = config.vidtv_valid_dvb_s_freqs;
14324fb190eSDaniel W. S. Almeida 		array_sz    = ARRAY_SIZE(config.vidtv_valid_dvb_s_freqs);
14424fb190eSDaniel W. S. Almeida 		break;
14524fb190eSDaniel W. S. Almeida 	case SYS_DVBC_ANNEX_A:
14624fb190eSDaniel W. S. Almeida 		valid_freqs = config.vidtv_valid_dvb_c_freqs;
14724fb190eSDaniel W. S. Almeida 		array_sz    = ARRAY_SIZE(config.vidtv_valid_dvb_c_freqs);
14824fb190eSDaniel W. S. Almeida 		break;
14924fb190eSDaniel W. S. Almeida 
15024fb190eSDaniel W. S. Almeida 	default:
1519cfb4d36SMauro Carvalho Chehab 		dev_warn(fe->dvb->device,
1529cfb4d36SMauro Carvalho Chehab 			 "%s: unsupported delivery system: %u\n",
15324fb190eSDaniel W. S. Almeida 			 __func__,
15424fb190eSDaniel W. S. Almeida 			 c->delivery_system);
15524fb190eSDaniel W. S. Almeida 
15624fb190eSDaniel W. S. Almeida 		return -EINVAL;
15724fb190eSDaniel W. S. Almeida 	}
15824fb190eSDaniel W. S. Almeida 
15924fb190eSDaniel W. S. Almeida 	for (i = 0; i < array_sz; i++) {
160f3ea9da2SMauro Carvalho Chehab 		if (!valid_freqs[i])
161f3ea9da2SMauro Carvalho Chehab 			break;
16224fb190eSDaniel W. S. Almeida 		shift = abs(c->frequency - valid_freqs[i]);
16324fb190eSDaniel W. S. Almeida 
16424fb190eSDaniel W. S. Almeida 		if (!shift)
16524fb190eSDaniel W. S. Almeida 			return 0;
16624fb190eSDaniel W. S. Almeida 
16724fb190eSDaniel W. S. Almeida 		/*
16824fb190eSDaniel W. S. Almeida 		 * This will provide a value from 0 to 100 that would
16924fb190eSDaniel W. S. Almeida 		 * indicate how far is the tuned frequency from the
17024fb190eSDaniel W. S. Almeida 		 * right one.
17124fb190eSDaniel W. S. Almeida 		 */
17224fb190eSDaniel W. S. Almeida 		if (shift < config.max_frequency_shift_hz)
17324fb190eSDaniel W. S. Almeida 			return shift * 100 / config.max_frequency_shift_hz;
17424fb190eSDaniel W. S. Almeida 	}
17524fb190eSDaniel W. S. Almeida 
17624fb190eSDaniel W. S. Almeida 	return -EINVAL;
17724fb190eSDaniel W. S. Almeida }
17824fb190eSDaniel W. S. Almeida 
17924fb190eSDaniel W. S. Almeida static int
18024fb190eSDaniel W. S. Almeida vidtv_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength)
18124fb190eSDaniel W. S. Almeida {
18224fb190eSDaniel W. S. Almeida 	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
183f3ea9da2SMauro Carvalho Chehab 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
18424fb190eSDaniel W. S. Almeida 	const struct vidtv_tuner_cnr_to_qual_s *cnr2qual = NULL;
1859cfb4d36SMauro Carvalho Chehab 	struct device *dev = fe->dvb->device;
18624fb190eSDaniel W. S. Almeida 	u32 array_size = 0;
18724fb190eSDaniel W. S. Almeida 	s32 shift;
18824fb190eSDaniel W. S. Almeida 	u32 i;
18924fb190eSDaniel W. S. Almeida 
19024fb190eSDaniel W. S. Almeida 	shift = vidtv_tuner_check_frequency_shift(fe);
191f3ea9da2SMauro Carvalho Chehab 	if (shift < 0) {
192f3ea9da2SMauro Carvalho Chehab 		tuner_dev->hw_state.lock_status = 0;
193f3ea9da2SMauro Carvalho Chehab 		*strength = 0;
194f3ea9da2SMauro Carvalho Chehab 		return 0;
195f3ea9da2SMauro Carvalho Chehab 	}
19624fb190eSDaniel W. S. Almeida 
19724fb190eSDaniel W. S. Almeida 	switch (c->delivery_system) {
19824fb190eSDaniel W. S. Almeida 	case SYS_DVBT:
19924fb190eSDaniel W. S. Almeida 	case SYS_DVBT2:
20024fb190eSDaniel W. S. Almeida 		cnr2qual   = vidtv_tuner_t_cnr_2_qual;
20124fb190eSDaniel W. S. Almeida 		array_size = ARRAY_SIZE(vidtv_tuner_t_cnr_2_qual);
20224fb190eSDaniel W. S. Almeida 		break;
20324fb190eSDaniel W. S. Almeida 
20424fb190eSDaniel W. S. Almeida 	case SYS_DVBS:
20524fb190eSDaniel W. S. Almeida 		cnr2qual   = vidtv_tuner_s_cnr_2_qual;
20624fb190eSDaniel W. S. Almeida 		array_size = ARRAY_SIZE(vidtv_tuner_s_cnr_2_qual);
20724fb190eSDaniel W. S. Almeida 		break;
20824fb190eSDaniel W. S. Almeida 
20924fb190eSDaniel W. S. Almeida 	case SYS_DVBS2:
21024fb190eSDaniel W. S. Almeida 		cnr2qual   = vidtv_tuner_s2_cnr_2_qual;
21124fb190eSDaniel W. S. Almeida 		array_size = ARRAY_SIZE(vidtv_tuner_s2_cnr_2_qual);
21224fb190eSDaniel W. S. Almeida 		break;
21324fb190eSDaniel W. S. Almeida 
21424fb190eSDaniel W. S. Almeida 	case SYS_DVBC_ANNEX_A:
21524fb190eSDaniel W. S. Almeida 		cnr2qual   = vidtv_tuner_c_cnr_2_qual;
21624fb190eSDaniel W. S. Almeida 		array_size = ARRAY_SIZE(vidtv_tuner_c_cnr_2_qual);
21724fb190eSDaniel W. S. Almeida 		break;
21824fb190eSDaniel W. S. Almeida 
21924fb190eSDaniel W. S. Almeida 	default:
2209cfb4d36SMauro Carvalho Chehab 		dev_warn_ratelimited(dev,
2219cfb4d36SMauro Carvalho Chehab 				     "%s: unsupported delivery system: %u\n",
22224fb190eSDaniel W. S. Almeida 				     __func__,
22324fb190eSDaniel W. S. Almeida 				     c->delivery_system);
22424fb190eSDaniel W. S. Almeida 		return -EINVAL;
22524fb190eSDaniel W. S. Almeida 	}
22624fb190eSDaniel W. S. Almeida 
22724fb190eSDaniel W. S. Almeida 	for (i = 0; i < array_size; i++) {
22824fb190eSDaniel W. S. Almeida 		if (cnr2qual[i].modulation != c->modulation ||
22924fb190eSDaniel W. S. Almeida 		    cnr2qual[i].fec != c->fec_inner)
23024fb190eSDaniel W. S. Almeida 			continue;
23124fb190eSDaniel W. S. Almeida 
23224fb190eSDaniel W. S. Almeida 		if (!shift) {
23324fb190eSDaniel W. S. Almeida 			*strength = cnr2qual[i].cnr_good;
23424fb190eSDaniel W. S. Almeida 			return 0;
23524fb190eSDaniel W. S. Almeida 		}
23624fb190eSDaniel W. S. Almeida 		/*
23724fb190eSDaniel W. S. Almeida 		 * Channel tuned at wrong frequency. Simulate that the
23824fb190eSDaniel W. S. Almeida 		 * Carrier S/N ratio is not too good.
23924fb190eSDaniel W. S. Almeida 		 */
24024fb190eSDaniel W. S. Almeida 
24124fb190eSDaniel W. S. Almeida 		*strength = cnr2qual[i].cnr_ok -
24224fb190eSDaniel W. S. Almeida 			    (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok);
24324fb190eSDaniel W. S. Almeida 		return 0;
24424fb190eSDaniel W. S. Almeida 	}
24524fb190eSDaniel W. S. Almeida 
24624fb190eSDaniel W. S. Almeida 	/*
24724fb190eSDaniel W. S. Almeida 	 * do a linear interpolation between 34dB and 10dB if we can't
24824fb190eSDaniel W. S. Almeida 	 * match against the table
24924fb190eSDaniel W. S. Almeida 	 */
2503e51a496SMauro Carvalho Chehab 	*strength = 34000 - 24000 * shift / 100;
25124fb190eSDaniel W. S. Almeida 	return 0;
25224fb190eSDaniel W. S. Almeida }
25324fb190eSDaniel W. S. Almeida 
25424fb190eSDaniel W. S. Almeida static int vidtv_tuner_init(struct dvb_frontend *fe)
25524fb190eSDaniel W. S. Almeida {
25624fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
25724fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_config config  = tuner_dev->config;
25824fb190eSDaniel W. S. Almeida 
25924fb190eSDaniel W. S. Almeida 	msleep_interruptible(config.mock_power_up_delay_msec);
26024fb190eSDaniel W. S. Almeida 
26124fb190eSDaniel W. S. Almeida 	tuner_dev->hw_state.asleep = false;
26224fb190eSDaniel W. S. Almeida 	tuner_dev->hw_state.if_frequency = 5000;
26324fb190eSDaniel W. S. Almeida 
26424fb190eSDaniel W. S. Almeida 	return 0;
26524fb190eSDaniel W. S. Almeida }
26624fb190eSDaniel W. S. Almeida 
26724fb190eSDaniel W. S. Almeida static int vidtv_tuner_sleep(struct dvb_frontend *fe)
26824fb190eSDaniel W. S. Almeida {
26924fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
27024fb190eSDaniel W. S. Almeida 
27124fb190eSDaniel W. S. Almeida 	tuner_dev->hw_state.asleep = true;
27224fb190eSDaniel W. S. Almeida 	return 0;
27324fb190eSDaniel W. S. Almeida }
27424fb190eSDaniel W. S. Almeida 
27524fb190eSDaniel W. S. Almeida static int vidtv_tuner_suspend(struct dvb_frontend *fe)
27624fb190eSDaniel W. S. Almeida {
27724fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
27824fb190eSDaniel W. S. Almeida 
27924fb190eSDaniel W. S. Almeida 	tuner_dev->hw_state.asleep = true;
28024fb190eSDaniel W. S. Almeida 	return 0;
28124fb190eSDaniel W. S. Almeida }
28224fb190eSDaniel W. S. Almeida 
28324fb190eSDaniel W. S. Almeida static int vidtv_tuner_resume(struct dvb_frontend *fe)
28424fb190eSDaniel W. S. Almeida {
28524fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
28624fb190eSDaniel W. S. Almeida 
28724fb190eSDaniel W. S. Almeida 	tuner_dev->hw_state.asleep = false;
28824fb190eSDaniel W. S. Almeida 	return 0;
28924fb190eSDaniel W. S. Almeida }
29024fb190eSDaniel W. S. Almeida 
29124fb190eSDaniel W. S. Almeida static int vidtv_tuner_set_params(struct dvb_frontend *fe)
29224fb190eSDaniel W. S. Almeida {
29324fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
29424fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_config config  = tuner_dev->config;
29524fb190eSDaniel W. S. Almeida 	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
296f3ea9da2SMauro Carvalho Chehab 	s32 shift;
29724fb190eSDaniel W. S. Almeida 
29824fb190eSDaniel W. S. Almeida 	u32 min_freq = fe->ops.tuner_ops.info.frequency_min_hz;
29924fb190eSDaniel W. S. Almeida 	u32 max_freq = fe->ops.tuner_ops.info.frequency_max_hz;
30024fb190eSDaniel W. S. Almeida 	u32 min_bw = fe->ops.tuner_ops.info.bandwidth_min;
30124fb190eSDaniel W. S. Almeida 	u32 max_bw = fe->ops.tuner_ops.info.bandwidth_max;
30224fb190eSDaniel W. S. Almeida 
30324fb190eSDaniel W. S. Almeida 	if (c->frequency < min_freq  || c->frequency > max_freq  ||
30424fb190eSDaniel W. S. Almeida 	    c->bandwidth_hz < min_bw || c->bandwidth_hz > max_bw) {
30524fb190eSDaniel W. S. Almeida 		tuner_dev->hw_state.lock_status = 0;
30624fb190eSDaniel W. S. Almeida 		return -EINVAL;
30724fb190eSDaniel W. S. Almeida 	}
30824fb190eSDaniel W. S. Almeida 
30924fb190eSDaniel W. S. Almeida 	tuner_dev->hw_state.tuned_frequency = c->frequency;
31024fb190eSDaniel W. S. Almeida 	tuner_dev->hw_state.bandwidth = c->bandwidth_hz;
31124fb190eSDaniel W. S. Almeida 	tuner_dev->hw_state.lock_status = TUNER_STATUS_LOCKED;
31224fb190eSDaniel W. S. Almeida 
31324fb190eSDaniel W. S. Almeida 	msleep_interruptible(config.mock_tune_delay_msec);
314f3ea9da2SMauro Carvalho Chehab 
315f3ea9da2SMauro Carvalho Chehab 	shift = vidtv_tuner_check_frequency_shift(fe);
316f3ea9da2SMauro Carvalho Chehab 	if (shift < 0) {
317f3ea9da2SMauro Carvalho Chehab 		tuner_dev->hw_state.lock_status = 0;
318f3ea9da2SMauro Carvalho Chehab 		return shift;
319f3ea9da2SMauro Carvalho Chehab 	}
320f3ea9da2SMauro Carvalho Chehab 
32124fb190eSDaniel W. S. Almeida 	return 0;
32224fb190eSDaniel W. S. Almeida }
32324fb190eSDaniel W. S. Almeida 
32424fb190eSDaniel W. S. Almeida static int vidtv_tuner_set_config(struct dvb_frontend *fe,
32524fb190eSDaniel W. S. Almeida 				  void *priv_cfg)
32624fb190eSDaniel W. S. Almeida {
32724fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
32824fb190eSDaniel W. S. Almeida 
32924fb190eSDaniel W. S. Almeida 	memcpy(&tuner_dev->config, priv_cfg, sizeof(tuner_dev->config));
33024fb190eSDaniel W. S. Almeida 
33124fb190eSDaniel W. S. Almeida 	return 0;
33224fb190eSDaniel W. S. Almeida }
33324fb190eSDaniel W. S. Almeida 
33424fb190eSDaniel W. S. Almeida static int vidtv_tuner_get_frequency(struct dvb_frontend *fe,
33524fb190eSDaniel W. S. Almeida 				     u32 *frequency)
33624fb190eSDaniel W. S. Almeida {
33724fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
33824fb190eSDaniel W. S. Almeida 
33924fb190eSDaniel W. S. Almeida 	*frequency = tuner_dev->hw_state.tuned_frequency;
34024fb190eSDaniel W. S. Almeida 
34124fb190eSDaniel W. S. Almeida 	return 0;
34224fb190eSDaniel W. S. Almeida }
34324fb190eSDaniel W. S. Almeida 
34424fb190eSDaniel W. S. Almeida static int vidtv_tuner_get_bandwidth(struct dvb_frontend *fe,
34524fb190eSDaniel W. S. Almeida 				     u32 *bandwidth)
34624fb190eSDaniel W. S. Almeida {
34724fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
34824fb190eSDaniel W. S. Almeida 
34924fb190eSDaniel W. S. Almeida 	*bandwidth = tuner_dev->hw_state.bandwidth;
35024fb190eSDaniel W. S. Almeida 
35124fb190eSDaniel W. S. Almeida 	return 0;
35224fb190eSDaniel W. S. Almeida }
35324fb190eSDaniel W. S. Almeida 
35424fb190eSDaniel W. S. Almeida static int vidtv_tuner_get_if_frequency(struct dvb_frontend *fe,
35524fb190eSDaniel W. S. Almeida 					u32 *frequency)
35624fb190eSDaniel W. S. Almeida {
35724fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
35824fb190eSDaniel W. S. Almeida 
35924fb190eSDaniel W. S. Almeida 	*frequency = tuner_dev->hw_state.if_frequency;
36024fb190eSDaniel W. S. Almeida 
36124fb190eSDaniel W. S. Almeida 	return 0;
36224fb190eSDaniel W. S. Almeida }
36324fb190eSDaniel W. S. Almeida 
36424fb190eSDaniel W. S. Almeida static int vidtv_tuner_get_status(struct dvb_frontend *fe, u32 *status)
36524fb190eSDaniel W. S. Almeida {
36624fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
36724fb190eSDaniel W. S. Almeida 
36824fb190eSDaniel W. S. Almeida 	*status = tuner_dev->hw_state.lock_status;
36924fb190eSDaniel W. S. Almeida 
37024fb190eSDaniel W. S. Almeida 	return 0;
37124fb190eSDaniel W. S. Almeida }
37224fb190eSDaniel W. S. Almeida 
37324fb190eSDaniel W. S. Almeida static const struct dvb_tuner_ops vidtv_tuner_ops = {
37424fb190eSDaniel W. S. Almeida 	.init             = vidtv_tuner_init,
37524fb190eSDaniel W. S. Almeida 	.sleep            = vidtv_tuner_sleep,
37624fb190eSDaniel W. S. Almeida 	.suspend          = vidtv_tuner_suspend,
37724fb190eSDaniel W. S. Almeida 	.resume           = vidtv_tuner_resume,
37824fb190eSDaniel W. S. Almeida 	.set_params       = vidtv_tuner_set_params,
37924fb190eSDaniel W. S. Almeida 	.set_config       = vidtv_tuner_set_config,
38024fb190eSDaniel W. S. Almeida 	.get_bandwidth    = vidtv_tuner_get_bandwidth,
38124fb190eSDaniel W. S. Almeida 	.get_frequency    = vidtv_tuner_get_frequency,
38224fb190eSDaniel W. S. Almeida 	.get_if_frequency = vidtv_tuner_get_if_frequency,
38324fb190eSDaniel W. S. Almeida 	.get_status       = vidtv_tuner_get_status,
38424fb190eSDaniel W. S. Almeida 	.get_rf_strength  = vidtv_tuner_get_signal_strength
38524fb190eSDaniel W. S. Almeida };
38624fb190eSDaniel W. S. Almeida 
38724fb190eSDaniel W. S. Almeida static const struct i2c_device_id vidtv_tuner_i2c_id_table[] = {
388*cc4cbd4bSUwe Kleine-König 	{ "dvb_vidtv_tuner" },
38924fb190eSDaniel W. S. Almeida 	{}
39024fb190eSDaniel W. S. Almeida };
39124fb190eSDaniel W. S. Almeida MODULE_DEVICE_TABLE(i2c, vidtv_tuner_i2c_id_table);
39224fb190eSDaniel W. S. Almeida 
3937d4833b1SUwe Kleine-König static int vidtv_tuner_i2c_probe(struct i2c_client *client)
39424fb190eSDaniel W. S. Almeida {
39524fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_config *config = client->dev.platform_data;
39624fb190eSDaniel W. S. Almeida 	struct dvb_frontend *fe           = config->fe;
39724fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = NULL;
39824fb190eSDaniel W. S. Almeida 
39924fb190eSDaniel W. S. Almeida 	tuner_dev = kzalloc(sizeof(*tuner_dev), GFP_KERNEL);
40024fb190eSDaniel W. S. Almeida 	if (!tuner_dev)
40124fb190eSDaniel W. S. Almeida 		return -ENOMEM;
40224fb190eSDaniel W. S. Almeida 
40324fb190eSDaniel W. S. Almeida 	tuner_dev->fe = config->fe;
40424fb190eSDaniel W. S. Almeida 	i2c_set_clientdata(client, tuner_dev);
40524fb190eSDaniel W. S. Almeida 
40624fb190eSDaniel W. S. Almeida 	memcpy(&fe->ops.tuner_ops,
40724fb190eSDaniel W. S. Almeida 	       &vidtv_tuner_ops,
40824fb190eSDaniel W. S. Almeida 	       sizeof(struct dvb_tuner_ops));
40924fb190eSDaniel W. S. Almeida 
410f3ea9da2SMauro Carvalho Chehab 	memcpy(&tuner_dev->config, config, sizeof(tuner_dev->config));
41124fb190eSDaniel W. S. Almeida 	fe->tuner_priv = client;
41224fb190eSDaniel W. S. Almeida 
41324fb190eSDaniel W. S. Almeida 	return 0;
41424fb190eSDaniel W. S. Almeida }
41524fb190eSDaniel W. S. Almeida 
416ed5c2f5fSUwe Kleine-König static void vidtv_tuner_i2c_remove(struct i2c_client *client)
41724fb190eSDaniel W. S. Almeida {
41824fb190eSDaniel W. S. Almeida 	struct vidtv_tuner_dev *tuner_dev = i2c_get_clientdata(client);
41924fb190eSDaniel W. S. Almeida 
42024fb190eSDaniel W. S. Almeida 	kfree(tuner_dev);
42124fb190eSDaniel W. S. Almeida }
42224fb190eSDaniel W. S. Almeida 
42324fb190eSDaniel W. S. Almeida static struct i2c_driver vidtv_tuner_i2c_driver = {
42424fb190eSDaniel W. S. Almeida 	.driver = {
42524fb190eSDaniel W. S. Almeida 		.name                = "dvb_vidtv_tuner",
42624fb190eSDaniel W. S. Almeida 		.suppress_bind_attrs = true,
42724fb190eSDaniel W. S. Almeida 	},
428aaeb31c0SUwe Kleine-König 	.probe    = vidtv_tuner_i2c_probe,
42924fb190eSDaniel W. S. Almeida 	.remove   = vidtv_tuner_i2c_remove,
43024fb190eSDaniel W. S. Almeida 	.id_table = vidtv_tuner_i2c_id_table,
43124fb190eSDaniel W. S. Almeida };
43224fb190eSDaniel W. S. Almeida module_i2c_driver(vidtv_tuner_i2c_driver);
43324fb190eSDaniel W. S. Almeida 
43424fb190eSDaniel W. S. Almeida MODULE_DESCRIPTION("Virtual DVB Tuner");
43524fb190eSDaniel W. S. Almeida MODULE_AUTHOR("Daniel W. S. Almeida");
43624fb190eSDaniel W. S. Almeida MODULE_LICENSE("GPL");
437