1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | Legend Silicon LGS-8GL5 DMB-TH OFDM demodulator driver |
4 | |
5 | Copyright (C) 2008 Sirius International (Hong Kong) Limited |
6 | Timothy Lee <timothy.lee@siriushk.com> |
7 | |
8 | |
9 | */ |
10 | |
11 | #include <linux/kernel.h> |
12 | #include <linux/init.h> |
13 | #include <linux/module.h> |
14 | #include <linux/string.h> |
15 | #include <linux/slab.h> |
16 | #include <media/dvb_frontend.h> |
17 | #include "lgs8gl5.h" |
18 | |
19 | |
20 | #define REG_RESET 0x02 |
21 | #define REG_RESET_OFF 0x01 |
22 | #define REG_03 0x03 |
23 | #define REG_04 0x04 |
24 | #define REG_07 0x07 |
25 | #define REG_09 0x09 |
26 | #define REG_0A 0x0a |
27 | #define REG_0B 0x0b |
28 | #define REG_0C 0x0c |
29 | #define REG_37 0x37 |
30 | #define REG_STRENGTH 0x4b |
31 | #define REG_STRENGTH_MASK 0x7f |
32 | #define REG_STRENGTH_CARRIER 0x80 |
33 | #define REG_INVERSION 0x7c |
34 | #define REG_INVERSION_ON 0x80 |
35 | #define REG_7D 0x7d |
36 | #define REG_7E 0x7e |
37 | #define REG_A2 0xa2 |
38 | #define REG_STATUS 0xa4 |
39 | #define REG_STATUS_SYNC 0x04 |
40 | #define REG_STATUS_LOCK 0x01 |
41 | |
42 | |
43 | struct lgs8gl5_state { |
44 | struct i2c_adapter *i2c; |
45 | const struct lgs8gl5_config *config; |
46 | struct dvb_frontend frontend; |
47 | }; |
48 | |
49 | |
50 | static int debug; |
51 | #define dprintk(args...) \ |
52 | do { \ |
53 | if (debug) \ |
54 | printk(KERN_DEBUG "lgs8gl5: " args); \ |
55 | } while (0) |
56 | |
57 | |
58 | /* Writes into demod's register */ |
59 | static int |
60 | lgs8gl5_write_reg(struct lgs8gl5_state *state, u8 reg, u8 data) |
61 | { |
62 | int ret; |
63 | u8 buf[] = {reg, data}; |
64 | struct i2c_msg msg = { |
65 | .addr = state->config->demod_address, |
66 | .flags = 0, |
67 | .buf = buf, |
68 | .len = 2 |
69 | }; |
70 | |
71 | ret = i2c_transfer(adap: state->i2c, msgs: &msg, num: 1); |
72 | if (ret != 1) |
73 | dprintk("%s: error (reg=0x%02x, val=0x%02x, ret=%i)\n" , |
74 | __func__, reg, data, ret); |
75 | return (ret != 1) ? -1 : 0; |
76 | } |
77 | |
78 | |
79 | /* Reads from demod's register */ |
80 | static int |
81 | lgs8gl5_read_reg(struct lgs8gl5_state *state, u8 reg) |
82 | { |
83 | int ret; |
84 | u8 b0[] = {reg}; |
85 | u8 b1[] = {0}; |
86 | struct i2c_msg msg[2] = { |
87 | { |
88 | .addr = state->config->demod_address, |
89 | .flags = 0, |
90 | .buf = b0, |
91 | .len = 1 |
92 | }, |
93 | { |
94 | .addr = state->config->demod_address, |
95 | .flags = I2C_M_RD, |
96 | .buf = b1, |
97 | .len = 1 |
98 | } |
99 | }; |
100 | |
101 | ret = i2c_transfer(adap: state->i2c, msgs: msg, num: 2); |
102 | if (ret != 2) |
103 | return -EIO; |
104 | |
105 | return b1[0]; |
106 | } |
107 | |
108 | |
109 | static int |
110 | lgs8gl5_update_reg(struct lgs8gl5_state *state, u8 reg, u8 data) |
111 | { |
112 | lgs8gl5_read_reg(state, reg); |
113 | lgs8gl5_write_reg(state, reg, data); |
114 | return 0; |
115 | } |
116 | |
117 | |
118 | /* Writes into alternate device's register */ |
119 | /* TODO: Find out what that device is for! */ |
120 | static int |
121 | lgs8gl5_update_alt_reg(struct lgs8gl5_state *state, u8 reg, u8 data) |
122 | { |
123 | int ret; |
124 | u8 b0[] = {reg}; |
125 | u8 b1[] = {0}; |
126 | u8 b2[] = {reg, data}; |
127 | struct i2c_msg msg[3] = { |
128 | { |
129 | .addr = state->config->demod_address + 2, |
130 | .flags = 0, |
131 | .buf = b0, |
132 | .len = 1 |
133 | }, |
134 | { |
135 | .addr = state->config->demod_address + 2, |
136 | .flags = I2C_M_RD, |
137 | .buf = b1, |
138 | .len = 1 |
139 | }, |
140 | { |
141 | .addr = state->config->demod_address + 2, |
142 | .flags = 0, |
143 | .buf = b2, |
144 | .len = 2 |
145 | }, |
146 | }; |
147 | |
148 | ret = i2c_transfer(adap: state->i2c, msgs: msg, num: 3); |
149 | return (ret != 3) ? -1 : 0; |
150 | } |
151 | |
152 | |
153 | static void |
154 | lgs8gl5_soft_reset(struct lgs8gl5_state *state) |
155 | { |
156 | u8 val; |
157 | |
158 | dprintk("%s\n" , __func__); |
159 | |
160 | val = lgs8gl5_read_reg(state, REG_RESET); |
161 | lgs8gl5_write_reg(state, REG_RESET, data: val & ~REG_RESET_OFF); |
162 | lgs8gl5_write_reg(state, REG_RESET, data: val | REG_RESET_OFF); |
163 | msleep(msecs: 5); |
164 | } |
165 | |
166 | |
167 | /* Starts demodulation */ |
168 | static void |
169 | lgs8gl5_start_demod(struct lgs8gl5_state *state) |
170 | { |
171 | u8 val; |
172 | int n; |
173 | |
174 | dprintk("%s\n" , __func__); |
175 | |
176 | lgs8gl5_update_alt_reg(state, reg: 0xc2, data: 0x28); |
177 | lgs8gl5_soft_reset(state); |
178 | lgs8gl5_update_reg(state, REG_07, data: 0x10); |
179 | lgs8gl5_update_reg(state, REG_07, data: 0x10); |
180 | lgs8gl5_write_reg(state, REG_09, data: 0x0e); |
181 | lgs8gl5_write_reg(state, REG_0A, data: 0xe5); |
182 | lgs8gl5_write_reg(state, REG_0B, data: 0x35); |
183 | lgs8gl5_write_reg(state, REG_0C, data: 0x30); |
184 | |
185 | lgs8gl5_update_reg(state, REG_03, data: 0x00); |
186 | lgs8gl5_update_reg(state, REG_7E, data: 0x01); |
187 | lgs8gl5_update_alt_reg(state, reg: 0xc5, data: 0x00); |
188 | lgs8gl5_update_reg(state, REG_04, data: 0x02); |
189 | lgs8gl5_update_reg(state, REG_37, data: 0x01); |
190 | lgs8gl5_soft_reset(state); |
191 | |
192 | /* Wait for carrier */ |
193 | for (n = 0; n < 10; n++) { |
194 | val = lgs8gl5_read_reg(state, REG_STRENGTH); |
195 | dprintk("Wait for carrier[%d] 0x%02X\n" , n, val); |
196 | if (val & REG_STRENGTH_CARRIER) |
197 | break; |
198 | msleep(msecs: 4); |
199 | } |
200 | if (!(val & REG_STRENGTH_CARRIER)) |
201 | return; |
202 | |
203 | /* Wait for lock */ |
204 | for (n = 0; n < 20; n++) { |
205 | val = lgs8gl5_read_reg(state, REG_STATUS); |
206 | dprintk("Wait for lock[%d] 0x%02X\n" , n, val); |
207 | if (val & REG_STATUS_LOCK) |
208 | break; |
209 | msleep(msecs: 12); |
210 | } |
211 | if (!(val & REG_STATUS_LOCK)) |
212 | return; |
213 | |
214 | lgs8gl5_write_reg(state, REG_7D, data: lgs8gl5_read_reg(state, REG_A2)); |
215 | lgs8gl5_soft_reset(state); |
216 | } |
217 | |
218 | |
219 | static int |
220 | lgs8gl5_init(struct dvb_frontend *fe) |
221 | { |
222 | struct lgs8gl5_state *state = fe->demodulator_priv; |
223 | |
224 | dprintk("%s\n" , __func__); |
225 | |
226 | lgs8gl5_update_alt_reg(state, reg: 0xc2, data: 0x28); |
227 | lgs8gl5_soft_reset(state); |
228 | lgs8gl5_update_reg(state, REG_07, data: 0x10); |
229 | lgs8gl5_update_reg(state, REG_07, data: 0x10); |
230 | lgs8gl5_write_reg(state, REG_09, data: 0x0e); |
231 | lgs8gl5_write_reg(state, REG_0A, data: 0xe5); |
232 | lgs8gl5_write_reg(state, REG_0B, data: 0x35); |
233 | lgs8gl5_write_reg(state, REG_0C, data: 0x30); |
234 | |
235 | return 0; |
236 | } |
237 | |
238 | |
239 | static int |
240 | lgs8gl5_read_status(struct dvb_frontend *fe, enum fe_status *status) |
241 | { |
242 | struct lgs8gl5_state *state = fe->demodulator_priv; |
243 | u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); |
244 | u8 flags = lgs8gl5_read_reg(state, REG_STATUS); |
245 | |
246 | *status = 0; |
247 | |
248 | if ((level & REG_STRENGTH_MASK) > 0) |
249 | *status |= FE_HAS_SIGNAL; |
250 | if (level & REG_STRENGTH_CARRIER) |
251 | *status |= FE_HAS_CARRIER; |
252 | if (flags & REG_STATUS_SYNC) |
253 | *status |= FE_HAS_SYNC; |
254 | if (flags & REG_STATUS_LOCK) |
255 | *status |= FE_HAS_LOCK; |
256 | |
257 | return 0; |
258 | } |
259 | |
260 | |
261 | static int |
262 | lgs8gl5_read_ber(struct dvb_frontend *fe, u32 *ber) |
263 | { |
264 | *ber = 0; |
265 | |
266 | return 0; |
267 | } |
268 | |
269 | |
270 | static int |
271 | lgs8gl5_read_signal_strength(struct dvb_frontend *fe, u16 *signal_strength) |
272 | { |
273 | struct lgs8gl5_state *state = fe->demodulator_priv; |
274 | u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); |
275 | *signal_strength = (level & REG_STRENGTH_MASK) << 8; |
276 | |
277 | return 0; |
278 | } |
279 | |
280 | |
281 | static int |
282 | lgs8gl5_read_snr(struct dvb_frontend *fe, u16 *snr) |
283 | { |
284 | struct lgs8gl5_state *state = fe->demodulator_priv; |
285 | u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); |
286 | *snr = (level & REG_STRENGTH_MASK) << 8; |
287 | |
288 | return 0; |
289 | } |
290 | |
291 | |
292 | static int |
293 | lgs8gl5_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) |
294 | { |
295 | *ucblocks = 0; |
296 | |
297 | return 0; |
298 | } |
299 | |
300 | |
301 | static int |
302 | lgs8gl5_set_frontend(struct dvb_frontend *fe) |
303 | { |
304 | struct dtv_frontend_properties *p = &fe->dtv_property_cache; |
305 | struct lgs8gl5_state *state = fe->demodulator_priv; |
306 | |
307 | dprintk("%s\n" , __func__); |
308 | |
309 | if (p->bandwidth_hz != 8000000) |
310 | return -EINVAL; |
311 | |
312 | if (fe->ops.tuner_ops.set_params) { |
313 | fe->ops.tuner_ops.set_params(fe); |
314 | if (fe->ops.i2c_gate_ctrl) |
315 | fe->ops.i2c_gate_ctrl(fe, 0); |
316 | } |
317 | |
318 | /* lgs8gl5_set_inversion(state, p->inversion); */ |
319 | |
320 | lgs8gl5_start_demod(state); |
321 | |
322 | return 0; |
323 | } |
324 | |
325 | |
326 | static int |
327 | lgs8gl5_get_frontend(struct dvb_frontend *fe, |
328 | struct dtv_frontend_properties *p) |
329 | { |
330 | struct lgs8gl5_state *state = fe->demodulator_priv; |
331 | |
332 | u8 inv = lgs8gl5_read_reg(state, REG_INVERSION); |
333 | |
334 | p->inversion = (inv & REG_INVERSION_ON) ? INVERSION_ON : INVERSION_OFF; |
335 | |
336 | p->code_rate_HP = FEC_1_2; |
337 | p->code_rate_LP = FEC_7_8; |
338 | p->guard_interval = GUARD_INTERVAL_1_32; |
339 | p->transmission_mode = TRANSMISSION_MODE_2K; |
340 | p->modulation = QAM_64; |
341 | p->hierarchy = HIERARCHY_NONE; |
342 | p->bandwidth_hz = 8000000; |
343 | |
344 | return 0; |
345 | } |
346 | |
347 | |
348 | static int |
349 | lgs8gl5_get_tune_settings(struct dvb_frontend *fe, |
350 | struct dvb_frontend_tune_settings *fesettings) |
351 | { |
352 | fesettings->min_delay_ms = 240; |
353 | fesettings->step_size = 0; |
354 | fesettings->max_drift = 0; |
355 | return 0; |
356 | } |
357 | |
358 | |
359 | static void |
360 | lgs8gl5_release(struct dvb_frontend *fe) |
361 | { |
362 | struct lgs8gl5_state *state = fe->demodulator_priv; |
363 | kfree(objp: state); |
364 | } |
365 | |
366 | |
367 | static const struct dvb_frontend_ops lgs8gl5_ops; |
368 | |
369 | |
370 | struct dvb_frontend* |
371 | lgs8gl5_attach(const struct lgs8gl5_config *config, struct i2c_adapter *i2c) |
372 | { |
373 | struct lgs8gl5_state *state = NULL; |
374 | |
375 | dprintk("%s\n" , __func__); |
376 | |
377 | /* Allocate memory for the internal state */ |
378 | state = kzalloc(size: sizeof(struct lgs8gl5_state), GFP_KERNEL); |
379 | if (state == NULL) |
380 | goto error; |
381 | |
382 | /* Setup the state */ |
383 | state->config = config; |
384 | state->i2c = i2c; |
385 | |
386 | /* Check if the demod is there */ |
387 | if (lgs8gl5_read_reg(state, REG_RESET) < 0) |
388 | goto error; |
389 | |
390 | /* Create dvb_frontend */ |
391 | memcpy(&state->frontend.ops, &lgs8gl5_ops, |
392 | sizeof(struct dvb_frontend_ops)); |
393 | state->frontend.demodulator_priv = state; |
394 | return &state->frontend; |
395 | |
396 | error: |
397 | kfree(objp: state); |
398 | return NULL; |
399 | } |
400 | EXPORT_SYMBOL(lgs8gl5_attach); |
401 | |
402 | |
403 | static const struct dvb_frontend_ops lgs8gl5_ops = { |
404 | .delsys = { SYS_DTMB }, |
405 | .info = { |
406 | .name = "Legend Silicon LGS-8GL5 DMB-TH" , |
407 | .frequency_min_hz = 474 * MHz, |
408 | .frequency_max_hz = 858 * MHz, |
409 | .frequency_stepsize_hz = 10 * kHz, |
410 | .caps = FE_CAN_FEC_AUTO | |
411 | FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_32 | |
412 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | |
413 | FE_CAN_TRANSMISSION_MODE_AUTO | |
414 | FE_CAN_BANDWIDTH_AUTO | |
415 | FE_CAN_GUARD_INTERVAL_AUTO | |
416 | FE_CAN_HIERARCHY_AUTO | |
417 | FE_CAN_RECOVER |
418 | }, |
419 | |
420 | .release = lgs8gl5_release, |
421 | |
422 | .init = lgs8gl5_init, |
423 | |
424 | .set_frontend = lgs8gl5_set_frontend, |
425 | .get_frontend = lgs8gl5_get_frontend, |
426 | .get_tune_settings = lgs8gl5_get_tune_settings, |
427 | |
428 | .read_status = lgs8gl5_read_status, |
429 | .read_ber = lgs8gl5_read_ber, |
430 | .read_signal_strength = lgs8gl5_read_signal_strength, |
431 | .read_snr = lgs8gl5_read_snr, |
432 | .read_ucblocks = lgs8gl5_read_ucblocks, |
433 | }; |
434 | |
435 | |
436 | module_param(debug, int, 0644); |
437 | MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)." ); |
438 | |
439 | MODULE_DESCRIPTION("Legend Silicon LGS-8GL5 DMB-TH Demodulator driver" ); |
440 | MODULE_AUTHOR("Timothy Lee" ); |
441 | MODULE_LICENSE("GPL" ); |
442 | |