Commit | Line | Data |
---|---|---|
3e0a4e85 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
41b44e04 PH |
2 | /* |
3 | * Abilis Systems Single DVB-T Receiver | |
4 | * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> | |
87ad567e | 5 | * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> |
41b44e04 | 6 | */ |
47f79129 | 7 | |
fada1935 | 8 | #include <media/dvb_frontend.h> |
47f79129 MCC |
9 | |
10 | #include "as102_fe.h" | |
41b44e04 | 11 | |
b601d9a5 MCC |
12 | struct as102_state { |
13 | struct dvb_frontend frontend; | |
14 | struct as10x_demod_stats demod_stats; | |
41b44e04 | 15 | |
47f79129 MCC |
16 | const struct as102_fe_ops *ops; |
17 | void *priv; | |
b601d9a5 | 18 | uint8_t elna_cfg; |
41b44e04 | 19 | |
b601d9a5 MCC |
20 | /* signal strength */ |
21 | uint16_t signal_strength; | |
22 | /* bit error rate */ | |
23 | uint32_t ber; | |
41b44e04 PH |
24 | }; |
25 | ||
0df289a2 | 26 | static uint8_t as102_fe_get_code_rate(enum fe_code_rate arg) |
87ad567e | 27 | { |
41b44e04 PH |
28 | uint8_t c; |
29 | ||
87ad567e DH |
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; | |
41b44e04 PH |
49 | } |
50 | ||
51 | return c; | |
52 | } | |
53 | ||
1d6207fd | 54 | static int as102_fe_set_frontend(struct dvb_frontend *fe) |
87ad567e | 55 | { |
1d6207fd MCC |
56 | struct as102_state *state = fe->demodulator_priv; |
57 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; | |
1d6207fd | 58 | struct as10x_tune_args tune_args = { 0 }; |
41b44e04 PH |
59 | |
60 | /* set frequency */ | |
1d6207fd | 61 | tune_args.freq = c->frequency / 1000; |
41b44e04 PH |
62 | |
63 | /* fix interleaving_mode */ | |
1d6207fd | 64 | tune_args.interleaving_mode = INTLV_NATIVE; |
41b44e04 | 65 | |
1d6207fd | 66 | switch (c->bandwidth_hz) { |
dfc64384 | 67 | case 8000000: |
1d6207fd | 68 | tune_args.bandwidth = BW_8_MHZ; |
87ad567e | 69 | break; |
dfc64384 | 70 | case 7000000: |
1d6207fd | 71 | tune_args.bandwidth = BW_7_MHZ; |
87ad567e | 72 | break; |
dfc64384 | 73 | case 6000000: |
1d6207fd | 74 | tune_args.bandwidth = BW_6_MHZ; |
87ad567e DH |
75 | break; |
76 | default: | |
1d6207fd | 77 | tune_args.bandwidth = BW_8_MHZ; |
41b44e04 PH |
78 | } |
79 | ||
1d6207fd | 80 | switch (c->guard_interval) { |
87ad567e | 81 | case GUARD_INTERVAL_1_32: |
1d6207fd | 82 | tune_args.guard_interval = GUARD_INT_1_32; |
87ad567e DH |
83 | break; |
84 | case GUARD_INTERVAL_1_16: | |
1d6207fd | 85 | tune_args.guard_interval = GUARD_INT_1_16; |
87ad567e DH |
86 | break; |
87 | case GUARD_INTERVAL_1_8: | |
1d6207fd | 88 | tune_args.guard_interval = GUARD_INT_1_8; |
87ad567e DH |
89 | break; |
90 | case GUARD_INTERVAL_1_4: | |
1d6207fd | 91 | tune_args.guard_interval = GUARD_INT_1_4; |
87ad567e DH |
92 | break; |
93 | case GUARD_INTERVAL_AUTO: | |
94 | default: | |
1d6207fd | 95 | tune_args.guard_interval = GUARD_UNKNOWN; |
87ad567e | 96 | break; |
41b44e04 PH |
97 | } |
98 | ||
1d6207fd | 99 | switch (c->modulation) { |
87ad567e | 100 | case QPSK: |
1d6207fd | 101 | tune_args.modulation = CONST_QPSK; |
87ad567e DH |
102 | break; |
103 | case QAM_16: | |
1d6207fd | 104 | tune_args.modulation = CONST_QAM16; |
87ad567e DH |
105 | break; |
106 | case QAM_64: | |
1d6207fd | 107 | tune_args.modulation = CONST_QAM64; |
87ad567e DH |
108 | break; |
109 | default: | |
1d6207fd | 110 | tune_args.modulation = CONST_UNKNOWN; |
87ad567e | 111 | break; |
41b44e04 PH |
112 | } |
113 | ||
1d6207fd | 114 | switch (c->transmission_mode) { |
87ad567e | 115 | case TRANSMISSION_MODE_2K: |
1d6207fd | 116 | tune_args.transmission_mode = TRANS_MODE_2K; |
87ad567e DH |
117 | break; |
118 | case TRANSMISSION_MODE_8K: | |
1d6207fd | 119 | tune_args.transmission_mode = TRANS_MODE_8K; |
87ad567e DH |
120 | break; |
121 | default: | |
1d6207fd | 122 | tune_args.transmission_mode = TRANS_MODE_UNKNOWN; |
41b44e04 PH |
123 | } |
124 | ||
1d6207fd | 125 | switch (c->hierarchy) { |
87ad567e | 126 | case HIERARCHY_NONE: |
1d6207fd | 127 | tune_args.hierarchy = HIER_NONE; |
87ad567e DH |
128 | break; |
129 | case HIERARCHY_1: | |
1d6207fd | 130 | tune_args.hierarchy = HIER_ALPHA_1; |
87ad567e DH |
131 | break; |
132 | case HIERARCHY_2: | |
1d6207fd | 133 | tune_args.hierarchy = HIER_ALPHA_2; |
87ad567e DH |
134 | break; |
135 | case HIERARCHY_4: | |
1d6207fd | 136 | tune_args.hierarchy = HIER_ALPHA_4; |
87ad567e DH |
137 | break; |
138 | case HIERARCHY_AUTO: | |
1d6207fd | 139 | tune_args.hierarchy = HIER_UNKNOWN; |
87ad567e | 140 | break; |
41b44e04 PH |
141 | } |
142 | ||
2179de60 | 143 | pr_debug("as102: tuner parameters: freq: %d bw: 0x%02x gi: 0x%02x\n", |
1d6207fd MCC |
144 | c->frequency, |
145 | tune_args.bandwidth, | |
146 | tune_args.guard_interval); | |
41b44e04 PH |
147 | |
148 | /* | |
149 | * Detect a hierarchy selection | |
150 | * if HP/LP are both set to FEC_NONE, HP will be selected. | |
151 | */ | |
1d6207fd MCC |
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(c->code_rate_HP); | |
41b44e04 PH |
160 | } |
161 | ||
1d6207fd MCC |
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(c->code_rate_LP); | |
41b44e04 PH |
166 | } |
167 | ||
2179de60 | 168 | pr_debug("as102: \thierarchy: 0x%02x selected: %s code_rate_%s: 0x%02x\n", |
1d6207fd MCC |
169 | tune_args.hierarchy, |
170 | tune_args.hier_select == HIER_HIGH_PRIORITY ? | |
87ad567e | 171 | "HP" : "LP", |
1d6207fd | 172 | tune_args.hier_select == HIER_HIGH_PRIORITY ? |
87ad567e | 173 | "HP" : "LP", |
1d6207fd | 174 | tune_args.code_rate); |
41b44e04 | 175 | } else { |
1d6207fd MCC |
176 | tune_args.code_rate = |
177 | as102_fe_get_code_rate(c->code_rate_HP); | |
41b44e04 | 178 | } |
b601d9a5 | 179 | |
1d6207fd | 180 | /* Set frontend arguments */ |
47f79129 | 181 | return state->ops->set_tune(state->priv, &tune_args); |
b601d9a5 MCC |
182 | } |
183 | ||
7e3e68bc MCC |
184 | static int as102_fe_get_frontend(struct dvb_frontend *fe, |
185 | struct dtv_frontend_properties *c) | |
b601d9a5 MCC |
186 | { |
187 | struct as102_state *state = fe->demodulator_priv; | |
b601d9a5 MCC |
188 | int ret = 0; |
189 | struct as10x_tps tps = { 0 }; | |
190 | ||
b601d9a5 | 191 | /* send abilis command: GET_TPS */ |
47f79129 | 192 | ret = state->ops->get_tps(state->priv, &tps); |
c098c219 MCC |
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; | |
b601d9a5 MCC |
290 | } |
291 | ||
292 | static int as102_fe_get_tune_settings(struct dvb_frontend *fe, | |
293 | struct dvb_frontend_tune_settings *settings) { | |
294 | ||
295 | settings->min_delay_ms = 1000; | |
296 | ||
297 | return 0; | |
298 | } | |
299 | ||
0df289a2 | 300 | static int as102_fe_read_status(struct dvb_frontend *fe, enum fe_status *status) |
b601d9a5 MCC |
301 | { |
302 | int ret = 0; | |
303 | struct as102_state *state = fe->demodulator_priv; | |
304 | struct as10x_tune_status tstate = { 0 }; | |
305 | ||
b601d9a5 | 306 | /* send abilis command: GET_TUNE_STATUS */ |
47f79129 MCC |
307 | ret = state->ops->get_status(state->priv, &tstate); |
308 | if (ret < 0) | |
309 | return ret; | |
b601d9a5 MCC |
310 | |
311 | state->signal_strength = tstate.signal_strength; | |
312 | state->ber = tstate.BER; | |
313 | ||
314 | switch (tstate.tune_state) { | |
315 | case TUNE_STATUS_SIGNAL_DVB_OK: | |
316 | *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; | |
317 | break; | |
318 | case TUNE_STATUS_STREAM_DETECTED: | |
4628f993 MCC |
319 | *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC | |
320 | FE_HAS_VITERBI; | |
b601d9a5 MCC |
321 | break; |
322 | case TUNE_STATUS_STREAM_TUNED: | |
323 | *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC | | |
4628f993 | 324 | FE_HAS_LOCK | FE_HAS_VITERBI; |
b601d9a5 MCC |
325 | break; |
326 | default: | |
327 | *status = TUNE_STATUS_NOT_TUNED; | |
328 | } | |
329 | ||
47f79129 MCC |
330 | pr_debug("as102: tuner status: 0x%02x, strength %d, per: %d, ber: %d\n", |
331 | tstate.tune_state, tstate.signal_strength, | |
332 | tstate.PER, tstate.BER); | |
333 | ||
334 | if (!(*status & FE_HAS_LOCK)) { | |
b601d9a5 | 335 | memset(&state->demod_stats, 0, sizeof(state->demod_stats)); |
47f79129 | 336 | return 0; |
b601d9a5 MCC |
337 | } |
338 | ||
47f79129 MCC |
339 | ret = state->ops->get_stats(state->priv, &state->demod_stats); |
340 | if (ret < 0) | |
341 | memset(&state->demod_stats, 0, sizeof(state->demod_stats)); | |
342 | ||
b601d9a5 MCC |
343 | return ret; |
344 | } | |
345 | ||
346 | /* | |
347 | * Note: | |
348 | * - in AS102 SNR=MER | |
349 | * - the SNR will be returned in linear terms, i.e. not in dB | |
350 | * - the accuracy equals ±2dB for a SNR range from 4dB to 30dB | |
351 | * - the accuracy is >2dB for SNR values outside this range | |
352 | */ | |
353 | static int as102_fe_read_snr(struct dvb_frontend *fe, u16 *snr) | |
354 | { | |
355 | struct as102_state *state = fe->demodulator_priv; | |
356 | ||
357 | *snr = state->demod_stats.mer; | |
358 | ||
359 | return 0; | |
360 | } | |
361 | ||
362 | static int as102_fe_read_ber(struct dvb_frontend *fe, u32 *ber) | |
363 | { | |
364 | struct as102_state *state = fe->demodulator_priv; | |
365 | ||
366 | *ber = state->ber; | |
367 | ||
368 | return 0; | |
369 | } | |
370 | ||
371 | static int as102_fe_read_signal_strength(struct dvb_frontend *fe, | |
372 | u16 *strength) | |
373 | { | |
374 | struct as102_state *state = fe->demodulator_priv; | |
375 | ||
376 | *strength = (((0xffff * 400) * state->signal_strength + 41000) * 2); | |
377 | ||
378 | return 0; | |
379 | } | |
380 | ||
381 | static int as102_fe_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) | |
382 | { | |
383 | struct as102_state *state = fe->demodulator_priv; | |
384 | ||
385 | if (state->demod_stats.has_started) | |
386 | *ucblocks = state->demod_stats.bad_frame_count; | |
387 | else | |
388 | *ucblocks = 0; | |
389 | ||
390 | return 0; | |
391 | } | |
392 | ||
393 | static int as102_fe_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) | |
394 | { | |
395 | struct as102_state *state = fe->demodulator_priv; | |
b601d9a5 | 396 | |
47f79129 MCC |
397 | return state->ops->stream_ctrl(state->priv, acquire, |
398 | state->elna_cfg); | |
b601d9a5 MCC |
399 | } |
400 | ||
5b6aa199 MCC |
401 | static void as102_fe_release(struct dvb_frontend *fe) |
402 | { | |
403 | struct as102_state *state = fe->demodulator_priv; | |
404 | ||
405 | kfree(state); | |
406 | } | |
407 | ||
408 | ||
bd336e63 | 409 | static const struct dvb_frontend_ops as102_fe_ops = { |
b601d9a5 MCC |
410 | .delsys = { SYS_DVBT }, |
411 | .info = { | |
412 | .name = "Abilis AS102 DVB-T", | |
f1b1eabf MCC |
413 | .frequency_min_hz = 174 * MHz, |
414 | .frequency_max_hz = 862 * MHz, | |
415 | .frequency_stepsize_hz = 166667, | |
b601d9a5 MCC |
416 | .caps = FE_CAN_INVERSION_AUTO |
417 | | FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | |
418 | | FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | |
419 | | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QPSK | |
420 | | FE_CAN_QAM_AUTO | |
421 | | FE_CAN_TRANSMISSION_MODE_AUTO | |
422 | | FE_CAN_GUARD_INTERVAL_AUTO | |
423 | | FE_CAN_HIERARCHY_AUTO | |
424 | | FE_CAN_RECOVER | |
425 | | FE_CAN_MUTE_TS | |
426 | }, | |
427 | ||
428 | .set_frontend = as102_fe_set_frontend, | |
429 | .get_frontend = as102_fe_get_frontend, | |
430 | .get_tune_settings = as102_fe_get_tune_settings, | |
431 | ||
432 | .read_status = as102_fe_read_status, | |
433 | .read_snr = as102_fe_read_snr, | |
434 | .read_ber = as102_fe_read_ber, | |
435 | .read_signal_strength = as102_fe_read_signal_strength, | |
436 | .read_ucblocks = as102_fe_read_ucblocks, | |
437 | .ts_bus_ctrl = as102_fe_ts_bus_ctrl, | |
5b6aa199 | 438 | .release = as102_fe_release, |
b601d9a5 MCC |
439 | }; |
440 | ||
441 | struct dvb_frontend *as102_attach(const char *name, | |
47f79129 MCC |
442 | const struct as102_fe_ops *ops, |
443 | void *priv, | |
b601d9a5 MCC |
444 | uint8_t elna_cfg) |
445 | { | |
446 | struct as102_state *state; | |
447 | struct dvb_frontend *fe; | |
448 | ||
2d3da59f | 449 | state = kzalloc(sizeof(*state), GFP_KERNEL); |
c38e8657 | 450 | if (!state) |
b601d9a5 | 451 | return NULL; |
c38e8657 | 452 | |
b601d9a5 MCC |
453 | fe = &state->frontend; |
454 | fe->demodulator_priv = state; | |
47f79129 MCC |
455 | state->ops = ops; |
456 | state->priv = priv; | |
b601d9a5 MCC |
457 | state->elna_cfg = elna_cfg; |
458 | ||
459 | /* init frontend callback ops */ | |
460 | memcpy(&fe->ops, &as102_fe_ops, sizeof(struct dvb_frontend_ops)); | |
85709cbf | 461 | strscpy(fe->ops.info.name, name, sizeof(fe->ops.info.name)); |
b601d9a5 MCC |
462 | |
463 | return fe; | |
464 | ||
465 | } | |
466 | EXPORT_SYMBOL_GPL(as102_attach); | |
dcae7781 MCC |
467 | |
468 | MODULE_DESCRIPTION("as102-fe"); | |
469 | MODULE_LICENSE("GPL"); | |
470 | MODULE_AUTHOR("Pierrick Hascoet <pierrick.hascoet@abilis.com>"); |