blob: 6374a1e010bff36b5b69277e1f3e79579cd9f10d [file] [log] [blame]
Antti Palosaari26eb7042011-04-09 20:07:30 -03001/*
2 * NXP TDA18212HN silicon tuner driver
3 *
4 * Copyright (C) 2011 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 along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
Joe Perches2b507632011-07-31 04:30:10 -030021#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
22
23#include "tda18212.h"
24
25struct tda18212_priv {
26 struct tda18212_config *cfg;
27 struct i2c_adapter *i2c;
28};
29
30#define dbg(fmt, arg...) \
31do { \
32 if (debug) \
33 pr_info("%s: " fmt, __func__, ##arg); \
34} while (0)
Antti Palosaari26eb7042011-04-09 20:07:30 -030035
36static int debug;
37module_param(debug, int, 0644);
38MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off).");
39
40/* write multiple registers */
41static int tda18212_wr_regs(struct tda18212_priv *priv, u8 reg, u8 *val,
42 int len)
43{
44 int ret;
45 u8 buf[len+1];
46 struct i2c_msg msg[1] = {
47 {
48 .addr = priv->cfg->i2c_address,
49 .flags = 0,
50 .len = sizeof(buf),
51 .buf = buf,
52 }
53 };
54
55 buf[0] = reg;
56 memcpy(&buf[1], val, len);
57
58 ret = i2c_transfer(priv->i2c, msg, 1);
59 if (ret == 1) {
60 ret = 0;
61 } else {
Joe Perches2b507632011-07-31 04:30:10 -030062 pr_warn("i2c wr failed ret:%d reg:%02x len:%d\n",
63 ret, reg, len);
Antti Palosaari26eb7042011-04-09 20:07:30 -030064 ret = -EREMOTEIO;
65 }
66 return ret;
67}
68
69/* read multiple registers */
70static int tda18212_rd_regs(struct tda18212_priv *priv, u8 reg, u8 *val,
71 int len)
72{
73 int ret;
74 u8 buf[len];
75 struct i2c_msg msg[2] = {
76 {
77 .addr = priv->cfg->i2c_address,
78 .flags = 0,
79 .len = 1,
80 .buf = &reg,
81 }, {
82 .addr = priv->cfg->i2c_address,
83 .flags = I2C_M_RD,
84 .len = sizeof(buf),
85 .buf = buf,
86 }
87 };
88
89 ret = i2c_transfer(priv->i2c, msg, 2);
90 if (ret == 2) {
91 memcpy(val, buf, len);
92 ret = 0;
93 } else {
Joe Perches2b507632011-07-31 04:30:10 -030094 pr_warn("i2c rd failed ret:%d reg:%02x len:%d\n",
95 ret, reg, len);
Antti Palosaari26eb7042011-04-09 20:07:30 -030096 ret = -EREMOTEIO;
97 }
98
99 return ret;
100}
101
102/* write single register */
103static int tda18212_wr_reg(struct tda18212_priv *priv, u8 reg, u8 val)
104{
105 return tda18212_wr_regs(priv, reg, &val, 1);
106}
107
108/* read single register */
109static int tda18212_rd_reg(struct tda18212_priv *priv, u8 reg, u8 *val)
110{
111 return tda18212_rd_regs(priv, reg, val, 1);
112}
113
114#if 0 /* keep, useful when developing driver */
115static void tda18212_dump_regs(struct tda18212_priv *priv)
116{
117 int i;
118 u8 buf[256];
119
120 #define TDA18212_RD_LEN 32
121 for (i = 0; i < sizeof(buf); i += TDA18212_RD_LEN)
122 tda18212_rd_regs(priv, i, &buf[i], TDA18212_RD_LEN);
123
124 print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 32, 1, buf,
125 sizeof(buf), true);
126
127 return;
128}
129#endif
130
131static int tda18212_set_params(struct dvb_frontend *fe,
132 struct dvb_frontend_parameters *p)
133{
134 struct tda18212_priv *priv = fe->tuner_priv;
135 struct dtv_frontend_properties *c = &fe->dtv_property_cache;
136 int ret, i;
137 u32 if_khz;
138 u8 buf[9];
Antti Palosaarib682ad12011-08-12 18:29:10 -0300139 #define DVBT_6 0
140 #define DVBT_7 1
141 #define DVBT_8 2
142 #define DVBT2_6 3
143 #define DVBT2_7 4
144 #define DVBT2_8 5
145 #define DVBC_6 6
146 #define DVBC_8 7
Antti Palosaari26eb7042011-04-09 20:07:30 -0300147 static const u8 bw_params[][3] = {
Antti Palosaarib682ad12011-08-12 18:29:10 -0300148 /* reg: 0f 13 23 */
149 [DVBT_6] = { 0xb3, 0x20, 0x03 },
150 [DVBT_7] = { 0xb3, 0x31, 0x01 },
151 [DVBT_8] = { 0xb3, 0x22, 0x01 },
152 [DVBT2_6] = { 0xbc, 0x20, 0x03 },
153 [DVBT2_7] = { 0xbc, 0x72, 0x03 },
154 [DVBT2_8] = { 0xbc, 0x22, 0x01 },
155 [DVBC_6] = { 0x92, 0x50, 0x03 },
156 [DVBC_8] = { 0x92, 0x53, 0x03 },
Antti Palosaari26eb7042011-04-09 20:07:30 -0300157 };
158
Joe Perches2b507632011-07-31 04:30:10 -0300159 dbg("delsys=%d RF=%d BW=%d\n",
160 c->delivery_system, c->frequency, c->bandwidth_hz);
Antti Palosaari26eb7042011-04-09 20:07:30 -0300161
162 if (fe->ops.i2c_gate_ctrl)
163 fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */
164
165 switch (c->delivery_system) {
166 case SYS_DVBT:
167 switch (c->bandwidth_hz) {
168 case 6000000:
169 if_khz = priv->cfg->if_dvbt_6;
Antti Palosaarib682ad12011-08-12 18:29:10 -0300170 i = DVBT_6;
Antti Palosaari26eb7042011-04-09 20:07:30 -0300171 break;
172 case 7000000:
173 if_khz = priv->cfg->if_dvbt_7;
Antti Palosaarib682ad12011-08-12 18:29:10 -0300174 i = DVBT_7;
Antti Palosaari26eb7042011-04-09 20:07:30 -0300175 break;
176 case 8000000:
177 if_khz = priv->cfg->if_dvbt_8;
Antti Palosaarib682ad12011-08-12 18:29:10 -0300178 i = DVBT_8;
179 break;
180 default:
181 ret = -EINVAL;
182 goto error;
183 }
184 break;
185 case SYS_DVBT2:
186 switch (c->bandwidth_hz) {
187 case 6000000:
188 if_khz = priv->cfg->if_dvbt2_6;
189 i = DVBT2_6;
190 break;
191 case 7000000:
192 if_khz = priv->cfg->if_dvbt2_7;
193 i = DVBT2_7;
194 break;
195 case 8000000:
196 if_khz = priv->cfg->if_dvbt2_8;
197 i = DVBT2_8;
Antti Palosaari26eb7042011-04-09 20:07:30 -0300198 break;
199 default:
200 ret = -EINVAL;
201 goto error;
202 }
203 break;
204 case SYS_DVBC_ANNEX_AC:
205 if_khz = priv->cfg->if_dvbc;
Antti Palosaarib682ad12011-08-12 18:29:10 -0300206 i = DVBC_8;
Antti Palosaari26eb7042011-04-09 20:07:30 -0300207 break;
208 default:
209 ret = -EINVAL;
210 goto error;
211 }
212
213 ret = tda18212_wr_reg(priv, 0x23, bw_params[i][2]);
214 if (ret)
215 goto error;
216
217 ret = tda18212_wr_reg(priv, 0x06, 0x00);
218 if (ret)
219 goto error;
220
221 ret = tda18212_wr_reg(priv, 0x0f, bw_params[i][0]);
222 if (ret)
223 goto error;
224
225 buf[0] = 0x02;
226 buf[1] = bw_params[i][1];
227 buf[2] = 0x03; /* default value */
228 buf[3] = if_khz / 50;
229 buf[4] = ((c->frequency / 1000) >> 16) & 0xff;
230 buf[5] = ((c->frequency / 1000) >> 8) & 0xff;
231 buf[6] = ((c->frequency / 1000) >> 0) & 0xff;
232 buf[7] = 0xc1;
233 buf[8] = 0x01;
234 ret = tda18212_wr_regs(priv, 0x12, buf, sizeof(buf));
235 if (ret)
236 goto error;
237
238exit:
239 if (fe->ops.i2c_gate_ctrl)
240 fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */
241
242 return ret;
243
244error:
Joe Perches2b507632011-07-31 04:30:10 -0300245 dbg("failed:%d\n", ret);
Antti Palosaari26eb7042011-04-09 20:07:30 -0300246 goto exit;
247}
248
249static int tda18212_release(struct dvb_frontend *fe)
250{
251 kfree(fe->tuner_priv);
252 fe->tuner_priv = NULL;
253 return 0;
254}
255
256static const struct dvb_tuner_ops tda18212_tuner_ops = {
257 .info = {
258 .name = "NXP TDA18212",
259
260 .frequency_min = 48000000,
261 .frequency_max = 864000000,
262 .frequency_step = 1000,
263 },
264
265 .release = tda18212_release,
266
267 .set_params = tda18212_set_params,
268};
269
270struct dvb_frontend *tda18212_attach(struct dvb_frontend *fe,
271 struct i2c_adapter *i2c, struct tda18212_config *cfg)
272{
273 struct tda18212_priv *priv = NULL;
274 int ret;
275 u8 val;
276
277 priv = kzalloc(sizeof(struct tda18212_priv), GFP_KERNEL);
278 if (priv == NULL)
279 return NULL;
280
281 priv->cfg = cfg;
282 priv->i2c = i2c;
283 fe->tuner_priv = priv;
284
285 if (fe->ops.i2c_gate_ctrl)
286 fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */
287
288 /* check if the tuner is there */
289 ret = tda18212_rd_reg(priv, 0x00, &val);
290
291 if (fe->ops.i2c_gate_ctrl)
292 fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */
293
Joe Perches2b507632011-07-31 04:30:10 -0300294 dbg("ret:%d chip ID:%02x\n", ret, val);
Antti Palosaari26eb7042011-04-09 20:07:30 -0300295 if (ret || val != 0xc7) {
296 kfree(priv);
297 return NULL;
298 }
299
Joe Perches2b507632011-07-31 04:30:10 -0300300 pr_info("NXP TDA18212HN successfully identified\n");
Antti Palosaari26eb7042011-04-09 20:07:30 -0300301
302 memcpy(&fe->ops.tuner_ops, &tda18212_tuner_ops,
303 sizeof(struct dvb_tuner_ops));
304
305 return fe;
306}
307EXPORT_SYMBOL(tda18212_attach);
308
309MODULE_DESCRIPTION("NXP TDA18212HN silicon tuner driver");
310MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
311MODULE_LICENSE("GPL");