blob: bcdd826dac38a2666eb5c5a63505bd01feabf3c3 [file] [log] [blame]
Antti Palosaari51ff2e22010-08-13 03:41:02 -03001/*
2 * NXP TDA18218HN silicon tuner driver
3 *
4 * Copyright (C) 2010 Antti Palosaari <crope@iki.fi>
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 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21#include "tda18218.h"
22#include "tda18218_priv.h"
23
24static int debug;
25module_param(debug, int, 0644);
26MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off).");
27
28/* write multiple registers */
29static int tda18218_wr_regs(struct tda18218_priv *priv, u8 reg, u8 *val, u8 len)
30{
Hans Verkuil0c8fc772010-12-31 12:26:53 -030031 int ret = 0;
Antti Palosaari51ff2e22010-08-13 03:41:02 -030032 u8 buf[1+len], quotient, remainder, i, msg_len, msg_len_max;
33 struct i2c_msg msg[1] = {
34 {
35 .addr = priv->cfg->i2c_address,
36 .flags = 0,
37 .buf = buf,
38 }
39 };
40
41 msg_len_max = priv->cfg->i2c_wr_max - 1;
42 quotient = len / msg_len_max;
43 remainder = len % msg_len_max;
44 msg_len = msg_len_max;
45 for (i = 0; (i <= quotient && remainder); i++) {
46 if (i == quotient) /* set len of the last msg */
47 msg_len = remainder;
48
49 msg[0].len = msg_len + 1;
50 buf[0] = reg + i * msg_len_max;
51 memcpy(&buf[1], &val[i * msg_len_max], msg_len);
52
53 ret = i2c_transfer(priv->i2c, msg, 1);
54 if (ret != 1)
55 break;
56 }
57
58 if (ret == 1) {
59 ret = 0;
60 } else {
61 warn("i2c wr failed ret:%d reg:%02x len:%d", ret, reg, len);
62 ret = -EREMOTEIO;
63 }
64
65 return ret;
66}
67
68/* read multiple registers */
69static int tda18218_rd_regs(struct tda18218_priv *priv, u8 reg, u8 *val, u8 len)
70{
71 int ret;
72 u8 buf[reg+len]; /* we must start read always from reg 0x00 */
73 struct i2c_msg msg[2] = {
74 {
75 .addr = priv->cfg->i2c_address,
76 .flags = 0,
77 .len = 1,
78 .buf = "\x00",
79 }, {
80 .addr = priv->cfg->i2c_address,
81 .flags = I2C_M_RD,
82 .len = sizeof(buf),
83 .buf = buf,
84 }
85 };
86
87 ret = i2c_transfer(priv->i2c, msg, 2);
88 if (ret == 2) {
89 memcpy(val, &buf[reg], len);
90 ret = 0;
91 } else {
92 warn("i2c rd failed ret:%d reg:%02x len:%d", ret, reg, len);
93 ret = -EREMOTEIO;
94 }
95
96 return ret;
97}
98
99/* write single register */
100static int tda18218_wr_reg(struct tda18218_priv *priv, u8 reg, u8 val)
101{
102 return tda18218_wr_regs(priv, reg, &val, 1);
103}
104
105/* read single register */
106
107static int tda18218_rd_reg(struct tda18218_priv *priv, u8 reg, u8 *val)
108{
109 return tda18218_rd_regs(priv, reg, val, 1);
110}
111
112static int tda18218_set_params(struct dvb_frontend *fe,
113 struct dvb_frontend_parameters *params)
114{
115 struct tda18218_priv *priv = fe->tuner_priv;
Mauro Carvalho Chehab67ccfe32011-12-21 07:47:27 -0300116 struct dtv_frontend_properties *c = &fe->dtv_property_cache;
117 u32 bw = c->bandwidth_hz;
Antti Palosaari51ff2e22010-08-13 03:41:02 -0300118 int ret;
119 u8 buf[3], i, BP_Filter, LP_Fc;
120 u32 LO_Frac;
121 /* TODO: find out correct AGC algorithm */
122 u8 agc[][2] = {
123 { R20_AGC11, 0x60 },
124 { R23_AGC21, 0x02 },
125 { R20_AGC11, 0xa0 },
126 { R23_AGC21, 0x09 },
127 { R20_AGC11, 0xe0 },
128 { R23_AGC21, 0x0c },
129 { R20_AGC11, 0x40 },
130 { R23_AGC21, 0x01 },
131 { R20_AGC11, 0x80 },
132 { R23_AGC21, 0x08 },
133 { R20_AGC11, 0xc0 },
134 { R23_AGC21, 0x0b },
135 { R24_AGC22, 0x1c },
136 { R24_AGC22, 0x0c },
137 };
138
139 if (fe->ops.i2c_gate_ctrl)
140 fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */
141
142 /* low-pass filter cut-off frequency */
Mauro Carvalho Chehab67ccfe32011-12-21 07:47:27 -0300143 if (bw <= 6000000) {
Antti Palosaari51ff2e22010-08-13 03:41:02 -0300144 LP_Fc = 0;
Mauro Carvalho Chehabb4d48c92011-12-30 13:59:37 -0200145 priv->if_frequency = 3000000;
Mauro Carvalho Chehab67ccfe32011-12-21 07:47:27 -0300146 } else if (bw <= 7000000) {
Antti Palosaari51ff2e22010-08-13 03:41:02 -0300147 LP_Fc = 1;
Antti Palosaari522fdf72011-11-13 00:19:56 -0300148 priv->if_frequency = 3500000;
Mauro Carvalho Chehab67ccfe32011-12-21 07:47:27 -0300149 } else {
Antti Palosaari51ff2e22010-08-13 03:41:02 -0300150 LP_Fc = 2;
Antti Palosaari522fdf72011-11-13 00:19:56 -0300151 priv->if_frequency = 4000000;
Antti Palosaari51ff2e22010-08-13 03:41:02 -0300152 }
153
Mauro Carvalho Chehab67ccfe32011-12-21 07:47:27 -0300154 LO_Frac = c->frequency + priv->if_frequency;
Antti Palosaari522fdf72011-11-13 00:19:56 -0300155
Antti Palosaari51ff2e22010-08-13 03:41:02 -0300156 /* band-pass filter */
157 if (LO_Frac < 188000000)
158 BP_Filter = 3;
159 else if (LO_Frac < 253000000)
160 BP_Filter = 4;
161 else if (LO_Frac < 343000000)
162 BP_Filter = 5;
163 else
164 BP_Filter = 6;
165
166 buf[0] = (priv->regs[R1A_IF1] & ~7) | BP_Filter; /* BP_Filter */
167 buf[1] = (priv->regs[R1B_IF2] & ~3) | LP_Fc; /* LP_Fc */
168 buf[2] = priv->regs[R1C_AGC2B];
169 ret = tda18218_wr_regs(priv, R1A_IF1, buf, 3);
170 if (ret)
171 goto error;
172
173 buf[0] = (LO_Frac / 1000) >> 12; /* LO_Frac_0 */
174 buf[1] = (LO_Frac / 1000) >> 4; /* LO_Frac_1 */
175 buf[2] = (LO_Frac / 1000) << 4 |
176 (priv->regs[R0C_MD5] & 0x0f); /* LO_Frac_2 */
177 ret = tda18218_wr_regs(priv, R0A_MD3, buf, 3);
178 if (ret)
179 goto error;
180
181 buf[0] = priv->regs[R0F_MD8] | (1 << 6); /* Freq_prog_Start */
182 ret = tda18218_wr_regs(priv, R0F_MD8, buf, 1);
183 if (ret)
184 goto error;
185
186 buf[0] = priv->regs[R0F_MD8] & ~(1 << 6); /* Freq_prog_Start */
187 ret = tda18218_wr_regs(priv, R0F_MD8, buf, 1);
188 if (ret)
189 goto error;
190
191 /* trigger AGC */
192 for (i = 0; i < ARRAY_SIZE(agc); i++) {
193 ret = tda18218_wr_reg(priv, agc[i][0], agc[i][1]);
194 if (ret)
195 goto error;
196 }
197
198error:
199 if (fe->ops.i2c_gate_ctrl)
200 fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */
201
202 if (ret)
203 dbg("%s: failed ret:%d", __func__, ret);
204
205 return ret;
206}
207
Antti Palosaari522fdf72011-11-13 00:19:56 -0300208static int tda18218_get_if_frequency(struct dvb_frontend *fe, u32 *frequency)
209{
210 struct tda18218_priv *priv = fe->tuner_priv;
211 *frequency = priv->if_frequency;
212 dbg("%s: if=%d", __func__, *frequency);
213 return 0;
214}
215
Antti Palosaari51ff2e22010-08-13 03:41:02 -0300216static int tda18218_sleep(struct dvb_frontend *fe)
217{
218 struct tda18218_priv *priv = fe->tuner_priv;
219 int ret;
220
221 if (fe->ops.i2c_gate_ctrl)
222 fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */
223
224 /* standby */
225 ret = tda18218_wr_reg(priv, R17_PD1, priv->regs[R17_PD1] | (1 << 0));
226
227 if (fe->ops.i2c_gate_ctrl)
228 fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */
229
230 if (ret)
231 dbg("%s: failed ret:%d", __func__, ret);
232
233 return ret;
234}
235
236static int tda18218_init(struct dvb_frontend *fe)
237{
238 struct tda18218_priv *priv = fe->tuner_priv;
239 int ret;
240
241 /* TODO: calibrations */
242
243 if (fe->ops.i2c_gate_ctrl)
244 fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */
245
246 ret = tda18218_wr_regs(priv, R00_ID, priv->regs, TDA18218_NUM_REGS);
247
248 if (fe->ops.i2c_gate_ctrl)
249 fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */
250
251 if (ret)
252 dbg("%s: failed ret:%d", __func__, ret);
253
254 return ret;
255}
256
257static int tda18218_release(struct dvb_frontend *fe)
258{
259 kfree(fe->tuner_priv);
260 fe->tuner_priv = NULL;
261 return 0;
262}
263
264static const struct dvb_tuner_ops tda18218_tuner_ops = {
265 .info = {
266 .name = "NXP TDA18218",
267
268 .frequency_min = 174000000,
269 .frequency_max = 864000000,
270 .frequency_step = 1000,
271 },
272
273 .release = tda18218_release,
274 .init = tda18218_init,
275 .sleep = tda18218_sleep,
276
277 .set_params = tda18218_set_params,
Antti Palosaari522fdf72011-11-13 00:19:56 -0300278
279 .get_if_frequency = tda18218_get_if_frequency,
Antti Palosaari51ff2e22010-08-13 03:41:02 -0300280};
281
282struct dvb_frontend *tda18218_attach(struct dvb_frontend *fe,
283 struct i2c_adapter *i2c, struct tda18218_config *cfg)
284{
285 struct tda18218_priv *priv = NULL;
286 u8 val;
287 int ret;
288 /* chip default registers values */
289 static u8 def_regs[] = {
290 0xc0, 0x88, 0x00, 0x8e, 0x03, 0x00, 0x00, 0xd0, 0x00, 0x40,
291 0x00, 0x00, 0x07, 0xff, 0x84, 0x09, 0x00, 0x13, 0x00, 0x00,
292 0x01, 0x84, 0x09, 0xf0, 0x19, 0x0a, 0x8e, 0x69, 0x98, 0x01,
293 0x00, 0x58, 0x10, 0x40, 0x8c, 0x00, 0x0c, 0x48, 0x85, 0xc9,
294 0xa7, 0x00, 0x00, 0x00, 0x30, 0x81, 0x80, 0x00, 0x39, 0x00,
295 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xf6
296 };
297
298 priv = kzalloc(sizeof(struct tda18218_priv), GFP_KERNEL);
299 if (priv == NULL)
300 return NULL;
301
302 priv->cfg = cfg;
303 priv->i2c = i2c;
304 fe->tuner_priv = priv;
305
306 if (fe->ops.i2c_gate_ctrl)
307 fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */
308
309 /* check if the tuner is there */
310 ret = tda18218_rd_reg(priv, R00_ID, &val);
311 dbg("%s: ret:%d chip ID:%02x", __func__, ret, val);
312 if (ret || val != def_regs[R00_ID]) {
313 kfree(priv);
314 return NULL;
315 }
316
317 info("NXP TDA18218HN successfully identified.");
318
319 memcpy(&fe->ops.tuner_ops, &tda18218_tuner_ops,
320 sizeof(struct dvb_tuner_ops));
321 memcpy(priv->regs, def_regs, sizeof(def_regs));
322
323 /* loop-through enabled chip default register values */
324 if (priv->cfg->loop_through) {
325 priv->regs[R17_PD1] = 0xb0;
326 priv->regs[R18_PD2] = 0x59;
327 }
328
329 /* standby */
330 ret = tda18218_wr_reg(priv, R17_PD1, priv->regs[R17_PD1] | (1 << 0));
331 if (ret)
332 dbg("%s: failed ret:%d", __func__, ret);
333
334 if (fe->ops.i2c_gate_ctrl)
335 fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */
336
337 return fe;
338}
339EXPORT_SYMBOL(tda18218_attach);
340
341MODULE_DESCRIPTION("NXP TDA18218HN silicon tuner driver");
342MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
343MODULE_LICENSE("GPL");