blob: 4ff3a0c9d977984d9129be0f4c78d7f019e6b94f [file] [log] [blame]
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -03001/*
2 * Fujitu mb86a20s ISDB-T/ISDB-Tsb Module driver
3 *
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -03004 * Copyright (C) 2010-2013 Mauro Carvalho Chehab <mchehab@redhat.com>
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -03005 * Copyright (C) 2009-2010 Douglas Landgraf <dougsland@redhat.com>
6 *
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -03007 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation version 2.
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 GNU
14 * General Public License for more details.
15 */
16
17#include <linux/kernel.h>
18#include <asm/div64.h>
19
20#include "dvb_frontend.h"
21#include "mb86a20s.h"
22
23static int debug = 1;
24module_param(debug, int, 0644);
25MODULE_PARM_DESC(debug, "Activates frontend debugging (default:0)");
26
27#define rc(args...) do { \
28 printk(KERN_ERR "mb86a20s: " args); \
29} while (0)
30
31#define dprintk(args...) \
32 do { \
33 if (debug) { \
34 printk(KERN_DEBUG "mb86a20s: %s: ", __func__); \
35 printk(args); \
36 } \
37 } while (0)
38
39struct mb86a20s_state {
40 struct i2c_adapter *i2c;
41 const struct mb86a20s_config *config;
42
43 struct dvb_frontend frontend;
Mauro Carvalho Chehabc736a5f2011-01-14 11:10:05 -030044
45 bool need_init;
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -030046};
47
48struct regdata {
49 u8 reg;
50 u8 data;
51};
52
53/*
54 * Initialization sequence: Use whatevere default values that PV SBTVD
55 * does on its initialisation, obtained via USB snoop
56 */
57static struct regdata mb86a20s_init[] = {
58 { 0x70, 0x0f },
59 { 0x70, 0xff },
60 { 0x08, 0x01 },
61 { 0x09, 0x3e },
Mauro Carvalho Chehaba7025ed2012-01-11 10:56:30 -020062 { 0x50, 0xd1 }, { 0x51, 0x22 },
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -030063 { 0x39, 0x01 },
64 { 0x71, 0x00 },
Mauro Carvalho Chehaba7025ed2012-01-11 10:56:30 -020065 { 0x28, 0x2a }, { 0x29, 0x00 }, { 0x2a, 0xff }, { 0x2b, 0x80 },
66 { 0x28, 0x20 }, { 0x29, 0x33 }, { 0x2a, 0xdf }, { 0x2b, 0xa9 },
Mauro Carvalho Chehabebe96742012-01-11 11:00:28 -020067 { 0x28, 0x22 }, { 0x29, 0x00 }, { 0x2a, 0x1f }, { 0x2b, 0xf0 },
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -030068 { 0x3b, 0x21 },
69 { 0x3c, 0x3a },
70 { 0x01, 0x0d },
Mauro Carvalho Chehaba7025ed2012-01-11 10:56:30 -020071 { 0x04, 0x08 }, { 0x05, 0x05 },
72 { 0x04, 0x0e }, { 0x05, 0x00 },
73 { 0x04, 0x0f }, { 0x05, 0x14 },
74 { 0x04, 0x0b }, { 0x05, 0x8c },
75 { 0x04, 0x00 }, { 0x05, 0x00 },
76 { 0x04, 0x01 }, { 0x05, 0x07 },
77 { 0x04, 0x02 }, { 0x05, 0x0f },
78 { 0x04, 0x03 }, { 0x05, 0xa0 },
79 { 0x04, 0x09 }, { 0x05, 0x00 },
80 { 0x04, 0x0a }, { 0x05, 0xff },
81 { 0x04, 0x27 }, { 0x05, 0x64 },
82 { 0x04, 0x28 }, { 0x05, 0x00 },
83 { 0x04, 0x1e }, { 0x05, 0xff },
84 { 0x04, 0x29 }, { 0x05, 0x0a },
85 { 0x04, 0x32 }, { 0x05, 0x0a },
86 { 0x04, 0x14 }, { 0x05, 0x02 },
87 { 0x04, 0x04 }, { 0x05, 0x00 },
88 { 0x04, 0x05 }, { 0x05, 0x22 },
89 { 0x04, 0x06 }, { 0x05, 0x0e },
90 { 0x04, 0x07 }, { 0x05, 0xd8 },
91 { 0x04, 0x12 }, { 0x05, 0x00 },
92 { 0x04, 0x13 }, { 0x05, 0xff },
Mauro Carvalho Chehabebe96742012-01-11 11:00:28 -020093 { 0x04, 0x15 }, { 0x05, 0x4e },
94 { 0x04, 0x16 }, { 0x05, 0x20 },
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -030095 { 0x52, 0x01 },
Mauro Carvalho Chehabebe96742012-01-11 11:00:28 -020096 { 0x50, 0xa7 }, { 0x51, 0xff },
Mauro Carvalho Chehaba7025ed2012-01-11 10:56:30 -020097 { 0x50, 0xa8 }, { 0x51, 0xff },
98 { 0x50, 0xa9 }, { 0x51, 0xff },
Mauro Carvalho Chehabebe96742012-01-11 11:00:28 -020099 { 0x50, 0xaa }, { 0x51, 0xff },
Mauro Carvalho Chehaba7025ed2012-01-11 10:56:30 -0200100 { 0x50, 0xab }, { 0x51, 0xff },
101 { 0x50, 0xac }, { 0x51, 0xff },
Mauro Carvalho Chehabebe96742012-01-11 11:00:28 -0200102 { 0x50, 0xad }, { 0x51, 0xff },
Mauro Carvalho Chehaba7025ed2012-01-11 10:56:30 -0200103 { 0x50, 0xae }, { 0x51, 0xff },
104 { 0x50, 0xaf }, { 0x51, 0xff },
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300105 { 0x5e, 0x07 },
Mauro Carvalho Chehaba7025ed2012-01-11 10:56:30 -0200106 { 0x50, 0xdc }, { 0x51, 0x01 },
107 { 0x50, 0xdd }, { 0x51, 0xf4 },
108 { 0x50, 0xde }, { 0x51, 0x01 },
109 { 0x50, 0xdf }, { 0x51, 0xf4 },
110 { 0x50, 0xe0 }, { 0x51, 0x01 },
111 { 0x50, 0xe1 }, { 0x51, 0xf4 },
112 { 0x50, 0xb0 }, { 0x51, 0x07 },
113 { 0x50, 0xb2 }, { 0x51, 0xff },
114 { 0x50, 0xb3 }, { 0x51, 0xff },
115 { 0x50, 0xb4 }, { 0x51, 0xff },
116 { 0x50, 0xb5 }, { 0x51, 0xff },
117 { 0x50, 0xb6 }, { 0x51, 0xff },
118 { 0x50, 0xb7 }, { 0x51, 0xff },
119 { 0x50, 0x50 }, { 0x51, 0x02 },
120 { 0x50, 0x51 }, { 0x51, 0x04 },
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300121 { 0x45, 0x04 },
122 { 0x48, 0x04 },
Mauro Carvalho Chehaba7025ed2012-01-11 10:56:30 -0200123 { 0x50, 0xd5 }, { 0x51, 0x01 }, /* Serial */
124 { 0x50, 0xd6 }, { 0x51, 0x1f },
125 { 0x50, 0xd2 }, { 0x51, 0x03 },
126 { 0x50, 0xd7 }, { 0x51, 0x3f },
Mauro Carvalho Chehabebe96742012-01-11 11:00:28 -0200127 { 0x28, 0x74 }, { 0x29, 0x00 }, { 0x28, 0x74 }, { 0x29, 0x40 },
128 { 0x28, 0x46 }, { 0x29, 0x2c }, { 0x28, 0x46 }, { 0x29, 0x0c },
129 { 0x04, 0x40 }, { 0x05, 0x01 },
130 { 0x28, 0x00 }, { 0x29, 0x10 },
131 { 0x28, 0x05 }, { 0x29, 0x02 },
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300132 { 0x1c, 0x01 },
Mauro Carvalho Chehaba7025ed2012-01-11 10:56:30 -0200133 { 0x28, 0x06 }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x03 },
134 { 0x28, 0x07 }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x0d },
135 { 0x28, 0x08 }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x02 },
136 { 0x28, 0x09 }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x01 },
137 { 0x28, 0x0a }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x21 },
138 { 0x28, 0x0b }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x29 },
139 { 0x28, 0x0c }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x16 },
140 { 0x28, 0x0d }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x31 },
141 { 0x28, 0x0e }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x0e },
142 { 0x28, 0x0f }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x4e },
143 { 0x28, 0x10 }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x46 },
144 { 0x28, 0x11 }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x0f },
145 { 0x28, 0x12 }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x56 },
146 { 0x28, 0x13 }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x35 },
147 { 0x28, 0x14 }, { 0x29, 0x00 }, { 0x2a, 0x01 }, { 0x2b, 0xbe },
148 { 0x28, 0x15 }, { 0x29, 0x00 }, { 0x2a, 0x01 }, { 0x2b, 0x84 },
149 { 0x28, 0x16 }, { 0x29, 0x00 }, { 0x2a, 0x03 }, { 0x2b, 0xee },
150 { 0x28, 0x17 }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x98 },
151 { 0x28, 0x18 }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x9f },
152 { 0x28, 0x19 }, { 0x29, 0x00 }, { 0x2a, 0x07 }, { 0x2b, 0xb2 },
153 { 0x28, 0x1a }, { 0x29, 0x00 }, { 0x2a, 0x06 }, { 0x2b, 0xc2 },
154 { 0x28, 0x1b }, { 0x29, 0x00 }, { 0x2a, 0x07 }, { 0x2b, 0x4a },
155 { 0x28, 0x1c }, { 0x29, 0x00 }, { 0x2a, 0x01 }, { 0x2b, 0xbc },
156 { 0x28, 0x1d }, { 0x29, 0x00 }, { 0x2a, 0x04 }, { 0x2b, 0xba },
157 { 0x28, 0x1e }, { 0x29, 0x00 }, { 0x2a, 0x06 }, { 0x2b, 0x14 },
158 { 0x50, 0x1e }, { 0x51, 0x5d },
159 { 0x50, 0x22 }, { 0x51, 0x00 },
160 { 0x50, 0x23 }, { 0x51, 0xc8 },
161 { 0x50, 0x24 }, { 0x51, 0x00 },
162 { 0x50, 0x25 }, { 0x51, 0xf0 },
163 { 0x50, 0x26 }, { 0x51, 0x00 },
164 { 0x50, 0x27 }, { 0x51, 0xc3 },
165 { 0x50, 0x39 }, { 0x51, 0x02 },
Mauro Carvalho Chehabebe96742012-01-11 11:00:28 -0200166 { 0x28, 0x6a }, { 0x29, 0x00 }, { 0x2a, 0x00 }, { 0x2b, 0x00 },
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300167 { 0xd0, 0x00 },
168};
169
170static struct regdata mb86a20s_reset_reception[] = {
171 { 0x70, 0xf0 },
172 { 0x70, 0xff },
173 { 0x08, 0x01 },
174 { 0x08, 0x00 },
175};
176
177static int mb86a20s_i2c_writereg(struct mb86a20s_state *state,
178 u8 i2c_addr, int reg, int data)
179{
180 u8 buf[] = { reg, data };
181 struct i2c_msg msg = {
182 .addr = i2c_addr, .flags = 0, .buf = buf, .len = 2
183 };
184 int rc;
185
186 rc = i2c_transfer(state->i2c, &msg, 1);
187 if (rc != 1) {
Mauro Carvalho Chehab75708002011-01-14 08:50:00 -0300188 printk("%s: writereg error (rc == %i, reg == 0x%02x,"
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300189 " data == 0x%02x)\n", __func__, rc, reg, data);
190 return rc;
191 }
192
193 return 0;
194}
195
196static int mb86a20s_i2c_writeregdata(struct mb86a20s_state *state,
197 u8 i2c_addr, struct regdata *rd, int size)
198{
199 int i, rc;
200
201 for (i = 0; i < size; i++) {
202 rc = mb86a20s_i2c_writereg(state, i2c_addr, rd[i].reg,
203 rd[i].data);
204 if (rc < 0)
205 return rc;
206 }
207 return 0;
208}
209
210static int mb86a20s_i2c_readreg(struct mb86a20s_state *state,
211 u8 i2c_addr, u8 reg)
212{
213 u8 val;
214 int rc;
215 struct i2c_msg msg[] = {
216 { .addr = i2c_addr, .flags = 0, .buf = &reg, .len = 1 },
217 { .addr = i2c_addr, .flags = I2C_M_RD, .buf = &val, .len = 1 }
218 };
219
220 rc = i2c_transfer(state->i2c, msg, 2);
221
222 if (rc != 2) {
Mauro Carvalho Chehab75708002011-01-14 08:50:00 -0300223 rc("%s: reg=0x%x (error=%d)\n", __func__, reg, rc);
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300224 return rc;
225 }
226
227 return val;
228}
229
230#define mb86a20s_readreg(state, reg) \
231 mb86a20s_i2c_readreg(state, state->config->demod_address, reg)
232#define mb86a20s_writereg(state, reg, val) \
233 mb86a20s_i2c_writereg(state, state->config->demod_address, reg, val)
234#define mb86a20s_writeregdata(state, regdata) \
235 mb86a20s_i2c_writeregdata(state, state->config->demod_address, \
236 regdata, ARRAY_SIZE(regdata))
237
238static int mb86a20s_initfe(struct dvb_frontend *fe)
239{
240 struct mb86a20s_state *state = fe->demodulator_priv;
241 int rc;
Mauro Carvalho Chehab7572f9c2010-10-03 16:48:49 -0300242 u8 regD5 = 1;
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300243
244 dprintk("\n");
245
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300246 if (fe->ops.i2c_gate_ctrl)
247 fe->ops.i2c_gate_ctrl(fe, 0);
248
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300249 /* Initialize the frontend */
250 rc = mb86a20s_writeregdata(state, mb86a20s_init);
251 if (rc < 0)
Mauro Carvalho Chehabc736a5f2011-01-14 11:10:05 -0300252 goto err;
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300253
Mauro Carvalho Chehab7572f9c2010-10-03 16:48:49 -0300254 if (!state->config->is_serial) {
255 regD5 &= ~1;
256
257 rc = mb86a20s_writereg(state, 0x50, 0xd5);
258 if (rc < 0)
Mauro Carvalho Chehabc736a5f2011-01-14 11:10:05 -0300259 goto err;
Mauro Carvalho Chehab7572f9c2010-10-03 16:48:49 -0300260 rc = mb86a20s_writereg(state, 0x51, regD5);
261 if (rc < 0)
Mauro Carvalho Chehabc736a5f2011-01-14 11:10:05 -0300262 goto err;
Mauro Carvalho Chehab7572f9c2010-10-03 16:48:49 -0300263 }
264
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300265 if (fe->ops.i2c_gate_ctrl)
266 fe->ops.i2c_gate_ctrl(fe, 1);
267
Mauro Carvalho Chehabc736a5f2011-01-14 11:10:05 -0300268err:
269 if (rc < 0) {
270 state->need_init = true;
271 printk(KERN_INFO "mb86a20s: Init failed. Will try again later\n");
272 } else {
273 state->need_init = false;
Lucas De Marchi25985ed2011-03-30 22:57:33 -0300274 dprintk("Initialization succeeded.\n");
Mauro Carvalho Chehabc736a5f2011-01-14 11:10:05 -0300275 }
276 return rc;
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300277}
278
279static int mb86a20s_read_signal_strength(struct dvb_frontend *fe, u16 *strength)
280{
281 struct mb86a20s_state *state = fe->demodulator_priv;
282 unsigned rf_max, rf_min, rf;
283 u8 val;
284
285 dprintk("\n");
286
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300287 if (fe->ops.i2c_gate_ctrl)
288 fe->ops.i2c_gate_ctrl(fe, 0);
289
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300290 /* Does a binary search to get RF strength */
291 rf_max = 0xfff;
292 rf_min = 0;
293 do {
294 rf = (rf_max + rf_min) / 2;
295 mb86a20s_writereg(state, 0x04, 0x1f);
296 mb86a20s_writereg(state, 0x05, rf >> 8);
297 mb86a20s_writereg(state, 0x04, 0x20);
298 mb86a20s_writereg(state, 0x04, rf);
299
300 val = mb86a20s_readreg(state, 0x02);
301 if (val & 0x08)
302 rf_min = (rf_max + rf_min) / 2;
303 else
304 rf_max = (rf_max + rf_min) / 2;
305 if (rf_max - rf_min < 4) {
306 *strength = (((rf_max + rf_min) / 2) * 65535) / 4095;
307 break;
308 }
309 } while (1);
310
311 dprintk("signal strength = %d\n", *strength);
312
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300313 if (fe->ops.i2c_gate_ctrl)
314 fe->ops.i2c_gate_ctrl(fe, 1);
315
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300316 return 0;
317}
318
319static int mb86a20s_read_status(struct dvb_frontend *fe, fe_status_t *status)
320{
321 struct mb86a20s_state *state = fe->demodulator_priv;
322 u8 val;
323
324 dprintk("\n");
325 *status = 0;
326
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300327 if (fe->ops.i2c_gate_ctrl)
328 fe->ops.i2c_gate_ctrl(fe, 0);
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300329 val = mb86a20s_readreg(state, 0x0a) & 0xf;
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300330 if (fe->ops.i2c_gate_ctrl)
331 fe->ops.i2c_gate_ctrl(fe, 1);
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300332
333 if (val >= 2)
334 *status |= FE_HAS_SIGNAL;
335
336 if (val >= 4)
337 *status |= FE_HAS_CARRIER;
338
339 if (val >= 5)
340 *status |= FE_HAS_VITERBI;
341
342 if (val >= 7)
343 *status |= FE_HAS_SYNC;
344
345 if (val >= 8) /* Maybe 9? */
346 *status |= FE_HAS_LOCK;
347
348 dprintk("val = %d, status = 0x%02x\n", val, *status);
349
350 return 0;
351}
352
Mauro Carvalho Chehab2d76e22b2011-12-26 12:11:51 -0300353static int mb86a20s_set_frontend(struct dvb_frontend *fe)
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300354{
355 struct mb86a20s_state *state = fe->demodulator_priv;
356 int rc;
Mauro Carvalho Chehab2d76e22b2011-12-26 12:11:51 -0300357#if 0
358 /*
359 * FIXME: Properly implement the set frontend properties
360 */
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300361 struct dtv_frontend_properties *c = &fe->dtv_property_cache;
Mauro Carvalho Chehab2d76e22b2011-12-26 12:11:51 -0300362#endif
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300363
364 dprintk("\n");
365
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300366 if (fe->ops.i2c_gate_ctrl)
367 fe->ops.i2c_gate_ctrl(fe, 1);
Mauro Carvalho Chehabc736a5f2011-01-14 11:10:05 -0300368 dprintk("Calling tuner set parameters\n");
Mauro Carvalho Chehab14d24d12011-12-24 12:24:33 -0300369 fe->ops.tuner_ops.set_params(fe);
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300370
Mauro Carvalho Chehabc736a5f2011-01-14 11:10:05 -0300371 /*
372 * Make it more reliable: if, for some reason, the initial
373 * device initialization doesn't happen, initialize it when
374 * a SBTVD parameters are adjusted.
375 *
376 * Unfortunately, due to a hard to track bug at tda829x/tda18271,
377 * the agc callback logic is not called during DVB attach time,
378 * causing mb86a20s to not be initialized with Kworld SBTVD.
379 * So, this hack is needed, in order to make Kworld SBTVD to work.
380 */
381 if (state->need_init)
382 mb86a20s_initfe(fe);
383
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300384 if (fe->ops.i2c_gate_ctrl)
385 fe->ops.i2c_gate_ctrl(fe, 0);
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300386 rc = mb86a20s_writeregdata(state, mb86a20s_reset_reception);
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300387 if (fe->ops.i2c_gate_ctrl)
388 fe->ops.i2c_gate_ctrl(fe, 1);
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300389
390 return rc;
391}
392
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200393static int mb86a20s_get_modulation(struct mb86a20s_state *state,
394 unsigned layer)
395{
396 int rc;
397 static unsigned char reg[] = {
398 [0] = 0x86, /* Layer A */
399 [1] = 0x8a, /* Layer B */
400 [2] = 0x8e, /* Layer C */
401 };
402
Dan Carpenter82033bc2012-01-13 02:28:34 -0300403 if (layer >= ARRAY_SIZE(reg))
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200404 return -EINVAL;
405 rc = mb86a20s_writereg(state, 0x6d, reg[layer]);
406 if (rc < 0)
407 return rc;
408 rc = mb86a20s_readreg(state, 0x6e);
409 if (rc < 0)
410 return rc;
411 switch ((rc & 0x70) >> 4) {
412 case 0:
413 return DQPSK;
414 case 1:
415 return QPSK;
416 case 2:
417 return QAM_16;
418 case 3:
419 return QAM_64;
420 default:
421 return QAM_AUTO;
422 }
423}
424
425static int mb86a20s_get_fec(struct mb86a20s_state *state,
426 unsigned layer)
427{
428 int rc;
429
430 static unsigned char reg[] = {
431 [0] = 0x87, /* Layer A */
432 [1] = 0x8b, /* Layer B */
433 [2] = 0x8f, /* Layer C */
434 };
435
Dan Carpenter82033bc2012-01-13 02:28:34 -0300436 if (layer >= ARRAY_SIZE(reg))
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200437 return -EINVAL;
438 rc = mb86a20s_writereg(state, 0x6d, reg[layer]);
439 if (rc < 0)
440 return rc;
441 rc = mb86a20s_readreg(state, 0x6e);
442 if (rc < 0)
443 return rc;
444 switch (rc) {
445 case 0:
446 return FEC_1_2;
447 case 1:
448 return FEC_2_3;
449 case 2:
450 return FEC_3_4;
451 case 3:
452 return FEC_5_6;
453 case 4:
454 return FEC_7_8;
455 default:
456 return FEC_AUTO;
457 }
458}
459
460static int mb86a20s_get_interleaving(struct mb86a20s_state *state,
461 unsigned layer)
462{
463 int rc;
464
465 static unsigned char reg[] = {
466 [0] = 0x88, /* Layer A */
467 [1] = 0x8c, /* Layer B */
468 [2] = 0x90, /* Layer C */
469 };
470
Dan Carpenter82033bc2012-01-13 02:28:34 -0300471 if (layer >= ARRAY_SIZE(reg))
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200472 return -EINVAL;
473 rc = mb86a20s_writereg(state, 0x6d, reg[layer]);
474 if (rc < 0)
475 return rc;
476 rc = mb86a20s_readreg(state, 0x6e);
477 if (rc < 0)
478 return rc;
479 if (rc > 3)
480 return -EINVAL; /* Not used */
481 return rc;
482}
483
484static int mb86a20s_get_segment_count(struct mb86a20s_state *state,
485 unsigned layer)
486{
487 int rc, count;
488
489 static unsigned char reg[] = {
490 [0] = 0x89, /* Layer A */
491 [1] = 0x8d, /* Layer B */
492 [2] = 0x91, /* Layer C */
493 };
494
Dan Carpenter82033bc2012-01-13 02:28:34 -0300495 if (layer >= ARRAY_SIZE(reg))
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200496 return -EINVAL;
497 rc = mb86a20s_writereg(state, 0x6d, reg[layer]);
498 if (rc < 0)
499 return rc;
500 rc = mb86a20s_readreg(state, 0x6e);
501 if (rc < 0)
502 return rc;
503 count = (rc >> 4) & 0x0f;
504
505 return count;
506}
507
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300508static void mb86a20s_reset_frontend_cache(struct dvb_frontend *fe)
509{
510 struct dtv_frontend_properties *c = &fe->dtv_property_cache;
511
512 /* Fixed parameters */
513 c->delivery_system = SYS_ISDBT;
514 c->bandwidth_hz = 6000000;
515
516 /* Initialize values that will be later autodetected */
517 c->isdbt_layer_enabled = 0;
518 c->transmission_mode = TRANSMISSION_MODE_AUTO;
519 c->guard_interval = GUARD_INTERVAL_AUTO;
520 c->isdbt_sb_mode = 0;
521 c->isdbt_sb_segment_count = 0;
522}
523
Mauro Carvalho Chehab7c61d802011-12-30 11:30:21 -0300524static int mb86a20s_get_frontend(struct dvb_frontend *fe)
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300525{
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200526 struct mb86a20s_state *state = fe->demodulator_priv;
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300527 struct dtv_frontend_properties *c = &fe->dtv_property_cache;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200528 int i, rc;
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300529
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300530 /* Reset frontend cache to default values */
531 mb86a20s_reset_frontend_cache(fe);
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200532
533 if (fe->ops.i2c_gate_ctrl)
534 fe->ops.i2c_gate_ctrl(fe, 0);
535
536 /* Check for partial reception */
537 rc = mb86a20s_writereg(state, 0x6d, 0x85);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300538 if (rc < 0)
539 return rc;
540 rc = mb86a20s_readreg(state, 0x6e);
541 if (rc < 0)
542 return rc;
543 c->isdbt_partial_reception = (rc & 0x10) ? 1 : 0;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200544
545 /* Get per-layer data */
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300546
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200547 for (i = 0; i < 3; i++) {
548 rc = mb86a20s_get_segment_count(state, i);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300549 if (rc < 0)
550 goto error;
551 if (rc >= 0 && rc < 14)
552 c->layer[i].segment_count = rc;
553 else {
554 c->layer[i].segment_count = 0;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200555 continue;
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300556 }
557 c->isdbt_layer_enabled |= 1 << i;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200558 rc = mb86a20s_get_modulation(state, i);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300559 if (rc < 0)
560 goto error;
561 c->layer[i].modulation = rc;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200562 rc = mb86a20s_get_fec(state, i);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300563 if (rc < 0)
564 goto error;
565 c->layer[i].fec = rc;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200566 rc = mb86a20s_get_interleaving(state, i);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300567 if (rc < 0)
568 goto error;
569 c->layer[i].interleaving = rc;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200570 }
571
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200572 rc = mb86a20s_writereg(state, 0x6d, 0x84);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300573 if (rc < 0)
574 return rc;
575 if ((rc & 0x60) == 0x20) {
576 c->isdbt_sb_mode = 1;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200577 /* At least, one segment should exist */
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300578 if (!c->isdbt_sb_segment_count)
579 c->isdbt_sb_segment_count = 1;
580 }
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200581
582 /* Get transmission mode and guard interval */
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200583 rc = mb86a20s_readreg(state, 0x07);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300584 if (rc < 0)
585 return rc;
586 if ((rc & 0x60) == 0x20) {
587 switch (rc & 0x0c >> 2) {
588 case 0:
589 c->transmission_mode = TRANSMISSION_MODE_2K;
590 break;
591 case 1:
592 c->transmission_mode = TRANSMISSION_MODE_4K;
593 break;
594 case 2:
595 c->transmission_mode = TRANSMISSION_MODE_8K;
596 break;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200597 }
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300598 }
599 if (!(rc & 0x10)) {
600 switch (rc & 0x3) {
601 case 0:
602 c->guard_interval = GUARD_INTERVAL_1_4;
603 break;
604 case 1:
605 c->guard_interval = GUARD_INTERVAL_1_8;
606 break;
607 case 2:
608 c->guard_interval = GUARD_INTERVAL_1_16;
609 break;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200610 }
611 }
612
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300613error:
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200614 if (fe->ops.i2c_gate_ctrl)
615 fe->ops.i2c_gate_ctrl(fe, 1);
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300616
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300617 return rc;
618
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300619}
620
621static int mb86a20s_tune(struct dvb_frontend *fe,
Mauro Carvalho Chehab7e072222011-12-26 17:48:33 -0300622 bool re_tune,
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300623 unsigned int mode_flags,
624 unsigned int *delay,
625 fe_status_t *status)
626{
627 int rc = 0;
628
629 dprintk("\n");
630
Mauro Carvalho Chehab7e072222011-12-26 17:48:33 -0300631 if (re_tune)
Mauro Carvalho Chehab2d76e22b2011-12-26 12:11:51 -0300632 rc = mb86a20s_set_frontend(fe);
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300633
634 if (!(mode_flags & FE_TUNE_MODE_ONESHOT))
635 mb86a20s_read_status(fe, status);
636
637 return rc;
638}
639
640static void mb86a20s_release(struct dvb_frontend *fe)
641{
642 struct mb86a20s_state *state = fe->demodulator_priv;
643
644 dprintk("\n");
645
646 kfree(state);
647}
648
649static struct dvb_frontend_ops mb86a20s_ops;
650
651struct dvb_frontend *mb86a20s_attach(const struct mb86a20s_config *config,
652 struct i2c_adapter *i2c)
653{
654 u8 rev;
655
656 /* allocate memory for the internal state */
657 struct mb86a20s_state *state =
658 kzalloc(sizeof(struct mb86a20s_state), GFP_KERNEL);
659
660 dprintk("\n");
661 if (state == NULL) {
662 rc("Unable to kzalloc\n");
663 goto error;
664 }
665
666 /* setup the state */
667 state->config = config;
668 state->i2c = i2c;
669
670 /* create dvb_frontend */
671 memcpy(&state->frontend.ops, &mb86a20s_ops,
672 sizeof(struct dvb_frontend_ops));
673 state->frontend.demodulator_priv = state;
674
675 /* Check if it is a mb86a20s frontend */
676 rev = mb86a20s_readreg(state, 0);
677
678 if (rev == 0x13) {
679 printk(KERN_INFO "Detected a Fujitsu mb86a20s frontend\n");
680 } else {
681 printk(KERN_ERR "Frontend revision %d is unknown - aborting.\n",
682 rev);
683 goto error;
684 }
685
686 return &state->frontend;
687
688error:
689 kfree(state);
690 return NULL;
691}
692EXPORT_SYMBOL(mb86a20s_attach);
693
694static struct dvb_frontend_ops mb86a20s_ops = {
Mauro Carvalho Chehab2d76e22b2011-12-26 12:11:51 -0300695 .delsys = { SYS_ISDBT },
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300696 /* Use dib8000 values per default */
697 .info = {
698 .name = "Fujitsu mb86A20s",
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300699 .caps = FE_CAN_INVERSION_AUTO | FE_CAN_RECOVER |
700 FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
701 FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
702 FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 |
703 FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_QAM_AUTO |
704 FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
705 /* Actually, those values depend on the used tuner */
706 .frequency_min = 45000000,
707 .frequency_max = 864000000,
708 .frequency_stepsize = 62500,
709 },
710
711 .release = mb86a20s_release,
712
713 .init = mb86a20s_initfe,
Mauro Carvalho Chehab2d76e22b2011-12-26 12:11:51 -0300714 .set_frontend = mb86a20s_set_frontend,
715 .get_frontend = mb86a20s_get_frontend,
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300716 .read_status = mb86a20s_read_status,
717 .read_signal_strength = mb86a20s_read_signal_strength,
718 .tune = mb86a20s_tune,
719};
720
721MODULE_DESCRIPTION("DVB Frontend module for Fujitsu mb86A20s hardware");
722MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>");
723MODULE_LICENSE("GPL");