437 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * The Virtual DVB test driver serves as a reference DVB driver and helps4 * validate the existing APIs in the media subsystem. It can also aid5 * developers working on userspace applications.6 *7 * The vidtv tuner should support common TV standards such as8 * DVB-T/T2/S/S2, ISDB-T and ATSC when completed.9 *10 * Copyright (C) 2020 Daniel W. S. Almeida11 */12 13#include <linux/errno.h>14#include <linux/i2c.h>15#include <linux/module.h>16#include <linux/printk.h>17#include <linux/ratelimit.h>18#include <linux/slab.h>19#include <linux/types.h>20 21#include <media/dvb_frontend.h>22 23#include "vidtv_tuner.h"24 25struct vidtv_tuner_cnr_to_qual_s {26 /* attempt to use the same values as libdvbv5 */27 u32 modulation;28 u32 fec;29 u32 cnr_ok;30 u32 cnr_good;31};32 33static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_c_cnr_2_qual[] = {34 /* from libdvbv5 source code, in milli db */35 { QAM_256, FEC_NONE, 34000, 38000},36 { QAM_64, FEC_NONE, 30000, 34000},37};38 39static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s_cnr_2_qual[] = {40 /* from libdvbv5 source code, in milli db */41 { QPSK, FEC_1_2, 7000, 10000},42 { QPSK, FEC_2_3, 9000, 12000},43 { QPSK, FEC_3_4, 10000, 13000},44 { QPSK, FEC_5_6, 11000, 14000},45 { QPSK, FEC_7_8, 12000, 15000},46};47 48static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s2_cnr_2_qual[] = {49 /* from libdvbv5 source code, in milli db */50 { QPSK, FEC_1_2, 9000, 12000},51 { QPSK, FEC_2_3, 11000, 14000},52 { QPSK, FEC_3_4, 12000, 15000},53 { QPSK, FEC_5_6, 12000, 15000},54 { QPSK, FEC_8_9, 13000, 16000},55 { QPSK, FEC_9_10, 13500, 16500},56 { PSK_8, FEC_2_3, 14500, 17500},57 { PSK_8, FEC_3_4, 16000, 19000},58 { PSK_8, FEC_5_6, 17500, 20500},59 { PSK_8, FEC_8_9, 19000, 22000},60};61 62static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_t_cnr_2_qual[] = {63 /* from libdvbv5 source code, in milli db*/64 { QPSK, FEC_1_2, 4100, 5900},65 { QPSK, FEC_2_3, 6100, 9600},66 { QPSK, FEC_3_4, 7200, 12400},67 { QPSK, FEC_5_6, 8500, 15600},68 { QPSK, FEC_7_8, 9200, 17500},69 { QAM_16, FEC_1_2, 9800, 11800},70 { QAM_16, FEC_2_3, 12100, 15300},71 { QAM_16, FEC_3_4, 13400, 18100},72 { QAM_16, FEC_5_6, 14800, 21300},73 { QAM_16, FEC_7_8, 15700, 23600},74 { QAM_64, FEC_1_2, 14000, 16000},75 { QAM_64, FEC_2_3, 19900, 25400},76 { QAM_64, FEC_3_4, 24900, 27900},77 { QAM_64, FEC_5_6, 21300, 23300},78 { QAM_64, FEC_7_8, 22000, 24000},79};80 81/**82 * struct vidtv_tuner_hardware_state - Simulate the tuner hardware status83 * @asleep: whether the tuner is asleep, i.e whether _sleep() or _suspend() was84 * called.85 * @lock_status: Whether the tuner has managed to lock on the requested86 * frequency.87 * @if_frequency: The tuner's intermediate frequency. Hardcoded for the purposes88 * of simulation.89 * @tuned_frequency: The actual tuned frequency.90 * @bandwidth: The actual bandwidth.91 *92 * This structure is meant to simulate the status of the tuner hardware, as if93 * we had a physical tuner hardware.94 */95struct vidtv_tuner_hardware_state {96 bool asleep;97 u32 lock_status;98 u32 if_frequency;99 u32 tuned_frequency;100 u32 bandwidth;101};102 103/**104 * struct vidtv_tuner_dev - The tuner struct105 * @fe: A pointer to the dvb_frontend structure allocated by vidtv_demod106 * @hw_state: A struct to simulate the tuner's hardware state as if we had a107 * physical tuner hardware.108 * @config: The configuration used to start the tuner module, usually filled109 * by a bridge driver. For vidtv, this is filled by vidtv_bridge before the110 * tuner module is probed.111 */112struct vidtv_tuner_dev {113 struct dvb_frontend *fe;114 struct vidtv_tuner_hardware_state hw_state;115 struct vidtv_tuner_config config;116};117 118static struct vidtv_tuner_dev*119vidtv_tuner_get_dev(struct dvb_frontend *fe)120{121 return i2c_get_clientdata(fe->tuner_priv);122}123 124static int vidtv_tuner_check_frequency_shift(struct dvb_frontend *fe)125{126 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);127 struct dtv_frontend_properties *c = &fe->dtv_property_cache;128 struct vidtv_tuner_config config = tuner_dev->config;129 u32 *valid_freqs = NULL;130 u32 array_sz = 0;131 u32 i;132 u32 shift;133 134 switch (c->delivery_system) {135 case SYS_DVBT:136 case SYS_DVBT2:137 valid_freqs = config.vidtv_valid_dvb_t_freqs;138 array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_t_freqs);139 break;140 case SYS_DVBS:141 case SYS_DVBS2:142 valid_freqs = config.vidtv_valid_dvb_s_freqs;143 array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_s_freqs);144 break;145 case SYS_DVBC_ANNEX_A:146 valid_freqs = config.vidtv_valid_dvb_c_freqs;147 array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_c_freqs);148 break;149 150 default:151 dev_warn(fe->dvb->device,152 "%s: unsupported delivery system: %u\n",153 __func__,154 c->delivery_system);155 156 return -EINVAL;157 }158 159 for (i = 0; i < array_sz; i++) {160 if (!valid_freqs[i])161 break;162 shift = abs(c->frequency - valid_freqs[i]);163 164 if (!shift)165 return 0;166 167 /*168 * This will provide a value from 0 to 100 that would169 * indicate how far is the tuned frequency from the170 * right one.171 */172 if (shift < config.max_frequency_shift_hz)173 return shift * 100 / config.max_frequency_shift_hz;174 }175 176 return -EINVAL;177}178 179static int180vidtv_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength)181{182 struct dtv_frontend_properties *c = &fe->dtv_property_cache;183 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);184 const struct vidtv_tuner_cnr_to_qual_s *cnr2qual = NULL;185 struct device *dev = fe->dvb->device;186 u32 array_size = 0;187 s32 shift;188 u32 i;189 190 shift = vidtv_tuner_check_frequency_shift(fe);191 if (shift < 0) {192 tuner_dev->hw_state.lock_status = 0;193 *strength = 0;194 return 0;195 }196 197 switch (c->delivery_system) {198 case SYS_DVBT:199 case SYS_DVBT2:200 cnr2qual = vidtv_tuner_t_cnr_2_qual;201 array_size = ARRAY_SIZE(vidtv_tuner_t_cnr_2_qual);202 break;203 204 case SYS_DVBS:205 cnr2qual = vidtv_tuner_s_cnr_2_qual;206 array_size = ARRAY_SIZE(vidtv_tuner_s_cnr_2_qual);207 break;208 209 case SYS_DVBS2:210 cnr2qual = vidtv_tuner_s2_cnr_2_qual;211 array_size = ARRAY_SIZE(vidtv_tuner_s2_cnr_2_qual);212 break;213 214 case SYS_DVBC_ANNEX_A:215 cnr2qual = vidtv_tuner_c_cnr_2_qual;216 array_size = ARRAY_SIZE(vidtv_tuner_c_cnr_2_qual);217 break;218 219 default:220 dev_warn_ratelimited(dev,221 "%s: unsupported delivery system: %u\n",222 __func__,223 c->delivery_system);224 return -EINVAL;225 }226 227 for (i = 0; i < array_size; i++) {228 if (cnr2qual[i].modulation != c->modulation ||229 cnr2qual[i].fec != c->fec_inner)230 continue;231 232 if (!shift) {233 *strength = cnr2qual[i].cnr_good;234 return 0;235 }236 /*237 * Channel tuned at wrong frequency. Simulate that the238 * Carrier S/N ratio is not too good.239 */240 241 *strength = cnr2qual[i].cnr_ok -242 (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok);243 return 0;244 }245 246 /*247 * do a linear interpolation between 34dB and 10dB if we can't248 * match against the table249 */250 *strength = 34000 - 24000 * shift / 100;251 return 0;252}253 254static int vidtv_tuner_init(struct dvb_frontend *fe)255{256 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);257 struct vidtv_tuner_config config = tuner_dev->config;258 259 msleep_interruptible(config.mock_power_up_delay_msec);260 261 tuner_dev->hw_state.asleep = false;262 tuner_dev->hw_state.if_frequency = 5000;263 264 return 0;265}266 267static int vidtv_tuner_sleep(struct dvb_frontend *fe)268{269 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);270 271 tuner_dev->hw_state.asleep = true;272 return 0;273}274 275static int vidtv_tuner_suspend(struct dvb_frontend *fe)276{277 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);278 279 tuner_dev->hw_state.asleep = true;280 return 0;281}282 283static int vidtv_tuner_resume(struct dvb_frontend *fe)284{285 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);286 287 tuner_dev->hw_state.asleep = false;288 return 0;289}290 291static int vidtv_tuner_set_params(struct dvb_frontend *fe)292{293 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);294 struct vidtv_tuner_config config = tuner_dev->config;295 struct dtv_frontend_properties *c = &fe->dtv_property_cache;296 s32 shift;297 298 u32 min_freq = fe->ops.tuner_ops.info.frequency_min_hz;299 u32 max_freq = fe->ops.tuner_ops.info.frequency_max_hz;300 u32 min_bw = fe->ops.tuner_ops.info.bandwidth_min;301 u32 max_bw = fe->ops.tuner_ops.info.bandwidth_max;302 303 if (c->frequency < min_freq || c->frequency > max_freq ||304 c->bandwidth_hz < min_bw || c->bandwidth_hz > max_bw) {305 tuner_dev->hw_state.lock_status = 0;306 return -EINVAL;307 }308 309 tuner_dev->hw_state.tuned_frequency = c->frequency;310 tuner_dev->hw_state.bandwidth = c->bandwidth_hz;311 tuner_dev->hw_state.lock_status = TUNER_STATUS_LOCKED;312 313 msleep_interruptible(config.mock_tune_delay_msec);314 315 shift = vidtv_tuner_check_frequency_shift(fe);316 if (shift < 0) {317 tuner_dev->hw_state.lock_status = 0;318 return shift;319 }320 321 return 0;322}323 324static int vidtv_tuner_set_config(struct dvb_frontend *fe,325 void *priv_cfg)326{327 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);328 329 memcpy(&tuner_dev->config, priv_cfg, sizeof(tuner_dev->config));330 331 return 0;332}333 334static int vidtv_tuner_get_frequency(struct dvb_frontend *fe,335 u32 *frequency)336{337 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);338 339 *frequency = tuner_dev->hw_state.tuned_frequency;340 341 return 0;342}343 344static int vidtv_tuner_get_bandwidth(struct dvb_frontend *fe,345 u32 *bandwidth)346{347 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);348 349 *bandwidth = tuner_dev->hw_state.bandwidth;350 351 return 0;352}353 354static int vidtv_tuner_get_if_frequency(struct dvb_frontend *fe,355 u32 *frequency)356{357 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);358 359 *frequency = tuner_dev->hw_state.if_frequency;360 361 return 0;362}363 364static int vidtv_tuner_get_status(struct dvb_frontend *fe, u32 *status)365{366 struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);367 368 *status = tuner_dev->hw_state.lock_status;369 370 return 0;371}372 373static const struct dvb_tuner_ops vidtv_tuner_ops = {374 .init = vidtv_tuner_init,375 .sleep = vidtv_tuner_sleep,376 .suspend = vidtv_tuner_suspend,377 .resume = vidtv_tuner_resume,378 .set_params = vidtv_tuner_set_params,379 .set_config = vidtv_tuner_set_config,380 .get_bandwidth = vidtv_tuner_get_bandwidth,381 .get_frequency = vidtv_tuner_get_frequency,382 .get_if_frequency = vidtv_tuner_get_if_frequency,383 .get_status = vidtv_tuner_get_status,384 .get_rf_strength = vidtv_tuner_get_signal_strength385};386 387static const struct i2c_device_id vidtv_tuner_i2c_id_table[] = {388 { "dvb_vidtv_tuner" },389 {}390};391MODULE_DEVICE_TABLE(i2c, vidtv_tuner_i2c_id_table);392 393static int vidtv_tuner_i2c_probe(struct i2c_client *client)394{395 struct vidtv_tuner_config *config = client->dev.platform_data;396 struct dvb_frontend *fe = config->fe;397 struct vidtv_tuner_dev *tuner_dev = NULL;398 399 tuner_dev = kzalloc(sizeof(*tuner_dev), GFP_KERNEL);400 if (!tuner_dev)401 return -ENOMEM;402 403 tuner_dev->fe = config->fe;404 i2c_set_clientdata(client, tuner_dev);405 406 memcpy(&fe->ops.tuner_ops,407 &vidtv_tuner_ops,408 sizeof(struct dvb_tuner_ops));409 410 memcpy(&tuner_dev->config, config, sizeof(tuner_dev->config));411 fe->tuner_priv = client;412 413 return 0;414}415 416static void vidtv_tuner_i2c_remove(struct i2c_client *client)417{418 struct vidtv_tuner_dev *tuner_dev = i2c_get_clientdata(client);419 420 kfree(tuner_dev);421}422 423static struct i2c_driver vidtv_tuner_i2c_driver = {424 .driver = {425 .name = "dvb_vidtv_tuner",426 .suppress_bind_attrs = true,427 },428 .probe = vidtv_tuner_i2c_probe,429 .remove = vidtv_tuner_i2c_remove,430 .id_table = vidtv_tuner_i2c_id_table,431};432module_i2c_driver(vidtv_tuner_i2c_driver);433 434MODULE_DESCRIPTION("Virtual DVB Tuner");435MODULE_AUTHOR("Daniel W. S. Almeida");436MODULE_LICENSE("GPL");437