brintos

brintos / linux-shallow public Read only

0
0
Text · 11.4 KiB · 4ba302d Raw
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