1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * TerraTec Cinergy T2/qanu USB2 DVB-T adapter. |
4 | * |
5 | * Copyright (C) 2007 Tomi Orava (tomimo@ncircle.nullnet.fi) |
6 | * |
7 | * Based on the dvb-usb-framework code and the |
8 | * original Terratec Cinergy T2 driver by: |
9 | * |
10 | * Copyright (C) 2004 Daniel Mack <daniel@qanu.de> and |
11 | * Holger Waechtler <holger@qanu.de> |
12 | * |
13 | * Protocol Spec published on http://qanu.de/specs/terratec_cinergyT2.pdf |
14 | */ |
15 | |
16 | #include "cinergyT2.h" |
17 | |
18 | |
19 | /* |
20 | * convert linux-dvb frontend parameter set into TPS. |
21 | * See ETSI ETS-300744, section 4.6.2, table 9 for details. |
22 | * |
23 | * This function is probably reusable and may better get placed in a support |
24 | * library. |
25 | * |
26 | * We replace erroneous fields by default TPS fields (the ones with value 0). |
27 | */ |
28 | |
29 | static uint16_t compute_tps(struct dtv_frontend_properties *op) |
30 | { |
31 | uint16_t tps = 0; |
32 | |
33 | switch (op->code_rate_HP) { |
34 | case FEC_2_3: |
35 | tps |= (1 << 7); |
36 | break; |
37 | case FEC_3_4: |
38 | tps |= (2 << 7); |
39 | break; |
40 | case FEC_5_6: |
41 | tps |= (3 << 7); |
42 | break; |
43 | case FEC_7_8: |
44 | tps |= (4 << 7); |
45 | break; |
46 | case FEC_1_2: |
47 | case FEC_AUTO: |
48 | default: |
49 | /* tps |= (0 << 7) */; |
50 | } |
51 | |
52 | switch (op->code_rate_LP) { |
53 | case FEC_2_3: |
54 | tps |= (1 << 4); |
55 | break; |
56 | case FEC_3_4: |
57 | tps |= (2 << 4); |
58 | break; |
59 | case FEC_5_6: |
60 | tps |= (3 << 4); |
61 | break; |
62 | case FEC_7_8: |
63 | tps |= (4 << 4); |
64 | break; |
65 | case FEC_1_2: |
66 | case FEC_AUTO: |
67 | default: |
68 | /* tps |= (0 << 4) */; |
69 | } |
70 | |
71 | switch (op->modulation) { |
72 | case QAM_16: |
73 | tps |= (1 << 13); |
74 | break; |
75 | case QAM_64: |
76 | tps |= (2 << 13); |
77 | break; |
78 | case QPSK: |
79 | default: |
80 | /* tps |= (0 << 13) */; |
81 | } |
82 | |
83 | switch (op->transmission_mode) { |
84 | case TRANSMISSION_MODE_8K: |
85 | tps |= (1 << 0); |
86 | break; |
87 | case TRANSMISSION_MODE_2K: |
88 | default: |
89 | /* tps |= (0 << 0) */; |
90 | } |
91 | |
92 | switch (op->guard_interval) { |
93 | case GUARD_INTERVAL_1_16: |
94 | tps |= (1 << 2); |
95 | break; |
96 | case GUARD_INTERVAL_1_8: |
97 | tps |= (2 << 2); |
98 | break; |
99 | case GUARD_INTERVAL_1_4: |
100 | tps |= (3 << 2); |
101 | break; |
102 | case GUARD_INTERVAL_1_32: |
103 | default: |
104 | /* tps |= (0 << 2) */; |
105 | } |
106 | |
107 | switch (op->hierarchy) { |
108 | case HIERARCHY_1: |
109 | tps |= (1 << 10); |
110 | break; |
111 | case HIERARCHY_2: |
112 | tps |= (2 << 10); |
113 | break; |
114 | case HIERARCHY_4: |
115 | tps |= (3 << 10); |
116 | break; |
117 | case HIERARCHY_NONE: |
118 | default: |
119 | /* tps |= (0 << 10) */; |
120 | } |
121 | |
122 | return tps; |
123 | } |
124 | |
125 | struct cinergyt2_fe_state { |
126 | struct dvb_frontend fe; |
127 | struct dvb_usb_device *d; |
128 | |
129 | unsigned char data[64]; |
130 | struct mutex data_mutex; |
131 | |
132 | struct dvbt_get_status_msg status; |
133 | }; |
134 | |
135 | static int cinergyt2_fe_read_status(struct dvb_frontend *fe, |
136 | enum fe_status *status) |
137 | { |
138 | struct cinergyt2_fe_state *state = fe->demodulator_priv; |
139 | int ret; |
140 | |
141 | mutex_lock(&state->data_mutex); |
142 | state->data[0] = CINERGYT2_EP1_GET_TUNER_STATUS; |
143 | |
144 | ret = dvb_usb_generic_rw(state->d, state->data, 1, |
145 | state->data, sizeof(state->status), 0); |
146 | if (!ret) |
147 | memcpy(&state->status, state->data, sizeof(state->status)); |
148 | mutex_unlock(lock: &state->data_mutex); |
149 | |
150 | if (ret < 0) |
151 | return ret; |
152 | |
153 | *status = 0; |
154 | |
155 | if (0xffff - le16_to_cpu(state->status.gain) > 30) |
156 | *status |= FE_HAS_SIGNAL; |
157 | if (state->status.lock_bits & (1 << 6)) |
158 | *status |= FE_HAS_LOCK; |
159 | if (state->status.lock_bits & (1 << 5)) |
160 | *status |= FE_HAS_SYNC; |
161 | if (state->status.lock_bits & (1 << 4)) |
162 | *status |= FE_HAS_CARRIER; |
163 | if (state->status.lock_bits & (1 << 1)) |
164 | *status |= FE_HAS_VITERBI; |
165 | |
166 | if ((*status & (FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC)) != |
167 | (FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC)) |
168 | *status &= ~FE_HAS_LOCK; |
169 | |
170 | return 0; |
171 | } |
172 | |
173 | static int cinergyt2_fe_read_ber(struct dvb_frontend *fe, u32 *ber) |
174 | { |
175 | struct cinergyt2_fe_state *state = fe->demodulator_priv; |
176 | |
177 | *ber = le32_to_cpu(state->status.viterbi_error_rate); |
178 | return 0; |
179 | } |
180 | |
181 | static int cinergyt2_fe_read_unc_blocks(struct dvb_frontend *fe, u32 *unc) |
182 | { |
183 | struct cinergyt2_fe_state *state = fe->demodulator_priv; |
184 | |
185 | *unc = le32_to_cpu(state->status.uncorrected_block_count); |
186 | return 0; |
187 | } |
188 | |
189 | static int cinergyt2_fe_read_signal_strength(struct dvb_frontend *fe, |
190 | u16 *strength) |
191 | { |
192 | struct cinergyt2_fe_state *state = fe->demodulator_priv; |
193 | |
194 | *strength = (0xffff - le16_to_cpu(state->status.gain)); |
195 | return 0; |
196 | } |
197 | |
198 | static int cinergyt2_fe_read_snr(struct dvb_frontend *fe, u16 *snr) |
199 | { |
200 | struct cinergyt2_fe_state *state = fe->demodulator_priv; |
201 | |
202 | *snr = (state->status.snr << 8) | state->status.snr; |
203 | return 0; |
204 | } |
205 | |
206 | static int cinergyt2_fe_init(struct dvb_frontend *fe) |
207 | { |
208 | return 0; |
209 | } |
210 | |
211 | static int cinergyt2_fe_sleep(struct dvb_frontend *fe) |
212 | { |
213 | deb_info("cinergyt2_fe_sleep() Called\n" ); |
214 | return 0; |
215 | } |
216 | |
217 | static int cinergyt2_fe_get_tune_settings(struct dvb_frontend *fe, |
218 | struct dvb_frontend_tune_settings *tune) |
219 | { |
220 | tune->min_delay_ms = 800; |
221 | return 0; |
222 | } |
223 | |
224 | static int cinergyt2_fe_set_frontend(struct dvb_frontend *fe) |
225 | { |
226 | struct dtv_frontend_properties *fep = &fe->dtv_property_cache; |
227 | struct cinergyt2_fe_state *state = fe->demodulator_priv; |
228 | struct dvbt_set_parameters_msg *param; |
229 | int err; |
230 | |
231 | mutex_lock(&state->data_mutex); |
232 | |
233 | param = (void *)state->data; |
234 | param->cmd = CINERGYT2_EP1_SET_TUNER_PARAMETERS; |
235 | param->tps = cpu_to_le16(compute_tps(fep)); |
236 | param->freq = cpu_to_le32(fep->frequency / 1000); |
237 | param->flags = 0; |
238 | |
239 | switch (fep->bandwidth_hz) { |
240 | default: |
241 | case 8000000: |
242 | param->bandwidth = 8; |
243 | break; |
244 | case 7000000: |
245 | param->bandwidth = 7; |
246 | break; |
247 | case 6000000: |
248 | param->bandwidth = 6; |
249 | break; |
250 | } |
251 | |
252 | err = dvb_usb_generic_rw(state->d, state->data, sizeof(*param), |
253 | state->data, 2, 0); |
254 | if (err < 0) |
255 | err("cinergyt2_fe_set_frontend() Failed! err=%d\n" , err); |
256 | |
257 | mutex_unlock(lock: &state->data_mutex); |
258 | return (err < 0) ? err : 0; |
259 | } |
260 | |
261 | static void cinergyt2_fe_release(struct dvb_frontend *fe) |
262 | { |
263 | struct cinergyt2_fe_state *state = fe->demodulator_priv; |
264 | kfree(objp: state); |
265 | } |
266 | |
267 | static const struct dvb_frontend_ops cinergyt2_fe_ops; |
268 | |
269 | struct dvb_frontend *cinergyt2_fe_attach(struct dvb_usb_device *d) |
270 | { |
271 | struct cinergyt2_fe_state *s = kzalloc(size: sizeof( |
272 | struct cinergyt2_fe_state), GFP_KERNEL); |
273 | if (s == NULL) |
274 | return NULL; |
275 | |
276 | s->d = d; |
277 | memcpy(&s->fe.ops, &cinergyt2_fe_ops, sizeof(struct dvb_frontend_ops)); |
278 | s->fe.demodulator_priv = s; |
279 | mutex_init(&s->data_mutex); |
280 | return &s->fe; |
281 | } |
282 | |
283 | |
284 | static const struct dvb_frontend_ops cinergyt2_fe_ops = { |
285 | .delsys = { SYS_DVBT }, |
286 | .info = { |
287 | .name = DRIVER_NAME, |
288 | .frequency_min_hz = 174 * MHz, |
289 | .frequency_max_hz = 862 * MHz, |
290 | .frequency_stepsize_hz = 166667, |
291 | .caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_1_2 |
292 | | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
293 | | FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 |
294 | | FE_CAN_FEC_AUTO | FE_CAN_QPSK |
295 | | FE_CAN_QAM_16 | FE_CAN_QAM_64 |
296 | | FE_CAN_QAM_AUTO |
297 | | FE_CAN_TRANSMISSION_MODE_AUTO |
298 | | FE_CAN_GUARD_INTERVAL_AUTO |
299 | | FE_CAN_HIERARCHY_AUTO |
300 | | FE_CAN_RECOVER |
301 | | FE_CAN_MUTE_TS |
302 | }, |
303 | |
304 | .release = cinergyt2_fe_release, |
305 | |
306 | .init = cinergyt2_fe_init, |
307 | .sleep = cinergyt2_fe_sleep, |
308 | |
309 | .set_frontend = cinergyt2_fe_set_frontend, |
310 | .get_tune_settings = cinergyt2_fe_get_tune_settings, |
311 | |
312 | .read_status = cinergyt2_fe_read_status, |
313 | .read_ber = cinergyt2_fe_read_ber, |
314 | .read_signal_strength = cinergyt2_fe_read_signal_strength, |
315 | .read_snr = cinergyt2_fe_read_snr, |
316 | .read_ucblocks = cinergyt2_fe_read_unc_blocks, |
317 | }; |
318 | |