1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Driver for Microtune MT2060 "Single chip dual conversion broadband tuner" |
4 | * |
5 | * Copyright (c) 2006 Olivier DANET <odanet@caramail.com> |
6 | */ |
7 | |
8 | /* In that file, frequencies are expressed in kiloHertz to avoid 32 bits overflows */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/dvb/frontend.h> |
13 | #include <linux/i2c.h> |
14 | #include <linux/slab.h> |
15 | |
16 | #include <media/dvb_frontend.h> |
17 | |
18 | #include "mt2060.h" |
19 | #include "mt2060_priv.h" |
20 | |
21 | static int debug; |
22 | module_param(debug, int, 0644); |
23 | MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)." ); |
24 | |
25 | #define dprintk(args...) do { if (debug) {printk(KERN_DEBUG "MT2060: " args); printk("\n"); }} while (0) |
26 | |
27 | // Reads a single register |
28 | static int mt2060_readreg(struct mt2060_priv *priv, u8 reg, u8 *val) |
29 | { |
30 | struct i2c_msg msg[2] = { |
31 | { .addr = priv->cfg->i2c_address, .flags = 0, .len = 1 }, |
32 | { .addr = priv->cfg->i2c_address, .flags = I2C_M_RD, .len = 1 }, |
33 | }; |
34 | int rc = 0; |
35 | u8 *b; |
36 | |
37 | b = kmalloc(size: 2, GFP_KERNEL); |
38 | if (!b) |
39 | return -ENOMEM; |
40 | |
41 | b[0] = reg; |
42 | b[1] = 0; |
43 | |
44 | msg[0].buf = b; |
45 | msg[1].buf = b + 1; |
46 | |
47 | if (i2c_transfer(adap: priv->i2c, msgs: msg, num: 2) != 2) { |
48 | printk(KERN_WARNING "mt2060 I2C read failed\n" ); |
49 | rc = -EREMOTEIO; |
50 | } |
51 | *val = b[1]; |
52 | kfree(objp: b); |
53 | |
54 | return rc; |
55 | } |
56 | |
57 | // Writes a single register |
58 | static int mt2060_writereg(struct mt2060_priv *priv, u8 reg, u8 val) |
59 | { |
60 | struct i2c_msg msg = { |
61 | .addr = priv->cfg->i2c_address, .flags = 0, .len = 2 |
62 | }; |
63 | u8 *buf; |
64 | int rc = 0; |
65 | |
66 | buf = kmalloc(size: 2, GFP_KERNEL); |
67 | if (!buf) |
68 | return -ENOMEM; |
69 | |
70 | buf[0] = reg; |
71 | buf[1] = val; |
72 | |
73 | msg.buf = buf; |
74 | |
75 | if (i2c_transfer(adap: priv->i2c, msgs: &msg, num: 1) != 1) { |
76 | printk(KERN_WARNING "mt2060 I2C write failed\n" ); |
77 | rc = -EREMOTEIO; |
78 | } |
79 | kfree(objp: buf); |
80 | return rc; |
81 | } |
82 | |
83 | // Writes a set of consecutive registers |
84 | static int mt2060_writeregs(struct mt2060_priv *priv,u8 *buf, u8 len) |
85 | { |
86 | int rem, val_len; |
87 | u8 *xfer_buf; |
88 | int rc = 0; |
89 | struct i2c_msg msg = { |
90 | .addr = priv->cfg->i2c_address, .flags = 0 |
91 | }; |
92 | |
93 | xfer_buf = kmalloc(size: 16, GFP_KERNEL); |
94 | if (!xfer_buf) |
95 | return -ENOMEM; |
96 | |
97 | msg.buf = xfer_buf; |
98 | |
99 | for (rem = len - 1; rem > 0; rem -= priv->i2c_max_regs) { |
100 | val_len = min_t(int, rem, priv->i2c_max_regs); |
101 | msg.len = 1 + val_len; |
102 | xfer_buf[0] = buf[0] + len - 1 - rem; |
103 | memcpy(&xfer_buf[1], &buf[1 + len - 1 - rem], val_len); |
104 | |
105 | if (i2c_transfer(adap: priv->i2c, msgs: &msg, num: 1) != 1) { |
106 | printk(KERN_WARNING "mt2060 I2C write failed (len=%i)\n" , val_len); |
107 | rc = -EREMOTEIO; |
108 | break; |
109 | } |
110 | } |
111 | |
112 | kfree(objp: xfer_buf); |
113 | return rc; |
114 | } |
115 | |
116 | // Initialisation sequences |
117 | // LNABAND=3, NUM1=0x3C, DIV1=0x74, NUM2=0x1080, DIV2=0x49 |
118 | static u8 mt2060_config1[] = { |
119 | REG_LO1C1, |
120 | 0x3F, 0x74, 0x00, 0x08, 0x93 |
121 | }; |
122 | |
123 | // FMCG=2, GP2=0, GP1=0 |
124 | static u8 mt2060_config2[] = { |
125 | REG_MISC_CTRL, |
126 | 0x20, 0x1E, 0x30, 0xff, 0x80, 0xff, 0x00, 0x2c, 0x42 |
127 | }; |
128 | |
129 | // VGAG=3, V1CSE=1 |
130 | |
131 | #ifdef MT2060_SPURCHECK |
132 | /* The function below calculates the frequency offset between the output frequency if2 |
133 | and the closer cross modulation subcarrier between lo1 and lo2 up to the tenth harmonic */ |
134 | static int mt2060_spurcalc(u32 lo1,u32 lo2,u32 if2) |
135 | { |
136 | int I,J; |
137 | int dia,diamin,diff; |
138 | diamin=1000000; |
139 | for (I = 1; I < 10; I++) { |
140 | J = ((2*I*lo1)/lo2+1)/2; |
141 | diff = I*(int)lo1-J*(int)lo2; |
142 | if (diff < 0) diff=-diff; |
143 | dia = (diff-(int)if2); |
144 | if (dia < 0) dia=-dia; |
145 | if (diamin > dia) diamin=dia; |
146 | } |
147 | return diamin; |
148 | } |
149 | |
150 | #define BANDWIDTH 4000 // kHz |
151 | |
152 | /* Calculates the frequency offset to add to avoid spurs. Returns 0 if no offset is needed */ |
153 | static int mt2060_spurcheck(u32 lo1,u32 lo2,u32 if2) |
154 | { |
155 | u32 Spur,Sp1,Sp2; |
156 | int I,J; |
157 | I=0; |
158 | J=1000; |
159 | |
160 | Spur=mt2060_spurcalc(lo1,lo2,if2); |
161 | if (Spur < BANDWIDTH) { |
162 | /* Potential spurs detected */ |
163 | dprintk("Spurs before : f_lo1: %d f_lo2: %d (kHz)" , |
164 | (int)lo1,(int)lo2); |
165 | I=1000; |
166 | Sp1 = mt2060_spurcalc(lo1+I,lo2+I,if2); |
167 | Sp2 = mt2060_spurcalc(lo1-I,lo2-I,if2); |
168 | |
169 | if (Sp1 < Sp2) { |
170 | J=-J; I=-I; Spur=Sp2; |
171 | } else |
172 | Spur=Sp1; |
173 | |
174 | while (Spur < BANDWIDTH) { |
175 | I += J; |
176 | Spur = mt2060_spurcalc(lo1+I,lo2+I,if2); |
177 | } |
178 | dprintk("Spurs after : f_lo1: %d f_lo2: %d (kHz)" , |
179 | (int)(lo1+I),(int)(lo2+I)); |
180 | } |
181 | return I; |
182 | } |
183 | #endif |
184 | |
185 | #define IF2 36150 // IF2 frequency = 36.150 MHz |
186 | #define FREF 16000 // Quartz oscillator 16 MHz |
187 | |
188 | static int mt2060_set_params(struct dvb_frontend *fe) |
189 | { |
190 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; |
191 | struct mt2060_priv *priv; |
192 | int i=0; |
193 | u32 freq; |
194 | u8 lnaband; |
195 | u32 f_lo1,f_lo2; |
196 | u32 div1,num1,div2,num2; |
197 | u8 b[8]; |
198 | u32 if1; |
199 | |
200 | priv = fe->tuner_priv; |
201 | |
202 | if1 = priv->if1_freq; |
203 | b[0] = REG_LO1B1; |
204 | b[1] = 0xFF; |
205 | |
206 | if (fe->ops.i2c_gate_ctrl) |
207 | fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ |
208 | |
209 | mt2060_writeregs(priv,buf: b,len: 2); |
210 | |
211 | freq = c->frequency / 1000; /* Hz -> kHz */ |
212 | |
213 | f_lo1 = freq + if1 * 1000; |
214 | f_lo1 = (f_lo1 / 250) * 250; |
215 | f_lo2 = f_lo1 - freq - IF2; |
216 | // From the Comtech datasheet, the step used is 50kHz. The tuner chip could be more precise |
217 | f_lo2 = ((f_lo2 + 25) / 50) * 50; |
218 | priv->frequency = (f_lo1 - f_lo2 - IF2) * 1000; |
219 | |
220 | #ifdef MT2060_SPURCHECK |
221 | // LO-related spurs detection and correction |
222 | num1 = mt2060_spurcheck(f_lo1,f_lo2,IF2); |
223 | f_lo1 += num1; |
224 | f_lo2 += num1; |
225 | #endif |
226 | //Frequency LO1 = 16MHz * (DIV1 + NUM1/64 ) |
227 | num1 = f_lo1 / (FREF / 64); |
228 | div1 = num1 / 64; |
229 | num1 &= 0x3f; |
230 | |
231 | // Frequency LO2 = 16MHz * (DIV2 + NUM2/8192 ) |
232 | num2 = f_lo2 * 64 / (FREF / 128); |
233 | div2 = num2 / 8192; |
234 | num2 &= 0x1fff; |
235 | |
236 | if (freq <= 95000) lnaband = 0xB0; else |
237 | if (freq <= 180000) lnaband = 0xA0; else |
238 | if (freq <= 260000) lnaband = 0x90; else |
239 | if (freq <= 335000) lnaband = 0x80; else |
240 | if (freq <= 425000) lnaband = 0x70; else |
241 | if (freq <= 480000) lnaband = 0x60; else |
242 | if (freq <= 570000) lnaband = 0x50; else |
243 | if (freq <= 645000) lnaband = 0x40; else |
244 | if (freq <= 730000) lnaband = 0x30; else |
245 | if (freq <= 810000) lnaband = 0x20; else lnaband = 0x10; |
246 | |
247 | b[0] = REG_LO1C1; |
248 | b[1] = lnaband | ((num1 >>2) & 0x0F); |
249 | b[2] = div1; |
250 | b[3] = (num2 & 0x0F) | ((num1 & 3) << 4); |
251 | b[4] = num2 >> 4; |
252 | b[5] = ((num2 >>12) & 1) | (div2 << 1); |
253 | |
254 | dprintk("IF1: %dMHz" ,(int)if1); |
255 | dprintk("PLL freq=%dkHz f_lo1=%dkHz f_lo2=%dkHz" ,(int)freq,(int)f_lo1,(int)f_lo2); |
256 | dprintk("PLL div1=%d num1=%d div2=%d num2=%d" ,(int)div1,(int)num1,(int)div2,(int)num2); |
257 | dprintk("PLL [1..5]: %2x %2x %2x %2x %2x" ,(int)b[1],(int)b[2],(int)b[3],(int)b[4],(int)b[5]); |
258 | |
259 | mt2060_writeregs(priv,buf: b,len: 6); |
260 | |
261 | //Waits for pll lock or timeout |
262 | i = 0; |
263 | do { |
264 | mt2060_readreg(priv,REG_LO_STATUS,val: b); |
265 | if ((b[0] & 0x88)==0x88) |
266 | break; |
267 | msleep(msecs: 4); |
268 | i++; |
269 | } while (i<10); |
270 | |
271 | if (fe->ops.i2c_gate_ctrl) |
272 | fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ |
273 | |
274 | return 0; |
275 | } |
276 | |
277 | static void mt2060_calibrate(struct mt2060_priv *priv) |
278 | { |
279 | u8 b = 0; |
280 | int i = 0; |
281 | |
282 | if (mt2060_writeregs(priv,buf: mt2060_config1,len: sizeof(mt2060_config1))) |
283 | return; |
284 | if (mt2060_writeregs(priv,buf: mt2060_config2,len: sizeof(mt2060_config2))) |
285 | return; |
286 | |
287 | /* initialize the clock output */ |
288 | mt2060_writereg(priv, REG_VGAG, val: (priv->cfg->clock_out << 6) | 0x30); |
289 | |
290 | do { |
291 | b |= (1 << 6); // FM1SS; |
292 | mt2060_writereg(priv, REG_LO2C1,val: b); |
293 | msleep(msecs: 20); |
294 | |
295 | if (i == 0) { |
296 | b |= (1 << 7); // FM1CA; |
297 | mt2060_writereg(priv, REG_LO2C1,val: b); |
298 | b &= ~(1 << 7); // FM1CA; |
299 | msleep(msecs: 20); |
300 | } |
301 | |
302 | b &= ~(1 << 6); // FM1SS |
303 | mt2060_writereg(priv, REG_LO2C1,val: b); |
304 | |
305 | msleep(msecs: 20); |
306 | i++; |
307 | } while (i < 9); |
308 | |
309 | i = 0; |
310 | while (i++ < 10 && mt2060_readreg(priv, REG_MISC_STAT, val: &b) == 0 && (b & (1 << 6)) == 0) |
311 | msleep(msecs: 20); |
312 | |
313 | if (i <= 10) { |
314 | mt2060_readreg(priv, REG_FM_FREQ, val: &priv->fmfreq); // now find out, what is fmreq used for :) |
315 | dprintk("calibration was successful: %d" , (int)priv->fmfreq); |
316 | } else |
317 | dprintk("FMCAL timed out" ); |
318 | } |
319 | |
320 | static int mt2060_get_frequency(struct dvb_frontend *fe, u32 *frequency) |
321 | { |
322 | struct mt2060_priv *priv = fe->tuner_priv; |
323 | *frequency = priv->frequency; |
324 | return 0; |
325 | } |
326 | |
327 | static int mt2060_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) |
328 | { |
329 | *frequency = IF2 * 1000; |
330 | return 0; |
331 | } |
332 | |
333 | static int mt2060_init(struct dvb_frontend *fe) |
334 | { |
335 | struct mt2060_priv *priv = fe->tuner_priv; |
336 | int ret; |
337 | |
338 | if (fe->ops.i2c_gate_ctrl) |
339 | fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ |
340 | |
341 | if (priv->sleep) { |
342 | ret = mt2060_writereg(priv, REG_MISC_CTRL, val: 0x20); |
343 | if (ret) |
344 | goto err_i2c_gate_ctrl; |
345 | } |
346 | |
347 | ret = mt2060_writereg(priv, REG_VGAG, |
348 | val: (priv->cfg->clock_out << 6) | 0x33); |
349 | |
350 | err_i2c_gate_ctrl: |
351 | if (fe->ops.i2c_gate_ctrl) |
352 | fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ |
353 | |
354 | return ret; |
355 | } |
356 | |
357 | static int mt2060_sleep(struct dvb_frontend *fe) |
358 | { |
359 | struct mt2060_priv *priv = fe->tuner_priv; |
360 | int ret; |
361 | |
362 | if (fe->ops.i2c_gate_ctrl) |
363 | fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ |
364 | |
365 | ret = mt2060_writereg(priv, REG_VGAG, |
366 | val: (priv->cfg->clock_out << 6) | 0x30); |
367 | if (ret) |
368 | goto err_i2c_gate_ctrl; |
369 | |
370 | if (priv->sleep) |
371 | ret = mt2060_writereg(priv, REG_MISC_CTRL, val: 0xe8); |
372 | |
373 | err_i2c_gate_ctrl: |
374 | if (fe->ops.i2c_gate_ctrl) |
375 | fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ |
376 | |
377 | return ret; |
378 | } |
379 | |
380 | static void mt2060_release(struct dvb_frontend *fe) |
381 | { |
382 | kfree(objp: fe->tuner_priv); |
383 | fe->tuner_priv = NULL; |
384 | } |
385 | |
386 | static const struct dvb_tuner_ops mt2060_tuner_ops = { |
387 | .info = { |
388 | .name = "Microtune MT2060" , |
389 | .frequency_min_hz = 48 * MHz, |
390 | .frequency_max_hz = 860 * MHz, |
391 | .frequency_step_hz = 50 * kHz, |
392 | }, |
393 | |
394 | .release = mt2060_release, |
395 | |
396 | .init = mt2060_init, |
397 | .sleep = mt2060_sleep, |
398 | |
399 | .set_params = mt2060_set_params, |
400 | .get_frequency = mt2060_get_frequency, |
401 | .get_if_frequency = mt2060_get_if_frequency, |
402 | }; |
403 | |
404 | /* This functions tries to identify a MT2060 tuner by reading the PART/REV register. This is hasty. */ |
405 | struct dvb_frontend * mt2060_attach(struct dvb_frontend *fe, struct i2c_adapter *i2c, struct mt2060_config *cfg, u16 if1) |
406 | { |
407 | struct mt2060_priv *priv = NULL; |
408 | u8 id = 0; |
409 | |
410 | priv = kzalloc(size: sizeof(struct mt2060_priv), GFP_KERNEL); |
411 | if (priv == NULL) |
412 | return NULL; |
413 | |
414 | priv->cfg = cfg; |
415 | priv->i2c = i2c; |
416 | priv->if1_freq = if1; |
417 | priv->i2c_max_regs = ~0; |
418 | |
419 | if (fe->ops.i2c_gate_ctrl) |
420 | fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ |
421 | |
422 | if (mt2060_readreg(priv,REG_PART_REV,val: &id) != 0) { |
423 | kfree(objp: priv); |
424 | return NULL; |
425 | } |
426 | |
427 | if (id != PART_REV) { |
428 | kfree(objp: priv); |
429 | return NULL; |
430 | } |
431 | printk(KERN_INFO "MT2060: successfully identified (IF1 = %d)\n" , if1); |
432 | memcpy(&fe->ops.tuner_ops, &mt2060_tuner_ops, sizeof(struct dvb_tuner_ops)); |
433 | |
434 | fe->tuner_priv = priv; |
435 | |
436 | mt2060_calibrate(priv); |
437 | |
438 | if (fe->ops.i2c_gate_ctrl) |
439 | fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ |
440 | |
441 | return fe; |
442 | } |
443 | EXPORT_SYMBOL_GPL(mt2060_attach); |
444 | |
445 | static int mt2060_probe(struct i2c_client *client) |
446 | { |
447 | struct mt2060_platform_data *pdata = client->dev.platform_data; |
448 | struct dvb_frontend *fe; |
449 | struct mt2060_priv *dev; |
450 | int ret; |
451 | u8 chip_id; |
452 | |
453 | dev_dbg(&client->dev, "\n" ); |
454 | |
455 | if (!pdata) { |
456 | dev_err(&client->dev, "Cannot proceed without platform data\n" ); |
457 | ret = -EINVAL; |
458 | goto err; |
459 | } |
460 | |
461 | dev = devm_kzalloc(dev: &client->dev, size: sizeof(*dev), GFP_KERNEL); |
462 | if (!dev) { |
463 | ret = -ENOMEM; |
464 | goto err; |
465 | } |
466 | |
467 | fe = pdata->dvb_frontend; |
468 | dev->config.i2c_address = client->addr; |
469 | dev->config.clock_out = pdata->clock_out; |
470 | dev->cfg = &dev->config; |
471 | dev->i2c = client->adapter; |
472 | dev->if1_freq = pdata->if1 ? pdata->if1 : 1220; |
473 | dev->client = client; |
474 | dev->i2c_max_regs = pdata->i2c_write_max ? pdata->i2c_write_max - 1 : ~0; |
475 | dev->sleep = true; |
476 | |
477 | ret = mt2060_readreg(priv: dev, REG_PART_REV, val: &chip_id); |
478 | if (ret) { |
479 | ret = -ENODEV; |
480 | goto err; |
481 | } |
482 | |
483 | dev_dbg(&client->dev, "chip id=%02x\n" , chip_id); |
484 | |
485 | if (chip_id != PART_REV) { |
486 | ret = -ENODEV; |
487 | goto err; |
488 | } |
489 | |
490 | /* Power on, calibrate, sleep */ |
491 | ret = mt2060_writereg(priv: dev, REG_MISC_CTRL, val: 0x20); |
492 | if (ret) |
493 | goto err; |
494 | mt2060_calibrate(priv: dev); |
495 | ret = mt2060_writereg(priv: dev, REG_MISC_CTRL, val: 0xe8); |
496 | if (ret) |
497 | goto err; |
498 | |
499 | dev_info(&client->dev, "Microtune MT2060 successfully identified\n" ); |
500 | memcpy(&fe->ops.tuner_ops, &mt2060_tuner_ops, sizeof(fe->ops.tuner_ops)); |
501 | fe->ops.tuner_ops.release = NULL; |
502 | fe->tuner_priv = dev; |
503 | i2c_set_clientdata(client, data: dev); |
504 | |
505 | return 0; |
506 | err: |
507 | dev_dbg(&client->dev, "failed=%d\n" , ret); |
508 | return ret; |
509 | } |
510 | |
511 | static void mt2060_remove(struct i2c_client *client) |
512 | { |
513 | dev_dbg(&client->dev, "\n" ); |
514 | } |
515 | |
516 | static const struct i2c_device_id mt2060_id_table[] = { |
517 | {"mt2060" , 0}, |
518 | {} |
519 | }; |
520 | MODULE_DEVICE_TABLE(i2c, mt2060_id_table); |
521 | |
522 | static struct i2c_driver mt2060_driver = { |
523 | .driver = { |
524 | .name = "mt2060" , |
525 | .suppress_bind_attrs = true, |
526 | }, |
527 | .probe = mt2060_probe, |
528 | .remove = mt2060_remove, |
529 | .id_table = mt2060_id_table, |
530 | }; |
531 | |
532 | module_i2c_driver(mt2060_driver); |
533 | |
534 | MODULE_AUTHOR("Olivier DANET" ); |
535 | MODULE_DESCRIPTION("Microtune MT2060 silicon tuner driver" ); |
536 | MODULE_LICENSE("GPL" ); |
537 | |