1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Support for AltoBeam GB20600 (a.k.a DMB-TH) demodulator |
4 | * ATBM8830, ATBM8831 |
5 | * |
6 | * Copyright (C) 2009 David T.L. Wong <davidtlwong@gmail.com> |
7 | */ |
8 | |
9 | #include <asm/div64.h> |
10 | #include <media/dvb_frontend.h> |
11 | |
12 | #include "atbm8830.h" |
13 | #include "atbm8830_priv.h" |
14 | |
15 | #define dprintk(args...) \ |
16 | do { \ |
17 | if (debug) \ |
18 | printk(KERN_DEBUG "atbm8830: " args); \ |
19 | } while (0) |
20 | |
21 | static int debug; |
22 | |
23 | module_param(debug, int, 0644); |
24 | MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)." ); |
25 | |
26 | static int atbm8830_write_reg(struct atbm_state *priv, u16 reg, u8 data) |
27 | { |
28 | int ret = 0; |
29 | u8 dev_addr; |
30 | u8 buf1[] = { reg >> 8, reg & 0xFF }; |
31 | u8 buf2[] = { data }; |
32 | struct i2c_msg msg1 = { .flags = 0, .buf = buf1, .len = 2 }; |
33 | struct i2c_msg msg2 = { .flags = 0, .buf = buf2, .len = 1 }; |
34 | |
35 | dev_addr = priv->config->demod_address; |
36 | msg1.addr = dev_addr; |
37 | msg2.addr = dev_addr; |
38 | |
39 | if (debug >= 2) |
40 | dprintk("%s: reg=0x%04X, data=0x%02X\n" , __func__, reg, data); |
41 | |
42 | ret = i2c_transfer(adap: priv->i2c, msgs: &msg1, num: 1); |
43 | if (ret != 1) |
44 | return -EIO; |
45 | |
46 | ret = i2c_transfer(adap: priv->i2c, msgs: &msg2, num: 1); |
47 | return (ret != 1) ? -EIO : 0; |
48 | } |
49 | |
50 | static int atbm8830_read_reg(struct atbm_state *priv, u16 reg, u8 *p_data) |
51 | { |
52 | int ret; |
53 | u8 dev_addr; |
54 | |
55 | u8 buf1[] = { reg >> 8, reg & 0xFF }; |
56 | u8 buf2[] = { 0 }; |
57 | struct i2c_msg msg1 = { .flags = 0, .buf = buf1, .len = 2 }; |
58 | struct i2c_msg msg2 = { .flags = I2C_M_RD, .buf = buf2, .len = 1 }; |
59 | |
60 | dev_addr = priv->config->demod_address; |
61 | msg1.addr = dev_addr; |
62 | msg2.addr = dev_addr; |
63 | |
64 | ret = i2c_transfer(adap: priv->i2c, msgs: &msg1, num: 1); |
65 | if (ret != 1) { |
66 | dprintk("%s: error reg=0x%04x, ret=%i\n" , __func__, reg, ret); |
67 | return -EIO; |
68 | } |
69 | |
70 | ret = i2c_transfer(adap: priv->i2c, msgs: &msg2, num: 1); |
71 | if (ret != 1) |
72 | return -EIO; |
73 | |
74 | *p_data = buf2[0]; |
75 | if (debug >= 2) |
76 | dprintk("%s: reg=0x%04X, data=0x%02X\n" , |
77 | __func__, reg, buf2[0]); |
78 | |
79 | return 0; |
80 | } |
81 | |
82 | /* Lock register latch so that multi-register read is atomic */ |
83 | static inline int atbm8830_reglatch_lock(struct atbm_state *priv, int lock) |
84 | { |
85 | return atbm8830_write_reg(priv, REG_READ_LATCH, data: lock ? 1 : 0); |
86 | } |
87 | |
88 | static int set_osc_freq(struct atbm_state *priv, u32 freq /*in kHz*/) |
89 | { |
90 | u32 val; |
91 | u64 t; |
92 | |
93 | /* 0x100000 * freq / 30.4MHz */ |
94 | t = (u64)0x100000 * freq; |
95 | do_div(t, 30400); |
96 | val = t; |
97 | |
98 | atbm8830_write_reg(priv, REG_OSC_CLK, data: val); |
99 | atbm8830_write_reg(priv, REG_OSC_CLK + 1, data: val >> 8); |
100 | atbm8830_write_reg(priv, REG_OSC_CLK + 2, data: val >> 16); |
101 | |
102 | return 0; |
103 | } |
104 | |
105 | static int set_if_freq(struct atbm_state *priv, u32 freq /*in kHz*/) |
106 | { |
107 | |
108 | u32 fs = priv->config->osc_clk_freq; |
109 | u64 t; |
110 | u32 val; |
111 | u8 dat; |
112 | |
113 | if (freq != 0) { |
114 | /* 2 * PI * (freq - fs) / fs * (2 ^ 22) */ |
115 | t = (u64) 2 * 31416 * (freq - fs); |
116 | t <<= 22; |
117 | do_div(t, fs); |
118 | do_div(t, 1000); |
119 | val = t; |
120 | |
121 | atbm8830_write_reg(priv, REG_TUNER_BASEBAND, data: 1); |
122 | atbm8830_write_reg(priv, REG_IF_FREQ, data: val); |
123 | atbm8830_write_reg(priv, REG_IF_FREQ+1, data: val >> 8); |
124 | atbm8830_write_reg(priv, REG_IF_FREQ+2, data: val >> 16); |
125 | |
126 | atbm8830_read_reg(priv, REG_ADC_CONFIG, p_data: &dat); |
127 | dat &= 0xFC; |
128 | atbm8830_write_reg(priv, REG_ADC_CONFIG, data: dat); |
129 | } else { |
130 | /* Zero IF */ |
131 | atbm8830_write_reg(priv, REG_TUNER_BASEBAND, data: 0); |
132 | |
133 | atbm8830_read_reg(priv, REG_ADC_CONFIG, p_data: &dat); |
134 | dat &= 0xFC; |
135 | dat |= 0x02; |
136 | atbm8830_write_reg(priv, REG_ADC_CONFIG, data: dat); |
137 | |
138 | if (priv->config->zif_swap_iq) |
139 | atbm8830_write_reg(priv, REG_SWAP_I_Q, data: 0x03); |
140 | else |
141 | atbm8830_write_reg(priv, REG_SWAP_I_Q, data: 0x01); |
142 | } |
143 | |
144 | return 0; |
145 | } |
146 | |
147 | static int is_locked(struct atbm_state *priv, u8 *locked) |
148 | { |
149 | u8 status; |
150 | |
151 | atbm8830_read_reg(priv, REG_LOCK_STATUS, p_data: &status); |
152 | |
153 | if (locked != NULL) |
154 | *locked = (status == 1); |
155 | return 0; |
156 | } |
157 | |
158 | static int set_agc_config(struct atbm_state *priv, |
159 | u8 min, u8 max, u8 hold_loop) |
160 | { |
161 | /* no effect if both min and max are zero */ |
162 | if (!min && !max) |
163 | return 0; |
164 | |
165 | atbm8830_write_reg(priv, REG_AGC_MIN, data: min); |
166 | atbm8830_write_reg(priv, REG_AGC_MAX, data: max); |
167 | atbm8830_write_reg(priv, REG_AGC_HOLD_LOOP, data: hold_loop); |
168 | |
169 | return 0; |
170 | } |
171 | |
172 | static int set_static_channel_mode(struct atbm_state *priv) |
173 | { |
174 | int i; |
175 | |
176 | for (i = 0; i < 5; i++) |
177 | atbm8830_write_reg(priv, reg: 0x099B + i, data: 0x08); |
178 | |
179 | atbm8830_write_reg(priv, reg: 0x095B, data: 0x7F); |
180 | atbm8830_write_reg(priv, reg: 0x09CB, data: 0x01); |
181 | atbm8830_write_reg(priv, reg: 0x09CC, data: 0x7F); |
182 | atbm8830_write_reg(priv, reg: 0x09CD, data: 0x7F); |
183 | atbm8830_write_reg(priv, reg: 0x0E01, data: 0x20); |
184 | |
185 | /* For single carrier */ |
186 | atbm8830_write_reg(priv, reg: 0x0B03, data: 0x0A); |
187 | atbm8830_write_reg(priv, reg: 0x0935, data: 0x10); |
188 | atbm8830_write_reg(priv, reg: 0x0936, data: 0x08); |
189 | atbm8830_write_reg(priv, reg: 0x093E, data: 0x08); |
190 | atbm8830_write_reg(priv, reg: 0x096E, data: 0x06); |
191 | |
192 | /* frame_count_max0 */ |
193 | atbm8830_write_reg(priv, reg: 0x0B09, data: 0x00); |
194 | /* frame_count_max1 */ |
195 | atbm8830_write_reg(priv, reg: 0x0B0A, data: 0x08); |
196 | |
197 | return 0; |
198 | } |
199 | |
200 | static int set_ts_config(struct atbm_state *priv) |
201 | { |
202 | const struct atbm8830_config *cfg = priv->config; |
203 | |
204 | /*Set parallel/serial ts mode*/ |
205 | atbm8830_write_reg(priv, REG_TS_SERIAL, data: cfg->serial_ts ? 1 : 0); |
206 | atbm8830_write_reg(priv, REG_TS_CLK_MODE, data: cfg->serial_ts ? 1 : 0); |
207 | /*Set ts sampling edge*/ |
208 | atbm8830_write_reg(priv, REG_TS_SAMPLE_EDGE, |
209 | data: cfg->ts_sampling_edge ? 1 : 0); |
210 | /*Set ts clock freerun*/ |
211 | atbm8830_write_reg(priv, REG_TS_CLK_FREERUN, |
212 | data: cfg->ts_clk_gated ? 0 : 1); |
213 | |
214 | return 0; |
215 | } |
216 | |
217 | static int atbm8830_init(struct dvb_frontend *fe) |
218 | { |
219 | struct atbm_state *priv = fe->demodulator_priv; |
220 | const struct atbm8830_config *cfg = priv->config; |
221 | |
222 | /*Set oscillator frequency*/ |
223 | set_osc_freq(priv, freq: cfg->osc_clk_freq); |
224 | |
225 | /*Set IF frequency*/ |
226 | set_if_freq(priv, freq: cfg->if_freq); |
227 | |
228 | /*Set AGC Config*/ |
229 | set_agc_config(priv, min: cfg->agc_min, max: cfg->agc_max, |
230 | hold_loop: cfg->agc_hold_loop); |
231 | |
232 | /*Set static channel mode*/ |
233 | set_static_channel_mode(priv); |
234 | |
235 | set_ts_config(priv); |
236 | /*Turn off DSP reset*/ |
237 | atbm8830_write_reg(priv, reg: 0x000A, data: 0); |
238 | |
239 | /*SW version test*/ |
240 | atbm8830_write_reg(priv, reg: 0x020C, data: 11); |
241 | |
242 | /* Run */ |
243 | atbm8830_write_reg(priv, REG_DEMOD_RUN, data: 1); |
244 | |
245 | return 0; |
246 | } |
247 | |
248 | |
249 | static void atbm8830_release(struct dvb_frontend *fe) |
250 | { |
251 | struct atbm_state *state = fe->demodulator_priv; |
252 | dprintk("%s\n" , __func__); |
253 | |
254 | kfree(objp: state); |
255 | } |
256 | |
257 | static int atbm8830_set_fe(struct dvb_frontend *fe) |
258 | { |
259 | struct atbm_state *priv = fe->demodulator_priv; |
260 | int i; |
261 | u8 locked = 0; |
262 | dprintk("%s\n" , __func__); |
263 | |
264 | /* set frequency */ |
265 | if (fe->ops.tuner_ops.set_params) { |
266 | if (fe->ops.i2c_gate_ctrl) |
267 | fe->ops.i2c_gate_ctrl(fe, 1); |
268 | fe->ops.tuner_ops.set_params(fe); |
269 | if (fe->ops.i2c_gate_ctrl) |
270 | fe->ops.i2c_gate_ctrl(fe, 0); |
271 | } |
272 | |
273 | /* start auto lock */ |
274 | for (i = 0; i < 10; i++) { |
275 | mdelay(100); |
276 | dprintk("Try %d\n" , i); |
277 | is_locked(priv, locked: &locked); |
278 | if (locked != 0) { |
279 | dprintk("ATBM8830 locked!\n" ); |
280 | break; |
281 | } |
282 | } |
283 | |
284 | return 0; |
285 | } |
286 | |
287 | static int atbm8830_get_fe(struct dvb_frontend *fe, |
288 | struct dtv_frontend_properties *c) |
289 | { |
290 | dprintk("%s\n" , __func__); |
291 | |
292 | /* TODO: get real readings from device */ |
293 | /* inversion status */ |
294 | c->inversion = INVERSION_OFF; |
295 | |
296 | /* bandwidth */ |
297 | c->bandwidth_hz = 8000000; |
298 | |
299 | c->code_rate_HP = FEC_AUTO; |
300 | c->code_rate_LP = FEC_AUTO; |
301 | |
302 | c->modulation = QAM_AUTO; |
303 | |
304 | /* transmission mode */ |
305 | c->transmission_mode = TRANSMISSION_MODE_AUTO; |
306 | |
307 | /* guard interval */ |
308 | c->guard_interval = GUARD_INTERVAL_AUTO; |
309 | |
310 | /* hierarchy */ |
311 | c->hierarchy = HIERARCHY_NONE; |
312 | |
313 | return 0; |
314 | } |
315 | |
316 | static int atbm8830_get_tune_settings(struct dvb_frontend *fe, |
317 | struct dvb_frontend_tune_settings *fesettings) |
318 | { |
319 | fesettings->min_delay_ms = 0; |
320 | fesettings->step_size = 0; |
321 | fesettings->max_drift = 0; |
322 | return 0; |
323 | } |
324 | |
325 | static int atbm8830_read_status(struct dvb_frontend *fe, |
326 | enum fe_status *fe_status) |
327 | { |
328 | struct atbm_state *priv = fe->demodulator_priv; |
329 | u8 locked = 0; |
330 | u8 agc_locked = 0; |
331 | |
332 | dprintk("%s\n" , __func__); |
333 | *fe_status = 0; |
334 | |
335 | is_locked(priv, locked: &locked); |
336 | if (locked) { |
337 | *fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | |
338 | FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; |
339 | } |
340 | dprintk("%s: fe_status=0x%x\n" , __func__, *fe_status); |
341 | |
342 | atbm8830_read_reg(priv, REG_AGC_LOCK, p_data: &agc_locked); |
343 | dprintk("AGC Lock: %d\n" , agc_locked); |
344 | |
345 | return 0; |
346 | } |
347 | |
348 | static int atbm8830_read_ber(struct dvb_frontend *fe, u32 *ber) |
349 | { |
350 | struct atbm_state *priv = fe->demodulator_priv; |
351 | u32 frame_err; |
352 | u8 t; |
353 | |
354 | dprintk("%s\n" , __func__); |
355 | |
356 | atbm8830_reglatch_lock(priv, lock: 1); |
357 | |
358 | atbm8830_read_reg(priv, REG_FRAME_ERR_CNT + 1, p_data: &t); |
359 | frame_err = t & 0x7F; |
360 | frame_err <<= 8; |
361 | atbm8830_read_reg(priv, REG_FRAME_ERR_CNT, p_data: &t); |
362 | frame_err |= t; |
363 | |
364 | atbm8830_reglatch_lock(priv, lock: 0); |
365 | |
366 | *ber = frame_err * 100 / 32767; |
367 | |
368 | dprintk("%s: ber=0x%x\n" , __func__, *ber); |
369 | return 0; |
370 | } |
371 | |
372 | static int atbm8830_read_signal_strength(struct dvb_frontend *fe, u16 *signal) |
373 | { |
374 | struct atbm_state *priv = fe->demodulator_priv; |
375 | u32 pwm; |
376 | u8 t; |
377 | |
378 | dprintk("%s\n" , __func__); |
379 | atbm8830_reglatch_lock(priv, lock: 1); |
380 | |
381 | atbm8830_read_reg(priv, REG_AGC_PWM_VAL + 1, p_data: &t); |
382 | pwm = t & 0x03; |
383 | pwm <<= 8; |
384 | atbm8830_read_reg(priv, REG_AGC_PWM_VAL, p_data: &t); |
385 | pwm |= t; |
386 | |
387 | atbm8830_reglatch_lock(priv, lock: 0); |
388 | |
389 | dprintk("AGC PWM = 0x%02X\n" , pwm); |
390 | pwm = 0x400 - pwm; |
391 | |
392 | *signal = pwm * 0x10000 / 0x400; |
393 | |
394 | return 0; |
395 | } |
396 | |
397 | static int atbm8830_read_snr(struct dvb_frontend *fe, u16 *snr) |
398 | { |
399 | dprintk("%s\n" , __func__); |
400 | *snr = 0; |
401 | return 0; |
402 | } |
403 | |
404 | static int atbm8830_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) |
405 | { |
406 | dprintk("%s\n" , __func__); |
407 | *ucblocks = 0; |
408 | return 0; |
409 | } |
410 | |
411 | static int atbm8830_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) |
412 | { |
413 | struct atbm_state *priv = fe->demodulator_priv; |
414 | |
415 | return atbm8830_write_reg(priv, REG_I2C_GATE, data: enable ? 1 : 0); |
416 | } |
417 | |
418 | static const struct dvb_frontend_ops atbm8830_ops = { |
419 | .delsys = { SYS_DTMB }, |
420 | .info = { |
421 | .name = "AltoBeam ATBM8830/8831 DMB-TH" , |
422 | .frequency_min_hz = 474 * MHz, |
423 | .frequency_max_hz = 858 * MHz, |
424 | .frequency_stepsize_hz = 10 * kHz, |
425 | .caps = |
426 | FE_CAN_FEC_AUTO | |
427 | FE_CAN_QAM_AUTO | |
428 | FE_CAN_TRANSMISSION_MODE_AUTO | |
429 | FE_CAN_GUARD_INTERVAL_AUTO |
430 | }, |
431 | |
432 | .release = atbm8830_release, |
433 | |
434 | .init = atbm8830_init, |
435 | .sleep = NULL, |
436 | .write = NULL, |
437 | .i2c_gate_ctrl = atbm8830_i2c_gate_ctrl, |
438 | |
439 | .set_frontend = atbm8830_set_fe, |
440 | .get_frontend = atbm8830_get_fe, |
441 | .get_tune_settings = atbm8830_get_tune_settings, |
442 | |
443 | .read_status = atbm8830_read_status, |
444 | .read_ber = atbm8830_read_ber, |
445 | .read_signal_strength = atbm8830_read_signal_strength, |
446 | .read_snr = atbm8830_read_snr, |
447 | .read_ucblocks = atbm8830_read_ucblocks, |
448 | }; |
449 | |
450 | struct dvb_frontend *atbm8830_attach(const struct atbm8830_config *config, |
451 | struct i2c_adapter *i2c) |
452 | { |
453 | struct atbm_state *priv = NULL; |
454 | u8 data = 0; |
455 | |
456 | dprintk("%s()\n" , __func__); |
457 | |
458 | if (config == NULL || i2c == NULL) |
459 | return NULL; |
460 | |
461 | priv = kzalloc(size: sizeof(struct atbm_state), GFP_KERNEL); |
462 | if (priv == NULL) |
463 | goto error_out; |
464 | |
465 | priv->config = config; |
466 | priv->i2c = i2c; |
467 | |
468 | /* check if the demod is there */ |
469 | if (atbm8830_read_reg(priv, REG_CHIP_ID, p_data: &data) != 0) { |
470 | dprintk("%s atbm8830/8831 not found at i2c addr 0x%02X\n" , |
471 | __func__, priv->config->demod_address); |
472 | goto error_out; |
473 | } |
474 | dprintk("atbm8830 chip id: 0x%02X\n" , data); |
475 | |
476 | memcpy(&priv->frontend.ops, &atbm8830_ops, |
477 | sizeof(struct dvb_frontend_ops)); |
478 | priv->frontend.demodulator_priv = priv; |
479 | |
480 | atbm8830_init(fe: &priv->frontend); |
481 | |
482 | atbm8830_i2c_gate_ctrl(fe: &priv->frontend, enable: 1); |
483 | |
484 | return &priv->frontend; |
485 | |
486 | error_out: |
487 | dprintk("%s() error_out\n" , __func__); |
488 | kfree(objp: priv); |
489 | return NULL; |
490 | |
491 | } |
492 | EXPORT_SYMBOL_GPL(atbm8830_attach); |
493 | |
494 | MODULE_DESCRIPTION("AltoBeam ATBM8830/8831 GB20600 demodulator driver" ); |
495 | MODULE_AUTHOR("David T. L. Wong <davidtlwong@gmail.com>" ); |
496 | MODULE_LICENSE("GPL" ); |
497 | |