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