1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Fitipower FC0011 tuner driver |
4 | * |
5 | * Copyright (C) 2012 Michael Buesch <m@bues.ch> |
6 | * |
7 | * Derived from FC0012 tuner driver: |
8 | * Copyright (C) 2012 Hans-Frieder Vogt <hfvogt@gmx.net> |
9 | */ |
10 | |
11 | #include "fc0011.h" |
12 | |
13 | |
14 | /* Tuner registers */ |
15 | enum { |
16 | FC11_REG_0, |
17 | FC11_REG_FA, /* FA */ |
18 | FC11_REG_FP, /* FP */ |
19 | FC11_REG_XINHI, /* XIN high 8 bit */ |
20 | FC11_REG_XINLO, /* XIN low 8 bit */ |
21 | FC11_REG_VCO, /* VCO */ |
22 | FC11_REG_VCOSEL, /* VCO select */ |
23 | FC11_REG_7, /* Unknown tuner reg 7 */ |
24 | FC11_REG_8, /* Unknown tuner reg 8 */ |
25 | FC11_REG_9, |
26 | FC11_REG_10, /* Unknown tuner reg 10 */ |
27 | FC11_REG_11, /* Unknown tuner reg 11 */ |
28 | FC11_REG_12, |
29 | FC11_REG_RCCAL, /* RC calibrate */ |
30 | FC11_REG_VCOCAL, /* VCO calibrate */ |
31 | FC11_REG_15, |
32 | FC11_REG_16, /* Unknown tuner reg 16 */ |
33 | FC11_REG_17, |
34 | |
35 | FC11_NR_REGS, /* Number of registers */ |
36 | }; |
37 | |
38 | enum FC11_REG_VCOSEL_bits { |
39 | FC11_VCOSEL_2 = 0x08, /* VCO select 2 */ |
40 | FC11_VCOSEL_1 = 0x10, /* VCO select 1 */ |
41 | FC11_VCOSEL_CLKOUT = 0x20, /* Fix clock out */ |
42 | FC11_VCOSEL_BW7M = 0x40, /* 7MHz bw */ |
43 | FC11_VCOSEL_BW6M = 0x80, /* 6MHz bw */ |
44 | }; |
45 | |
46 | enum FC11_REG_RCCAL_bits { |
47 | FC11_RCCAL_FORCE = 0x10, /* force */ |
48 | }; |
49 | |
50 | enum FC11_REG_VCOCAL_bits { |
51 | FC11_VCOCAL_RUN = 0, /* VCO calibration run */ |
52 | FC11_VCOCAL_VALUEMASK = 0x3F, /* VCO calibration value mask */ |
53 | FC11_VCOCAL_OK = 0x40, /* VCO calibration Ok */ |
54 | FC11_VCOCAL_RESET = 0x80, /* VCO calibration reset */ |
55 | }; |
56 | |
57 | |
58 | struct fc0011_priv { |
59 | struct i2c_adapter *i2c; |
60 | u8 addr; |
61 | |
62 | u32 frequency; |
63 | u32 bandwidth; |
64 | }; |
65 | |
66 | |
67 | static int fc0011_writereg(struct fc0011_priv *priv, u8 reg, u8 val) |
68 | { |
69 | u8 buf[2] = { reg, val }; |
70 | struct i2c_msg msg = { .addr = priv->addr, |
71 | .flags = 0, .buf = buf, .len = 2 }; |
72 | |
73 | if (i2c_transfer(adap: priv->i2c, msgs: &msg, num: 1) != 1) { |
74 | dev_err(&priv->i2c->dev, |
75 | "I2C write reg failed, reg: %02x, val: %02x\n" , |
76 | reg, val); |
77 | return -EIO; |
78 | } |
79 | |
80 | return 0; |
81 | } |
82 | |
83 | static int fc0011_readreg(struct fc0011_priv *priv, u8 reg, u8 *val) |
84 | { |
85 | u8 dummy; |
86 | struct i2c_msg msg[2] = { |
87 | { .addr = priv->addr, |
88 | .flags = 0, .buf = ®, .len = 1 }, |
89 | { .addr = priv->addr, |
90 | .flags = I2C_M_RD, .buf = val ? : &dummy, .len = 1 }, |
91 | }; |
92 | |
93 | if (i2c_transfer(adap: priv->i2c, msgs: msg, num: 2) != 2) { |
94 | dev_err(&priv->i2c->dev, |
95 | "I2C read failed, reg: %02x\n" , reg); |
96 | return -EIO; |
97 | } |
98 | |
99 | return 0; |
100 | } |
101 | |
102 | static void fc0011_release(struct dvb_frontend *fe) |
103 | { |
104 | kfree(objp: fe->tuner_priv); |
105 | fe->tuner_priv = NULL; |
106 | } |
107 | |
108 | static int fc0011_init(struct dvb_frontend *fe) |
109 | { |
110 | struct fc0011_priv *priv = fe->tuner_priv; |
111 | int err; |
112 | |
113 | if (WARN_ON(!fe->callback)) |
114 | return -EINVAL; |
115 | |
116 | err = fe->callback(priv->i2c, DVB_FRONTEND_COMPONENT_TUNER, |
117 | FC0011_FE_CALLBACK_POWER, priv->addr); |
118 | if (err) { |
119 | dev_err(&priv->i2c->dev, "Power-on callback failed\n" ); |
120 | return err; |
121 | } |
122 | err = fe->callback(priv->i2c, DVB_FRONTEND_COMPONENT_TUNER, |
123 | FC0011_FE_CALLBACK_RESET, priv->addr); |
124 | if (err) { |
125 | dev_err(&priv->i2c->dev, "Reset callback failed\n" ); |
126 | return err; |
127 | } |
128 | |
129 | return 0; |
130 | } |
131 | |
132 | /* Initiate VCO calibration */ |
133 | static int fc0011_vcocal_trigger(struct fc0011_priv *priv) |
134 | { |
135 | int err; |
136 | |
137 | err = fc0011_writereg(priv, reg: FC11_REG_VCOCAL, val: FC11_VCOCAL_RESET); |
138 | if (err) |
139 | return err; |
140 | err = fc0011_writereg(priv, reg: FC11_REG_VCOCAL, val: FC11_VCOCAL_RUN); |
141 | if (err) |
142 | return err; |
143 | |
144 | return 0; |
145 | } |
146 | |
147 | /* Read VCO calibration value */ |
148 | static int fc0011_vcocal_read(struct fc0011_priv *priv, u8 *value) |
149 | { |
150 | int err; |
151 | |
152 | err = fc0011_writereg(priv, reg: FC11_REG_VCOCAL, val: FC11_VCOCAL_RUN); |
153 | if (err) |
154 | return err; |
155 | usleep_range(min: 10000, max: 20000); |
156 | err = fc0011_readreg(priv, reg: FC11_REG_VCOCAL, val: value); |
157 | if (err) |
158 | return err; |
159 | |
160 | return 0; |
161 | } |
162 | |
163 | static int fc0011_set_params(struct dvb_frontend *fe) |
164 | { |
165 | struct dtv_frontend_properties *p = &fe->dtv_property_cache; |
166 | struct fc0011_priv *priv = fe->tuner_priv; |
167 | int err; |
168 | unsigned int i, vco_retries; |
169 | u32 freq = p->frequency / 1000; |
170 | u32 bandwidth = p->bandwidth_hz / 1000; |
171 | u32 fvco, xin, frac, xdiv, xdivr; |
172 | u8 fa, fp, vco_sel, vco_cal; |
173 | u8 regs[FC11_NR_REGS] = { }; |
174 | |
175 | regs[FC11_REG_7] = 0x0F; |
176 | regs[FC11_REG_8] = 0x3E; |
177 | regs[FC11_REG_10] = 0xB8; |
178 | regs[FC11_REG_11] = 0x80; |
179 | regs[FC11_REG_RCCAL] = 0x04; |
180 | err = fc0011_writereg(priv, reg: FC11_REG_7, val: regs[FC11_REG_7]); |
181 | err |= fc0011_writereg(priv, reg: FC11_REG_8, val: regs[FC11_REG_8]); |
182 | err |= fc0011_writereg(priv, reg: FC11_REG_10, val: regs[FC11_REG_10]); |
183 | err |= fc0011_writereg(priv, reg: FC11_REG_11, val: regs[FC11_REG_11]); |
184 | err |= fc0011_writereg(priv, reg: FC11_REG_RCCAL, val: regs[FC11_REG_RCCAL]); |
185 | if (err) |
186 | return -EIO; |
187 | |
188 | /* Set VCO freq and VCO div */ |
189 | if (freq < 54000) { |
190 | fvco = freq * 64; |
191 | regs[FC11_REG_VCO] = 0x82; |
192 | } else if (freq < 108000) { |
193 | fvco = freq * 32; |
194 | regs[FC11_REG_VCO] = 0x42; |
195 | } else if (freq < 216000) { |
196 | fvco = freq * 16; |
197 | regs[FC11_REG_VCO] = 0x22; |
198 | } else if (freq < 432000) { |
199 | fvco = freq * 8; |
200 | regs[FC11_REG_VCO] = 0x12; |
201 | } else { |
202 | fvco = freq * 4; |
203 | regs[FC11_REG_VCO] = 0x0A; |
204 | } |
205 | |
206 | /* Calc XIN. The PLL reference frequency is 18 MHz. */ |
207 | xdiv = fvco / 18000; |
208 | WARN_ON(xdiv > 0xFF); |
209 | frac = fvco - xdiv * 18000; |
210 | frac = (frac << 15) / 18000; |
211 | if (frac >= 16384) |
212 | frac += 32786; |
213 | if (!frac) |
214 | xin = 0; |
215 | else |
216 | xin = clamp_t(u32, frac, 512, 65024); |
217 | regs[FC11_REG_XINHI] = xin >> 8; |
218 | regs[FC11_REG_XINLO] = xin; |
219 | |
220 | /* Calc FP and FA */ |
221 | xdivr = xdiv; |
222 | if (fvco - xdiv * 18000 >= 9000) |
223 | xdivr += 1; /* round */ |
224 | fp = xdivr / 8; |
225 | fa = xdivr - fp * 8; |
226 | if (fa < 2) { |
227 | fp -= 1; |
228 | fa += 8; |
229 | } |
230 | if (fp > 0x1F) { |
231 | fp = 0x1F; |
232 | fa = 0xF; |
233 | } |
234 | if (fa >= fp) { |
235 | dev_warn(&priv->i2c->dev, |
236 | "fa %02X >= fp %02X, but trying to continue\n" , |
237 | (unsigned int)(u8)fa, (unsigned int)(u8)fp); |
238 | } |
239 | regs[FC11_REG_FA] = fa; |
240 | regs[FC11_REG_FP] = fp; |
241 | |
242 | /* Select bandwidth */ |
243 | switch (bandwidth) { |
244 | case 8000: |
245 | break; |
246 | case 7000: |
247 | regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_BW7M; |
248 | break; |
249 | default: |
250 | dev_warn(&priv->i2c->dev, "Unsupported bandwidth %u kHz. Using 6000 kHz.\n" , |
251 | bandwidth); |
252 | bandwidth = 6000; |
253 | fallthrough; |
254 | case 6000: |
255 | regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_BW6M; |
256 | break; |
257 | } |
258 | |
259 | /* Pre VCO select */ |
260 | if (fvco < 2320000) { |
261 | vco_sel = 0; |
262 | regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2); |
263 | } else if (fvco < 3080000) { |
264 | vco_sel = 1; |
265 | regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2); |
266 | regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_1; |
267 | } else { |
268 | vco_sel = 2; |
269 | regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2); |
270 | regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_2; |
271 | } |
272 | |
273 | /* Fix for low freqs */ |
274 | if (freq < 45000) { |
275 | regs[FC11_REG_FA] = 0x6; |
276 | regs[FC11_REG_FP] = 0x11; |
277 | } |
278 | |
279 | /* Clock out fix */ |
280 | regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_CLKOUT; |
281 | |
282 | /* Write the cached registers */ |
283 | for (i = FC11_REG_FA; i <= FC11_REG_VCOSEL; i++) { |
284 | err = fc0011_writereg(priv, reg: i, val: regs[i]); |
285 | if (err) |
286 | return err; |
287 | } |
288 | |
289 | /* VCO calibration */ |
290 | err = fc0011_vcocal_trigger(priv); |
291 | if (err) |
292 | return err; |
293 | err = fc0011_vcocal_read(priv, value: &vco_cal); |
294 | if (err) |
295 | return err; |
296 | vco_retries = 0; |
297 | while (!(vco_cal & FC11_VCOCAL_OK) && vco_retries < 3) { |
298 | /* Reset the tuner and try again */ |
299 | err = fe->callback(priv->i2c, DVB_FRONTEND_COMPONENT_TUNER, |
300 | FC0011_FE_CALLBACK_RESET, priv->addr); |
301 | if (err) { |
302 | dev_err(&priv->i2c->dev, "Failed to reset tuner\n" ); |
303 | return err; |
304 | } |
305 | /* Reinit tuner config */ |
306 | err = 0; |
307 | for (i = FC11_REG_FA; i <= FC11_REG_VCOSEL; i++) |
308 | err |= fc0011_writereg(priv, reg: i, val: regs[i]); |
309 | err |= fc0011_writereg(priv, reg: FC11_REG_7, val: regs[FC11_REG_7]); |
310 | err |= fc0011_writereg(priv, reg: FC11_REG_8, val: regs[FC11_REG_8]); |
311 | err |= fc0011_writereg(priv, reg: FC11_REG_10, val: regs[FC11_REG_10]); |
312 | err |= fc0011_writereg(priv, reg: FC11_REG_11, val: regs[FC11_REG_11]); |
313 | err |= fc0011_writereg(priv, reg: FC11_REG_RCCAL, val: regs[FC11_REG_RCCAL]); |
314 | if (err) |
315 | return -EIO; |
316 | /* VCO calibration */ |
317 | err = fc0011_vcocal_trigger(priv); |
318 | if (err) |
319 | return err; |
320 | err = fc0011_vcocal_read(priv, value: &vco_cal); |
321 | if (err) |
322 | return err; |
323 | vco_retries++; |
324 | } |
325 | if (!(vco_cal & FC11_VCOCAL_OK)) { |
326 | dev_err(&priv->i2c->dev, |
327 | "Failed to read VCO calibration value (got %02X)\n" , |
328 | (unsigned int)vco_cal); |
329 | return -EIO; |
330 | } |
331 | vco_cal &= FC11_VCOCAL_VALUEMASK; |
332 | |
333 | switch (vco_sel) { |
334 | default: |
335 | WARN_ON(1); |
336 | return -EINVAL; |
337 | case 0: |
338 | if (vco_cal < 8) { |
339 | regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2); |
340 | regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_1; |
341 | err = fc0011_writereg(priv, reg: FC11_REG_VCOSEL, |
342 | val: regs[FC11_REG_VCOSEL]); |
343 | if (err) |
344 | return err; |
345 | err = fc0011_vcocal_trigger(priv); |
346 | if (err) |
347 | return err; |
348 | } else { |
349 | regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2); |
350 | err = fc0011_writereg(priv, reg: FC11_REG_VCOSEL, |
351 | val: regs[FC11_REG_VCOSEL]); |
352 | if (err) |
353 | return err; |
354 | } |
355 | break; |
356 | case 1: |
357 | if (vco_cal < 5) { |
358 | regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2); |
359 | regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_2; |
360 | err = fc0011_writereg(priv, reg: FC11_REG_VCOSEL, |
361 | val: regs[FC11_REG_VCOSEL]); |
362 | if (err) |
363 | return err; |
364 | err = fc0011_vcocal_trigger(priv); |
365 | if (err) |
366 | return err; |
367 | } else if (vco_cal <= 48) { |
368 | regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2); |
369 | regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_1; |
370 | err = fc0011_writereg(priv, reg: FC11_REG_VCOSEL, |
371 | val: regs[FC11_REG_VCOSEL]); |
372 | if (err) |
373 | return err; |
374 | } else { |
375 | regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2); |
376 | err = fc0011_writereg(priv, reg: FC11_REG_VCOSEL, |
377 | val: regs[FC11_REG_VCOSEL]); |
378 | if (err) |
379 | return err; |
380 | err = fc0011_vcocal_trigger(priv); |
381 | if (err) |
382 | return err; |
383 | } |
384 | break; |
385 | case 2: |
386 | if (vco_cal > 53) { |
387 | regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2); |
388 | regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_1; |
389 | err = fc0011_writereg(priv, reg: FC11_REG_VCOSEL, |
390 | val: regs[FC11_REG_VCOSEL]); |
391 | if (err) |
392 | return err; |
393 | err = fc0011_vcocal_trigger(priv); |
394 | if (err) |
395 | return err; |
396 | } else { |
397 | regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2); |
398 | regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_2; |
399 | err = fc0011_writereg(priv, reg: FC11_REG_VCOSEL, |
400 | val: regs[FC11_REG_VCOSEL]); |
401 | if (err) |
402 | return err; |
403 | } |
404 | break; |
405 | } |
406 | err = fc0011_vcocal_read(priv, NULL); |
407 | if (err) |
408 | return err; |
409 | usleep_range(min: 10000, max: 50000); |
410 | |
411 | err = fc0011_readreg(priv, reg: FC11_REG_RCCAL, val: ®s[FC11_REG_RCCAL]); |
412 | if (err) |
413 | return err; |
414 | regs[FC11_REG_RCCAL] |= FC11_RCCAL_FORCE; |
415 | err = fc0011_writereg(priv, reg: FC11_REG_RCCAL, val: regs[FC11_REG_RCCAL]); |
416 | if (err) |
417 | return err; |
418 | regs[FC11_REG_16] = 0xB; |
419 | err = fc0011_writereg(priv, reg: FC11_REG_16, val: regs[FC11_REG_16]); |
420 | if (err) |
421 | return err; |
422 | |
423 | dev_dbg(&priv->i2c->dev, "Tuned to fa=%02X fp=%02X xin=%02X%02X vco=%02X vcosel=%02X vcocal=%02X(%u) bw=%u\n" , |
424 | (unsigned int)regs[FC11_REG_FA], |
425 | (unsigned int)regs[FC11_REG_FP], |
426 | (unsigned int)regs[FC11_REG_XINHI], |
427 | (unsigned int)regs[FC11_REG_XINLO], |
428 | (unsigned int)regs[FC11_REG_VCO], |
429 | (unsigned int)regs[FC11_REG_VCOSEL], |
430 | (unsigned int)vco_cal, vco_retries, |
431 | (unsigned int)bandwidth); |
432 | |
433 | priv->frequency = p->frequency; |
434 | priv->bandwidth = p->bandwidth_hz; |
435 | |
436 | return 0; |
437 | } |
438 | |
439 | static int fc0011_get_frequency(struct dvb_frontend *fe, u32 *frequency) |
440 | { |
441 | struct fc0011_priv *priv = fe->tuner_priv; |
442 | |
443 | *frequency = priv->frequency; |
444 | |
445 | return 0; |
446 | } |
447 | |
448 | static int fc0011_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) |
449 | { |
450 | *frequency = 0; |
451 | |
452 | return 0; |
453 | } |
454 | |
455 | static int fc0011_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth) |
456 | { |
457 | struct fc0011_priv *priv = fe->tuner_priv; |
458 | |
459 | *bandwidth = priv->bandwidth; |
460 | |
461 | return 0; |
462 | } |
463 | |
464 | static const struct dvb_tuner_ops fc0011_tuner_ops = { |
465 | .info = { |
466 | .name = "Fitipower FC0011" , |
467 | |
468 | .frequency_min_hz = 45 * MHz, |
469 | .frequency_max_hz = 1000 * MHz, |
470 | }, |
471 | |
472 | .release = fc0011_release, |
473 | .init = fc0011_init, |
474 | |
475 | .set_params = fc0011_set_params, |
476 | |
477 | .get_frequency = fc0011_get_frequency, |
478 | .get_if_frequency = fc0011_get_if_frequency, |
479 | .get_bandwidth = fc0011_get_bandwidth, |
480 | }; |
481 | |
482 | struct dvb_frontend *fc0011_attach(struct dvb_frontend *fe, |
483 | struct i2c_adapter *i2c, |
484 | const struct fc0011_config *config) |
485 | { |
486 | struct fc0011_priv *priv; |
487 | |
488 | priv = kzalloc(size: sizeof(struct fc0011_priv), GFP_KERNEL); |
489 | if (!priv) |
490 | return NULL; |
491 | |
492 | priv->i2c = i2c; |
493 | priv->addr = config->i2c_address; |
494 | |
495 | fe->tuner_priv = priv; |
496 | fe->ops.tuner_ops = fc0011_tuner_ops; |
497 | |
498 | dev_info(&priv->i2c->dev, "Fitipower FC0011 tuner attached\n" ); |
499 | |
500 | return fe; |
501 | } |
502 | EXPORT_SYMBOL_GPL(fc0011_attach); |
503 | |
504 | MODULE_DESCRIPTION("Fitipower FC0011 silicon tuner driver" ); |
505 | MODULE_AUTHOR("Michael Buesch <m@bues.ch>" ); |
506 | MODULE_LICENSE("GPL" ); |
507 | |