blob: 3c8587e38a05b20bb5a7bcde654af7a01cb87721 [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 Chehabfd537442013-01-14 10:16:07 -0300265err:
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300266 if (fe->ops.i2c_gate_ctrl)
267 fe->ops.i2c_gate_ctrl(fe, 1);
268
Mauro Carvalho Chehabc736a5f2011-01-14 11:10:05 -0300269 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 Chehabfd537442013-01-14 10:16:07 -0300366 /*
367 * Gate should already be opened, but it doesn't hurt to
368 * double-check
369 */
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300370 if (fe->ops.i2c_gate_ctrl)
371 fe->ops.i2c_gate_ctrl(fe, 1);
Mauro Carvalho Chehabc736a5f2011-01-14 11:10:05 -0300372 dprintk("Calling tuner set parameters\n");
Mauro Carvalho Chehab14d24d12011-12-24 12:24:33 -0300373 fe->ops.tuner_ops.set_params(fe);
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300374
Mauro Carvalho Chehabc736a5f2011-01-14 11:10:05 -0300375 /*
376 * Make it more reliable: if, for some reason, the initial
377 * device initialization doesn't happen, initialize it when
378 * a SBTVD parameters are adjusted.
379 *
380 * Unfortunately, due to a hard to track bug at tda829x/tda18271,
381 * the agc callback logic is not called during DVB attach time,
382 * causing mb86a20s to not be initialized with Kworld SBTVD.
383 * So, this hack is needed, in order to make Kworld SBTVD to work.
384 */
385 if (state->need_init)
386 mb86a20s_initfe(fe);
387
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300388 if (fe->ops.i2c_gate_ctrl)
389 fe->ops.i2c_gate_ctrl(fe, 0);
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300390 rc = mb86a20s_writeregdata(state, mb86a20s_reset_reception);
Mauro Carvalho Chehab68541cd2010-10-03 05:27:59 -0300391 if (fe->ops.i2c_gate_ctrl)
392 fe->ops.i2c_gate_ctrl(fe, 1);
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300393
394 return rc;
395}
396
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200397static int mb86a20s_get_modulation(struct mb86a20s_state *state,
398 unsigned layer)
399{
400 int rc;
401 static unsigned char reg[] = {
402 [0] = 0x86, /* Layer A */
403 [1] = 0x8a, /* Layer B */
404 [2] = 0x8e, /* Layer C */
405 };
406
Dan Carpenter82033bc2012-01-13 02:28:34 -0300407 if (layer >= ARRAY_SIZE(reg))
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200408 return -EINVAL;
409 rc = mb86a20s_writereg(state, 0x6d, reg[layer]);
410 if (rc < 0)
411 return rc;
412 rc = mb86a20s_readreg(state, 0x6e);
413 if (rc < 0)
414 return rc;
415 switch ((rc & 0x70) >> 4) {
416 case 0:
417 return DQPSK;
418 case 1:
419 return QPSK;
420 case 2:
421 return QAM_16;
422 case 3:
423 return QAM_64;
424 default:
425 return QAM_AUTO;
426 }
427}
428
429static int mb86a20s_get_fec(struct mb86a20s_state *state,
430 unsigned layer)
431{
432 int rc;
433
434 static unsigned char reg[] = {
435 [0] = 0x87, /* Layer A */
436 [1] = 0x8b, /* Layer B */
437 [2] = 0x8f, /* Layer C */
438 };
439
Dan Carpenter82033bc2012-01-13 02:28:34 -0300440 if (layer >= ARRAY_SIZE(reg))
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200441 return -EINVAL;
442 rc = mb86a20s_writereg(state, 0x6d, reg[layer]);
443 if (rc < 0)
444 return rc;
445 rc = mb86a20s_readreg(state, 0x6e);
446 if (rc < 0)
447 return rc;
448 switch (rc) {
449 case 0:
450 return FEC_1_2;
451 case 1:
452 return FEC_2_3;
453 case 2:
454 return FEC_3_4;
455 case 3:
456 return FEC_5_6;
457 case 4:
458 return FEC_7_8;
459 default:
460 return FEC_AUTO;
461 }
462}
463
464static int mb86a20s_get_interleaving(struct mb86a20s_state *state,
465 unsigned layer)
466{
467 int rc;
468
469 static unsigned char reg[] = {
470 [0] = 0x88, /* Layer A */
471 [1] = 0x8c, /* Layer B */
472 [2] = 0x90, /* Layer C */
473 };
474
Dan Carpenter82033bc2012-01-13 02:28:34 -0300475 if (layer >= ARRAY_SIZE(reg))
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200476 return -EINVAL;
477 rc = mb86a20s_writereg(state, 0x6d, reg[layer]);
478 if (rc < 0)
479 return rc;
480 rc = mb86a20s_readreg(state, 0x6e);
481 if (rc < 0)
482 return rc;
483 if (rc > 3)
484 return -EINVAL; /* Not used */
485 return rc;
486}
487
488static int mb86a20s_get_segment_count(struct mb86a20s_state *state,
489 unsigned layer)
490{
491 int rc, count;
492
493 static unsigned char reg[] = {
494 [0] = 0x89, /* Layer A */
495 [1] = 0x8d, /* Layer B */
496 [2] = 0x91, /* Layer C */
497 };
498
Dan Carpenter82033bc2012-01-13 02:28:34 -0300499 if (layer >= ARRAY_SIZE(reg))
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200500 return -EINVAL;
501 rc = mb86a20s_writereg(state, 0x6d, reg[layer]);
502 if (rc < 0)
503 return rc;
504 rc = mb86a20s_readreg(state, 0x6e);
505 if (rc < 0)
506 return rc;
507 count = (rc >> 4) & 0x0f;
508
509 return count;
510}
511
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300512static void mb86a20s_reset_frontend_cache(struct dvb_frontend *fe)
513{
514 struct dtv_frontend_properties *c = &fe->dtv_property_cache;
515
516 /* Fixed parameters */
517 c->delivery_system = SYS_ISDBT;
518 c->bandwidth_hz = 6000000;
519
520 /* Initialize values that will be later autodetected */
521 c->isdbt_layer_enabled = 0;
522 c->transmission_mode = TRANSMISSION_MODE_AUTO;
523 c->guard_interval = GUARD_INTERVAL_AUTO;
524 c->isdbt_sb_mode = 0;
525 c->isdbt_sb_segment_count = 0;
526}
527
Mauro Carvalho Chehab7c61d802011-12-30 11:30:21 -0300528static int mb86a20s_get_frontend(struct dvb_frontend *fe)
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300529{
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200530 struct mb86a20s_state *state = fe->demodulator_priv;
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300531 struct dtv_frontend_properties *c = &fe->dtv_property_cache;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200532 int i, rc;
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300533
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300534 /* Reset frontend cache to default values */
535 mb86a20s_reset_frontend_cache(fe);
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200536
537 if (fe->ops.i2c_gate_ctrl)
538 fe->ops.i2c_gate_ctrl(fe, 0);
539
540 /* Check for partial reception */
541 rc = mb86a20s_writereg(state, 0x6d, 0x85);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300542 if (rc < 0)
543 return rc;
544 rc = mb86a20s_readreg(state, 0x6e);
545 if (rc < 0)
546 return rc;
547 c->isdbt_partial_reception = (rc & 0x10) ? 1 : 0;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200548
549 /* Get per-layer data */
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300550
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200551 for (i = 0; i < 3; i++) {
552 rc = mb86a20s_get_segment_count(state, i);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300553 if (rc < 0)
554 goto error;
555 if (rc >= 0 && rc < 14)
556 c->layer[i].segment_count = rc;
557 else {
558 c->layer[i].segment_count = 0;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200559 continue;
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300560 }
561 c->isdbt_layer_enabled |= 1 << i;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200562 rc = mb86a20s_get_modulation(state, i);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300563 if (rc < 0)
564 goto error;
565 c->layer[i].modulation = rc;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200566 rc = mb86a20s_get_fec(state, i);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300567 if (rc < 0)
568 goto error;
569 c->layer[i].fec = rc;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200570 rc = mb86a20s_get_interleaving(state, i);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300571 if (rc < 0)
572 goto error;
573 c->layer[i].interleaving = rc;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200574 }
575
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200576 rc = mb86a20s_writereg(state, 0x6d, 0x84);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300577 if (rc < 0)
578 return rc;
579 if ((rc & 0x60) == 0x20) {
580 c->isdbt_sb_mode = 1;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200581 /* At least, one segment should exist */
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300582 if (!c->isdbt_sb_segment_count)
583 c->isdbt_sb_segment_count = 1;
584 }
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200585
586 /* Get transmission mode and guard interval */
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200587 rc = mb86a20s_readreg(state, 0x07);
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300588 if (rc < 0)
589 return rc;
590 if ((rc & 0x60) == 0x20) {
591 switch (rc & 0x0c >> 2) {
592 case 0:
593 c->transmission_mode = TRANSMISSION_MODE_2K;
594 break;
595 case 1:
596 c->transmission_mode = TRANSMISSION_MODE_4K;
597 break;
598 case 2:
599 c->transmission_mode = TRANSMISSION_MODE_8K;
600 break;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200601 }
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300602 }
603 if (!(rc & 0x10)) {
604 switch (rc & 0x3) {
605 case 0:
606 c->guard_interval = GUARD_INTERVAL_1_4;
607 break;
608 case 1:
609 c->guard_interval = GUARD_INTERVAL_1_8;
610 break;
611 case 2:
612 c->guard_interval = GUARD_INTERVAL_1_16;
613 break;
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200614 }
615 }
616
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300617error:
Mauro Carvalho Chehab959a1192012-01-09 18:16:36 -0200618 if (fe->ops.i2c_gate_ctrl)
619 fe->ops.i2c_gate_ctrl(fe, 1);
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300620
Mauro Carvalho Chehaba77cfca2013-01-14 09:26:09 -0300621 return rc;
622
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300623}
624
625static int mb86a20s_tune(struct dvb_frontend *fe,
Mauro Carvalho Chehab7e072222011-12-26 17:48:33 -0300626 bool re_tune,
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300627 unsigned int mode_flags,
628 unsigned int *delay,
629 fe_status_t *status)
630{
631 int rc = 0;
632
633 dprintk("\n");
634
Mauro Carvalho Chehab7e072222011-12-26 17:48:33 -0300635 if (re_tune)
Mauro Carvalho Chehab2d76e22b2011-12-26 12:11:51 -0300636 rc = mb86a20s_set_frontend(fe);
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300637
638 if (!(mode_flags & FE_TUNE_MODE_ONESHOT))
639 mb86a20s_read_status(fe, status);
640
641 return rc;
642}
643
644static void mb86a20s_release(struct dvb_frontend *fe)
645{
646 struct mb86a20s_state *state = fe->demodulator_priv;
647
648 dprintk("\n");
649
650 kfree(state);
651}
652
653static struct dvb_frontend_ops mb86a20s_ops;
654
655struct dvb_frontend *mb86a20s_attach(const struct mb86a20s_config *config,
656 struct i2c_adapter *i2c)
657{
658 u8 rev;
659
660 /* allocate memory for the internal state */
661 struct mb86a20s_state *state =
662 kzalloc(sizeof(struct mb86a20s_state), GFP_KERNEL);
663
664 dprintk("\n");
665 if (state == NULL) {
666 rc("Unable to kzalloc\n");
667 goto error;
668 }
669
670 /* setup the state */
671 state->config = config;
672 state->i2c = i2c;
673
674 /* create dvb_frontend */
675 memcpy(&state->frontend.ops, &mb86a20s_ops,
676 sizeof(struct dvb_frontend_ops));
677 state->frontend.demodulator_priv = state;
678
679 /* Check if it is a mb86a20s frontend */
680 rev = mb86a20s_readreg(state, 0);
681
682 if (rev == 0x13) {
683 printk(KERN_INFO "Detected a Fujitsu mb86a20s frontend\n");
684 } else {
685 printk(KERN_ERR "Frontend revision %d is unknown - aborting.\n",
686 rev);
687 goto error;
688 }
689
690 return &state->frontend;
691
692error:
693 kfree(state);
694 return NULL;
695}
696EXPORT_SYMBOL(mb86a20s_attach);
697
698static struct dvb_frontend_ops mb86a20s_ops = {
Mauro Carvalho Chehab2d76e22b2011-12-26 12:11:51 -0300699 .delsys = { SYS_ISDBT },
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300700 /* Use dib8000 values per default */
701 .info = {
702 .name = "Fujitsu mb86A20s",
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300703 .caps = FE_CAN_INVERSION_AUTO | FE_CAN_RECOVER |
704 FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
705 FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
706 FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 |
707 FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_QAM_AUTO |
708 FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
709 /* Actually, those values depend on the used tuner */
710 .frequency_min = 45000000,
711 .frequency_max = 864000000,
712 .frequency_stepsize = 62500,
713 },
714
715 .release = mb86a20s_release,
716
717 .init = mb86a20s_initfe,
Mauro Carvalho Chehab2d76e22b2011-12-26 12:11:51 -0300718 .set_frontend = mb86a20s_set_frontend,
719 .get_frontend = mb86a20s_get_frontend,
Mauro Carvalho Chehabb9ede792010-09-27 20:52:43 -0300720 .read_status = mb86a20s_read_status,
721 .read_signal_strength = mb86a20s_read_signal_strength,
722 .tune = mb86a20s_tune,
723};
724
725MODULE_DESCRIPTION("DVB Frontend module for Fujitsu mb86A20s hardware");
726MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>");
727MODULE_LICENSE("GPL");