1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Abilis Systems Single DVB-T Receiver |
4 | * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> |
5 | * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> |
6 | */ |
7 | |
8 | #include <media/dvb_frontend.h> |
9 | |
10 | #include "as102_fe.h" |
11 | |
12 | struct as102_state { |
13 | struct dvb_frontend frontend; |
14 | struct as10x_demod_stats demod_stats; |
15 | |
16 | const struct as102_fe_ops *ops; |
17 | void *priv; |
18 | uint8_t elna_cfg; |
19 | |
20 | /* signal strength */ |
21 | uint16_t signal_strength; |
22 | /* bit error rate */ |
23 | uint32_t ber; |
24 | }; |
25 | |
26 | static uint8_t as102_fe_get_code_rate(enum fe_code_rate arg) |
27 | { |
28 | uint8_t c; |
29 | |
30 | switch (arg) { |
31 | case FEC_1_2: |
32 | c = CODE_RATE_1_2; |
33 | break; |
34 | case FEC_2_3: |
35 | c = CODE_RATE_2_3; |
36 | break; |
37 | case FEC_3_4: |
38 | c = CODE_RATE_3_4; |
39 | break; |
40 | case FEC_5_6: |
41 | c = CODE_RATE_5_6; |
42 | break; |
43 | case FEC_7_8: |
44 | c = CODE_RATE_7_8; |
45 | break; |
46 | default: |
47 | c = CODE_RATE_UNKNOWN; |
48 | break; |
49 | } |
50 | |
51 | return c; |
52 | } |
53 | |
54 | static int as102_fe_set_frontend(struct dvb_frontend *fe) |
55 | { |
56 | struct as102_state *state = fe->demodulator_priv; |
57 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; |
58 | struct as10x_tune_args tune_args = { 0 }; |
59 | |
60 | /* set frequency */ |
61 | tune_args.freq = c->frequency / 1000; |
62 | |
63 | /* fix interleaving_mode */ |
64 | tune_args.interleaving_mode = INTLV_NATIVE; |
65 | |
66 | switch (c->bandwidth_hz) { |
67 | case 8000000: |
68 | tune_args.bandwidth = BW_8_MHZ; |
69 | break; |
70 | case 7000000: |
71 | tune_args.bandwidth = BW_7_MHZ; |
72 | break; |
73 | case 6000000: |
74 | tune_args.bandwidth = BW_6_MHZ; |
75 | break; |
76 | default: |
77 | tune_args.bandwidth = BW_8_MHZ; |
78 | } |
79 | |
80 | switch (c->guard_interval) { |
81 | case GUARD_INTERVAL_1_32: |
82 | tune_args.guard_interval = GUARD_INT_1_32; |
83 | break; |
84 | case GUARD_INTERVAL_1_16: |
85 | tune_args.guard_interval = GUARD_INT_1_16; |
86 | break; |
87 | case GUARD_INTERVAL_1_8: |
88 | tune_args.guard_interval = GUARD_INT_1_8; |
89 | break; |
90 | case GUARD_INTERVAL_1_4: |
91 | tune_args.guard_interval = GUARD_INT_1_4; |
92 | break; |
93 | case GUARD_INTERVAL_AUTO: |
94 | default: |
95 | tune_args.guard_interval = GUARD_UNKNOWN; |
96 | break; |
97 | } |
98 | |
99 | switch (c->modulation) { |
100 | case QPSK: |
101 | tune_args.modulation = CONST_QPSK; |
102 | break; |
103 | case QAM_16: |
104 | tune_args.modulation = CONST_QAM16; |
105 | break; |
106 | case QAM_64: |
107 | tune_args.modulation = CONST_QAM64; |
108 | break; |
109 | default: |
110 | tune_args.modulation = CONST_UNKNOWN; |
111 | break; |
112 | } |
113 | |
114 | switch (c->transmission_mode) { |
115 | case TRANSMISSION_MODE_2K: |
116 | tune_args.transmission_mode = TRANS_MODE_2K; |
117 | break; |
118 | case TRANSMISSION_MODE_8K: |
119 | tune_args.transmission_mode = TRANS_MODE_8K; |
120 | break; |
121 | default: |
122 | tune_args.transmission_mode = TRANS_MODE_UNKNOWN; |
123 | } |
124 | |
125 | switch (c->hierarchy) { |
126 | case HIERARCHY_NONE: |
127 | tune_args.hierarchy = HIER_NONE; |
128 | break; |
129 | case HIERARCHY_1: |
130 | tune_args.hierarchy = HIER_ALPHA_1; |
131 | break; |
132 | case HIERARCHY_2: |
133 | tune_args.hierarchy = HIER_ALPHA_2; |
134 | break; |
135 | case HIERARCHY_4: |
136 | tune_args.hierarchy = HIER_ALPHA_4; |
137 | break; |
138 | case HIERARCHY_AUTO: |
139 | tune_args.hierarchy = HIER_UNKNOWN; |
140 | break; |
141 | } |
142 | |
143 | pr_debug("as102: tuner parameters: freq: %d bw: 0x%02x gi: 0x%02x\n" , |
144 | c->frequency, |
145 | tune_args.bandwidth, |
146 | tune_args.guard_interval); |
147 | |
148 | /* |
149 | * Detect a hierarchy selection |
150 | * if HP/LP are both set to FEC_NONE, HP will be selected. |
151 | */ |
152 | if ((tune_args.hierarchy != HIER_NONE) && |
153 | ((c->code_rate_LP == FEC_NONE) || |
154 | (c->code_rate_HP == FEC_NONE))) { |
155 | |
156 | if (c->code_rate_LP == FEC_NONE) { |
157 | tune_args.hier_select = HIER_HIGH_PRIORITY; |
158 | tune_args.code_rate = |
159 | as102_fe_get_code_rate(arg: c->code_rate_HP); |
160 | } |
161 | |
162 | if (c->code_rate_HP == FEC_NONE) { |
163 | tune_args.hier_select = HIER_LOW_PRIORITY; |
164 | tune_args.code_rate = |
165 | as102_fe_get_code_rate(arg: c->code_rate_LP); |
166 | } |
167 | |
168 | pr_debug("as102: \thierarchy: 0x%02x selected: %s code_rate_%s: 0x%02x\n" , |
169 | tune_args.hierarchy, |
170 | tune_args.hier_select == HIER_HIGH_PRIORITY ? |
171 | "HP" : "LP" , |
172 | tune_args.hier_select == HIER_HIGH_PRIORITY ? |
173 | "HP" : "LP" , |
174 | tune_args.code_rate); |
175 | } else { |
176 | tune_args.code_rate = |
177 | as102_fe_get_code_rate(arg: c->code_rate_HP); |
178 | } |
179 | |
180 | /* Set frontend arguments */ |
181 | return state->ops->set_tune(state->priv, &tune_args); |
182 | } |
183 | |
184 | static int as102_fe_get_frontend(struct dvb_frontend *fe, |
185 | struct dtv_frontend_properties *c) |
186 | { |
187 | struct as102_state *state = fe->demodulator_priv; |
188 | int ret = 0; |
189 | struct as10x_tps tps = { 0 }; |
190 | |
191 | /* send abilis command: GET_TPS */ |
192 | ret = state->ops->get_tps(state->priv, &tps); |
193 | if (ret < 0) |
194 | return ret; |
195 | |
196 | /* extract constellation */ |
197 | switch (tps.modulation) { |
198 | case CONST_QPSK: |
199 | c->modulation = QPSK; |
200 | break; |
201 | case CONST_QAM16: |
202 | c->modulation = QAM_16; |
203 | break; |
204 | case CONST_QAM64: |
205 | c->modulation = QAM_64; |
206 | break; |
207 | } |
208 | |
209 | /* extract hierarchy */ |
210 | switch (tps.hierarchy) { |
211 | case HIER_NONE: |
212 | c->hierarchy = HIERARCHY_NONE; |
213 | break; |
214 | case HIER_ALPHA_1: |
215 | c->hierarchy = HIERARCHY_1; |
216 | break; |
217 | case HIER_ALPHA_2: |
218 | c->hierarchy = HIERARCHY_2; |
219 | break; |
220 | case HIER_ALPHA_4: |
221 | c->hierarchy = HIERARCHY_4; |
222 | break; |
223 | } |
224 | |
225 | /* extract code rate HP */ |
226 | switch (tps.code_rate_HP) { |
227 | case CODE_RATE_1_2: |
228 | c->code_rate_HP = FEC_1_2; |
229 | break; |
230 | case CODE_RATE_2_3: |
231 | c->code_rate_HP = FEC_2_3; |
232 | break; |
233 | case CODE_RATE_3_4: |
234 | c->code_rate_HP = FEC_3_4; |
235 | break; |
236 | case CODE_RATE_5_6: |
237 | c->code_rate_HP = FEC_5_6; |
238 | break; |
239 | case CODE_RATE_7_8: |
240 | c->code_rate_HP = FEC_7_8; |
241 | break; |
242 | } |
243 | |
244 | /* extract code rate LP */ |
245 | switch (tps.code_rate_LP) { |
246 | case CODE_RATE_1_2: |
247 | c->code_rate_LP = FEC_1_2; |
248 | break; |
249 | case CODE_RATE_2_3: |
250 | c->code_rate_LP = FEC_2_3; |
251 | break; |
252 | case CODE_RATE_3_4: |
253 | c->code_rate_LP = FEC_3_4; |
254 | break; |
255 | case CODE_RATE_5_6: |
256 | c->code_rate_LP = FEC_5_6; |
257 | break; |
258 | case CODE_RATE_7_8: |
259 | c->code_rate_LP = FEC_7_8; |
260 | break; |
261 | } |
262 | |
263 | /* extract guard interval */ |
264 | switch (tps.guard_interval) { |
265 | case GUARD_INT_1_32: |
266 | c->guard_interval = GUARD_INTERVAL_1_32; |
267 | break; |
268 | case GUARD_INT_1_16: |
269 | c->guard_interval = GUARD_INTERVAL_1_16; |
270 | break; |
271 | case GUARD_INT_1_8: |
272 | c->guard_interval = GUARD_INTERVAL_1_8; |
273 | break; |
274 | case GUARD_INT_1_4: |
275 | c->guard_interval = GUARD_INTERVAL_1_4; |
276 | break; |
277 | } |
278 | |
279 | /* extract transmission mode */ |
280 | switch (tps.transmission_mode) { |
281 | case TRANS_MODE_2K: |
282 | c->transmission_mode = TRANSMISSION_MODE_2K; |
283 | break; |
284 | case TRANS_MODE_8K: |
285 | c->transmission_mode = TRANSMISSION_MODE_8K; |
286 | break; |
287 | } |
288 | |
289 | return 0; |
290 | } |
291 | |
292 | static int as102_fe_get_tune_settings(struct dvb_frontend *fe, |
293 | struct dvb_frontend_tune_settings *settings) |
294 | { |
295 | |
296 | settings->min_delay_ms = 1000; |
297 | |
298 | return 0; |
299 | } |
300 | |
301 | static int as102_fe_read_status(struct dvb_frontend *fe, enum fe_status *status) |
302 | { |
303 | int ret = 0; |
304 | struct as102_state *state = fe->demodulator_priv; |
305 | struct as10x_tune_status tstate = { 0 }; |
306 | |
307 | /* send abilis command: GET_TUNE_STATUS */ |
308 | ret = state->ops->get_status(state->priv, &tstate); |
309 | if (ret < 0) |
310 | return ret; |
311 | |
312 | state->signal_strength = tstate.signal_strength; |
313 | state->ber = tstate.BER; |
314 | |
315 | switch (tstate.tune_state) { |
316 | case TUNE_STATUS_SIGNAL_DVB_OK: |
317 | *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; |
318 | break; |
319 | case TUNE_STATUS_STREAM_DETECTED: |
320 | *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC | |
321 | FE_HAS_VITERBI; |
322 | break; |
323 | case TUNE_STATUS_STREAM_TUNED: |
324 | *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC | |
325 | FE_HAS_LOCK | FE_HAS_VITERBI; |
326 | break; |
327 | default: |
328 | *status = TUNE_STATUS_NOT_TUNED; |
329 | } |
330 | |
331 | pr_debug("as102: tuner status: 0x%02x, strength %d, per: %d, ber: %d\n" , |
332 | tstate.tune_state, tstate.signal_strength, |
333 | tstate.PER, tstate.BER); |
334 | |
335 | if (!(*status & FE_HAS_LOCK)) { |
336 | memset(&state->demod_stats, 0, sizeof(state->demod_stats)); |
337 | return 0; |
338 | } |
339 | |
340 | ret = state->ops->get_stats(state->priv, &state->demod_stats); |
341 | if (ret < 0) |
342 | memset(&state->demod_stats, 0, sizeof(state->demod_stats)); |
343 | |
344 | return ret; |
345 | } |
346 | |
347 | /* |
348 | * Note: |
349 | * - in AS102 SNR=MER |
350 | * - the SNR will be returned in linear terms, i.e. not in dB |
351 | * - the accuracy equals ±2dB for a SNR range from 4dB to 30dB |
352 | * - the accuracy is >2dB for SNR values outside this range |
353 | */ |
354 | static int as102_fe_read_snr(struct dvb_frontend *fe, u16 *snr) |
355 | { |
356 | struct as102_state *state = fe->demodulator_priv; |
357 | |
358 | *snr = state->demod_stats.mer; |
359 | |
360 | return 0; |
361 | } |
362 | |
363 | static int as102_fe_read_ber(struct dvb_frontend *fe, u32 *ber) |
364 | { |
365 | struct as102_state *state = fe->demodulator_priv; |
366 | |
367 | *ber = state->ber; |
368 | |
369 | return 0; |
370 | } |
371 | |
372 | static int as102_fe_read_signal_strength(struct dvb_frontend *fe, |
373 | u16 *strength) |
374 | { |
375 | struct as102_state *state = fe->demodulator_priv; |
376 | |
377 | *strength = (((0xffff * 400) * state->signal_strength + 41000) * 2); |
378 | |
379 | return 0; |
380 | } |
381 | |
382 | static int as102_fe_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) |
383 | { |
384 | struct as102_state *state = fe->demodulator_priv; |
385 | |
386 | if (state->demod_stats.has_started) |
387 | *ucblocks = state->demod_stats.bad_frame_count; |
388 | else |
389 | *ucblocks = 0; |
390 | |
391 | return 0; |
392 | } |
393 | |
394 | static int as102_fe_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) |
395 | { |
396 | struct as102_state *state = fe->demodulator_priv; |
397 | |
398 | return state->ops->stream_ctrl(state->priv, acquire, |
399 | state->elna_cfg); |
400 | } |
401 | |
402 | static void as102_fe_release(struct dvb_frontend *fe) |
403 | { |
404 | struct as102_state *state = fe->demodulator_priv; |
405 | |
406 | kfree(objp: state); |
407 | } |
408 | |
409 | |
410 | static const struct dvb_frontend_ops as102_fe_ops = { |
411 | .delsys = { SYS_DVBT }, |
412 | .info = { |
413 | .name = "Abilis AS102 DVB-T" , |
414 | .frequency_min_hz = 174 * MHz, |
415 | .frequency_max_hz = 862 * MHz, |
416 | .frequency_stepsize_hz = 166667, |
417 | .caps = FE_CAN_INVERSION_AUTO |
418 | | FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
419 | | FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
420 | | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QPSK |
421 | | FE_CAN_QAM_AUTO |
422 | | FE_CAN_TRANSMISSION_MODE_AUTO |
423 | | FE_CAN_GUARD_INTERVAL_AUTO |
424 | | FE_CAN_HIERARCHY_AUTO |
425 | | FE_CAN_RECOVER |
426 | | FE_CAN_MUTE_TS |
427 | }, |
428 | |
429 | .set_frontend = as102_fe_set_frontend, |
430 | .get_frontend = as102_fe_get_frontend, |
431 | .get_tune_settings = as102_fe_get_tune_settings, |
432 | |
433 | .read_status = as102_fe_read_status, |
434 | .read_snr = as102_fe_read_snr, |
435 | .read_ber = as102_fe_read_ber, |
436 | .read_signal_strength = as102_fe_read_signal_strength, |
437 | .read_ucblocks = as102_fe_read_ucblocks, |
438 | .ts_bus_ctrl = as102_fe_ts_bus_ctrl, |
439 | .release = as102_fe_release, |
440 | }; |
441 | |
442 | struct dvb_frontend *as102_attach(const char *name, |
443 | const struct as102_fe_ops *ops, |
444 | void *priv, |
445 | uint8_t elna_cfg) |
446 | { |
447 | struct as102_state *state; |
448 | struct dvb_frontend *fe; |
449 | |
450 | state = kzalloc(size: sizeof(*state), GFP_KERNEL); |
451 | if (!state) |
452 | return NULL; |
453 | |
454 | fe = &state->frontend; |
455 | fe->demodulator_priv = state; |
456 | state->ops = ops; |
457 | state->priv = priv; |
458 | state->elna_cfg = elna_cfg; |
459 | |
460 | /* init frontend callback ops */ |
461 | memcpy(&fe->ops, &as102_fe_ops, sizeof(struct dvb_frontend_ops)); |
462 | strscpy(p: fe->ops.info.name, q: name, size: sizeof(fe->ops.info.name)); |
463 | |
464 | return fe; |
465 | |
466 | } |
467 | EXPORT_SYMBOL_GPL(as102_attach); |
468 | |
469 | MODULE_DESCRIPTION("as102-fe" ); |
470 | MODULE_LICENSE("GPL" ); |
471 | MODULE_AUTHOR("Pierrick Hascoet <pierrick.hascoet@abilis.com>" ); |
472 | |