diff options
Diffstat (limited to 'drivers/media/dvb-frontends/cx24113.c')
-rw-r--r-- | drivers/media/dvb-frontends/cx24113.c | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/drivers/media/dvb-frontends/cx24113.c b/drivers/media/dvb-frontends/cx24113.c new file mode 100644 index 000000000000..3883c3b31aef --- /dev/null +++ b/drivers/media/dvb-frontends/cx24113.c | |||
@@ -0,0 +1,618 @@ | |||
1 | /* | ||
2 | * Driver for Conexant CX24113/CX24128 Tuner (Satellite) | ||
3 | * | ||
4 | * Copyright (C) 2007-8 Patrick Boettcher <pb@linuxtv.org> | ||
5 | * | ||
6 | * Developed for BBTI / Technisat | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program; if not, write to the Free Software | ||
21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
22 | */ | ||
23 | |||
24 | #include <linux/slab.h> | ||
25 | #include <linux/kernel.h> | ||
26 | #include <linux/module.h> | ||
27 | #include <linux/init.h> | ||
28 | |||
29 | #include "dvb_frontend.h" | ||
30 | #include "cx24113.h" | ||
31 | |||
32 | static int debug; | ||
33 | |||
34 | #define cx_info(args...) do { printk(KERN_INFO "CX24113: " args); } while (0) | ||
35 | #define cx_err(args...) do { printk(KERN_ERR "CX24113: " args); } while (0) | ||
36 | |||
37 | #define dprintk(args...) \ | ||
38 | do { \ | ||
39 | if (debug) { \ | ||
40 | printk(KERN_DEBUG "CX24113: %s: ", __func__); \ | ||
41 | printk(args); \ | ||
42 | } \ | ||
43 | } while (0) | ||
44 | |||
45 | struct cx24113_state { | ||
46 | struct i2c_adapter *i2c; | ||
47 | const struct cx24113_config *config; | ||
48 | |||
49 | #define REV_CX24113 0x23 | ||
50 | u8 rev; | ||
51 | u8 ver; | ||
52 | |||
53 | u8 icp_mode:1; | ||
54 | |||
55 | #define ICP_LEVEL1 0 | ||
56 | #define ICP_LEVEL2 1 | ||
57 | #define ICP_LEVEL3 2 | ||
58 | #define ICP_LEVEL4 3 | ||
59 | u8 icp_man:2; | ||
60 | u8 icp_auto_low:2; | ||
61 | u8 icp_auto_mlow:2; | ||
62 | u8 icp_auto_mhi:2; | ||
63 | u8 icp_auto_hi:2; | ||
64 | u8 icp_dig; | ||
65 | |||
66 | #define LNA_MIN_GAIN 0 | ||
67 | #define LNA_MID_GAIN 1 | ||
68 | #define LNA_MAX_GAIN 2 | ||
69 | u8 lna_gain:2; | ||
70 | |||
71 | u8 acp_on:1; | ||
72 | |||
73 | u8 vco_mode:2; | ||
74 | u8 vco_shift:1; | ||
75 | #define VCOBANDSEL_6 0x80 | ||
76 | #define VCOBANDSEL_5 0x01 | ||
77 | #define VCOBANDSEL_4 0x02 | ||
78 | #define VCOBANDSEL_3 0x04 | ||
79 | #define VCOBANDSEL_2 0x08 | ||
80 | #define VCOBANDSEL_1 0x10 | ||
81 | u8 vco_band; | ||
82 | |||
83 | #define VCODIV4 4 | ||
84 | #define VCODIV2 2 | ||
85 | u8 vcodiv; | ||
86 | |||
87 | u8 bs_delay:4; | ||
88 | u16 bs_freqcnt:13; | ||
89 | u16 bs_rdiv; | ||
90 | u8 prescaler_mode:1; | ||
91 | |||
92 | u8 rfvga_bias_ctrl; | ||
93 | |||
94 | s16 tuner_gain_thres; | ||
95 | u8 gain_level; | ||
96 | |||
97 | u32 frequency; | ||
98 | |||
99 | u8 refdiv; | ||
100 | |||
101 | u8 Fwindow_enabled; | ||
102 | }; | ||
103 | |||
104 | static int cx24113_writereg(struct cx24113_state *state, int reg, int data) | ||
105 | { | ||
106 | u8 buf[] = { reg, data }; | ||
107 | struct i2c_msg msg = { .addr = state->config->i2c_addr, | ||
108 | .flags = 0, .buf = buf, .len = 2 }; | ||
109 | int err = i2c_transfer(state->i2c, &msg, 1); | ||
110 | if (err != 1) { | ||
111 | printk(KERN_DEBUG "%s: writereg error(err == %i, reg == 0x%02x," | ||
112 | " data == 0x%02x)\n", __func__, err, reg, data); | ||
113 | return err; | ||
114 | } | ||
115 | |||
116 | return 0; | ||
117 | } | ||
118 | |||
119 | static int cx24113_readreg(struct cx24113_state *state, u8 reg) | ||
120 | { | ||
121 | int ret; | ||
122 | u8 b; | ||
123 | struct i2c_msg msg[] = { | ||
124 | { .addr = state->config->i2c_addr, | ||
125 | .flags = 0, .buf = ®, .len = 1 }, | ||
126 | { .addr = state->config->i2c_addr, | ||
127 | .flags = I2C_M_RD, .buf = &b, .len = 1 } | ||
128 | }; | ||
129 | |||
130 | ret = i2c_transfer(state->i2c, msg, 2); | ||
131 | |||
132 | if (ret != 2) { | ||
133 | printk(KERN_DEBUG "%s: reg=0x%x (error=%d)\n", | ||
134 | __func__, reg, ret); | ||
135 | return ret; | ||
136 | } | ||
137 | |||
138 | return b; | ||
139 | } | ||
140 | |||
141 | static void cx24113_set_parameters(struct cx24113_state *state) | ||
142 | { | ||
143 | u8 r; | ||
144 | |||
145 | r = cx24113_readreg(state, 0x10) & 0x82; | ||
146 | r |= state->icp_mode; | ||
147 | r |= state->icp_man << 4; | ||
148 | r |= state->icp_dig << 2; | ||
149 | r |= state->prescaler_mode << 5; | ||
150 | cx24113_writereg(state, 0x10, r); | ||
151 | |||
152 | r = (state->icp_auto_low << 0) | (state->icp_auto_mlow << 2) | ||
153 | | (state->icp_auto_mhi << 4) | (state->icp_auto_hi << 6); | ||
154 | cx24113_writereg(state, 0x11, r); | ||
155 | |||
156 | if (state->rev == REV_CX24113) { | ||
157 | r = cx24113_readreg(state, 0x20) & 0xec; | ||
158 | r |= state->lna_gain; | ||
159 | r |= state->rfvga_bias_ctrl << 4; | ||
160 | cx24113_writereg(state, 0x20, r); | ||
161 | } | ||
162 | |||
163 | r = cx24113_readreg(state, 0x12) & 0x03; | ||
164 | r |= state->acp_on << 2; | ||
165 | r |= state->bs_delay << 4; | ||
166 | cx24113_writereg(state, 0x12, r); | ||
167 | |||
168 | r = cx24113_readreg(state, 0x18) & 0x40; | ||
169 | r |= state->vco_shift; | ||
170 | if (state->vco_band == VCOBANDSEL_6) | ||
171 | r |= (1 << 7); | ||
172 | else | ||
173 | r |= (state->vco_band << 1); | ||
174 | cx24113_writereg(state, 0x18, r); | ||
175 | |||
176 | r = cx24113_readreg(state, 0x14) & 0x20; | ||
177 | r |= (state->vco_mode << 6) | ((state->bs_freqcnt >> 8) & 0x1f); | ||
178 | cx24113_writereg(state, 0x14, r); | ||
179 | cx24113_writereg(state, 0x15, (state->bs_freqcnt & 0xff)); | ||
180 | |||
181 | cx24113_writereg(state, 0x16, (state->bs_rdiv >> 4) & 0xff); | ||
182 | r = (cx24113_readreg(state, 0x17) & 0x0f) | | ||
183 | ((state->bs_rdiv & 0x0f) << 4); | ||
184 | cx24113_writereg(state, 0x17, r); | ||
185 | } | ||
186 | |||
187 | #define VGA_0 0x00 | ||
188 | #define VGA_1 0x04 | ||
189 | #define VGA_2 0x02 | ||
190 | #define VGA_3 0x06 | ||
191 | #define VGA_4 0x01 | ||
192 | #define VGA_5 0x05 | ||
193 | #define VGA_6 0x03 | ||
194 | #define VGA_7 0x07 | ||
195 | |||
196 | #define RFVGA_0 0x00 | ||
197 | #define RFVGA_1 0x01 | ||
198 | #define RFVGA_2 0x02 | ||
199 | #define RFVGA_3 0x03 | ||
200 | |||
201 | static int cx24113_set_gain_settings(struct cx24113_state *state, | ||
202 | s16 power_estimation) | ||
203 | { | ||
204 | u8 ampout = cx24113_readreg(state, 0x1d) & 0xf0, | ||
205 | vga = cx24113_readreg(state, 0x1f) & 0x3f, | ||
206 | rfvga = cx24113_readreg(state, 0x20) & 0xf3; | ||
207 | u8 gain_level = power_estimation >= state->tuner_gain_thres; | ||
208 | |||
209 | dprintk("power estimation: %d, thres: %d, gain_level: %d/%d\n", | ||
210 | power_estimation, state->tuner_gain_thres, | ||
211 | state->gain_level, gain_level); | ||
212 | |||
213 | if (gain_level == state->gain_level) | ||
214 | return 0; /* nothing to be done */ | ||
215 | |||
216 | ampout |= 0xf; | ||
217 | |||
218 | if (gain_level) { | ||
219 | rfvga |= RFVGA_0 << 2; | ||
220 | vga |= (VGA_7 << 3) | VGA_7; | ||
221 | } else { | ||
222 | rfvga |= RFVGA_2 << 2; | ||
223 | vga |= (VGA_6 << 3) | VGA_2; | ||
224 | } | ||
225 | state->gain_level = gain_level; | ||
226 | |||
227 | cx24113_writereg(state, 0x1d, ampout); | ||
228 | cx24113_writereg(state, 0x1f, vga); | ||
229 | cx24113_writereg(state, 0x20, rfvga); | ||
230 | |||
231 | return 1; /* did something */ | ||
232 | } | ||
233 | |||
234 | static int cx24113_set_Fref(struct cx24113_state *state, u8 high) | ||
235 | { | ||
236 | u8 xtal = cx24113_readreg(state, 0x02); | ||
237 | if (state->rev == 0x43 && state->vcodiv == VCODIV4) | ||
238 | high = 1; | ||
239 | |||
240 | xtal &= ~0x2; | ||
241 | if (high) | ||
242 | xtal |= high << 1; | ||
243 | return cx24113_writereg(state, 0x02, xtal); | ||
244 | } | ||
245 | |||
246 | static int cx24113_enable(struct cx24113_state *state, u8 enable) | ||
247 | { | ||
248 | u8 r21 = (cx24113_readreg(state, 0x21) & 0xc0) | enable; | ||
249 | if (state->rev == REV_CX24113) | ||
250 | r21 |= (1 << 1); | ||
251 | return cx24113_writereg(state, 0x21, r21); | ||
252 | } | ||
253 | |||
254 | static int cx24113_set_bandwidth(struct cx24113_state *state, u32 bandwidth_khz) | ||
255 | { | ||
256 | u8 r; | ||
257 | |||
258 | if (bandwidth_khz <= 19000) | ||
259 | r = 0x03 << 6; | ||
260 | else if (bandwidth_khz <= 25000) | ||
261 | r = 0x02 << 6; | ||
262 | else | ||
263 | r = 0x01 << 6; | ||
264 | |||
265 | dprintk("bandwidth to be set: %d\n", bandwidth_khz); | ||
266 | bandwidth_khz *= 10; | ||
267 | bandwidth_khz -= 10000; | ||
268 | bandwidth_khz /= 1000; | ||
269 | bandwidth_khz += 5; | ||
270 | bandwidth_khz /= 10; | ||
271 | |||
272 | dprintk("bandwidth: %d %d\n", r >> 6, bandwidth_khz); | ||
273 | |||
274 | r |= bandwidth_khz & 0x3f; | ||
275 | |||
276 | return cx24113_writereg(state, 0x1e, r); | ||
277 | } | ||
278 | |||
279 | static int cx24113_set_clk_inversion(struct cx24113_state *state, u8 on) | ||
280 | { | ||
281 | u8 r = (cx24113_readreg(state, 0x10) & 0x7f) | ((on & 0x1) << 7); | ||
282 | return cx24113_writereg(state, 0x10, r); | ||
283 | } | ||
284 | |||
285 | static int cx24113_get_status(struct dvb_frontend *fe, u32 *status) | ||
286 | { | ||
287 | struct cx24113_state *state = fe->tuner_priv; | ||
288 | u8 r = (cx24113_readreg(state, 0x10) & 0x02) >> 1; | ||
289 | if (r) | ||
290 | *status |= TUNER_STATUS_LOCKED; | ||
291 | dprintk("PLL locked: %d\n", r); | ||
292 | return 0; | ||
293 | } | ||
294 | |||
295 | static u8 cx24113_set_ref_div(struct cx24113_state *state, u8 refdiv) | ||
296 | { | ||
297 | if (state->rev == 0x43 && state->vcodiv == VCODIV4) | ||
298 | refdiv = 2; | ||
299 | return state->refdiv = refdiv; | ||
300 | } | ||
301 | |||
302 | static void cx24113_calc_pll_nf(struct cx24113_state *state, u16 *n, s32 *f) | ||
303 | { | ||
304 | s32 N; | ||
305 | s64 F; | ||
306 | u64 dividend; | ||
307 | u8 R, r; | ||
308 | u8 vcodiv; | ||
309 | u8 factor; | ||
310 | s32 freq_hz = state->frequency * 1000; | ||
311 | |||
312 | if (state->config->xtal_khz < 20000) | ||
313 | factor = 1; | ||
314 | else | ||
315 | factor = 2; | ||
316 | |||
317 | if (state->rev == REV_CX24113) { | ||
318 | if (state->frequency >= 1100000) | ||
319 | vcodiv = VCODIV2; | ||
320 | else | ||
321 | vcodiv = VCODIV4; | ||
322 | } else { | ||
323 | if (state->frequency >= 1165000) | ||
324 | vcodiv = VCODIV2; | ||
325 | else | ||
326 | vcodiv = VCODIV4; | ||
327 | } | ||
328 | state->vcodiv = vcodiv; | ||
329 | |||
330 | dprintk("calculating N/F for %dHz with vcodiv %d\n", freq_hz, vcodiv); | ||
331 | R = 0; | ||
332 | do { | ||
333 | R = cx24113_set_ref_div(state, R + 1); | ||
334 | |||
335 | /* calculate tuner PLL settings: */ | ||
336 | N = (freq_hz / 100 * vcodiv) * R; | ||
337 | N /= (state->config->xtal_khz) * factor * 2; | ||
338 | N += 5; /* For round up. */ | ||
339 | N /= 10; | ||
340 | N -= 32; | ||
341 | } while (N < 6 && R < 3); | ||
342 | |||
343 | if (N < 6) { | ||
344 | cx_err("strange frequency: N < 6\n"); | ||
345 | return; | ||
346 | } | ||
347 | F = freq_hz; | ||
348 | F *= (u64) (R * vcodiv * 262144); | ||
349 | dprintk("1 N: %d, F: %lld, R: %d\n", N, (long long)F, R); | ||
350 | /* do_div needs an u64 as first argument */ | ||
351 | dividend = F; | ||
352 | do_div(dividend, state->config->xtal_khz * 1000 * factor * 2); | ||
353 | F = dividend; | ||
354 | dprintk("2 N: %d, F: %lld, R: %d\n", N, (long long)F, R); | ||
355 | F -= (N + 32) * 262144; | ||
356 | |||
357 | dprintk("3 N: %d, F: %lld, R: %d\n", N, (long long)F, R); | ||
358 | |||
359 | if (state->Fwindow_enabled) { | ||
360 | if (F > (262144 / 2 - 1638)) | ||
361 | F = 262144 / 2 - 1638; | ||
362 | if (F < (-262144 / 2 + 1638)) | ||
363 | F = -262144 / 2 + 1638; | ||
364 | if ((F < 3277 && F > 0) || (F > -3277 && F < 0)) { | ||
365 | F = 0; | ||
366 | r = cx24113_readreg(state, 0x10); | ||
367 | cx24113_writereg(state, 0x10, r | (1 << 6)); | ||
368 | } | ||
369 | } | ||
370 | dprintk("4 N: %d, F: %lld, R: %d\n", N, (long long)F, R); | ||
371 | |||
372 | *n = (u16) N; | ||
373 | *f = (s32) F; | ||
374 | } | ||
375 | |||
376 | |||
377 | static void cx24113_set_nfr(struct cx24113_state *state, u16 n, s32 f, u8 r) | ||
378 | { | ||
379 | u8 reg; | ||
380 | cx24113_writereg(state, 0x19, (n >> 1) & 0xff); | ||
381 | |||
382 | reg = ((n & 0x1) << 7) | ((f >> 11) & 0x7f); | ||
383 | cx24113_writereg(state, 0x1a, reg); | ||
384 | |||
385 | cx24113_writereg(state, 0x1b, (f >> 3) & 0xff); | ||
386 | |||
387 | reg = cx24113_readreg(state, 0x1c) & 0x1f; | ||
388 | cx24113_writereg(state, 0x1c, reg | ((f & 0x7) << 5)); | ||
389 | |||
390 | cx24113_set_Fref(state, r - 1); | ||
391 | } | ||
392 | |||
393 | static int cx24113_set_frequency(struct cx24113_state *state, u32 frequency) | ||
394 | { | ||
395 | u8 r = 1; /* or 2 */ | ||
396 | u16 n = 6; | ||
397 | s32 f = 0; | ||
398 | |||
399 | r = cx24113_readreg(state, 0x14); | ||
400 | cx24113_writereg(state, 0x14, r & 0x3f); | ||
401 | |||
402 | r = cx24113_readreg(state, 0x10); | ||
403 | cx24113_writereg(state, 0x10, r & 0xbf); | ||
404 | |||
405 | state->frequency = frequency; | ||
406 | |||
407 | dprintk("tuning to frequency: %d\n", frequency); | ||
408 | |||
409 | cx24113_calc_pll_nf(state, &n, &f); | ||
410 | cx24113_set_nfr(state, n, f, state->refdiv); | ||
411 | |||
412 | r = cx24113_readreg(state, 0x18) & 0xbf; | ||
413 | if (state->vcodiv != VCODIV2) | ||
414 | r |= 1 << 6; | ||
415 | cx24113_writereg(state, 0x18, r); | ||
416 | |||
417 | /* The need for this sleep is not clear. But helps in some cases */ | ||
418 | msleep(5); | ||
419 | |||
420 | r = cx24113_readreg(state, 0x1c) & 0xef; | ||
421 | cx24113_writereg(state, 0x1c, r | (1 << 4)); | ||
422 | return 0; | ||
423 | } | ||
424 | |||
425 | static int cx24113_init(struct dvb_frontend *fe) | ||
426 | { | ||
427 | struct cx24113_state *state = fe->tuner_priv; | ||
428 | int ret; | ||
429 | |||
430 | state->tuner_gain_thres = -50; | ||
431 | state->gain_level = 255; /* to force a gain-setting initialization */ | ||
432 | state->icp_mode = 0; | ||
433 | |||
434 | if (state->config->xtal_khz < 11000) { | ||
435 | state->icp_auto_hi = ICP_LEVEL4; | ||
436 | state->icp_auto_mhi = ICP_LEVEL4; | ||
437 | state->icp_auto_mlow = ICP_LEVEL3; | ||
438 | state->icp_auto_low = ICP_LEVEL3; | ||
439 | } else { | ||
440 | state->icp_auto_hi = ICP_LEVEL4; | ||
441 | state->icp_auto_mhi = ICP_LEVEL4; | ||
442 | state->icp_auto_mlow = ICP_LEVEL3; | ||
443 | state->icp_auto_low = ICP_LEVEL2; | ||
444 | } | ||
445 | |||
446 | state->icp_dig = ICP_LEVEL3; | ||
447 | state->icp_man = ICP_LEVEL1; | ||
448 | state->acp_on = 1; | ||
449 | state->vco_mode = 0; | ||
450 | state->vco_shift = 0; | ||
451 | state->vco_band = VCOBANDSEL_1; | ||
452 | state->bs_delay = 8; | ||
453 | state->bs_freqcnt = 0x0fff; | ||
454 | state->bs_rdiv = 0x0fff; | ||
455 | state->prescaler_mode = 0; | ||
456 | state->lna_gain = LNA_MAX_GAIN; | ||
457 | state->rfvga_bias_ctrl = 1; | ||
458 | state->Fwindow_enabled = 1; | ||
459 | |||
460 | cx24113_set_Fref(state, 0); | ||
461 | cx24113_enable(state, 0x3d); | ||
462 | cx24113_set_parameters(state); | ||
463 | |||
464 | cx24113_set_gain_settings(state, -30); | ||
465 | |||
466 | cx24113_set_bandwidth(state, 18025); | ||
467 | cx24113_set_clk_inversion(state, 1); | ||
468 | |||
469 | if (state->config->xtal_khz >= 40000) | ||
470 | ret = cx24113_writereg(state, 0x02, | ||
471 | (cx24113_readreg(state, 0x02) & 0xfb) | (1 << 2)); | ||
472 | else | ||
473 | ret = cx24113_writereg(state, 0x02, | ||
474 | (cx24113_readreg(state, 0x02) & 0xfb) | (0 << 2)); | ||
475 | |||
476 | return ret; | ||
477 | } | ||
478 | |||
479 | static int cx24113_set_params(struct dvb_frontend *fe) | ||
480 | { | ||
481 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; | ||
482 | struct cx24113_state *state = fe->tuner_priv; | ||
483 | /* for a ROLL-OFF factor of 0.35, 0.2: 600, 0.25: 625 */ | ||
484 | u32 roll_off = 675; | ||
485 | u32 bw; | ||
486 | |||
487 | bw = ((c->symbol_rate/100) * roll_off) / 1000; | ||
488 | bw += (10000000/100) + 5; | ||
489 | bw /= 10; | ||
490 | bw += 1000; | ||
491 | cx24113_set_bandwidth(state, bw); | ||
492 | |||
493 | cx24113_set_frequency(state, c->frequency); | ||
494 | msleep(5); | ||
495 | return cx24113_get_status(fe, &bw); | ||
496 | } | ||
497 | |||
498 | static s8 cx24113_agc_table[2][10] = { | ||
499 | {-54, -41, -35, -30, -25, -21, -16, -10, -6, -2}, | ||
500 | {-39, -35, -30, -25, -19, -15, -11, -5, 1, 9}, | ||
501 | }; | ||
502 | |||
503 | void cx24113_agc_callback(struct dvb_frontend *fe) | ||
504 | { | ||
505 | struct cx24113_state *state = fe->tuner_priv; | ||
506 | s16 s, i; | ||
507 | if (!fe->ops.read_signal_strength) | ||
508 | return; | ||
509 | |||
510 | do { | ||
511 | /* this only works with the current CX24123 implementation */ | ||
512 | fe->ops.read_signal_strength(fe, (u16 *) &s); | ||
513 | s >>= 8; | ||
514 | dprintk("signal strength: %d\n", s); | ||
515 | for (i = 0; i < sizeof(cx24113_agc_table[0]); i++) | ||
516 | if (cx24113_agc_table[state->gain_level][i] > s) | ||
517 | break; | ||
518 | s = -25 - i*5; | ||
519 | } while (cx24113_set_gain_settings(state, s)); | ||
520 | } | ||
521 | EXPORT_SYMBOL(cx24113_agc_callback); | ||
522 | |||
523 | static int cx24113_get_frequency(struct dvb_frontend *fe, u32 *frequency) | ||
524 | { | ||
525 | struct cx24113_state *state = fe->tuner_priv; | ||
526 | *frequency = state->frequency; | ||
527 | return 0; | ||
528 | } | ||
529 | |||
530 | static int cx24113_release(struct dvb_frontend *fe) | ||
531 | { | ||
532 | struct cx24113_state *state = fe->tuner_priv; | ||
533 | dprintk("\n"); | ||
534 | fe->tuner_priv = NULL; | ||
535 | kfree(state); | ||
536 | return 0; | ||
537 | } | ||
538 | |||
539 | static const struct dvb_tuner_ops cx24113_tuner_ops = { | ||
540 | .info = { | ||
541 | .name = "Conexant CX24113", | ||
542 | .frequency_min = 950000, | ||
543 | .frequency_max = 2150000, | ||
544 | .frequency_step = 125, | ||
545 | }, | ||
546 | |||
547 | .release = cx24113_release, | ||
548 | |||
549 | .init = cx24113_init, | ||
550 | |||
551 | .set_params = cx24113_set_params, | ||
552 | .get_frequency = cx24113_get_frequency, | ||
553 | .get_status = cx24113_get_status, | ||
554 | }; | ||
555 | |||
556 | struct dvb_frontend *cx24113_attach(struct dvb_frontend *fe, | ||
557 | const struct cx24113_config *config, struct i2c_adapter *i2c) | ||
558 | { | ||
559 | /* allocate memory for the internal state */ | ||
560 | struct cx24113_state *state = | ||
561 | kzalloc(sizeof(struct cx24113_state), GFP_KERNEL); | ||
562 | int rc; | ||
563 | if (state == NULL) { | ||
564 | cx_err("Unable to kzalloc\n"); | ||
565 | goto error; | ||
566 | } | ||
567 | |||
568 | /* setup the state */ | ||
569 | state->config = config; | ||
570 | state->i2c = i2c; | ||
571 | |||
572 | cx_info("trying to detect myself\n"); | ||
573 | |||
574 | /* making a dummy read, because of some expected troubles | ||
575 | * after power on */ | ||
576 | cx24113_readreg(state, 0x00); | ||
577 | |||
578 | rc = cx24113_readreg(state, 0x00); | ||
579 | if (rc < 0) { | ||
580 | cx_info("CX24113 not found.\n"); | ||
581 | goto error; | ||
582 | } | ||
583 | state->rev = rc; | ||
584 | |||
585 | switch (rc) { | ||
586 | case 0x43: | ||
587 | cx_info("detected CX24113 variant\n"); | ||
588 | break; | ||
589 | case REV_CX24113: | ||
590 | cx_info("successfully detected\n"); | ||
591 | break; | ||
592 | default: | ||
593 | cx_err("unsupported device id: %x\n", state->rev); | ||
594 | goto error; | ||
595 | } | ||
596 | state->ver = cx24113_readreg(state, 0x01); | ||
597 | cx_info("version: %x\n", state->ver); | ||
598 | |||
599 | /* create dvb_frontend */ | ||
600 | memcpy(&fe->ops.tuner_ops, &cx24113_tuner_ops, | ||
601 | sizeof(struct dvb_tuner_ops)); | ||
602 | fe->tuner_priv = state; | ||
603 | return fe; | ||
604 | |||
605 | error: | ||
606 | kfree(state); | ||
607 | |||
608 | return NULL; | ||
609 | } | ||
610 | EXPORT_SYMBOL(cx24113_attach); | ||
611 | |||
612 | module_param(debug, int, 0644); | ||
613 | MODULE_PARM_DESC(debug, "Activates frontend debugging (default:0)"); | ||
614 | |||
615 | MODULE_AUTHOR("Patrick Boettcher <pb@linuxtv.org>"); | ||
616 | MODULE_DESCRIPTION("DVB Frontend module for Conexant CX24113/CX24128hardware"); | ||
617 | MODULE_LICENSE("GPL"); | ||
618 | |||