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