Commit | Line | Data |
---|---|---|
95963cbc DW |
1 | /* |
2 | * Driver for Maxim MAX2165 silicon tuner | |
3 | * | |
4 | * Copyright (c) 2009 David T. L. Wong <davidtlwong@gmail.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 | * | |
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 <linux/module.h> | |
23 | #include <linux/moduleparam.h> | |
24 | #include <linux/videodev2.h> | |
25 | #include <linux/delay.h> | |
26 | #include <linux/dvb/frontend.h> | |
27 | #include <linux/i2c.h> | |
5a0e3ad6 | 28 | #include <linux/slab.h> |
95963cbc DW |
29 | |
30 | #include "dvb_frontend.h" | |
31 | ||
32 | #include "max2165.h" | |
33 | #include "max2165_priv.h" | |
34 | #include "tuner-i2c.h" | |
35 | ||
36 | #define dprintk(args...) \ | |
37 | do { \ | |
38 | if (debug) \ | |
39 | printk(KERN_DEBUG "max2165: " args); \ | |
40 | } while (0) | |
41 | ||
42 | static int debug; | |
43 | module_param(debug, int, 0644); | |
44 | MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); | |
45 | ||
46 | static int max2165_write_reg(struct max2165_priv *priv, u8 reg, u8 data) | |
47 | { | |
48 | int ret; | |
49 | u8 buf[] = { reg, data }; | |
50 | struct i2c_msg msg = { .flags = 0, .buf = buf, .len = 2 }; | |
51 | ||
52 | msg.addr = priv->config->i2c_address; | |
53 | ||
54 | if (debug >= 2) | |
55 | printk(KERN_DEBUG "%s: reg=0x%02X, data=0x%02X\n", | |
56 | __func__, reg, data); | |
57 | ||
58 | ret = i2c_transfer(priv->i2c, &msg, 1); | |
59 | ||
60 | if (ret != 1) | |
61 | dprintk(KERN_DEBUG "%s: error reg=0x%x, data=0x%x, ret=%i\n", | |
62 | __func__, reg, data, ret); | |
63 | ||
64 | return (ret != 1) ? -EIO : 0; | |
65 | } | |
66 | ||
67 | static int max2165_read_reg(struct max2165_priv *priv, u8 reg, u8 *p_data) | |
68 | { | |
69 | int ret; | |
70 | u8 dev_addr = priv->config->i2c_address; | |
71 | ||
72 | u8 b0[] = { reg }; | |
73 | u8 b1[] = { 0 }; | |
74 | struct i2c_msg msg[] = { | |
75 | { .addr = dev_addr, .flags = 0, .buf = b0, .len = 1 }, | |
76 | { .addr = dev_addr, .flags = I2C_M_RD, .buf = b1, .len = 1 }, | |
77 | }; | |
78 | ||
79 | ret = i2c_transfer(priv->i2c, msg, 2); | |
80 | if (ret != 2) { | |
81 | dprintk(KERN_DEBUG "%s: error reg=0x%x, ret=%i\n", | |
82 | __func__, reg, ret); | |
83 | return -EIO; | |
84 | } | |
85 | ||
86 | *p_data = b1[0]; | |
87 | if (debug >= 2) | |
88 | printk(KERN_DEBUG "%s: reg=0x%02X, data=0x%02X\n", | |
89 | __func__, reg, b1[0]); | |
90 | return 0; | |
91 | } | |
92 | ||
93 | static int max2165_mask_write_reg(struct max2165_priv *priv, u8 reg, | |
94 | u8 mask, u8 data) | |
95 | { | |
96 | int ret; | |
97 | u8 v; | |
98 | ||
99 | data &= mask; | |
100 | ret = max2165_read_reg(priv, reg, &v); | |
101 | if (ret != 0) | |
102 | return ret; | |
103 | v &= ~mask; | |
104 | v |= data; | |
105 | ret = max2165_write_reg(priv, reg, v); | |
106 | ||
107 | return ret; | |
108 | } | |
109 | ||
110 | static int max2165_read_rom_table(struct max2165_priv *priv) | |
111 | { | |
112 | u8 dat[3]; | |
113 | int i; | |
114 | ||
115 | for (i = 0; i < 3; i++) { | |
116 | max2165_write_reg(priv, REG_ROM_TABLE_ADDR, i + 1); | |
117 | max2165_read_reg(priv, REG_ROM_TABLE_DATA, &dat[i]); | |
118 | } | |
119 | ||
120 | priv->tf_ntch_low_cfg = dat[0] >> 4; | |
121 | priv->tf_ntch_hi_cfg = dat[0] & 0x0F; | |
122 | priv->tf_balun_low_ref = dat[1] & 0x0F; | |
123 | priv->tf_balun_hi_ref = dat[1] >> 4; | |
124 | priv->bb_filter_7mhz_cfg = dat[2] & 0x0F; | |
125 | priv->bb_filter_8mhz_cfg = dat[2] >> 4; | |
126 | ||
127 | dprintk("tf_ntch_low_cfg = 0x%X\n", priv->tf_ntch_low_cfg); | |
128 | dprintk("tf_ntch_hi_cfg = 0x%X\n", priv->tf_ntch_hi_cfg); | |
129 | dprintk("tf_balun_low_ref = 0x%X\n", priv->tf_balun_low_ref); | |
130 | dprintk("tf_balun_hi_ref = 0x%X\n", priv->tf_balun_hi_ref); | |
131 | dprintk("bb_filter_7mhz_cfg = 0x%X\n", priv->bb_filter_7mhz_cfg); | |
132 | dprintk("bb_filter_8mhz_cfg = 0x%X\n", priv->bb_filter_8mhz_cfg); | |
133 | ||
134 | return 0; | |
135 | } | |
136 | ||
137 | static int max2165_set_osc(struct max2165_priv *priv, u8 osc /*MHz*/) | |
138 | { | |
139 | u8 v; | |
140 | ||
141 | v = (osc / 2); | |
142 | if (v == 2) | |
143 | v = 0x7; | |
144 | else | |
145 | v -= 8; | |
146 | ||
147 | max2165_mask_write_reg(priv, REG_PLL_CFG, 0x07, v); | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
152 | static int max2165_set_bandwidth(struct max2165_priv *priv, u32 bw) | |
153 | { | |
154 | u8 val; | |
155 | ||
156 | if (bw == BANDWIDTH_8_MHZ) | |
157 | val = priv->bb_filter_8mhz_cfg; | |
158 | else | |
159 | val = priv->bb_filter_7mhz_cfg; | |
160 | ||
161 | max2165_mask_write_reg(priv, REG_BASEBAND_CTRL, 0xF0, val << 4); | |
162 | ||
163 | return 0; | |
164 | } | |
165 | ||
166 | int fixpt_div32(u32 dividend, u32 divisor, u32 *quotient, u32 *fraction) | |
167 | { | |
168 | u32 remainder; | |
169 | u32 q, f = 0; | |
170 | int i; | |
171 | ||
172 | if (0 == divisor) | |
173 | return -1; | |
174 | ||
175 | q = dividend / divisor; | |
176 | remainder = dividend - q * divisor; | |
177 | ||
178 | for (i = 0; i < 31; i++) { | |
179 | remainder <<= 1; | |
180 | if (remainder >= divisor) { | |
181 | f += 1; | |
182 | remainder -= divisor; | |
183 | } | |
184 | f <<= 1; | |
185 | } | |
186 | ||
187 | *quotient = q; | |
188 | *fraction = f; | |
189 | ||
190 | return 0; | |
191 | } | |
192 | ||
193 | static int max2165_set_rf(struct max2165_priv *priv, u32 freq) | |
194 | { | |
195 | u8 tf; | |
196 | u8 tf_ntch; | |
5476ffd2 | 197 | u32 t; |
95963cbc DW |
198 | u32 quotient, fraction; |
199 | ||
200 | /* Set PLL divider according to RF frequency */ | |
201 | fixpt_div32(freq / 1000, priv->config->osc_clk * 1000, | |
202 | "ient, &fraction); | |
203 | ||
204 | /* 20-bit fraction */ | |
205 | fraction >>= 12; | |
206 | ||
207 | max2165_write_reg(priv, REG_NDIV_INT, quotient); | |
208 | max2165_mask_write_reg(priv, REG_NDIV_FRAC2, 0x0F, fraction >> 16); | |
209 | max2165_write_reg(priv, REG_NDIV_FRAC1, fraction >> 8); | |
210 | max2165_write_reg(priv, REG_NDIV_FRAC0, fraction); | |
211 | ||
212 | /* Norch Filter */ | |
213 | tf_ntch = (freq < 725000000) ? | |
214 | priv->tf_ntch_low_cfg : priv->tf_ntch_hi_cfg; | |
215 | ||
216 | /* Tracking filter balun */ | |
217 | t = priv->tf_balun_low_ref; | |
218 | t += (priv->tf_balun_hi_ref - priv->tf_balun_low_ref) | |
219 | * (freq / 1000 - 470000) / (780000 - 470000); | |
220 | ||
221 | tf = t; | |
222 | dprintk("tf = %X\n", tf); | |
223 | tf |= tf_ntch << 4; | |
224 | ||
225 | max2165_write_reg(priv, REG_TRACK_FILTER, tf); | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
230 | static void max2165_debug_status(struct max2165_priv *priv) | |
231 | { | |
232 | u8 status, autotune; | |
233 | u8 auto_vco_success, auto_vco_active; | |
234 | u8 pll_locked; | |
235 | u8 dc_offset_low, dc_offset_hi; | |
236 | u8 signal_lv_over_threshold; | |
237 | u8 vco, vco_sub_band, adc; | |
238 | ||
239 | max2165_read_reg(priv, REG_STATUS, &status); | |
240 | max2165_read_reg(priv, REG_AUTOTUNE, &autotune); | |
241 | ||
242 | auto_vco_success = (status >> 6) & 0x01; | |
243 | auto_vco_active = (status >> 5) & 0x01; | |
244 | pll_locked = (status >> 4) & 0x01; | |
245 | dc_offset_low = (status >> 3) & 0x01; | |
246 | dc_offset_hi = (status >> 2) & 0x01; | |
247 | signal_lv_over_threshold = status & 0x01; | |
248 | ||
249 | vco = autotune >> 6; | |
250 | vco_sub_band = (autotune >> 3) & 0x7; | |
251 | adc = autotune & 0x7; | |
252 | ||
253 | dprintk("auto VCO active: %d, auto VCO success: %d\n", | |
254 | auto_vco_active, auto_vco_success); | |
255 | dprintk("PLL locked: %d\n", pll_locked); | |
256 | dprintk("DC offset low: %d, DC offset high: %d\n", | |
257 | dc_offset_low, dc_offset_hi); | |
258 | dprintk("Signal lvl over threshold: %d\n", signal_lv_over_threshold); | |
259 | dprintk("VCO: %d, VCO Sub-band: %d, ADC: %d\n", vco, vco_sub_band, adc); | |
260 | } | |
261 | ||
262 | static int max2165_set_params(struct dvb_frontend *fe, | |
263 | struct dvb_frontend_parameters *params) | |
264 | { | |
265 | struct max2165_priv *priv = fe->tuner_priv; | |
266 | int ret; | |
267 | ||
268 | dprintk("%s() frequency=%d (Hz)\n", __func__, params->frequency); | |
269 | if (fe->ops.info.type == FE_ATSC) { | |
270 | return -EINVAL; | |
271 | } else if (fe->ops.info.type == FE_OFDM) { | |
272 | dprintk("%s() OFDM\n", __func__); | |
273 | switch (params->u.ofdm.bandwidth) { | |
274 | case BANDWIDTH_6_MHZ: | |
275 | return -EINVAL; | |
276 | case BANDWIDTH_7_MHZ: | |
277 | case BANDWIDTH_8_MHZ: | |
278 | priv->frequency = params->frequency; | |
279 | priv->bandwidth = params->u.ofdm.bandwidth; | |
280 | break; | |
281 | default: | |
282 | printk(KERN_ERR "MAX2165 bandwidth not set!\n"); | |
283 | return -EINVAL; | |
284 | } | |
285 | } else { | |
286 | printk(KERN_ERR "MAX2165 modulation type not supported!\n"); | |
287 | return -EINVAL; | |
288 | } | |
289 | ||
290 | dprintk("%s() frequency=%d\n", __func__, priv->frequency); | |
291 | ||
292 | if (fe->ops.i2c_gate_ctrl) | |
293 | fe->ops.i2c_gate_ctrl(fe, 1); | |
294 | max2165_set_bandwidth(priv, priv->bandwidth); | |
295 | ret = max2165_set_rf(priv, priv->frequency); | |
296 | mdelay(50); | |
297 | max2165_debug_status(priv); | |
298 | if (fe->ops.i2c_gate_ctrl) | |
299 | fe->ops.i2c_gate_ctrl(fe, 0); | |
300 | ||
301 | if (ret != 0) | |
302 | return -EREMOTEIO; | |
303 | ||
304 | return 0; | |
305 | } | |
306 | ||
307 | static int max2165_get_frequency(struct dvb_frontend *fe, u32 *freq) | |
308 | { | |
309 | struct max2165_priv *priv = fe->tuner_priv; | |
310 | dprintk("%s()\n", __func__); | |
311 | *freq = priv->frequency; | |
312 | return 0; | |
313 | } | |
314 | ||
315 | static int max2165_get_bandwidth(struct dvb_frontend *fe, u32 *bw) | |
316 | { | |
317 | struct max2165_priv *priv = fe->tuner_priv; | |
318 | dprintk("%s()\n", __func__); | |
319 | ||
320 | *bw = priv->bandwidth; | |
321 | return 0; | |
322 | } | |
323 | ||
324 | static int max2165_get_status(struct dvb_frontend *fe, u32 *status) | |
325 | { | |
326 | struct max2165_priv *priv = fe->tuner_priv; | |
327 | u16 lock_status = 0; | |
328 | ||
329 | dprintk("%s()\n", __func__); | |
330 | ||
331 | if (fe->ops.i2c_gate_ctrl) | |
332 | fe->ops.i2c_gate_ctrl(fe, 1); | |
333 | ||
334 | max2165_debug_status(priv); | |
335 | *status = lock_status; | |
336 | ||
337 | if (fe->ops.i2c_gate_ctrl) | |
338 | fe->ops.i2c_gate_ctrl(fe, 0); | |
339 | ||
340 | return 0; | |
341 | } | |
342 | ||
343 | static int max2165_sleep(struct dvb_frontend *fe) | |
344 | { | |
345 | dprintk("%s()\n", __func__); | |
346 | return 0; | |
347 | } | |
348 | ||
349 | static int max2165_init(struct dvb_frontend *fe) | |
350 | { | |
351 | struct max2165_priv *priv = fe->tuner_priv; | |
352 | dprintk("%s()\n", __func__); | |
353 | ||
354 | if (fe->ops.i2c_gate_ctrl) | |
355 | fe->ops.i2c_gate_ctrl(fe, 1); | |
356 | ||
357 | /* Setup initial values */ | |
358 | /* Fractional Mode on */ | |
359 | max2165_write_reg(priv, REG_NDIV_FRAC2, 0x18); | |
360 | /* LNA on */ | |
361 | max2165_write_reg(priv, REG_LNA, 0x01); | |
362 | max2165_write_reg(priv, REG_PLL_CFG, 0x7A); | |
363 | max2165_write_reg(priv, REG_TEST, 0x08); | |
364 | max2165_write_reg(priv, REG_SHUTDOWN, 0x40); | |
365 | max2165_write_reg(priv, REG_VCO_CTRL, 0x84); | |
366 | max2165_write_reg(priv, REG_BASEBAND_CTRL, 0xC3); | |
367 | max2165_write_reg(priv, REG_DC_OFFSET_CTRL, 0x75); | |
368 | max2165_write_reg(priv, REG_DC_OFFSET_DAC, 0x00); | |
369 | max2165_write_reg(priv, REG_ROM_TABLE_ADDR, 0x00); | |
370 | ||
371 | max2165_set_osc(priv, priv->config->osc_clk); | |
372 | ||
373 | max2165_read_rom_table(priv); | |
374 | ||
375 | max2165_set_bandwidth(priv, BANDWIDTH_8_MHZ); | |
376 | ||
377 | if (fe->ops.i2c_gate_ctrl) | |
378 | fe->ops.i2c_gate_ctrl(fe, 0); | |
379 | ||
380 | return 0; | |
381 | } | |
382 | ||
383 | static int max2165_release(struct dvb_frontend *fe) | |
384 | { | |
385 | struct max2165_priv *priv = fe->tuner_priv; | |
386 | dprintk("%s()\n", __func__); | |
387 | ||
388 | kfree(priv); | |
389 | fe->tuner_priv = NULL; | |
390 | ||
391 | return 0; | |
392 | } | |
393 | ||
394 | static const struct dvb_tuner_ops max2165_tuner_ops = { | |
395 | .info = { | |
396 | .name = "Maxim MAX2165", | |
397 | .frequency_min = 470000000, | |
398 | .frequency_max = 780000000, | |
399 | .frequency_step = 50000, | |
400 | }, | |
401 | ||
402 | .release = max2165_release, | |
403 | .init = max2165_init, | |
404 | .sleep = max2165_sleep, | |
405 | ||
406 | .set_params = max2165_set_params, | |
407 | .set_analog_params = NULL, | |
408 | .get_frequency = max2165_get_frequency, | |
409 | .get_bandwidth = max2165_get_bandwidth, | |
410 | .get_status = max2165_get_status | |
411 | }; | |
412 | ||
413 | struct dvb_frontend *max2165_attach(struct dvb_frontend *fe, | |
414 | struct i2c_adapter *i2c, | |
415 | struct max2165_config *cfg) | |
416 | { | |
417 | struct max2165_priv *priv = NULL; | |
418 | ||
419 | dprintk("%s(%d-%04x)\n", __func__, | |
420 | i2c ? i2c_adapter_id(i2c) : -1, | |
421 | cfg ? cfg->i2c_address : -1); | |
422 | ||
423 | priv = kzalloc(sizeof(struct max2165_priv), GFP_KERNEL); | |
424 | if (priv == NULL) | |
425 | return NULL; | |
426 | ||
427 | memcpy(&fe->ops.tuner_ops, &max2165_tuner_ops, | |
428 | sizeof(struct dvb_tuner_ops)); | |
429 | ||
430 | priv->config = cfg; | |
431 | priv->i2c = i2c; | |
432 | fe->tuner_priv = priv; | |
433 | ||
434 | max2165_init(fe); | |
435 | max2165_debug_status(priv); | |
436 | ||
437 | return fe; | |
438 | } | |
439 | EXPORT_SYMBOL(max2165_attach); | |
440 | ||
441 | MODULE_AUTHOR("David T. L. Wong <davidtlwong@gmail.com>"); | |
442 | MODULE_DESCRIPTION("Maxim MAX2165 silicon tuner driver"); | |
443 | MODULE_LICENSE("GPL"); |