1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * The Virtual DVB test driver serves as a reference DVB driver and helps |
4 | * validate the existing APIs in the media subsystem. It can also aid |
5 | * developers working on userspace applications. |
6 | * |
7 | * The vidtv tuner should support common TV standards such as |
8 | * DVB-T/T2/S/S2, ISDB-T and ATSC when completed. |
9 | * |
10 | * Copyright (C) 2020 Daniel W. S. Almeida |
11 | */ |
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 | |
25 | struct 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 | |
33 | static 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 | |
39 | static 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 | |
48 | static 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 | |
62 | static 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 status |
83 | * @asleep: whether the tuner is asleep, i.e whether _sleep() or _suspend() was |
84 | * called. |
85 | * @lock_status: Whether the tuner has managed to lock on the requested |
86 | * frequency. |
87 | * @if_frequency: The tuner's intermediate frequency. Hardcoded for the purposes |
88 | * 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 if |
93 | * we had a physical tuner hardware. |
94 | */ |
95 | struct 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 struct |
105 | * @fe: A pointer to the dvb_frontend structure allocated by vidtv_demod |
106 | * @hw_state: A struct to simulate the tuner's hardware state as if we had a |
107 | * physical tuner hardware. |
108 | * @config: The configuration used to start the tuner module, usually filled |
109 | * by a bridge driver. For vidtv, this is filled by vidtv_bridge before the |
110 | * tuner module is probed. |
111 | */ |
112 | struct vidtv_tuner_dev { |
113 | struct dvb_frontend *fe; |
114 | struct vidtv_tuner_hardware_state hw_state; |
115 | struct vidtv_tuner_config config; |
116 | }; |
117 | |
118 | static struct vidtv_tuner_dev* |
119 | vidtv_tuner_get_dev(struct dvb_frontend *fe) |
120 | { |
121 | return i2c_get_clientdata(client: fe->tuner_priv); |
122 | } |
123 | |
124 | static 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 would |
169 | * indicate how far is the tuned frequency from the |
170 | * 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 | |
179 | static int |
180 | vidtv_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 the |
238 | * 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't |
248 | * match against the table |
249 | */ |
250 | *strength = 34000 - 24000 * shift / 100; |
251 | return 0; |
252 | } |
253 | |
254 | static 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(msecs: 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 | |
267 | static 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 | |
275 | static 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 | |
283 | static 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 | |
291 | static 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(msecs: 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 | |
324 | static 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 | |
334 | static 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 | |
344 | static 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 | |
354 | static 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 | |
364 | static 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 | |
373 | static 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_strength |
385 | }; |
386 | |
387 | static const struct i2c_device_id vidtv_tuner_i2c_id_table[] = { |
388 | {"dvb_vidtv_tuner" , 0}, |
389 | {} |
390 | }; |
391 | MODULE_DEVICE_TABLE(i2c, vidtv_tuner_i2c_id_table); |
392 | |
393 | static 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(size: sizeof(*tuner_dev), GFP_KERNEL); |
400 | if (!tuner_dev) |
401 | return -ENOMEM; |
402 | |
403 | tuner_dev->fe = config->fe; |
404 | i2c_set_clientdata(client, data: 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 | |
416 | static void vidtv_tuner_i2c_remove(struct i2c_client *client) |
417 | { |
418 | struct vidtv_tuner_dev *tuner_dev = i2c_get_clientdata(client); |
419 | |
420 | kfree(objp: tuner_dev); |
421 | } |
422 | |
423 | static 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 | }; |
432 | module_i2c_driver(vidtv_tuner_i2c_driver); |
433 | |
434 | MODULE_DESCRIPTION("Virtual DVB Tuner" ); |
435 | MODULE_AUTHOR("Daniel W. S. Almeida" ); |
436 | MODULE_LICENSE("GPL" ); |
437 | |