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