blob: 7b2f301b1d5ca19deb0de3377f37b4f569d988bc [file] [log] [blame]
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -03001/*
2 Montage Technology TS2020 - Silicon Tuner driver
3 Copyright (C) 2009-2012 Konstantin Dimitrov <kosio.dimitrov@gmail.com>
4
5 Copyright (C) 2009-2012 TurboSight.com
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22#include "dvb_frontend.h"
23#include "ts2020.h"
24
25#define TS2020_XTAL_FREQ 27000 /* in kHz */
Igor M. Liplianinb858c332012-12-28 19:40:33 -030026#define FREQ_OFFSET_LOW_SYM_RATE 3000
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -030027
Igor M. Liplianinb858c332012-12-28 19:40:33 -030028struct ts2020_priv {
Antti Palosaarie6ad9ce2015-03-26 20:20:42 -030029 struct i2c_client *client;
Antti Palosaaridc245a52015-03-23 18:26:55 -030030 struct dvb_frontend *fe;
Igor M. Liplianinb858c332012-12-28 19:40:33 -030031 /* i2c details */
32 int i2c_address;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -030033 struct i2c_adapter *i2c;
Antti Palosaariabd90252015-03-23 14:14:40 -030034 u8 clk_out:2;
35 u8 clk_out_div:5;
Antti Palosaariaf9d5252015-03-26 10:04:09 -030036 u32 frequency_div; /* LO output divider switch frequency */
37 u32 frequency_khz; /* actual used LO frequency */
Antti Palosaariabd90252015-03-23 14:14:40 -030038#define TS2020_M88TS2020 0
39#define TS2020_M88TS2022 1
40 u8 tuner;
41 u8 loop_through:1;
42};
43
44struct ts2020_reg_val {
45 u8 reg;
46 u8 val;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -030047};
48
Igor M. Liplianinb858c332012-12-28 19:40:33 -030049static int ts2020_release(struct dvb_frontend *fe)
50{
Antti Palosaarie6ad9ce2015-03-26 20:20:42 -030051 struct ts2020_priv *priv = fe->tuner_priv;
52 struct i2c_client *client = priv->client;
53
54 dev_dbg(&client->dev, "\n");
55
56 i2c_unregister_device(client);
Igor M. Liplianinb858c332012-12-28 19:40:33 -030057 return 0;
58}
59
60static int ts2020_writereg(struct dvb_frontend *fe, int reg, int data)
61{
62 struct ts2020_priv *priv = fe->tuner_priv;
63 u8 buf[] = { reg, data };
64 struct i2c_msg msg[] = {
65 {
66 .addr = priv->i2c_address,
67 .flags = 0,
68 .buf = buf,
69 .len = 2
70 }
71 };
72 int err;
73
74 if (fe->ops.i2c_gate_ctrl)
75 fe->ops.i2c_gate_ctrl(fe, 1);
76
77 err = i2c_transfer(priv->i2c, msg, 1);
78 if (err != 1) {
79 printk(KERN_ERR
80 "%s: writereg error(err == %i, reg == 0x%02x, value == 0x%02x)\n",
81 __func__, err, reg, data);
82 return -EREMOTEIO;
83 }
84
85 if (fe->ops.i2c_gate_ctrl)
86 fe->ops.i2c_gate_ctrl(fe, 0);
87
88 return 0;
89}
90
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -030091static int ts2020_readreg(struct dvb_frontend *fe, u8 reg)
92{
Igor M. Liplianinb858c332012-12-28 19:40:33 -030093 struct ts2020_priv *priv = fe->tuner_priv;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -030094 int ret;
95 u8 b0[] = { reg };
96 u8 b1[] = { 0 };
97 struct i2c_msg msg[] = {
98 {
Igor M. Liplianinb858c332012-12-28 19:40:33 -030099 .addr = priv->i2c_address,
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300100 .flags = 0,
101 .buf = b0,
102 .len = 1
103 }, {
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300104 .addr = priv->i2c_address,
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300105 .flags = I2C_M_RD,
106 .buf = b1,
107 .len = 1
108 }
109 };
110
111 if (fe->ops.i2c_gate_ctrl)
112 fe->ops.i2c_gate_ctrl(fe, 1);
113
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300114 ret = i2c_transfer(priv->i2c, msg, 2);
115
116 if (ret != 2) {
117 printk(KERN_ERR "%s: reg=0x%x(error=%d)\n",
118 __func__, reg, ret);
119 return ret;
120 }
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300121
122 if (fe->ops.i2c_gate_ctrl)
123 fe->ops.i2c_gate_ctrl(fe, 0);
124
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300125 return b1[0];
126}
127
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300128static int ts2020_sleep(struct dvb_frontend *fe)
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300129{
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300130 struct ts2020_priv *priv = fe->tuner_priv;
Antti Palosaarib3226f92015-03-24 09:40:58 -0300131 u8 u8tmp;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300132
Antti Palosaarib3226f92015-03-24 09:40:58 -0300133 if (priv->tuner == TS2020_M88TS2020)
134 u8tmp = 0x0a; /* XXX: probably wrong */
135 else
136 u8tmp = 0x00;
Antti Palosaariabd90252015-03-23 14:14:40 -0300137
Antti Palosaarib3226f92015-03-24 09:40:58 -0300138 return ts2020_writereg(fe, u8tmp, 0x00);
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300139}
140
141static int ts2020_init(struct dvb_frontend *fe)
142{
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300143 struct ts2020_priv *priv = fe->tuner_priv;
Antti Palosaariabd90252015-03-23 14:14:40 -0300144 int i;
145 u8 u8tmp;
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300146
Antti Palosaariabd90252015-03-23 14:14:40 -0300147 if (priv->tuner == TS2020_M88TS2020) {
148 ts2020_writereg(fe, 0x42, 0x73);
149 ts2020_writereg(fe, 0x05, priv->clk_out_div);
150 ts2020_writereg(fe, 0x20, 0x27);
151 ts2020_writereg(fe, 0x07, 0x02);
152 ts2020_writereg(fe, 0x11, 0xff);
153 ts2020_writereg(fe, 0x60, 0xf9);
154 ts2020_writereg(fe, 0x08, 0x01);
155 ts2020_writereg(fe, 0x00, 0x41);
156 } else {
157 static const struct ts2020_reg_val reg_vals[] = {
158 {0x7d, 0x9d},
159 {0x7c, 0x9a},
160 {0x7a, 0x76},
161 {0x3b, 0x01},
162 {0x63, 0x88},
163 {0x61, 0x85},
164 {0x22, 0x30},
165 {0x30, 0x40},
166 {0x20, 0x23},
167 {0x24, 0x02},
168 {0x12, 0xa0},
169 };
170
171 ts2020_writereg(fe, 0x00, 0x01);
172 ts2020_writereg(fe, 0x00, 0x03);
173
174 switch (priv->clk_out) {
175 case TS2020_CLK_OUT_DISABLED:
176 u8tmp = 0x60;
177 break;
178 case TS2020_CLK_OUT_ENABLED:
179 u8tmp = 0x70;
180 ts2020_writereg(fe, 0x05, priv->clk_out_div);
181 break;
182 case TS2020_CLK_OUT_ENABLED_XTALOUT:
183 u8tmp = 0x6c;
184 break;
185 default:
186 u8tmp = 0x60;
187 break;
188 }
189
190 ts2020_writereg(fe, 0x42, u8tmp);
191
192 if (priv->loop_through)
193 u8tmp = 0xec;
194 else
195 u8tmp = 0x6c;
196
197 ts2020_writereg(fe, 0x62, u8tmp);
198
199 for (i = 0; i < ARRAY_SIZE(reg_vals); i++)
200 ts2020_writereg(fe, reg_vals[i].reg, reg_vals[i].val);
201 }
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300202
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300203 return 0;
204}
205
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300206static int ts2020_tuner_gate_ctrl(struct dvb_frontend *fe, u8 offset)
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300207{
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300208 int ret;
209 ret = ts2020_writereg(fe, 0x51, 0x1f - offset);
210 ret |= ts2020_writereg(fe, 0x51, 0x1f);
211 ret |= ts2020_writereg(fe, 0x50, offset);
212 ret |= ts2020_writereg(fe, 0x50, 0x00);
213 msleep(20);
214 return ret;
215}
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300216
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300217static int ts2020_set_tuner_rf(struct dvb_frontend *fe)
218{
219 int reg;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300220
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300221 reg = ts2020_readreg(fe, 0x3d);
222 reg &= 0x7f;
223 if (reg < 0x16)
224 reg = 0xa1;
225 else if (reg == 0x16)
226 reg = 0x99;
227 else
228 reg = 0xf9;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300229
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300230 ts2020_writereg(fe, 0x60, reg);
231 reg = ts2020_tuner_gate_ctrl(fe, 0x08);
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300232
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300233 return reg;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300234}
235
236static int ts2020_set_params(struct dvb_frontend *fe)
237{
238 struct dtv_frontend_properties *c = &fe->dtv_property_cache;
Malcolm Priestley9898df62013-01-03 16:37:25 -0300239 struct ts2020_priv *priv = fe->tuner_priv;
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300240 int ret;
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300241 u32 f3db, gdiv28;
Antti Palosaariaf9d5252015-03-26 10:04:09 -0300242 u16 u16tmp, value, lpf_coeff;
243 u8 buf[3], reg10, lpf_mxdiv, mlpf_max, mlpf_min, nlpf;
244 unsigned int f_ref_khz, f_vco_khz, div_ref, div_out, pll_n;
245 unsigned int frequency_khz = c->frequency;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300246
Antti Palosaariaf9d5252015-03-26 10:04:09 -0300247 /*
248 * Integer-N PLL synthesizer
249 * kHz is used for all calculations to keep calculations within 32-bit
250 */
251 f_ref_khz = TS2020_XTAL_FREQ;
252 div_ref = DIV_ROUND_CLOSEST(f_ref_khz, 2000);
253
254 /* select LO output divider */
255 if (frequency_khz < priv->frequency_div) {
256 div_out = 4;
257 reg10 = 0x10;
258 } else {
259 div_out = 2;
260 reg10 = 0x00;
261 }
262
263 f_vco_khz = frequency_khz * div_out;
264 pll_n = f_vco_khz * div_ref / f_ref_khz;
265 pll_n += pll_n % 2;
266 priv->frequency_khz = pll_n * f_ref_khz / div_ref / div_out;
267
268 pr_debug("frequency=%u offset=%d f_vco_khz=%u pll_n=%u div_ref=%u div_out=%u\n",
269 priv->frequency_khz, priv->frequency_khz - c->frequency,
270 f_vco_khz, pll_n, div_ref, div_out);
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300271
Antti Palosaariabd90252015-03-23 14:14:40 -0300272 if (priv->tuner == TS2020_M88TS2020) {
273 lpf_coeff = 2766;
Antti Palosaariaf9d5252015-03-26 10:04:09 -0300274 reg10 |= 0x01;
275 ret = ts2020_writereg(fe, 0x10, reg10);
Antti Palosaariabd90252015-03-23 14:14:40 -0300276 } else {
277 lpf_coeff = 3200;
Antti Palosaariaf9d5252015-03-26 10:04:09 -0300278 reg10 |= 0x0b;
279 ret = ts2020_writereg(fe, 0x10, reg10);
Antti Palosaariabd90252015-03-23 14:14:40 -0300280 ret |= ts2020_writereg(fe, 0x11, 0x40);
281 }
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300282
Antti Palosaariaf9d5252015-03-26 10:04:09 -0300283 u16tmp = pll_n - 1024;
284 buf[0] = (u16tmp >> 8) & 0xff;
285 buf[1] = (u16tmp >> 0) & 0xff;
286 buf[2] = div_ref - 8;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300287
Antti Palosaariaf9d5252015-03-26 10:04:09 -0300288 ret |= ts2020_writereg(fe, 0x01, buf[0]);
289 ret |= ts2020_writereg(fe, 0x02, buf[1]);
290 ret |= ts2020_writereg(fe, 0x03, buf[2]);
291
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300292 ret |= ts2020_tuner_gate_ctrl(fe, 0x10);
293 if (ret < 0)
294 return -ENODEV;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300295
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300296 ret |= ts2020_tuner_gate_ctrl(fe, 0x08);
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300297
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300298 /* Tuner RF */
Antti Palosaariabd90252015-03-23 14:14:40 -0300299 if (priv->tuner == TS2020_M88TS2020)
300 ret |= ts2020_set_tuner_rf(fe);
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300301
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300302 gdiv28 = (TS2020_XTAL_FREQ / 1000 * 1694 + 500) / 1000;
303 ret |= ts2020_writereg(fe, 0x04, gdiv28 & 0xff);
304 ret |= ts2020_tuner_gate_ctrl(fe, 0x04);
305 if (ret < 0)
306 return -ENODEV;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300307
Antti Palosaariabd90252015-03-23 14:14:40 -0300308 if (priv->tuner == TS2020_M88TS2022) {
309 ret = ts2020_writereg(fe, 0x25, 0x00);
310 ret |= ts2020_writereg(fe, 0x27, 0x70);
311 ret |= ts2020_writereg(fe, 0x41, 0x09);
312 ret |= ts2020_writereg(fe, 0x08, 0x0b);
313 if (ret < 0)
314 return -ENODEV;
315 }
316
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300317 value = ts2020_readreg(fe, 0x26);
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300318
Antti Palosaari2ca58f42015-03-26 10:49:17 -0300319 f3db = (c->bandwidth_hz / 1000 / 2) + 2000;
320 f3db += FREQ_OFFSET_LOW_SYM_RATE; /* FIXME: ~always too wide filter */
321 f3db = clamp(f3db, 7000U, 40000U);
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300322
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300323 gdiv28 = gdiv28 * 207 / (value * 2 + 151);
324 mlpf_max = gdiv28 * 135 / 100;
325 mlpf_min = gdiv28 * 78 / 100;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300326 if (mlpf_max > 63)
327 mlpf_max = 63;
328
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300329 nlpf = (f3db * gdiv28 * 2 / lpf_coeff /
330 (TS2020_XTAL_FREQ / 1000) + 1) / 2;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300331 if (nlpf > 23)
332 nlpf = 23;
333 if (nlpf < 1)
334 nlpf = 1;
335
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300336 lpf_mxdiv = (nlpf * (TS2020_XTAL_FREQ / 1000)
337 * lpf_coeff * 2 / f3db + 1) / 2;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300338
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300339 if (lpf_mxdiv < mlpf_min) {
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300340 nlpf++;
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300341 lpf_mxdiv = (nlpf * (TS2020_XTAL_FREQ / 1000)
342 * lpf_coeff * 2 / f3db + 1) / 2;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300343 }
344
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300345 if (lpf_mxdiv > mlpf_max)
346 lpf_mxdiv = mlpf_max;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300347
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300348 ret = ts2020_writereg(fe, 0x04, lpf_mxdiv);
349 ret |= ts2020_writereg(fe, 0x06, nlpf);
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300350
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300351 ret |= ts2020_tuner_gate_ctrl(fe, 0x04);
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300352
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300353 ret |= ts2020_tuner_gate_ctrl(fe, 0x01);
354
355 msleep(80);
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300356
357 return (ret < 0) ? -EINVAL : 0;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300358}
359
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300360static int ts2020_get_frequency(struct dvb_frontend *fe, u32 *frequency)
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300361{
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300362 struct ts2020_priv *priv = fe->tuner_priv;
Antti Palosaariabd90252015-03-23 14:14:40 -0300363
Antti Palosaariaf9d5252015-03-26 10:04:09 -0300364 *frequency = priv->frequency_khz;
Antti Palosaariabd90252015-03-23 14:14:40 -0300365 return 0;
366}
367
368static int ts2020_get_if_frequency(struct dvb_frontend *fe, u32 *frequency)
369{
370 *frequency = 0; /* Zero-IF */
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300371 return 0;
372}
373
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300374/* read TS2020 signal strength */
375static int ts2020_read_signal_strength(struct dvb_frontend *fe,
376 u16 *signal_strength)
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300377{
378 u16 sig_reading, sig_strength;
379 u8 rfgain, bbgain;
380
381 rfgain = ts2020_readreg(fe, 0x3d) & 0x1f;
382 bbgain = ts2020_readreg(fe, 0x21) & 0x1f;
383
384 if (rfgain > 15)
385 rfgain = 15;
386 if (bbgain > 13)
387 bbgain = 13;
388
389 sig_reading = rfgain * 2 + bbgain * 3;
390
391 sig_strength = 40 + (64 - sig_reading) * 50 / 64 ;
392
393 /* cook the value to be suitable for szap-s2 human readable output */
394 *signal_strength = sig_strength * 1000;
395
396 return 0;
397}
398
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300399static struct dvb_tuner_ops ts2020_tuner_ops = {
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300400 .info = {
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300401 .name = "TS2020",
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300402 .frequency_min = 950000,
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300403 .frequency_max = 2150000
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300404 },
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300405 .init = ts2020_init,
406 .release = ts2020_release,
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300407 .sleep = ts2020_sleep,
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300408 .set_params = ts2020_set_params,
409 .get_frequency = ts2020_get_frequency,
Antti Palosaariabd90252015-03-23 14:14:40 -0300410 .get_if_frequency = ts2020_get_if_frequency,
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300411 .get_rf_strength = ts2020_read_signal_strength,
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300412};
413
414struct dvb_frontend *ts2020_attach(struct dvb_frontend *fe,
Igor M. Liplianinb858c332012-12-28 19:40:33 -0300415 const struct ts2020_config *config,
416 struct i2c_adapter *i2c)
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300417{
Antti Palosaarie6ad9ce2015-03-26 20:20:42 -0300418 struct i2c_client *client;
419 struct i2c_board_info board_info;
420 struct ts2020_config pdata;
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300421
Antti Palosaarie6ad9ce2015-03-26 20:20:42 -0300422 memcpy(&pdata, config, sizeof(pdata));
423 pdata.fe = fe;
424 pdata.attach_in_use = true;
425
426 memset(&board_info, 0, sizeof(board_info));
427 strlcpy(board_info.type, "ts2020", I2C_NAME_SIZE);
428 board_info.addr = config->tuner_address;
429 board_info.platform_data = &pdata;
430 client = i2c_new_device(i2c, &board_info);
431 if (!client || !client->dev.driver)
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300432 return NULL;
433
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300434 return fe;
435}
436EXPORT_SYMBOL(ts2020_attach);
437
Antti Palosaaridc245a52015-03-23 18:26:55 -0300438static int ts2020_probe(struct i2c_client *client,
439 const struct i2c_device_id *id)
440{
441 struct ts2020_config *pdata = client->dev.platform_data;
442 struct dvb_frontend *fe = pdata->fe;
443 struct ts2020_priv *dev;
444 int ret;
445 u8 u8tmp;
446 unsigned int utmp;
447 char *chip_str;
448
449 dev = kzalloc(sizeof(*dev), GFP_KERNEL);
450 if (!dev) {
451 ret = -ENOMEM;
452 goto err;
453 }
454
455 dev->i2c = client->adapter;
456 dev->i2c_address = client->addr;
457 dev->clk_out = pdata->clk_out;
458 dev->clk_out_div = pdata->clk_out_div;
459 dev->frequency_div = pdata->frequency_div;
460 dev->fe = fe;
461 fe->tuner_priv = dev;
Antti Palosaarie6ad9ce2015-03-26 20:20:42 -0300462 dev->client = client;
Antti Palosaaridc245a52015-03-23 18:26:55 -0300463
464 /* check if the tuner is there */
465 ret = ts2020_readreg(fe, 0x00);
466 if (ret < 0)
467 goto err;
468 utmp = ret;
469
470 if ((utmp & 0x03) == 0x00) {
471 ret = ts2020_writereg(fe, 0x00, 0x01);
472 if (ret)
473 goto err;
474
475 usleep_range(2000, 50000);
476 }
477
478 ret = ts2020_writereg(fe, 0x00, 0x03);
479 if (ret)
480 goto err;
481
482 usleep_range(2000, 50000);
483
484 ret = ts2020_readreg(fe, 0x00);
485 if (ret < 0)
486 goto err;
487 utmp = ret;
488
489 dev_dbg(&client->dev, "chip_id=%02x\n", utmp);
490
491 switch (utmp) {
492 case 0x01:
493 case 0x41:
494 case 0x81:
495 dev->tuner = TS2020_M88TS2020;
496 chip_str = "TS2020";
497 if (!dev->frequency_div)
498 dev->frequency_div = 1060000;
499 break;
500 case 0xc3:
501 case 0x83:
502 dev->tuner = TS2020_M88TS2022;
503 chip_str = "TS2022";
504 if (!dev->frequency_div)
505 dev->frequency_div = 1103000;
506 break;
507 default:
508 ret = -ENODEV;
509 goto err;
510 }
511
512 if (dev->tuner == TS2020_M88TS2022) {
513 switch (dev->clk_out) {
514 case TS2020_CLK_OUT_DISABLED:
515 u8tmp = 0x60;
516 break;
517 case TS2020_CLK_OUT_ENABLED:
518 u8tmp = 0x70;
519 ret = ts2020_writereg(fe, 0x05, dev->clk_out_div);
520 if (ret)
521 goto err;
522 break;
523 case TS2020_CLK_OUT_ENABLED_XTALOUT:
524 u8tmp = 0x6c;
525 break;
526 default:
527 ret = -EINVAL;
528 goto err;
529 }
530
531 ret = ts2020_writereg(fe, 0x42, u8tmp);
532 if (ret)
533 goto err;
534
535 if (dev->loop_through)
536 u8tmp = 0xec;
537 else
538 u8tmp = 0x6c;
539
540 ret = ts2020_writereg(fe, 0x62, u8tmp);
541 if (ret)
542 goto err;
543 }
544
545 /* sleep */
546 ret = ts2020_writereg(fe, 0x00, 0x00);
547 if (ret)
548 goto err;
549
550 dev_info(&client->dev,
551 "Montage Technology %s successfully identified\n", chip_str);
552
553 memcpy(&fe->ops.tuner_ops, &ts2020_tuner_ops,
554 sizeof(struct dvb_tuner_ops));
Antti Palosaarie6ad9ce2015-03-26 20:20:42 -0300555 if (!pdata->attach_in_use)
556 fe->ops.tuner_ops.release = NULL;
Antti Palosaaridc245a52015-03-23 18:26:55 -0300557
558 i2c_set_clientdata(client, dev);
559 return 0;
560err:
561 dev_dbg(&client->dev, "failed=%d\n", ret);
562 kfree(dev);
563 return ret;
564}
565
566static int ts2020_remove(struct i2c_client *client)
567{
568 struct ts2020_priv *dev = i2c_get_clientdata(client);
Antti Palosaaridc245a52015-03-23 18:26:55 -0300569
570 dev_dbg(&client->dev, "\n");
571
Antti Palosaaridc245a52015-03-23 18:26:55 -0300572 kfree(dev);
Antti Palosaaridc245a52015-03-23 18:26:55 -0300573 return 0;
574}
575
576static const struct i2c_device_id ts2020_id_table[] = {
577 {"ts2020", 0},
578 {"ts2022", 0},
579 {}
580};
581MODULE_DEVICE_TABLE(i2c, ts2020_id_table);
582
583static struct i2c_driver ts2020_driver = {
584 .driver = {
585 .owner = THIS_MODULE,
586 .name = "ts2020",
587 },
588 .probe = ts2020_probe,
589 .remove = ts2020_remove,
590 .id_table = ts2020_id_table,
591};
592
593module_i2c_driver(ts2020_driver);
594
Konstantin Dimitrov6fef4fc2012-12-23 19:25:27 -0300595MODULE_AUTHOR("Konstantin Dimitrov <kosio.dimitrov@gmail.com>");
596MODULE_DESCRIPTION("Montage Technology TS2020 - Silicon tuner driver module");
597MODULE_LICENSE("GPL");