Commit | Line | Data |
---|---|---|
ba92ae0f | 1 | /* |
17ad09f1 | 2 | * Silicon Labs Si2146/2147/2148/2157/2158 silicon tuner driver |
ba92ae0f AP |
3 | * |
4 | * Copyright (C) 2014 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 | ||
930a8730 AP |
17 | #include "si2157_priv.h" |
18 | ||
1b92373f OS |
19 | static const struct dvb_tuner_ops si2157_ops; |
20 | ||
930a8730 AP |
21 | /* execute firmware command */ |
22 | static int si2157_cmd_execute(struct si2157 *s, struct si2157_cmd *cmd) | |
23 | { | |
24 | int ret; | |
930a8730 AP |
25 | unsigned long timeout; |
26 | ||
27 | mutex_lock(&s->i2c_mutex); | |
28 | ||
e6b4380f | 29 | if (cmd->wlen) { |
930a8730 | 30 | /* write cmd and args for firmware */ |
e6b4380f | 31 | ret = i2c_master_send(s->client, cmd->args, cmd->wlen); |
930a8730 AP |
32 | if (ret < 0) { |
33 | goto err_mutex_unlock; | |
e6b4380f | 34 | } else if (ret != cmd->wlen) { |
930a8730 AP |
35 | ret = -EREMOTEIO; |
36 | goto err_mutex_unlock; | |
37 | } | |
38 | } | |
39 | ||
e6b4380f AP |
40 | if (cmd->rlen) { |
41 | /* wait cmd execution terminate */ | |
42 | #define TIMEOUT 80 | |
43 | timeout = jiffies + msecs_to_jiffies(TIMEOUT); | |
44 | while (!time_after(jiffies, timeout)) { | |
45 | ret = i2c_master_recv(s->client, cmd->args, cmd->rlen); | |
46 | if (ret < 0) { | |
47 | goto err_mutex_unlock; | |
48 | } else if (ret != cmd->rlen) { | |
49 | ret = -EREMOTEIO; | |
50 | goto err_mutex_unlock; | |
51 | } | |
52 | ||
53 | /* firmware ready? */ | |
54 | if ((cmd->args[0] >> 7) & 0x01) | |
55 | break; | |
930a8730 AP |
56 | } |
57 | ||
67d0113a | 58 | dev_dbg(&s->client->dev, "cmd execution took %d ms\n", |
e6b4380f AP |
59 | jiffies_to_msecs(jiffies) - |
60 | (jiffies_to_msecs(timeout) - TIMEOUT)); | |
930a8730 | 61 | |
e6b4380f AP |
62 | if (!((cmd->args[0] >> 7) & 0x01)) { |
63 | ret = -ETIMEDOUT; | |
64 | goto err_mutex_unlock; | |
65 | } | |
930a8730 AP |
66 | } |
67 | ||
e6b4380f AP |
68 | ret = 0; |
69 | ||
930a8730 AP |
70 | err_mutex_unlock: |
71 | mutex_unlock(&s->i2c_mutex); | |
72 | if (ret) | |
73 | goto err; | |
74 | ||
75 | return 0; | |
76 | err: | |
67d0113a | 77 | dev_dbg(&s->client->dev, "failed=%d\n", ret); |
930a8730 AP |
78 | return ret; |
79 | } | |
80 | ||
81 | static int si2157_init(struct dvb_frontend *fe) | |
82 | { | |
83 | struct si2157 *s = fe->tuner_priv; | |
7d6bc608 | 84 | int ret, len, remaining; |
b154121c | 85 | struct si2157_cmd cmd; |
1b92373f OS |
86 | const struct firmware *fw = NULL; |
87 | u8 *fw_file; | |
7d6bc608 | 88 | unsigned int chip_id; |
930a8730 | 89 | |
67d0113a | 90 | dev_dbg(&s->client->dev, "\n"); |
930a8730 | 91 | |
4cbf6ed9 OS |
92 | if (s->fw_loaded) |
93 | goto warm; | |
94 | ||
95 | /* power up */ | |
073f3849 OS |
96 | if (s->chiptype == SI2157_CHIPTYPE_SI2146) { |
97 | memcpy(cmd.args, "\xc0\x05\x01\x00\x00\x0b\x00\x00\x01", 9); | |
98 | cmd.wlen = 9; | |
99 | } else { | |
100 | memcpy(cmd.args, "\xc0\x00\x0c\x00\x00\x01\x01\x01\x01\x01\x01\x02\x00\x00\x01", 15); | |
101 | cmd.wlen = 15; | |
102 | } | |
b154121c OS |
103 | cmd.rlen = 1; |
104 | ret = si2157_cmd_execute(s, &cmd); | |
105 | if (ret) | |
106 | goto err; | |
107 | ||
108 | /* query chip revision */ | |
109 | memcpy(cmd.args, "\x02", 1); | |
110 | cmd.wlen = 1; | |
111 | cmd.rlen = 13; | |
112 | ret = si2157_cmd_execute(s, &cmd); | |
113 | if (ret) | |
114 | goto err; | |
115 | ||
7d6bc608 AP |
116 | chip_id = cmd.args[1] << 24 | cmd.args[2] << 16 | cmd.args[3] << 8 | |
117 | cmd.args[4] << 0; | |
118 | ||
119 | #define SI2158_A20 ('A' << 24 | 58 << 16 | '2' << 8 | '0' << 0) | |
17ad09f1 | 120 | #define SI2148_A20 ('A' << 24 | 48 << 16 | '2' << 8 | '0' << 0) |
7d6bc608 | 121 | #define SI2157_A30 ('A' << 24 | 57 << 16 | '3' << 8 | '0' << 0) |
d87a5058 | 122 | #define SI2147_A30 ('A' << 24 | 47 << 16 | '3' << 8 | '0' << 0) |
073f3849 | 123 | #define SI2146_A10 ('A' << 24 | 46 << 16 | '1' << 8 | '0' << 0) |
7d6bc608 AP |
124 | |
125 | switch (chip_id) { | |
126 | case SI2158_A20: | |
17ad09f1 | 127 | case SI2148_A20: |
7d6bc608 AP |
128 | fw_file = SI2158_A20_FIRMWARE; |
129 | break; | |
130 | case SI2157_A30: | |
d87a5058 | 131 | case SI2147_A30: |
073f3849 | 132 | case SI2146_A10: |
7d6bc608 | 133 | goto skip_fw_download; |
7d6bc608 AP |
134 | default: |
135 | dev_err(&s->client->dev, | |
67d0113a OS |
136 | "unknown chip version Si21%d-%c%c%c\n", |
137 | cmd.args[2], cmd.args[1], | |
7d6bc608 AP |
138 | cmd.args[3], cmd.args[4]); |
139 | ret = -EINVAL; | |
140 | goto err; | |
141 | } | |
1b92373f | 142 | |
7d6bc608 | 143 | /* cold state - try to download firmware */ |
67d0113a OS |
144 | dev_info(&s->client->dev, "found a '%s' in cold state\n", |
145 | si2157_ops.info.name); | |
1b92373f | 146 | |
7d6bc608 AP |
147 | /* request the firmware, this will block and timeout */ |
148 | ret = request_firmware(&fw, fw_file, &s->client->dev); | |
149 | if (ret) { | |
67d0113a OS |
150 | dev_err(&s->client->dev, "firmware file '%s' not found\n", |
151 | fw_file); | |
7d6bc608 AP |
152 | goto err; |
153 | } | |
1b92373f | 154 | |
7d6bc608 AP |
155 | /* firmware should be n chunks of 17 bytes */ |
156 | if (fw->size % 17 != 0) { | |
67d0113a OS |
157 | dev_err(&s->client->dev, "firmware file '%s' is invalid\n", |
158 | fw_file); | |
7d6bc608 AP |
159 | ret = -EINVAL; |
160 | goto err; | |
161 | } | |
1b92373f | 162 | |
67d0113a OS |
163 | dev_info(&s->client->dev, "downloading firmware from file '%s'\n", |
164 | fw_file); | |
1b92373f | 165 | |
7d6bc608 AP |
166 | for (remaining = fw->size; remaining > 0; remaining -= 17) { |
167 | len = fw->data[fw->size - remaining]; | |
168 | memcpy(cmd.args, &fw->data[(fw->size - remaining) + 1], len); | |
169 | cmd.wlen = len; | |
170 | cmd.rlen = 1; | |
171 | ret = si2157_cmd_execute(s, &cmd); | |
172 | if (ret) { | |
173 | dev_err(&s->client->dev, | |
67d0113a OS |
174 | "firmware download failed=%d\n", |
175 | ret); | |
7d6bc608 | 176 | goto err; |
1b92373f | 177 | } |
1b92373f OS |
178 | } |
179 | ||
7d6bc608 AP |
180 | release_firmware(fw); |
181 | fw = NULL; | |
182 | ||
183 | skip_fw_download: | |
b154121c OS |
184 | /* reboot the tuner with new firmware? */ |
185 | memcpy(cmd.args, "\x01\x01", 2); | |
186 | cmd.wlen = 2; | |
187 | cmd.rlen = 1; | |
188 | ret = si2157_cmd_execute(s, &cmd); | |
189 | if (ret) | |
190 | goto err; | |
191 | ||
4cbf6ed9 | 192 | s->fw_loaded = true; |
930a8730 | 193 | |
4cbf6ed9 OS |
194 | warm: |
195 | s->active = true; | |
930a8730 | 196 | return 0; |
4cbf6ed9 | 197 | |
b154121c | 198 | err: |
7d6bc608 AP |
199 | if (fw) |
200 | release_firmware(fw); | |
201 | ||
67d0113a | 202 | dev_dbg(&s->client->dev, "failed=%d\n", ret); |
b154121c | 203 | return ret; |
930a8730 AP |
204 | } |
205 | ||
206 | static int si2157_sleep(struct dvb_frontend *fe) | |
207 | { | |
208 | struct si2157 *s = fe->tuner_priv; | |
a83d7d17 AP |
209 | int ret; |
210 | struct si2157_cmd cmd; | |
930a8730 | 211 | |
67d0113a | 212 | dev_dbg(&s->client->dev, "\n"); |
930a8730 AP |
213 | |
214 | s->active = false; | |
215 | ||
0e38233d OS |
216 | /* standby */ |
217 | memcpy(cmd.args, "\x16\x00", 2); | |
218 | cmd.wlen = 2; | |
219 | cmd.rlen = 1; | |
a83d7d17 AP |
220 | ret = si2157_cmd_execute(s, &cmd); |
221 | if (ret) | |
222 | goto err; | |
223 | ||
930a8730 | 224 | return 0; |
a83d7d17 | 225 | err: |
67d0113a | 226 | dev_dbg(&s->client->dev, "failed=%d\n", ret); |
a83d7d17 | 227 | return ret; |
930a8730 AP |
228 | } |
229 | ||
230 | static int si2157_set_params(struct dvb_frontend *fe) | |
231 | { | |
232 | struct si2157 *s = fe->tuner_priv; | |
233 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; | |
234 | int ret; | |
235 | struct si2157_cmd cmd; | |
a1dad50d | 236 | u8 bandwidth, delivery_system; |
930a8730 AP |
237 | |
238 | dev_dbg(&s->client->dev, | |
67d0113a OS |
239 | "delivery_system=%d frequency=%u bandwidth_hz=%u\n", |
240 | c->delivery_system, c->frequency, | |
930a8730 AP |
241 | c->bandwidth_hz); |
242 | ||
243 | if (!s->active) { | |
244 | ret = -EAGAIN; | |
245 | goto err; | |
246 | } | |
247 | ||
a1dad50d OS |
248 | if (c->bandwidth_hz <= 6000000) |
249 | bandwidth = 0x06; | |
250 | else if (c->bandwidth_hz <= 7000000) | |
251 | bandwidth = 0x07; | |
252 | else if (c->bandwidth_hz <= 8000000) | |
253 | bandwidth = 0x08; | |
254 | else | |
255 | bandwidth = 0x0f; | |
256 | ||
257 | switch (c->delivery_system) { | |
5cd62db7 OS |
258 | case SYS_ATSC: |
259 | delivery_system = 0x00; | |
260 | break; | |
0d1165fc OS |
261 | case SYS_DVBC_ANNEX_B: |
262 | delivery_system = 0x10; | |
263 | break; | |
a1dad50d OS |
264 | case SYS_DVBT: |
265 | case SYS_DVBT2: /* it seems DVB-T and DVB-T2 both are 0x20 here */ | |
266 | delivery_system = 0x20; | |
267 | break; | |
268 | case SYS_DVBC_ANNEX_A: | |
269 | delivery_system = 0x30; | |
270 | break; | |
271 | default: | |
272 | ret = -EINVAL; | |
273 | goto err; | |
274 | } | |
275 | ||
276 | memcpy(cmd.args, "\x14\x00\x03\x07\x00\x00", 6); | |
277 | cmd.args[4] = delivery_system | bandwidth; | |
05024efe MS |
278 | if (s->inversion) |
279 | cmd.args[5] = 0x01; | |
a1dad50d | 280 | cmd.wlen = 6; |
d87a5058 OS |
281 | cmd.rlen = 4; |
282 | ret = si2157_cmd_execute(s, &cmd); | |
283 | if (ret) | |
284 | goto err; | |
285 | ||
073f3849 OS |
286 | if (s->chiptype == SI2157_CHIPTYPE_SI2146) |
287 | memcpy(cmd.args, "\x14\x00\x02\x07\x00\x01", 6); | |
288 | else | |
289 | memcpy(cmd.args, "\x14\x00\x02\x07\x01\x00", 6); | |
d87a5058 OS |
290 | cmd.wlen = 6; |
291 | cmd.rlen = 4; | |
a1dad50d OS |
292 | ret = si2157_cmd_execute(s, &cmd); |
293 | if (ret) | |
294 | goto err; | |
295 | ||
930a8730 | 296 | /* set frequency */ |
b154121c | 297 | memcpy(cmd.args, "\x41\x00\x00\x00\x00\x00\x00\x00", 8); |
930a8730 AP |
298 | cmd.args[4] = (c->frequency >> 0) & 0xff; |
299 | cmd.args[5] = (c->frequency >> 8) & 0xff; | |
300 | cmd.args[6] = (c->frequency >> 16) & 0xff; | |
301 | cmd.args[7] = (c->frequency >> 24) & 0xff; | |
e6b4380f AP |
302 | cmd.wlen = 8; |
303 | cmd.rlen = 1; | |
930a8730 AP |
304 | ret = si2157_cmd_execute(s, &cmd); |
305 | if (ret) | |
306 | goto err; | |
307 | ||
308 | return 0; | |
309 | err: | |
67d0113a | 310 | dev_dbg(&s->client->dev, "failed=%d\n", ret); |
930a8730 AP |
311 | return ret; |
312 | } | |
313 | ||
1140540d MS |
314 | static int si2157_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) |
315 | { | |
316 | *frequency = 5000000; /* default value of property 0x0706 */ | |
317 | return 0; | |
318 | } | |
319 | ||
6cc8a35d | 320 | static const struct dvb_tuner_ops si2157_ops = { |
930a8730 | 321 | .info = { |
17ad09f1 | 322 | .name = "Silicon Labs Si2146/2147/2148/2157/2158", |
ae4c8919 | 323 | .frequency_min = 110000000, |
930a8730 AP |
324 | .frequency_max = 862000000, |
325 | }, | |
326 | ||
327 | .init = si2157_init, | |
328 | .sleep = si2157_sleep, | |
329 | .set_params = si2157_set_params, | |
1140540d | 330 | .get_if_frequency = si2157_get_if_frequency, |
930a8730 AP |
331 | }; |
332 | ||
333 | static int si2157_probe(struct i2c_client *client, | |
334 | const struct i2c_device_id *id) | |
335 | { | |
336 | struct si2157_config *cfg = client->dev.platform_data; | |
337 | struct dvb_frontend *fe = cfg->fe; | |
338 | struct si2157 *s; | |
339 | struct si2157_cmd cmd; | |
340 | int ret; | |
341 | ||
342 | s = kzalloc(sizeof(struct si2157), GFP_KERNEL); | |
343 | if (!s) { | |
344 | ret = -ENOMEM; | |
67d0113a | 345 | dev_err(&client->dev, "kzalloc() failed\n"); |
930a8730 AP |
346 | goto err; |
347 | } | |
348 | ||
349 | s->client = client; | |
350 | s->fe = cfg->fe; | |
05024efe | 351 | s->inversion = cfg->inversion; |
4cbf6ed9 | 352 | s->fw_loaded = false; |
073f3849 | 353 | s->chiptype = (u8)id->driver_data; |
930a8730 AP |
354 | mutex_init(&s->i2c_mutex); |
355 | ||
356 | /* check if the tuner is there */ | |
e6b4380f AP |
357 | cmd.wlen = 0; |
358 | cmd.rlen = 1; | |
930a8730 AP |
359 | ret = si2157_cmd_execute(s, &cmd); |
360 | if (ret) | |
361 | goto err; | |
362 | ||
363 | fe->tuner_priv = s; | |
6cc8a35d | 364 | memcpy(&fe->ops.tuner_ops, &si2157_ops, |
930a8730 AP |
365 | sizeof(struct dvb_tuner_ops)); |
366 | ||
367 | i2c_set_clientdata(client, s); | |
368 | ||
369 | dev_info(&s->client->dev, | |
073f3849 OS |
370 | "Silicon Labs %s successfully attached\n", |
371 | s->chiptype == SI2157_CHIPTYPE_SI2146 ? | |
17ad09f1 | 372 | "Si2146" : "Si2147/2148/2157/2158"); |
073f3849 | 373 | |
930a8730 AP |
374 | return 0; |
375 | err: | |
67d0113a | 376 | dev_dbg(&client->dev, "failed=%d\n", ret); |
930a8730 AP |
377 | kfree(s); |
378 | ||
379 | return ret; | |
380 | } | |
381 | ||
382 | static int si2157_remove(struct i2c_client *client) | |
383 | { | |
384 | struct si2157 *s = i2c_get_clientdata(client); | |
385 | struct dvb_frontend *fe = s->fe; | |
386 | ||
67d0113a | 387 | dev_dbg(&client->dev, "\n"); |
930a8730 AP |
388 | |
389 | memset(&fe->ops.tuner_ops, 0, sizeof(struct dvb_tuner_ops)); | |
390 | fe->tuner_priv = NULL; | |
391 | kfree(s); | |
392 | ||
393 | return 0; | |
394 | } | |
395 | ||
396 | static const struct i2c_device_id si2157_id[] = { | |
397 | {"si2157", 0}, | |
073f3849 | 398 | {"si2146", 1}, |
930a8730 AP |
399 | {} |
400 | }; | |
401 | MODULE_DEVICE_TABLE(i2c, si2157_id); | |
402 | ||
403 | static struct i2c_driver si2157_driver = { | |
404 | .driver = { | |
405 | .owner = THIS_MODULE, | |
406 | .name = "si2157", | |
407 | }, | |
408 | .probe = si2157_probe, | |
409 | .remove = si2157_remove, | |
410 | .id_table = si2157_id, | |
411 | }; | |
412 | ||
413 | module_i2c_driver(si2157_driver); | |
414 | ||
17ad09f1 | 415 | MODULE_DESCRIPTION("Silicon Labs Si2146/2147/2148/2157/2158 silicon tuner driver"); |
930a8730 AP |
416 | MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); |
417 | MODULE_LICENSE("GPL"); | |
bac53a2c | 418 | MODULE_FIRMWARE(SI2158_A20_FIRMWARE); |