diff options
Diffstat (limited to 'drivers/media/dvb/frontends/atbm8830.c')
-rw-r--r-- | drivers/media/dvb/frontends/atbm8830.c | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/drivers/media/dvb/frontends/atbm8830.c b/drivers/media/dvb/frontends/atbm8830.c new file mode 100644 index 00000000000..1539ea1f81a --- /dev/null +++ b/drivers/media/dvb/frontends/atbm8830.c | |||
@@ -0,0 +1,509 @@ | |||
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 | |||
22 | #include <asm/div64.h> | ||
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) | ||
53 | dprintk("%s: reg=0x%04X, data=0x%02X\n", __func__, reg, data); | ||
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) { | ||
79 | dprintk("%s: error reg=0x%04x, ret=%i\n", __func__, reg, ret); | ||
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) | ||
89 | dprintk("%s: reg=0x%04X, data=0x%02X\n", | ||
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; | ||
104 | u64 t; | ||
105 | |||
106 | /* 0x100000 * freq / 30.4MHz */ | ||
107 | t = (u64)0x100000 * freq; | ||
108 | do_div(t, 30400); | ||
109 | val = t; | ||
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 | { | ||
120 | |||
121 | u32 fs = priv->config->osc_clk_freq; | ||
122 | u64 t; | ||
123 | u32 val; | ||
124 | u8 dat; | ||
125 | |||
126 | if (freq != 0) { | ||
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 | |||
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 | |||
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 | } | ||
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 | |||
241 | /*Set AGC Config*/ | ||
242 | set_agc_config(priv, cfg->agc_min, cfg->agc_max, | ||
243 | cfg->agc_hold_loop); | ||
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 | |||
270 | static int atbm8830_set_fe(struct dvb_frontend *fe, | ||
271 | struct dvb_frontend_parameters *fe_params) | ||
272 | { | ||
273 | struct atbm_state *priv = fe->demodulator_priv; | ||
274 | int i; | ||
275 | u8 locked = 0; | ||
276 | dprintk("%s\n", __func__); | ||
277 | |||
278 | /* set frequency */ | ||
279 | if (fe->ops.tuner_ops.set_params) { | ||
280 | if (fe->ops.i2c_gate_ctrl) | ||
281 | fe->ops.i2c_gate_ctrl(fe, 1); | ||
282 | fe->ops.tuner_ops.set_params(fe, fe_params); | ||
283 | if (fe->ops.i2c_gate_ctrl) | ||
284 | fe->ops.i2c_gate_ctrl(fe, 0); | ||
285 | } | ||
286 | |||
287 | /* start auto lock */ | ||
288 | for (i = 0; i < 10; i++) { | ||
289 | mdelay(100); | ||
290 | dprintk("Try %d\n", i); | ||
291 | is_locked(priv, &locked); | ||
292 | if (locked != 0) { | ||
293 | dprintk("ATBM8830 locked!\n"); | ||
294 | break; | ||
295 | } | ||
296 | } | ||
297 | |||
298 | return 0; | ||
299 | } | ||
300 | |||
301 | static int atbm8830_get_fe(struct dvb_frontend *fe, | ||
302 | struct dvb_frontend_parameters *fe_params) | ||
303 | { | ||
304 | dprintk("%s\n", __func__); | ||
305 | |||
306 | /* TODO: get real readings from device */ | ||
307 | /* inversion status */ | ||
308 | fe_params->inversion = INVERSION_OFF; | ||
309 | |||
310 | /* bandwidth */ | ||
311 | fe_params->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; | ||
312 | |||
313 | fe_params->u.ofdm.code_rate_HP = FEC_AUTO; | ||
314 | fe_params->u.ofdm.code_rate_LP = FEC_AUTO; | ||
315 | |||
316 | fe_params->u.ofdm.constellation = QAM_AUTO; | ||
317 | |||
318 | /* transmission mode */ | ||
319 | fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_AUTO; | ||
320 | |||
321 | /* guard interval */ | ||
322 | fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_AUTO; | ||
323 | |||
324 | /* hierarchy */ | ||
325 | fe_params->u.ofdm.hierarchy_information = HIERARCHY_NONE; | ||
326 | |||
327 | return 0; | ||
328 | } | ||
329 | |||
330 | static int atbm8830_get_tune_settings(struct dvb_frontend *fe, | ||
331 | struct dvb_frontend_tune_settings *fesettings) | ||
332 | { | ||
333 | fesettings->min_delay_ms = 0; | ||
334 | fesettings->step_size = 0; | ||
335 | fesettings->max_drift = 0; | ||
336 | return 0; | ||
337 | } | ||
338 | |||
339 | static int atbm8830_read_status(struct dvb_frontend *fe, fe_status_t *fe_status) | ||
340 | { | ||
341 | struct atbm_state *priv = fe->demodulator_priv; | ||
342 | u8 locked = 0; | ||
343 | u8 agc_locked = 0; | ||
344 | |||
345 | dprintk("%s\n", __func__); | ||
346 | *fe_status = 0; | ||
347 | |||
348 | is_locked(priv, &locked); | ||
349 | if (locked) { | ||
350 | *fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | | ||
351 | FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; | ||
352 | } | ||
353 | dprintk("%s: fe_status=0x%x\n", __func__, *fe_status); | ||
354 | |||
355 | atbm8830_read_reg(priv, REG_AGC_LOCK, &agc_locked); | ||
356 | dprintk("AGC Lock: %d\n", agc_locked); | ||
357 | |||
358 | return 0; | ||
359 | } | ||
360 | |||
361 | static int atbm8830_read_ber(struct dvb_frontend *fe, u32 *ber) | ||
362 | { | ||
363 | struct atbm_state *priv = fe->demodulator_priv; | ||
364 | u32 frame_err; | ||
365 | u8 t; | ||
366 | |||
367 | dprintk("%s\n", __func__); | ||
368 | |||
369 | atbm8830_reglatch_lock(priv, 1); | ||
370 | |||
371 | atbm8830_read_reg(priv, REG_FRAME_ERR_CNT + 1, &t); | ||
372 | frame_err = t & 0x7F; | ||
373 | frame_err <<= 8; | ||
374 | atbm8830_read_reg(priv, REG_FRAME_ERR_CNT, &t); | ||
375 | frame_err |= t; | ||
376 | |||
377 | atbm8830_reglatch_lock(priv, 0); | ||
378 | |||
379 | *ber = frame_err * 100 / 32767; | ||
380 | |||
381 | dprintk("%s: ber=0x%x\n", __func__, *ber); | ||
382 | return 0; | ||
383 | } | ||
384 | |||
385 | static int atbm8830_read_signal_strength(struct dvb_frontend *fe, u16 *signal) | ||
386 | { | ||
387 | struct atbm_state *priv = fe->demodulator_priv; | ||
388 | u32 pwm; | ||
389 | u8 t; | ||
390 | |||
391 | dprintk("%s\n", __func__); | ||
392 | atbm8830_reglatch_lock(priv, 1); | ||
393 | |||
394 | atbm8830_read_reg(priv, REG_AGC_PWM_VAL + 1, &t); | ||
395 | pwm = t & 0x03; | ||
396 | pwm <<= 8; | ||
397 | atbm8830_read_reg(priv, REG_AGC_PWM_VAL, &t); | ||
398 | pwm |= t; | ||
399 | |||
400 | atbm8830_reglatch_lock(priv, 0); | ||
401 | |||
402 | dprintk("AGC PWM = 0x%02X\n", pwm); | ||
403 | pwm = 0x400 - pwm; | ||
404 | |||
405 | *signal = pwm * 0x10000 / 0x400; | ||
406 | |||
407 | return 0; | ||
408 | } | ||
409 | |||
410 | static int atbm8830_read_snr(struct dvb_frontend *fe, u16 *snr) | ||
411 | { | ||
412 | dprintk("%s\n", __func__); | ||
413 | *snr = 0; | ||
414 | return 0; | ||
415 | } | ||
416 | |||
417 | static int atbm8830_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) | ||
418 | { | ||
419 | dprintk("%s\n", __func__); | ||
420 | *ucblocks = 0; | ||
421 | return 0; | ||
422 | } | ||
423 | |||
424 | static int atbm8830_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) | ||
425 | { | ||
426 | struct atbm_state *priv = fe->demodulator_priv; | ||
427 | |||
428 | return atbm8830_write_reg(priv, REG_I2C_GATE, enable ? 1 : 0); | ||
429 | } | ||
430 | |||
431 | static struct dvb_frontend_ops atbm8830_ops = { | ||
432 | .info = { | ||
433 | .name = "AltoBeam ATBM8830/8831 DMB-TH", | ||
434 | .type = FE_OFDM, | ||
435 | .frequency_min = 474000000, | ||
436 | .frequency_max = 858000000, | ||
437 | .frequency_stepsize = 10000, | ||
438 | .caps = | ||
439 | FE_CAN_FEC_AUTO | | ||
440 | FE_CAN_QAM_AUTO | | ||
441 | FE_CAN_TRANSMISSION_MODE_AUTO | | ||
442 | FE_CAN_GUARD_INTERVAL_AUTO | ||
443 | }, | ||
444 | |||
445 | .release = atbm8830_release, | ||
446 | |||
447 | .init = atbm8830_init, | ||
448 | .sleep = NULL, | ||
449 | .write = NULL, | ||
450 | .i2c_gate_ctrl = atbm8830_i2c_gate_ctrl, | ||
451 | |||
452 | .set_frontend = atbm8830_set_fe, | ||
453 | .get_frontend = atbm8830_get_fe, | ||
454 | .get_tune_settings = atbm8830_get_tune_settings, | ||
455 | |||
456 | .read_status = atbm8830_read_status, | ||
457 | .read_ber = atbm8830_read_ber, | ||
458 | .read_signal_strength = atbm8830_read_signal_strength, | ||
459 | .read_snr = atbm8830_read_snr, | ||
460 | .read_ucblocks = atbm8830_read_ucblocks, | ||
461 | }; | ||
462 | |||
463 | struct dvb_frontend *atbm8830_attach(const struct atbm8830_config *config, | ||
464 | struct i2c_adapter *i2c) | ||
465 | { | ||
466 | struct atbm_state *priv = NULL; | ||
467 | u8 data = 0; | ||
468 | |||
469 | dprintk("%s()\n", __func__); | ||
470 | |||
471 | if (config == NULL || i2c == NULL) | ||
472 | return NULL; | ||
473 | |||
474 | priv = kzalloc(sizeof(struct atbm_state), GFP_KERNEL); | ||
475 | if (priv == NULL) | ||
476 | goto error_out; | ||
477 | |||
478 | priv->config = config; | ||
479 | priv->i2c = i2c; | ||
480 | |||
481 | /* check if the demod is there */ | ||
482 | if (atbm8830_read_reg(priv, REG_CHIP_ID, &data) != 0) { | ||
483 | dprintk("%s atbm8830/8831 not found at i2c addr 0x%02X\n", | ||
484 | __func__, priv->config->demod_address); | ||
485 | goto error_out; | ||
486 | } | ||
487 | dprintk("atbm8830 chip id: 0x%02X\n", data); | ||
488 | |||
489 | memcpy(&priv->frontend.ops, &atbm8830_ops, | ||
490 | sizeof(struct dvb_frontend_ops)); | ||
491 | priv->frontend.demodulator_priv = priv; | ||
492 | |||
493 | atbm8830_init(&priv->frontend); | ||
494 | |||
495 | atbm8830_i2c_gate_ctrl(&priv->frontend, 1); | ||
496 | |||
497 | return &priv->frontend; | ||
498 | |||
499 | error_out: | ||
500 | dprintk("%s() error_out\n", __func__); | ||
501 | kfree(priv); | ||
502 | return NULL; | ||
503 | |||
504 | } | ||
505 | EXPORT_SYMBOL(atbm8830_attach); | ||
506 | |||
507 | MODULE_DESCRIPTION("AltoBeam ATBM8830/8831 GB20600 demodulator driver"); | ||
508 | MODULE_AUTHOR("David T. L. Wong <davidtlwong@gmail.com>"); | ||
509 | MODULE_LICENSE("GPL"); | ||