[media] ts2020: add support for TS2022
[linux-2.6-block.git] / drivers / media / dvb-frontends / ts2020.c
CommitLineData
6fef4fc7
KD
1/*
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 */
b858c331 26#define FREQ_OFFSET_LOW_SYM_RATE 3000
6fef4fc7 27
b858c331
IL
28struct ts2020_priv {
29 /* i2c details */
30 int i2c_address;
6fef4fc7 31 struct i2c_adapter *i2c;
abd9025b
AP
32 u8 clk_out:2;
33 u8 clk_out_div:5;
b858c331 34 u32 frequency;
03a67279 35 u32 frequency_div;
abd9025b
AP
36#define TS2020_M88TS2020 0
37#define TS2020_M88TS2022 1
38 u8 tuner;
39 u8 loop_through:1;
40};
41
42struct ts2020_reg_val {
43 u8 reg;
44 u8 val;
6fef4fc7
KD
45};
46
b858c331 47static int ts2020_release(struct dvb_frontend *fe)
6fef4fc7 48{
b858c331
IL
49 kfree(fe->tuner_priv);
50 fe->tuner_priv = NULL;
51 return 0;
52}
53
54static int ts2020_writereg(struct dvb_frontend *fe, int reg, int data)
55{
56 struct ts2020_priv *priv = fe->tuner_priv;
57 u8 buf[] = { reg, data };
58 struct i2c_msg msg[] = {
59 {
60 .addr = priv->i2c_address,
61 .flags = 0,
62 .buf = buf,
63 .len = 2
64 }
65 };
66 int err;
67
68 if (fe->ops.i2c_gate_ctrl)
69 fe->ops.i2c_gate_ctrl(fe, 1);
70
71 err = i2c_transfer(priv->i2c, msg, 1);
72 if (err != 1) {
73 printk(KERN_ERR
74 "%s: writereg error(err == %i, reg == 0x%02x, value == 0x%02x)\n",
75 __func__, err, reg, data);
76 return -EREMOTEIO;
77 }
6fef4fc7 78
b858c331
IL
79 if (fe->ops.i2c_gate_ctrl)
80 fe->ops.i2c_gate_ctrl(fe, 0);
81
82 return 0;
83}
84
85static int ts2020_readreg(struct dvb_frontend *fe, u8 reg)
86{
87 struct ts2020_priv *priv = fe->tuner_priv;
6fef4fc7
KD
88 int ret;
89 u8 b0[] = { reg };
90 u8 b1[] = { 0 };
91 struct i2c_msg msg[] = {
92 {
b858c331 93 .addr = priv->i2c_address,
6fef4fc7
KD
94 .flags = 0,
95 .buf = b0,
96 .len = 1
97 }, {
b858c331 98 .addr = priv->i2c_address,
6fef4fc7
KD
99 .flags = I2C_M_RD,
100 .buf = b1,
101 .len = 1
102 }
103 };
104
105 if (fe->ops.i2c_gate_ctrl)
106 fe->ops.i2c_gate_ctrl(fe, 1);
107
b858c331 108 ret = i2c_transfer(priv->i2c, msg, 2);
6fef4fc7
KD
109
110 if (ret != 2) {
b858c331
IL
111 printk(KERN_ERR "%s: reg=0x%x(error=%d)\n",
112 __func__, reg, ret);
6fef4fc7
KD
113 return ret;
114 }
115
b858c331
IL
116 if (fe->ops.i2c_gate_ctrl)
117 fe->ops.i2c_gate_ctrl(fe, 0);
118
6fef4fc7
KD
119 return b1[0];
120}
121
b858c331 122static int ts2020_sleep(struct dvb_frontend *fe)
6fef4fc7 123{
b858c331
IL
124 struct ts2020_priv *priv = fe->tuner_priv;
125 int ret;
126 u8 buf[] = { 10, 0 };
127 struct i2c_msg msg = {
128 .addr = priv->i2c_address,
129 .flags = 0,
130 .buf = buf,
131 .len = 2
132 };
6fef4fc7 133
abd9025b
AP
134 if (priv->tuner == TS2020_M88TS2022)
135 buf[0] = 0x00;
136
6fef4fc7
KD
137 if (fe->ops.i2c_gate_ctrl)
138 fe->ops.i2c_gate_ctrl(fe, 1);
139
b858c331
IL
140 ret = i2c_transfer(priv->i2c, &msg, 1);
141 if (ret != 1)
142 printk(KERN_ERR "%s: i2c error\n", __func__);
6fef4fc7
KD
143
144 if (fe->ops.i2c_gate_ctrl)
145 fe->ops.i2c_gate_ctrl(fe, 0);
146
b858c331 147 return (ret == 1) ? 0 : ret;
6fef4fc7
KD
148}
149
150static int ts2020_init(struct dvb_frontend *fe)
151{
b858c331 152 struct ts2020_priv *priv = fe->tuner_priv;
abd9025b
AP
153 int i;
154 u8 u8tmp;
155
156 if (priv->tuner == TS2020_M88TS2020) {
157 ts2020_writereg(fe, 0x42, 0x73);
158 ts2020_writereg(fe, 0x05, priv->clk_out_div);
159 ts2020_writereg(fe, 0x20, 0x27);
160 ts2020_writereg(fe, 0x07, 0x02);
161 ts2020_writereg(fe, 0x11, 0xff);
162 ts2020_writereg(fe, 0x60, 0xf9);
163 ts2020_writereg(fe, 0x08, 0x01);
164 ts2020_writereg(fe, 0x00, 0x41);
165 } else {
166 static const struct ts2020_reg_val reg_vals[] = {
167 {0x7d, 0x9d},
168 {0x7c, 0x9a},
169 {0x7a, 0x76},
170 {0x3b, 0x01},
171 {0x63, 0x88},
172 {0x61, 0x85},
173 {0x22, 0x30},
174 {0x30, 0x40},
175 {0x20, 0x23},
176 {0x24, 0x02},
177 {0x12, 0xa0},
178 };
179
180 ts2020_writereg(fe, 0x00, 0x01);
181 ts2020_writereg(fe, 0x00, 0x03);
182
183 switch (priv->clk_out) {
184 case TS2020_CLK_OUT_DISABLED:
185 u8tmp = 0x60;
186 break;
187 case TS2020_CLK_OUT_ENABLED:
188 u8tmp = 0x70;
189 ts2020_writereg(fe, 0x05, priv->clk_out_div);
190 break;
191 case TS2020_CLK_OUT_ENABLED_XTALOUT:
192 u8tmp = 0x6c;
193 break;
194 default:
195 u8tmp = 0x60;
196 break;
197 }
198
199 ts2020_writereg(fe, 0x42, u8tmp);
200
201 if (priv->loop_through)
202 u8tmp = 0xec;
203 else
204 u8tmp = 0x6c;
b858c331 205
abd9025b
AP
206 ts2020_writereg(fe, 0x62, u8tmp);
207
208 for (i = 0; i < ARRAY_SIZE(reg_vals); i++)
209 ts2020_writereg(fe, reg_vals[i].reg, reg_vals[i].val);
210 }
b858c331 211
6fef4fc7
KD
212 return 0;
213}
214
b858c331 215static int ts2020_tuner_gate_ctrl(struct dvb_frontend *fe, u8 offset)
6fef4fc7 216{
b858c331
IL
217 int ret;
218 ret = ts2020_writereg(fe, 0x51, 0x1f - offset);
219 ret |= ts2020_writereg(fe, 0x51, 0x1f);
220 ret |= ts2020_writereg(fe, 0x50, offset);
221 ret |= ts2020_writereg(fe, 0x50, 0x00);
222 msleep(20);
223 return ret;
224}
6fef4fc7 225
b858c331
IL
226static int ts2020_set_tuner_rf(struct dvb_frontend *fe)
227{
228 int reg;
6fef4fc7 229
b858c331
IL
230 reg = ts2020_readreg(fe, 0x3d);
231 reg &= 0x7f;
232 if (reg < 0x16)
233 reg = 0xa1;
234 else if (reg == 0x16)
235 reg = 0x99;
236 else
237 reg = 0xf9;
6fef4fc7 238
b858c331
IL
239 ts2020_writereg(fe, 0x60, reg);
240 reg = ts2020_tuner_gate_ctrl(fe, 0x08);
6fef4fc7 241
b858c331 242 return reg;
6fef4fc7
KD
243}
244
245static int ts2020_set_params(struct dvb_frontend *fe)
246{
247 struct dtv_frontend_properties *c = &fe->dtv_property_cache;
9898df64 248 struct ts2020_priv *priv = fe->tuner_priv;
b858c331
IL
249 int ret;
250 u32 frequency = c->frequency;
251 s32 offset_khz;
252 u32 symbol_rate = (c->symbol_rate / 1000);
253 u32 f3db, gdiv28;
254 u16 value, ndiv, lpf_coeff;
255 u8 lpf_mxdiv, mlpf_max, mlpf_min, nlpf;
256 u8 lo = 0x01, div4 = 0x0;
257
258 /* Calculate frequency divider */
03a67279 259 if (frequency < priv->frequency_div) {
b858c331
IL
260 lo |= 0x10;
261 div4 = 0x1;
262 ndiv = (frequency * 14 * 4) / TS2020_XTAL_FREQ;
263 } else
264 ndiv = (frequency * 14 * 2) / TS2020_XTAL_FREQ;
265 ndiv = ndiv + ndiv % 2;
266 ndiv = ndiv - 1024;
267
abd9025b
AP
268 if (priv->tuner == TS2020_M88TS2020) {
269 lpf_coeff = 2766;
270 ret = ts2020_writereg(fe, 0x10, 0x80 | lo);
271 } else {
272 lpf_coeff = 3200;
273 ret = ts2020_writereg(fe, 0x10, 0x0b);
274 ret |= ts2020_writereg(fe, 0x11, 0x40);
275 }
b858c331
IL
276
277 /* Set frequency divider */
278 ret |= ts2020_writereg(fe, 0x01, (ndiv >> 8) & 0xf);
279 ret |= ts2020_writereg(fe, 0x02, ndiv & 0xff);
280
281 ret |= ts2020_writereg(fe, 0x03, 0x06);
282 ret |= ts2020_tuner_gate_ctrl(fe, 0x10);
283 if (ret < 0)
284 return -ENODEV;
285
286 /* Tuner Frequency Range */
287 ret = ts2020_writereg(fe, 0x10, lo);
288
289 ret |= ts2020_tuner_gate_ctrl(fe, 0x08);
290
291 /* Tuner RF */
abd9025b
AP
292 if (priv->tuner == TS2020_M88TS2020)
293 ret |= ts2020_set_tuner_rf(fe);
b858c331
IL
294
295 gdiv28 = (TS2020_XTAL_FREQ / 1000 * 1694 + 500) / 1000;
296 ret |= ts2020_writereg(fe, 0x04, gdiv28 & 0xff);
297 ret |= ts2020_tuner_gate_ctrl(fe, 0x04);
298 if (ret < 0)
299 return -ENODEV;
6fef4fc7 300
abd9025b
AP
301 if (priv->tuner == TS2020_M88TS2022) {
302 ret = ts2020_writereg(fe, 0x25, 0x00);
303 ret |= ts2020_writereg(fe, 0x27, 0x70);
304 ret |= ts2020_writereg(fe, 0x41, 0x09);
305 ret |= ts2020_writereg(fe, 0x08, 0x0b);
306 if (ret < 0)
307 return -ENODEV;
308 }
309
b858c331 310 value = ts2020_readreg(fe, 0x26);
6fef4fc7 311
b858c331
IL
312 f3db = (symbol_rate * 135) / 200 + 2000;
313 f3db += FREQ_OFFSET_LOW_SYM_RATE;
6fef4fc7
KD
314 if (f3db < 7000)
315 f3db = 7000;
316 if (f3db > 40000)
317 f3db = 40000;
318
b858c331
IL
319 gdiv28 = gdiv28 * 207 / (value * 2 + 151);
320 mlpf_max = gdiv28 * 135 / 100;
321 mlpf_min = gdiv28 * 78 / 100;
6fef4fc7
KD
322 if (mlpf_max > 63)
323 mlpf_max = 63;
324
b858c331
IL
325 nlpf = (f3db * gdiv28 * 2 / lpf_coeff /
326 (TS2020_XTAL_FREQ / 1000) + 1) / 2;
6fef4fc7
KD
327 if (nlpf > 23)
328 nlpf = 23;
329 if (nlpf < 1)
330 nlpf = 1;
331
b858c331
IL
332 lpf_mxdiv = (nlpf * (TS2020_XTAL_FREQ / 1000)
333 * lpf_coeff * 2 / f3db + 1) / 2;
6fef4fc7 334
b858c331 335 if (lpf_mxdiv < mlpf_min) {
6fef4fc7 336 nlpf++;
b858c331
IL
337 lpf_mxdiv = (nlpf * (TS2020_XTAL_FREQ / 1000)
338 * lpf_coeff * 2 / f3db + 1) / 2;
6fef4fc7
KD
339 }
340
b858c331
IL
341 if (lpf_mxdiv > mlpf_max)
342 lpf_mxdiv = mlpf_max;
6fef4fc7 343
b858c331
IL
344 ret = ts2020_writereg(fe, 0x04, lpf_mxdiv);
345 ret |= ts2020_writereg(fe, 0x06, nlpf);
6fef4fc7 346
b858c331 347 ret |= ts2020_tuner_gate_ctrl(fe, 0x04);
6fef4fc7 348
b858c331 349 ret |= ts2020_tuner_gate_ctrl(fe, 0x01);
6fef4fc7 350
b858c331
IL
351 msleep(80);
352 /* calculate offset assuming 96000kHz*/
353 offset_khz = (ndiv - ndiv % 2 + 1024) * TS2020_XTAL_FREQ
354 / (6 + 8) / (div4 + 1) / 2;
6fef4fc7 355
b858c331
IL
356 priv->frequency = offset_khz;
357
358 return (ret < 0) ? -EINVAL : 0;
359}
6fef4fc7 360
b858c331
IL
361static int ts2020_get_frequency(struct dvb_frontend *fe, u32 *frequency)
362{
363 struct ts2020_priv *priv = fe->tuner_priv;
364 *frequency = priv->frequency;
abd9025b
AP
365
366 return 0;
367}
368
369static int ts2020_get_if_frequency(struct dvb_frontend *fe, u32 *frequency)
370{
371 *frequency = 0; /* Zero-IF */
6fef4fc7
KD
372 return 0;
373}
374
b858c331
IL
375/* read TS2020 signal strength */
376static int ts2020_read_signal_strength(struct dvb_frontend *fe,
377 u16 *signal_strength)
6fef4fc7
KD
378{
379 u16 sig_reading, sig_strength;
380 u8 rfgain, bbgain;
381
382 rfgain = ts2020_readreg(fe, 0x3d) & 0x1f;
383 bbgain = ts2020_readreg(fe, 0x21) & 0x1f;
384
385 if (rfgain > 15)
386 rfgain = 15;
387 if (bbgain > 13)
388 bbgain = 13;
389
390 sig_reading = rfgain * 2 + bbgain * 3;
391
392 sig_strength = 40 + (64 - sig_reading) * 50 / 64 ;
393
394 /* cook the value to be suitable for szap-s2 human readable output */
395 *signal_strength = sig_strength * 1000;
396
397 return 0;
398}
399
b858c331 400static struct dvb_tuner_ops ts2020_tuner_ops = {
6fef4fc7 401 .info = {
b858c331 402 .name = "TS2020",
6fef4fc7 403 .frequency_min = 950000,
b858c331 404 .frequency_max = 2150000
6fef4fc7 405 },
6fef4fc7
KD
406 .init = ts2020_init,
407 .release = ts2020_release,
b858c331 408 .sleep = ts2020_sleep,
6fef4fc7
KD
409 .set_params = ts2020_set_params,
410 .get_frequency = ts2020_get_frequency,
abd9025b 411 .get_if_frequency = ts2020_get_if_frequency,
b858c331 412 .get_rf_strength = ts2020_read_signal_strength,
6fef4fc7
KD
413};
414
415struct dvb_frontend *ts2020_attach(struct dvb_frontend *fe,
b858c331
IL
416 const struct ts2020_config *config,
417 struct i2c_adapter *i2c)
6fef4fc7 418{
b858c331
IL
419 struct ts2020_priv *priv = NULL;
420 u8 buf;
421
422 priv = kzalloc(sizeof(struct ts2020_priv), GFP_KERNEL);
423 if (priv == NULL)
424 return NULL;
6fef4fc7 425
b858c331
IL
426 priv->i2c_address = config->tuner_address;
427 priv->i2c = i2c;
abd9025b 428 priv->clk_out = config->clk_out;
b858c331 429 priv->clk_out_div = config->clk_out_div;
03a67279 430 priv->frequency_div = config->frequency_div;
b858c331
IL
431 fe->tuner_priv = priv;
432
b4559ace
MCC
433 if (!priv->frequency_div)
434 priv->frequency_div = 1060000;
435
b858c331
IL
436 /* Wake Up the tuner */
437 if ((0x03 & ts2020_readreg(fe, 0x00)) == 0x00) {
438 ts2020_writereg(fe, 0x00, 0x01);
439 msleep(2);
440 }
441
442 ts2020_writereg(fe, 0x00, 0x03);
443 msleep(2);
444
445 /* Check the tuner version */
446 buf = ts2020_readreg(fe, 0x00);
abd9025b 447 if ((buf == 0x01) || (buf == 0x41) || (buf == 0x81)) {
b858c331 448 printk(KERN_INFO "%s: Find tuner TS2020!\n", __func__);
abd9025b
AP
449 priv->tuner = TS2020_M88TS2020;
450 } else if ((buf == 0x83) || (buf == 0xc3)) {
451 printk(KERN_INFO "%s: Find tuner TS2022!\n", __func__);
452 priv->tuner = TS2020_M88TS2022;
453 } else {
b858c331
IL
454 printk(KERN_ERR "%s: Read tuner reg[0] = %d\n", __func__, buf);
455 kfree(priv);
6fef4fc7 456 return NULL;
b858c331 457 }
6fef4fc7 458
b858c331
IL
459 memcpy(&fe->ops.tuner_ops, &ts2020_tuner_ops,
460 sizeof(struct dvb_tuner_ops));
6fef4fc7
KD
461
462 return fe;
463}
464EXPORT_SYMBOL(ts2020_attach);
465
466MODULE_AUTHOR("Konstantin Dimitrov <kosio.dimitrov@gmail.com>");
467MODULE_DESCRIPTION("Montage Technology TS2020 - Silicon tuner driver module");
468MODULE_LICENSE("GPL");