Commit | Line | Data |
---|---|---|
265a6510 ST |
1 | /* |
2 | Auvitek AU8522 QAM/8VSB demodulator driver | |
3 | ||
4 | Copyright (C) 2008 Steven Toth <stoth@hauppauge.com> | |
5 | ||
6 | This program is free software; you can redistribute it and/or modify | |
7 | it under the terms of the GNU General Public License as published by | |
8 | the Free Software Foundation; either version 2 of the License, or | |
9 | (at your option) any later version. | |
10 | ||
11 | This program is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | GNU General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU General Public License | |
17 | along with this program; if not, write to the Free Software | |
18 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
19 | ||
20 | */ | |
21 | ||
22 | #include <linux/kernel.h> | |
23 | #include <linux/init.h> | |
24 | #include <linux/module.h> | |
25 | #include <linux/string.h> | |
26 | #include <linux/slab.h> | |
27 | #include <linux/delay.h> | |
28 | #include "dvb_frontend.h" | |
29 | #include "dvb-pll.h" | |
30 | #include "au8522.h" | |
31 | ||
32 | struct au8522_state { | |
33 | ||
e059b0fa | 34 | struct i2c_adapter *i2c; |
265a6510 ST |
35 | |
36 | /* configuration settings */ | |
e059b0fa | 37 | const struct au8522_config *config; |
265a6510 ST |
38 | |
39 | struct dvb_frontend frontend; | |
40 | ||
41 | u32 current_frequency; | |
42 | fe_modulation_t current_modulation; | |
43 | ||
44 | }; | |
45 | ||
46 | static int debug = 0; | |
47 | #define dprintk if (debug) printk | |
48 | ||
49 | /* 16 bit registers, 8 bit values */ | |
e059b0fa | 50 | static int au8522_writereg(struct au8522_state *state, u16 reg, u8 data) |
265a6510 ST |
51 | { |
52 | int ret; | |
53 | u8 buf [] = { reg >> 8, reg & 0xff, data }; | |
54 | ||
55 | struct i2c_msg msg = { .addr = state->config->demod_address, | |
56 | .flags = 0, .buf = buf, .len = 3 }; | |
57 | ||
58 | ret = i2c_transfer(state->i2c, &msg, 1); | |
59 | ||
60 | if (ret != 1) | |
61 | printk("%s: writereg error (reg == 0x%02x, val == 0x%04x, " | |
ce1719a6 | 62 | "ret == %i)\n", __func__, reg, data, ret); |
265a6510 ST |
63 | |
64 | return (ret != 1) ? -1 : 0; | |
65 | } | |
66 | ||
e059b0fa | 67 | static u8 au8522_readreg(struct au8522_state *state, u16 reg) |
265a6510 ST |
68 | { |
69 | int ret; | |
70 | u8 b0 [] = { reg >> 8, reg & 0xff }; | |
71 | u8 b1 [] = { 0 }; | |
72 | ||
73 | struct i2c_msg msg [] = { | |
74 | { .addr = state->config->demod_address, .flags = 0, | |
75 | .buf = b0, .len = 2 }, | |
76 | { .addr = state->config->demod_address, .flags = I2C_M_RD, | |
77 | .buf = b1, .len = 1 } }; | |
78 | ||
79 | ret = i2c_transfer(state->i2c, msg, 2); | |
80 | ||
81 | if (ret != 2) | |
ce1719a6 MK |
82 | printk(KERN_ERR "%s: readreg error (ret == %i)\n", |
83 | __func__, ret); | |
265a6510 ST |
84 | return b1[0]; |
85 | } | |
86 | ||
e059b0fa | 87 | static int au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) |
265a6510 | 88 | { |
e059b0fa | 89 | struct au8522_state *state = fe->demodulator_priv; |
265a6510 | 90 | |
ce1719a6 | 91 | dprintk("%s(%d)\n", __func__, enable); |
265a6510 ST |
92 | |
93 | if (enable) | |
94 | return au8522_writereg(state, 0x106, 1); | |
95 | else | |
96 | return au8522_writereg(state, 0x106, 0); | |
97 | } | |
98 | ||
e059b0fa MK |
99 | static int au8522_enable_modulation(struct dvb_frontend *fe, |
100 | fe_modulation_t m) | |
265a6510 | 101 | { |
e059b0fa | 102 | struct au8522_state *state = fe->demodulator_priv; |
265a6510 | 103 | |
ce1719a6 | 104 | dprintk("%s(0x%08x)\n", __func__, m); |
265a6510 ST |
105 | |
106 | switch(m) { | |
107 | case VSB_8: | |
ce1719a6 | 108 | dprintk("%s() VSB_8\n", __func__); |
265a6510 ST |
109 | |
110 | //au8522_writereg(state, 0x410b, 0x84); // Serial | |
111 | ||
112 | //au8522_writereg(state, 0x8090, 0x82); | |
113 | au8522_writereg(state, 0x8090, 0x84); | |
114 | au8522_writereg(state, 0x4092, 0x11); | |
115 | au8522_writereg(state, 0x2005, 0x00); | |
116 | au8522_writereg(state, 0x8091, 0x80); | |
117 | ||
118 | au8522_writereg(state, 0x80a3, 0x0c); | |
119 | au8522_writereg(state, 0x80a4, 0xe8); | |
120 | au8522_writereg(state, 0x8081, 0xc4); | |
121 | au8522_writereg(state, 0x80a5, 0x40); | |
122 | au8522_writereg(state, 0x80a7, 0x40); | |
123 | au8522_writereg(state, 0x80a6, 0x67); | |
124 | au8522_writereg(state, 0x8262, 0x20); | |
125 | au8522_writereg(state, 0x821c, 0x30); | |
126 | au8522_writereg(state, 0x80d8, 0x1a); | |
127 | au8522_writereg(state, 0x8227, 0xa0); | |
128 | au8522_writereg(state, 0x8121, 0xff); | |
129 | au8522_writereg(state, 0x80a8, 0xf0); | |
130 | au8522_writereg(state, 0x80a9, 0x05); | |
131 | au8522_writereg(state, 0x80aa, 0x77); | |
132 | au8522_writereg(state, 0x80ab, 0xf0); | |
133 | au8522_writereg(state, 0x80ac, 0x05); | |
134 | au8522_writereg(state, 0x80ad, 0x77); | |
135 | au8522_writereg(state, 0x80ae, 0x41); | |
136 | au8522_writereg(state, 0x80af, 0x66); | |
137 | au8522_writereg(state, 0x821b, 0xcc); | |
138 | au8522_writereg(state, 0x821d, 0x80); | |
139 | au8522_writereg(state, 0x80b5, 0xfb); | |
140 | au8522_writereg(state, 0x80b6, 0x8e); | |
141 | au8522_writereg(state, 0x80b7, 0x39); | |
142 | au8522_writereg(state, 0x80a4, 0xe8); | |
143 | au8522_writereg(state, 0x8231, 0x13); | |
144 | break; | |
145 | case QAM_64: | |
146 | case QAM_256: | |
147 | au8522_writereg(state, 0x80a3, 0x09); | |
148 | au8522_writereg(state, 0x80a4, 0x00); | |
149 | au8522_writereg(state, 0x8081, 0xc4); | |
150 | au8522_writereg(state, 0x80a5, 0x40); | |
151 | au8522_writereg(state, 0x80b5, 0xfb); | |
152 | au8522_writereg(state, 0x80b6, 0x8e); | |
153 | au8522_writereg(state, 0x80b7, 0x39); | |
154 | au8522_writereg(state, 0x80aa, 0x77); | |
155 | au8522_writereg(state, 0x80ad, 0x77); | |
156 | au8522_writereg(state, 0x80a6, 0x67); | |
157 | au8522_writereg(state, 0x8262, 0x20); | |
158 | au8522_writereg(state, 0x821c, 0x30); | |
159 | au8522_writereg(state, 0x80b8, 0x3e); | |
160 | au8522_writereg(state, 0x80b9, 0xf0); | |
161 | au8522_writereg(state, 0x80ba, 0x01); | |
162 | au8522_writereg(state, 0x80bb, 0x18); | |
163 | au8522_writereg(state, 0x80bc, 0x50); | |
164 | au8522_writereg(state, 0x80bd, 0x00); | |
165 | au8522_writereg(state, 0x80be, 0xea); | |
166 | au8522_writereg(state, 0x80bf, 0xef); | |
167 | au8522_writereg(state, 0x80c0, 0xfc); | |
168 | au8522_writereg(state, 0x80c1, 0xbd); | |
169 | au8522_writereg(state, 0x80c2, 0x1f); | |
170 | au8522_writereg(state, 0x80c3, 0xfc); | |
171 | au8522_writereg(state, 0x80c4, 0xdd); | |
172 | au8522_writereg(state, 0x80c5, 0xaf); | |
173 | au8522_writereg(state, 0x80c6, 0x00); | |
174 | au8522_writereg(state, 0x80c7, 0x38); | |
175 | au8522_writereg(state, 0x80c8, 0x30); | |
176 | au8522_writereg(state, 0x80c9, 0x05); | |
177 | au8522_writereg(state, 0x80ca, 0x4a); | |
178 | au8522_writereg(state, 0x80cb, 0xd0); | |
179 | au8522_writereg(state, 0x80cc, 0x01); | |
180 | au8522_writereg(state, 0x80cd, 0xd9); | |
181 | au8522_writereg(state, 0x80ce, 0x6f); | |
182 | au8522_writereg(state, 0x80cf, 0xf9); | |
183 | au8522_writereg(state, 0x80d0, 0x70); | |
184 | au8522_writereg(state, 0x80d1, 0xdf); | |
185 | au8522_writereg(state, 0x80d2, 0xf7); | |
186 | au8522_writereg(state, 0x80d3, 0xc2); | |
187 | au8522_writereg(state, 0x80d4, 0xdf); | |
188 | au8522_writereg(state, 0x80d5, 0x02); | |
189 | au8522_writereg(state, 0x80d6, 0x9a); | |
190 | au8522_writereg(state, 0x80d7, 0xd0); | |
191 | au8522_writereg(state, 0x8250, 0x0d); | |
192 | au8522_writereg(state, 0x8251, 0xcd); | |
193 | au8522_writereg(state, 0x8252, 0xe0); | |
194 | au8522_writereg(state, 0x8253, 0x05); | |
195 | au8522_writereg(state, 0x8254, 0xa7); | |
196 | au8522_writereg(state, 0x8255, 0xff); | |
197 | au8522_writereg(state, 0x8256, 0xed); | |
198 | au8522_writereg(state, 0x8257, 0x5b); | |
199 | au8522_writereg(state, 0x8258, 0xae); | |
200 | au8522_writereg(state, 0x8259, 0xe6); | |
201 | au8522_writereg(state, 0x825a, 0x3d); | |
202 | au8522_writereg(state, 0x825b, 0x0f); | |
203 | au8522_writereg(state, 0x825c, 0x0d); | |
204 | au8522_writereg(state, 0x825d, 0xea); | |
205 | au8522_writereg(state, 0x825e, 0xf2); | |
206 | au8522_writereg(state, 0x825f, 0x51); | |
207 | au8522_writereg(state, 0x8260, 0xf5); | |
208 | au8522_writereg(state, 0x8261, 0x06); | |
209 | au8522_writereg(state, 0x821a, 0x00); | |
210 | au8522_writereg(state, 0x8546, 0x40); | |
211 | au8522_writereg(state, 0x8210, 0x26); | |
212 | au8522_writereg(state, 0x8211, 0xf6); | |
213 | au8522_writereg(state, 0x8212, 0x84); | |
214 | au8522_writereg(state, 0x8213, 0x02); | |
215 | au8522_writereg(state, 0x8502, 0x01); | |
216 | au8522_writereg(state, 0x8121, 0x04); | |
217 | au8522_writereg(state, 0x8122, 0x04); | |
218 | au8522_writereg(state, 0x852e, 0x10); | |
219 | au8522_writereg(state, 0x80a4, 0xca); | |
220 | au8522_writereg(state, 0x80a7, 0x40); | |
221 | au8522_writereg(state, 0x8526, 0x01); | |
222 | break; | |
223 | default: | |
ce1719a6 | 224 | dprintk("%s() Invalid modulation\n", __func__); |
265a6510 ST |
225 | return -EINVAL; |
226 | } | |
227 | ||
228 | state->current_modulation = m; | |
229 | ||
230 | return 0; | |
231 | } | |
232 | ||
233 | /* Talk to the demod, set the FEC, GUARD, QAM settings etc */ | |
e059b0fa MK |
234 | static int au8522_set_frontend(struct dvb_frontend *fe, |
235 | struct dvb_frontend_parameters *p) | |
265a6510 | 236 | { |
e059b0fa | 237 | struct au8522_state *state = fe->demodulator_priv; |
265a6510 | 238 | |
ce1719a6 | 239 | dprintk("%s(frequency=%d)\n", __func__, p->frequency); |
265a6510 ST |
240 | |
241 | state->current_frequency = p->frequency; | |
242 | ||
243 | au8522_enable_modulation(fe, p->u.vsb.modulation); | |
244 | ||
245 | /* Allow the demod to settle */ | |
246 | msleep(100); | |
247 | ||
248 | if (fe->ops.tuner_ops.set_params) { | |
249 | if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 1); | |
250 | fe->ops.tuner_ops.set_params(fe, p); | |
251 | if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 0); | |
252 | } | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
257 | /* Reset the demod hardware and reset all of the configuration registers | |
258 | to a default state. */ | |
e059b0fa | 259 | static int au8522_init(struct dvb_frontend *fe) |
265a6510 | 260 | { |
e059b0fa | 261 | struct au8522_state *state = fe->demodulator_priv; |
ce1719a6 | 262 | dprintk("%s()\n", __func__); |
265a6510 ST |
263 | |
264 | au8522_writereg(state, 0xa4, 1 << 5); | |
265 | ||
266 | au8522_i2c_gate_ctrl(fe, 1); | |
267 | ||
268 | return 0; | |
269 | } | |
270 | ||
e059b0fa | 271 | static int au8522_read_status(struct dvb_frontend *fe, fe_status_t *status) |
265a6510 | 272 | { |
e059b0fa | 273 | struct au8522_state *state = fe->demodulator_priv; |
265a6510 ST |
274 | u8 reg; |
275 | u32 tuner_status = 0; | |
276 | ||
277 | *status = 0; | |
278 | ||
279 | if (state->current_modulation == VSB_8) { | |
ce1719a6 | 280 | dprintk("%s() Checking VSB_8\n", __func__); |
265a6510 ST |
281 | //au8522_writereg(state, 0x80a4, 0x20); |
282 | reg = au8522_readreg(state, 0x4088); | |
e059b0fa | 283 | if (reg & 0x01) |
265a6510 | 284 | *status |= FE_HAS_VITERBI; |
e059b0fa | 285 | if (reg & 0x02) |
265a6510 ST |
286 | *status |= FE_HAS_LOCK | FE_HAS_SYNC; |
287 | } else { | |
ce1719a6 | 288 | dprintk("%s() Checking QAM\n", __func__); |
265a6510 | 289 | reg = au8522_readreg(state, 0x4541); |
e059b0fa | 290 | if (reg & 0x80) |
265a6510 | 291 | *status |= FE_HAS_VITERBI; |
e059b0fa | 292 | if (reg & 0x20) |
265a6510 ST |
293 | *status |= FE_HAS_LOCK | FE_HAS_SYNC; |
294 | } | |
295 | ||
296 | switch(state->config->status_mode) { | |
297 | case AU8522_DEMODLOCKING: | |
ce1719a6 | 298 | dprintk("%s() DEMODLOCKING\n", __func__); |
265a6510 ST |
299 | if (*status & FE_HAS_VITERBI) |
300 | *status |= FE_HAS_CARRIER | FE_HAS_SIGNAL; | |
301 | break; | |
302 | case AU8522_TUNERLOCKING: | |
303 | /* Get the tuner status */ | |
ce1719a6 | 304 | dprintk("%s() TUNERLOCKING\n", __func__); |
265a6510 ST |
305 | if (fe->ops.tuner_ops.get_status) { |
306 | if (fe->ops.i2c_gate_ctrl) | |
307 | fe->ops.i2c_gate_ctrl(fe, 1); | |
308 | ||
309 | fe->ops.tuner_ops.get_status(fe, &tuner_status); | |
310 | ||
311 | if (fe->ops.i2c_gate_ctrl) | |
312 | fe->ops.i2c_gate_ctrl(fe, 0); | |
313 | } | |
314 | if (tuner_status) | |
315 | *status |= FE_HAS_CARRIER | FE_HAS_SIGNAL; | |
316 | break; | |
317 | } | |
318 | ||
ce1719a6 | 319 | dprintk("%s() status 0x%08x\n", __func__, *status); |
265a6510 ST |
320 | |
321 | return 0; | |
322 | } | |
323 | ||
e059b0fa | 324 | static int au8522_read_snr(struct dvb_frontend *fe, u16 *snr) |
265a6510 | 325 | { |
ce1719a6 | 326 | dprintk("%s()\n", __func__); |
265a6510 ST |
327 | |
328 | *snr = 0; | |
329 | ||
330 | return 0; | |
331 | } | |
332 | ||
e059b0fa MK |
333 | static int au8522_read_signal_strength(struct dvb_frontend *fe, |
334 | u16 *signal_strength) | |
265a6510 ST |
335 | { |
336 | return au8522_read_snr(fe, signal_strength); | |
337 | } | |
338 | ||
e059b0fa | 339 | static int au8522_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) |
265a6510 | 340 | { |
e059b0fa | 341 | struct au8522_state *state = fe->demodulator_priv; |
265a6510 | 342 | |
8973dc4b MK |
343 | if (state->current_modulation == VSB_8) |
344 | *ucblocks = au8522_readreg(state, 0x4087); | |
345 | else | |
346 | *ucblocks = au8522_readreg(state, 0x4543); | |
265a6510 ST |
347 | |
348 | return 0; | |
349 | } | |
350 | ||
e059b0fa | 351 | static int au8522_read_ber(struct dvb_frontend *fe, u32 *ber) |
265a6510 ST |
352 | { |
353 | return au8522_read_ucblocks(fe, ber); | |
354 | } | |
355 | ||
e059b0fa | 356 | static int au8522_get_frontend(struct dvb_frontend *fe, |
265a6510 ST |
357 | struct dvb_frontend_parameters *p) |
358 | { | |
e059b0fa | 359 | struct au8522_state *state = fe->demodulator_priv; |
265a6510 ST |
360 | |
361 | p->frequency = state->current_frequency; | |
362 | p->u.vsb.modulation = state->current_modulation; | |
363 | ||
364 | return 0; | |
365 | } | |
366 | ||
e059b0fa MK |
367 | static int au8522_get_tune_settings(struct dvb_frontend *fe, |
368 | struct dvb_frontend_tune_settings *tune) | |
265a6510 ST |
369 | { |
370 | tune->min_delay_ms = 1000; | |
371 | return 0; | |
372 | } | |
373 | ||
e059b0fa | 374 | static void au8522_release(struct dvb_frontend *fe) |
265a6510 | 375 | { |
e059b0fa | 376 | struct au8522_state *state = fe->demodulator_priv; |
265a6510 ST |
377 | kfree(state); |
378 | } | |
379 | ||
380 | static struct dvb_frontend_ops au8522_ops; | |
381 | ||
e059b0fa MK |
382 | struct dvb_frontend *au8522_attach(const struct au8522_config *config, |
383 | struct i2c_adapter *i2c) | |
265a6510 | 384 | { |
e059b0fa | 385 | struct au8522_state *state = NULL; |
265a6510 ST |
386 | |
387 | /* allocate memory for the internal state */ | |
388 | state = kmalloc(sizeof(struct au8522_state), GFP_KERNEL); | |
389 | if (state == NULL) | |
390 | goto error; | |
391 | ||
392 | /* setup the state */ | |
393 | state->config = config; | |
394 | state->i2c = i2c; | |
395 | /* create dvb_frontend */ | |
396 | memcpy(&state->frontend.ops, &au8522_ops, | |
397 | sizeof(struct dvb_frontend_ops)); | |
398 | state->frontend.demodulator_priv = state; | |
399 | ||
400 | if (au8522_init(&state->frontend) != 0) { | |
401 | printk(KERN_ERR "%s: Failed to initialize correctly\n", | |
ce1719a6 | 402 | __func__); |
265a6510 ST |
403 | goto error; |
404 | } | |
405 | ||
406 | /* Note: Leaving the I2C gate open here. */ | |
407 | au8522_i2c_gate_ctrl(&state->frontend, 1); | |
408 | ||
409 | return &state->frontend; | |
410 | ||
411 | error: | |
412 | kfree(state); | |
413 | return NULL; | |
414 | } | |
415 | ||
416 | static struct dvb_frontend_ops au8522_ops = { | |
417 | ||
418 | .info = { | |
419 | .name = "Auvitek AU8522 QAM/8VSB Frontend", | |
420 | .type = FE_ATSC, | |
421 | .frequency_min = 54000000, | |
422 | .frequency_max = 858000000, | |
423 | .frequency_stepsize = 62500, | |
424 | .caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB | |
425 | }, | |
426 | ||
427 | .init = au8522_init, | |
428 | .i2c_gate_ctrl = au8522_i2c_gate_ctrl, | |
429 | .set_frontend = au8522_set_frontend, | |
430 | .get_frontend = au8522_get_frontend, | |
431 | .get_tune_settings = au8522_get_tune_settings, | |
432 | .read_status = au8522_read_status, | |
433 | .read_ber = au8522_read_ber, | |
434 | .read_signal_strength = au8522_read_signal_strength, | |
435 | .read_snr = au8522_read_snr, | |
436 | .read_ucblocks = au8522_read_ucblocks, | |
437 | .release = au8522_release, | |
438 | }; | |
439 | ||
440 | module_param(debug, int, 0644); | |
441 | MODULE_PARM_DESC(debug, "Enable verbose debug messages"); | |
442 | ||
443 | MODULE_DESCRIPTION("Auvitek AU8522 QAM-B/ATSC Demodulator driver"); | |
444 | MODULE_AUTHOR("Steven Toth"); | |
445 | MODULE_LICENSE("GPL"); | |
446 | ||
447 | EXPORT_SYMBOL(au8522_attach); | |
448 | ||
449 | /* | |
450 | * Local variables: | |
451 | * c-basic-offset: 8 | |
452 | */ |