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