diff options
-rw-r--r-- | drivers/media/tuners/Kconfig | 7 | ||||
-rw-r--r-- | drivers/media/tuners/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/tuners/m88ts2022.c | 664 | ||||
-rw-r--r-- | drivers/media/tuners/m88ts2022.h | 72 | ||||
-rw-r--r-- | drivers/media/tuners/m88ts2022_priv.h | 38 |
5 files changed, 782 insertions, 0 deletions
diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig index 15665debc572..ba2e365296cf 100644 --- a/drivers/media/tuners/Kconfig +++ b/drivers/media/tuners/Kconfig | |||
@@ -215,6 +215,13 @@ config MEDIA_TUNER_FC2580 | |||
215 | help | 215 | help |
216 | FCI FC2580 silicon tuner driver. | 216 | FCI FC2580 silicon tuner driver. |
217 | 217 | ||
218 | config MEDIA_TUNER_M88TS2022 | ||
219 | tristate "Montage M88TS2022 silicon tuner" | ||
220 | depends on MEDIA_SUPPORT && I2C | ||
221 | default m if !MEDIA_SUBDRV_AUTOSELECT | ||
222 | help | ||
223 | Montage M88TS2022 silicon tuner driver. | ||
224 | |||
218 | config MEDIA_TUNER_TUA9001 | 225 | config MEDIA_TUNER_TUA9001 |
219 | tristate "Infineon TUA 9001 silicon tuner" | 226 | tristate "Infineon TUA 9001 silicon tuner" |
220 | depends on MEDIA_SUPPORT && I2C | 227 | depends on MEDIA_SUPPORT && I2C |
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile index 308f108eadba..efe82a904b12 100644 --- a/drivers/media/tuners/Makefile +++ b/drivers/media/tuners/Makefile | |||
@@ -31,6 +31,7 @@ obj-$(CONFIG_MEDIA_TUNER_TDA18212) += tda18212.o | |||
31 | obj-$(CONFIG_MEDIA_TUNER_E4000) += e4000.o | 31 | obj-$(CONFIG_MEDIA_TUNER_E4000) += e4000.o |
32 | obj-$(CONFIG_MEDIA_TUNER_FC2580) += fc2580.o | 32 | obj-$(CONFIG_MEDIA_TUNER_FC2580) += fc2580.o |
33 | obj-$(CONFIG_MEDIA_TUNER_TUA9001) += tua9001.o | 33 | obj-$(CONFIG_MEDIA_TUNER_TUA9001) += tua9001.o |
34 | obj-$(CONFIG_MEDIA_TUNER_M88TS2022) += m88ts2022.o | ||
34 | obj-$(CONFIG_MEDIA_TUNER_FC0011) += fc0011.o | 35 | obj-$(CONFIG_MEDIA_TUNER_FC0011) += fc0011.o |
35 | obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o | 36 | obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o |
36 | obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o | 37 | obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o |
diff --git a/drivers/media/tuners/m88ts2022.c b/drivers/media/tuners/m88ts2022.c new file mode 100644 index 000000000000..0625e36d0a92 --- /dev/null +++ b/drivers/media/tuners/m88ts2022.c | |||
@@ -0,0 +1,664 @@ | |||
1 | /* | ||
2 | * Montage M88TS2022 silicon tuner driver | ||
3 | * | ||
4 | * Copyright (C) 2013 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 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | * | ||
20 | * Some calculations are taken from existing TS2020 driver. | ||
21 | */ | ||
22 | |||
23 | #include "m88ts2022_priv.h" | ||
24 | |||
25 | /* write multiple registers */ | ||
26 | static int m88ts2022_wr_regs(struct m88ts2022_priv *priv, | ||
27 | u8 reg, const u8 *val, int len) | ||
28 | { | ||
29 | int ret; | ||
30 | u8 buf[1 + len]; | ||
31 | struct i2c_msg msg[1] = { | ||
32 | { | ||
33 | .addr = priv->cfg->i2c_addr, | ||
34 | .flags = 0, | ||
35 | .len = sizeof(buf), | ||
36 | .buf = buf, | ||
37 | } | ||
38 | }; | ||
39 | |||
40 | buf[0] = reg; | ||
41 | memcpy(&buf[1], val, len); | ||
42 | |||
43 | ret = i2c_transfer(priv->i2c, msg, 1); | ||
44 | if (ret == 1) { | ||
45 | ret = 0; | ||
46 | } else { | ||
47 | dev_warn(&priv->i2c->dev, | ||
48 | "%s: i2c wr failed=%d reg=%02x len=%d\n", | ||
49 | KBUILD_MODNAME, ret, reg, len); | ||
50 | ret = -EREMOTEIO; | ||
51 | } | ||
52 | |||
53 | return ret; | ||
54 | } | ||
55 | |||
56 | /* read multiple registers */ | ||
57 | static int m88ts2022_rd_regs(struct m88ts2022_priv *priv, u8 reg, | ||
58 | u8 *val, int len) | ||
59 | { | ||
60 | int ret; | ||
61 | u8 buf[len]; | ||
62 | struct i2c_msg msg[2] = { | ||
63 | { | ||
64 | .addr = priv->cfg->i2c_addr, | ||
65 | .flags = 0, | ||
66 | .len = 1, | ||
67 | .buf = ®, | ||
68 | }, { | ||
69 | .addr = priv->cfg->i2c_addr, | ||
70 | .flags = I2C_M_RD, | ||
71 | .len = sizeof(buf), | ||
72 | .buf = buf, | ||
73 | } | ||
74 | }; | ||
75 | |||
76 | ret = i2c_transfer(priv->i2c, msg, 2); | ||
77 | if (ret == 2) { | ||
78 | memcpy(val, buf, len); | ||
79 | ret = 0; | ||
80 | } else { | ||
81 | dev_warn(&priv->i2c->dev, | ||
82 | "%s: i2c rd failed=%d reg=%02x len=%d\n", | ||
83 | KBUILD_MODNAME, ret, reg, len); | ||
84 | ret = -EREMOTEIO; | ||
85 | } | ||
86 | |||
87 | return ret; | ||
88 | } | ||
89 | |||
90 | /* write single register */ | ||
91 | static int m88ts2022_wr_reg(struct m88ts2022_priv *priv, u8 reg, u8 val) | ||
92 | { | ||
93 | return m88ts2022_wr_regs(priv, reg, &val, 1); | ||
94 | } | ||
95 | |||
96 | /* read single register */ | ||
97 | static int m88ts2022_rd_reg(struct m88ts2022_priv *priv, u8 reg, u8 *val) | ||
98 | { | ||
99 | return m88ts2022_rd_regs(priv, reg, val, 1); | ||
100 | } | ||
101 | |||
102 | /* write single register with mask */ | ||
103 | static int m88ts2022_wr_reg_mask(struct m88ts2022_priv *priv, | ||
104 | u8 reg, u8 val, u8 mask) | ||
105 | { | ||
106 | int ret; | ||
107 | u8 u8tmp; | ||
108 | |||
109 | /* no need for read if whole reg is written */ | ||
110 | if (mask != 0xff) { | ||
111 | ret = m88ts2022_rd_regs(priv, reg, &u8tmp, 1); | ||
112 | if (ret) | ||
113 | return ret; | ||
114 | |||
115 | val &= mask; | ||
116 | u8tmp &= ~mask; | ||
117 | val |= u8tmp; | ||
118 | } | ||
119 | |||
120 | return m88ts2022_wr_regs(priv, reg, &val, 1); | ||
121 | } | ||
122 | |||
123 | static int m88ts2022_cmd(struct dvb_frontend *fe, | ||
124 | int op, int sleep, u8 reg, u8 mask, u8 val, u8 *reg_val) | ||
125 | { | ||
126 | struct m88ts2022_priv *priv = fe->tuner_priv; | ||
127 | int ret, i; | ||
128 | u8 u8tmp; | ||
129 | struct m88ts2022_reg_val reg_vals[] = { | ||
130 | {0x51, 0x1f - op}, | ||
131 | {0x51, 0x1f}, | ||
132 | {0x50, 0x00 + op}, | ||
133 | {0x50, 0x00}, | ||
134 | }; | ||
135 | |||
136 | for (i = 0; i < 2; i++) { | ||
137 | dev_dbg(&priv->i2c->dev, | ||
138 | "%s: i=%d op=%02x reg=%02x mask=%02x val=%02x\n", | ||
139 | __func__, i, op, reg, mask, val); | ||
140 | |||
141 | for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { | ||
142 | ret = m88ts2022_wr_reg(priv, reg_vals[i].reg, | ||
143 | reg_vals[i].val); | ||
144 | if (ret) | ||
145 | goto err; | ||
146 | } | ||
147 | |||
148 | usleep_range(sleep * 1000, sleep * 10000); | ||
149 | |||
150 | ret = m88ts2022_rd_reg(priv, reg, &u8tmp); | ||
151 | if (ret) | ||
152 | goto err; | ||
153 | |||
154 | if ((u8tmp & mask) != val) | ||
155 | break; | ||
156 | } | ||
157 | |||
158 | if (reg_val) | ||
159 | *reg_val = u8tmp; | ||
160 | err: | ||
161 | return ret; | ||
162 | } | ||
163 | |||
164 | static int m88ts2022_set_params(struct dvb_frontend *fe) | ||
165 | { | ||
166 | struct m88ts2022_priv *priv = fe->tuner_priv; | ||
167 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; | ||
168 | int ret = 0, div; | ||
169 | u8 buf[3], u8tmp, cap_code, lpf_mxdiv, div_max, div_min; | ||
170 | u16 N_reg, N, K; | ||
171 | u32 lpf_gm, lpf_coeff, gdiv28, frequency_khz, frequency_offset; | ||
172 | u32 freq_3db; | ||
173 | dev_dbg(&priv->i2c->dev, | ||
174 | "%s: frequency=%d symbol_rate=%d rolloff=%d\n", | ||
175 | __func__, c->frequency, c->symbol_rate, c->rolloff); | ||
176 | |||
177 | if (c->symbol_rate < 5000000) | ||
178 | frequency_offset = 3000000; /* 3 MHz */ | ||
179 | else | ||
180 | frequency_offset = 0; | ||
181 | |||
182 | frequency_khz = c->frequency + (frequency_offset / 1000); | ||
183 | |||
184 | if (frequency_khz < 1103000) { | ||
185 | div = 2; | ||
186 | u8tmp = 0x1b; | ||
187 | } else { | ||
188 | div = 1; | ||
189 | u8tmp = 0x0b; | ||
190 | } | ||
191 | |||
192 | buf[0] = u8tmp; | ||
193 | buf[1] = 0x40; | ||
194 | ret = m88ts2022_wr_regs(priv, 0x10, buf, 2); | ||
195 | if (ret) | ||
196 | goto err; | ||
197 | |||
198 | K = DIV_ROUND_CLOSEST((priv->cfg->clock / 2), 1000000); | ||
199 | N = 1ul * frequency_khz * K * div * 2 / (priv->cfg->clock / 1000); | ||
200 | N += N % 2; | ||
201 | |||
202 | if (N < 4095) | ||
203 | N_reg = N - 1024; | ||
204 | else if (N < 6143) | ||
205 | N_reg = N + 1024; | ||
206 | else | ||
207 | N_reg = N + 3072; | ||
208 | |||
209 | buf[0] = (N_reg >> 8) & 0x3f; | ||
210 | buf[1] = (N_reg >> 0) & 0xff; | ||
211 | buf[2] = K - 8; | ||
212 | ret = m88ts2022_wr_regs(priv, 0x01, buf, 3); | ||
213 | if (ret) | ||
214 | goto err; | ||
215 | |||
216 | priv->frequency_khz = 1ul * N * (priv->cfg->clock / 1000) / K / div / 2; | ||
217 | |||
218 | dev_dbg(&priv->i2c->dev, | ||
219 | "%s: frequency=%d offset=%d K=%d N=%d div=%d\n", | ||
220 | __func__, priv->frequency_khz, | ||
221 | priv->frequency_khz - c->frequency, K, N, div); | ||
222 | |||
223 | ret = m88ts2022_cmd(fe, 0x10, 5, 0x15, 0x40, 0x00, NULL); | ||
224 | if (ret) | ||
225 | goto err; | ||
226 | |||
227 | ret = m88ts2022_rd_reg(priv, 0x14, &u8tmp); | ||
228 | if (ret) | ||
229 | goto err; | ||
230 | |||
231 | u8tmp &= 0x7f; | ||
232 | if (u8tmp < 64) { | ||
233 | ret = m88ts2022_wr_reg_mask(priv, 0x10, 0x80, 0x80); | ||
234 | if (ret) | ||
235 | goto err; | ||
236 | |||
237 | ret = m88ts2022_wr_reg(priv, 0x11, 0x6f); | ||
238 | if (ret) | ||
239 | goto err; | ||
240 | |||
241 | ret = m88ts2022_cmd(fe, 0x10, 5, 0x15, 0x40, 0x00, NULL); | ||
242 | if (ret) | ||
243 | goto err; | ||
244 | } | ||
245 | |||
246 | ret = m88ts2022_rd_reg(priv, 0x14, &u8tmp); | ||
247 | if (ret) | ||
248 | goto err; | ||
249 | |||
250 | u8tmp &= 0x1f; | ||
251 | if (u8tmp > 19) { | ||
252 | ret = m88ts2022_wr_reg_mask(priv, 0x10, 0x00, 0x02); | ||
253 | if (ret) | ||
254 | goto err; | ||
255 | } | ||
256 | |||
257 | ret = m88ts2022_cmd(fe, 0x08, 5, 0x3c, 0xff, 0x00, NULL); | ||
258 | if (ret) | ||
259 | goto err; | ||
260 | |||
261 | ret = m88ts2022_wr_reg(priv, 0x25, 0x00); | ||
262 | if (ret) | ||
263 | goto err; | ||
264 | |||
265 | ret = m88ts2022_wr_reg(priv, 0x27, 0x70); | ||
266 | if (ret) | ||
267 | goto err; | ||
268 | |||
269 | ret = m88ts2022_wr_reg(priv, 0x41, 0x09); | ||
270 | if (ret) | ||
271 | goto err; | ||
272 | |||
273 | ret = m88ts2022_wr_reg(priv, 0x08, 0x0b); | ||
274 | if (ret) | ||
275 | goto err; | ||
276 | |||
277 | gdiv28 = DIV_ROUND_CLOSEST(priv->cfg->clock / 1000000 * 1694, 1000); | ||
278 | |||
279 | ret = m88ts2022_wr_reg(priv, 0x04, gdiv28); | ||
280 | if (ret) | ||
281 | goto err; | ||
282 | |||
283 | ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); | ||
284 | if (ret) | ||
285 | goto err; | ||
286 | |||
287 | cap_code = u8tmp & 0x3f; | ||
288 | |||
289 | ret = m88ts2022_wr_reg(priv, 0x41, 0x0d); | ||
290 | if (ret) | ||
291 | goto err; | ||
292 | |||
293 | ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); | ||
294 | if (ret) | ||
295 | goto err; | ||
296 | |||
297 | u8tmp &= 0x3f; | ||
298 | cap_code = (cap_code + u8tmp) / 2; | ||
299 | gdiv28 = gdiv28 * 207 / (cap_code * 2 + 151); | ||
300 | div_max = gdiv28 * 135 / 100; | ||
301 | div_min = gdiv28 * 78 / 100; | ||
302 | if (div_max > 63) | ||
303 | div_max = 63; | ||
304 | |||
305 | freq_3db = 1ul * c->symbol_rate * 135 / 200 + 2000000; | ||
306 | freq_3db += frequency_offset; | ||
307 | if (freq_3db < 7000000) | ||
308 | freq_3db = 7000000; | ||
309 | if (freq_3db > 40000000) | ||
310 | freq_3db = 40000000; | ||
311 | |||
312 | lpf_coeff = 3200; | ||
313 | lpf_gm = DIV_ROUND_CLOSEST(freq_3db * gdiv28, lpf_coeff * | ||
314 | (priv->cfg->clock / 1000)); | ||
315 | if (lpf_gm > 23) | ||
316 | lpf_gm = 23; | ||
317 | if (lpf_gm < 1) | ||
318 | lpf_gm = 1; | ||
319 | |||
320 | lpf_mxdiv = DIV_ROUND_CLOSEST(lpf_gm * lpf_coeff * | ||
321 | (priv->cfg->clock / 1000), freq_3db); | ||
322 | |||
323 | if (lpf_mxdiv < div_min) { | ||
324 | lpf_gm++; | ||
325 | lpf_mxdiv = DIV_ROUND_CLOSEST(lpf_gm * lpf_coeff * | ||
326 | (priv->cfg->clock / 1000), freq_3db); | ||
327 | } | ||
328 | |||
329 | if (lpf_mxdiv > div_max) | ||
330 | lpf_mxdiv = div_max; | ||
331 | |||
332 | ret = m88ts2022_wr_reg(priv, 0x04, lpf_mxdiv); | ||
333 | if (ret) | ||
334 | goto err; | ||
335 | |||
336 | ret = m88ts2022_wr_reg(priv, 0x06, lpf_gm); | ||
337 | if (ret) | ||
338 | goto err; | ||
339 | |||
340 | ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); | ||
341 | if (ret) | ||
342 | goto err; | ||
343 | |||
344 | cap_code = u8tmp & 0x3f; | ||
345 | |||
346 | ret = m88ts2022_wr_reg(priv, 0x41, 0x09); | ||
347 | if (ret) | ||
348 | goto err; | ||
349 | |||
350 | ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp); | ||
351 | if (ret) | ||
352 | goto err; | ||
353 | |||
354 | u8tmp &= 0x3f; | ||
355 | cap_code = (cap_code + u8tmp) / 2; | ||
356 | |||
357 | u8tmp = cap_code | 0x80; | ||
358 | ret = m88ts2022_wr_reg(priv, 0x25, u8tmp); | ||
359 | if (ret) | ||
360 | goto err; | ||
361 | |||
362 | ret = m88ts2022_wr_reg(priv, 0x27, 0x30); | ||
363 | if (ret) | ||
364 | goto err; | ||
365 | |||
366 | ret = m88ts2022_wr_reg(priv, 0x08, 0x09); | ||
367 | if (ret) | ||
368 | goto err; | ||
369 | |||
370 | ret = m88ts2022_cmd(fe, 0x01, 20, 0x21, 0xff, 0x00, NULL); | ||
371 | if (ret) | ||
372 | goto err; | ||
373 | err: | ||
374 | if (ret) | ||
375 | dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); | ||
376 | |||
377 | return ret; | ||
378 | } | ||
379 | |||
380 | static int m88ts2022_init(struct dvb_frontend *fe) | ||
381 | { | ||
382 | struct m88ts2022_priv *priv = fe->tuner_priv; | ||
383 | int ret, i; | ||
384 | u8 u8tmp; | ||
385 | static const struct m88ts2022_reg_val reg_vals[] = { | ||
386 | {0x7d, 0x9d}, | ||
387 | {0x7c, 0x9a}, | ||
388 | {0x7a, 0x76}, | ||
389 | {0x3b, 0x01}, | ||
390 | {0x63, 0x88}, | ||
391 | {0x61, 0x85}, | ||
392 | {0x22, 0x30}, | ||
393 | {0x30, 0x40}, | ||
394 | {0x20, 0x23}, | ||
395 | {0x24, 0x02}, | ||
396 | {0x12, 0xa0}, | ||
397 | }; | ||
398 | dev_dbg(&priv->i2c->dev, "%s:\n", __func__); | ||
399 | |||
400 | ret = m88ts2022_wr_reg(priv, 0x00, 0x01); | ||
401 | if (ret) | ||
402 | goto err; | ||
403 | |||
404 | ret = m88ts2022_wr_reg(priv, 0x00, 0x03); | ||
405 | if (ret) | ||
406 | goto err; | ||
407 | |||
408 | switch (priv->cfg->clock_out) { | ||
409 | case M88TS2022_CLOCK_OUT_DISABLED: | ||
410 | u8tmp = 0x60; | ||
411 | break; | ||
412 | case M88TS2022_CLOCK_OUT_ENABLED: | ||
413 | u8tmp = 0x70; | ||
414 | ret = m88ts2022_wr_reg(priv, 0x05, priv->cfg->clock_out_div); | ||
415 | if (ret) | ||
416 | goto err; | ||
417 | break; | ||
418 | case M88TS2022_CLOCK_OUT_ENABLED_XTALOUT: | ||
419 | u8tmp = 0x6c; | ||
420 | break; | ||
421 | default: | ||
422 | goto err; | ||
423 | } | ||
424 | |||
425 | ret = m88ts2022_wr_reg(priv, 0x42, u8tmp); | ||
426 | if (ret) | ||
427 | goto err; | ||
428 | |||
429 | if (priv->cfg->loop_through) | ||
430 | u8tmp = 0xec; | ||
431 | else | ||
432 | u8tmp = 0x6c; | ||
433 | |||
434 | ret = m88ts2022_wr_reg(priv, 0x62, u8tmp); | ||
435 | if (ret) | ||
436 | goto err; | ||
437 | |||
438 | for (i = 0; i < ARRAY_SIZE(reg_vals); i++) { | ||
439 | ret = m88ts2022_wr_reg(priv, reg_vals[i].reg, reg_vals[i].val); | ||
440 | if (ret) | ||
441 | goto err; | ||
442 | } | ||
443 | err: | ||
444 | if (ret) | ||
445 | dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); | ||
446 | return ret; | ||
447 | } | ||
448 | |||
449 | static int m88ts2022_sleep(struct dvb_frontend *fe) | ||
450 | { | ||
451 | struct m88ts2022_priv *priv = fe->tuner_priv; | ||
452 | int ret; | ||
453 | dev_dbg(&priv->i2c->dev, "%s:\n", __func__); | ||
454 | |||
455 | ret = m88ts2022_wr_reg(priv, 0x00, 0x00); | ||
456 | if (ret) | ||
457 | goto err; | ||
458 | err: | ||
459 | if (ret) | ||
460 | dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); | ||
461 | return ret; | ||
462 | } | ||
463 | |||
464 | static int m88ts2022_get_frequency(struct dvb_frontend *fe, u32 *frequency) | ||
465 | { | ||
466 | struct m88ts2022_priv *priv = fe->tuner_priv; | ||
467 | dev_dbg(&priv->i2c->dev, "%s:\n", __func__); | ||
468 | |||
469 | *frequency = priv->frequency_khz; | ||
470 | return 0; | ||
471 | } | ||
472 | |||
473 | static int m88ts2022_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) | ||
474 | { | ||
475 | struct m88ts2022_priv *priv = fe->tuner_priv; | ||
476 | dev_dbg(&priv->i2c->dev, "%s:\n", __func__); | ||
477 | |||
478 | *frequency = 0; /* Zero-IF */ | ||
479 | return 0; | ||
480 | } | ||
481 | |||
482 | static int m88ts2022_get_rf_strength(struct dvb_frontend *fe, u16 *strength) | ||
483 | { | ||
484 | struct m88ts2022_priv *priv = fe->tuner_priv; | ||
485 | u8 u8tmp, gain1, gain2, gain3; | ||
486 | u16 gain, u16tmp; | ||
487 | int ret; | ||
488 | |||
489 | ret = m88ts2022_rd_reg(priv, 0x3d, &u8tmp); | ||
490 | if (ret) | ||
491 | goto err; | ||
492 | |||
493 | gain1 = (u8tmp >> 0) & 0x1f; | ||
494 | if (gain1 > 15) | ||
495 | gain1 = 15; | ||
496 | |||
497 | ret = m88ts2022_rd_reg(priv, 0x21, &u8tmp); | ||
498 | if (ret) | ||
499 | goto err; | ||
500 | |||
501 | gain2 = (u8tmp >> 0) & 0x1f; | ||
502 | if (gain2 < 2) | ||
503 | gain2 = 2; | ||
504 | if (gain2 > 16) | ||
505 | gain2 = 16; | ||
506 | |||
507 | ret = m88ts2022_rd_reg(priv, 0x66, &u8tmp); | ||
508 | if (ret) | ||
509 | goto err; | ||
510 | |||
511 | gain3 = (u8tmp >> 3) & 0x07; | ||
512 | if (gain3 > 6) | ||
513 | gain3 = 6; | ||
514 | |||
515 | gain = gain1 * 265 + gain2 * 338 + gain3 * 285; | ||
516 | |||
517 | /* scale value to 0x0000-0xffff */ | ||
518 | u16tmp = (0xffff - gain); | ||
519 | if (u16tmp < 59000) | ||
520 | u16tmp = 59000; | ||
521 | else if (u16tmp > 61500) | ||
522 | u16tmp = 61500; | ||
523 | |||
524 | *strength = (u16tmp - 59000) * 0xffff / (61500 - 59000); | ||
525 | err: | ||
526 | if (ret) | ||
527 | dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); | ||
528 | return ret; | ||
529 | } | ||
530 | |||
531 | static int m88ts2022_release(struct dvb_frontend *fe) | ||
532 | { | ||
533 | struct m88ts2022_priv *priv = fe->tuner_priv; | ||
534 | dev_dbg(&priv->i2c->dev, "%s:\n", __func__); | ||
535 | |||
536 | kfree(fe->tuner_priv); | ||
537 | return 0; | ||
538 | } | ||
539 | |||
540 | static const struct dvb_tuner_ops m88ts2022_tuner_ops = { | ||
541 | .info = { | ||
542 | .name = "Montage M88TS2022", | ||
543 | .frequency_min = 950000, | ||
544 | .frequency_max = 2150000, | ||
545 | }, | ||
546 | |||
547 | .release = m88ts2022_release, | ||
548 | |||
549 | .init = m88ts2022_init, | ||
550 | .sleep = m88ts2022_sleep, | ||
551 | .set_params = m88ts2022_set_params, | ||
552 | |||
553 | .get_frequency = m88ts2022_get_frequency, | ||
554 | .get_if_frequency = m88ts2022_get_if_frequency, | ||
555 | .get_rf_strength = m88ts2022_get_rf_strength, | ||
556 | }; | ||
557 | |||
558 | struct dvb_frontend *m88ts2022_attach(struct dvb_frontend *fe, | ||
559 | struct i2c_adapter *i2c, const struct m88ts2022_config *cfg) | ||
560 | { | ||
561 | struct m88ts2022_priv *priv; | ||
562 | int ret; | ||
563 | u8 chip_id, u8tmp; | ||
564 | |||
565 | priv = kzalloc(sizeof(struct m88ts2022_priv), GFP_KERNEL); | ||
566 | if (!priv) { | ||
567 | ret = -ENOMEM; | ||
568 | dev_err(&i2c->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); | ||
569 | goto err; | ||
570 | } | ||
571 | |||
572 | priv->cfg = cfg; | ||
573 | priv->i2c = i2c; | ||
574 | priv->fe = fe; | ||
575 | |||
576 | /* check if the tuner is there */ | ||
577 | ret = m88ts2022_rd_reg(priv, 0x00, &u8tmp); | ||
578 | if (ret) | ||
579 | goto err; | ||
580 | |||
581 | if ((u8tmp & 0x03) == 0x00) { | ||
582 | ret = m88ts2022_wr_reg(priv, 0x00, 0x01); | ||
583 | if (ret < 0) | ||
584 | goto err; | ||
585 | |||
586 | usleep_range(2000, 50000); | ||
587 | } | ||
588 | |||
589 | ret = m88ts2022_wr_reg(priv, 0x00, 0x03); | ||
590 | if (ret) | ||
591 | goto err; | ||
592 | |||
593 | usleep_range(2000, 50000); | ||
594 | |||
595 | ret = m88ts2022_rd_reg(priv, 0x00, &chip_id); | ||
596 | if (ret) | ||
597 | goto err; | ||
598 | |||
599 | dev_dbg(&priv->i2c->dev, "%s: chip_id=%02x\n", __func__, chip_id); | ||
600 | |||
601 | switch (chip_id) { | ||
602 | case 0xc3: | ||
603 | case 0x83: | ||
604 | break; | ||
605 | default: | ||
606 | goto err; | ||
607 | } | ||
608 | |||
609 | switch (priv->cfg->clock_out) { | ||
610 | case M88TS2022_CLOCK_OUT_DISABLED: | ||
611 | u8tmp = 0x60; | ||
612 | break; | ||
613 | case M88TS2022_CLOCK_OUT_ENABLED: | ||
614 | u8tmp = 0x70; | ||
615 | ret = m88ts2022_wr_reg(priv, 0x05, priv->cfg->clock_out_div); | ||
616 | if (ret) | ||
617 | goto err; | ||
618 | break; | ||
619 | case M88TS2022_CLOCK_OUT_ENABLED_XTALOUT: | ||
620 | u8tmp = 0x6c; | ||
621 | break; | ||
622 | default: | ||
623 | goto err; | ||
624 | } | ||
625 | |||
626 | ret = m88ts2022_wr_reg(priv, 0x42, u8tmp); | ||
627 | if (ret) | ||
628 | goto err; | ||
629 | |||
630 | if (priv->cfg->loop_through) | ||
631 | u8tmp = 0xec; | ||
632 | else | ||
633 | u8tmp = 0x6c; | ||
634 | |||
635 | ret = m88ts2022_wr_reg(priv, 0x62, u8tmp); | ||
636 | if (ret) | ||
637 | goto err; | ||
638 | |||
639 | /* sleep */ | ||
640 | ret = m88ts2022_wr_reg(priv, 0x00, 0x00); | ||
641 | if (ret) | ||
642 | goto err; | ||
643 | |||
644 | dev_info(&priv->i2c->dev, | ||
645 | "%s: Montage M88TS2022 successfully identified\n", | ||
646 | KBUILD_MODNAME); | ||
647 | |||
648 | fe->tuner_priv = priv; | ||
649 | memcpy(&fe->ops.tuner_ops, &m88ts2022_tuner_ops, | ||
650 | sizeof(struct dvb_tuner_ops)); | ||
651 | err: | ||
652 | if (ret) { | ||
653 | dev_dbg(&i2c->dev, "%s: failed=%d\n", __func__, ret); | ||
654 | kfree(priv); | ||
655 | return NULL; | ||
656 | } | ||
657 | |||
658 | return fe; | ||
659 | } | ||
660 | EXPORT_SYMBOL(m88ts2022_attach); | ||
661 | |||
662 | MODULE_DESCRIPTION("Montage M88TS2022 silicon tuner driver"); | ||
663 | MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); | ||
664 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/media/tuners/m88ts2022.h b/drivers/media/tuners/m88ts2022.h new file mode 100644 index 000000000000..fa1112cad656 --- /dev/null +++ b/drivers/media/tuners/m88ts2022.h | |||
@@ -0,0 +1,72 @@ | |||
1 | /* | ||
2 | * Montage M88TS2022 silicon tuner driver | ||
3 | * | ||
4 | * Copyright (C) 2013 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 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | */ | ||
20 | |||
21 | #ifndef M88TS2022_H | ||
22 | #define M88TS2022_H | ||
23 | |||
24 | #include "dvb_frontend.h" | ||
25 | |||
26 | struct m88ts2022_config { | ||
27 | /* | ||
28 | * I2C address | ||
29 | * 0x60, ... | ||
30 | */ | ||
31 | u8 i2c_addr; | ||
32 | |||
33 | /* | ||
34 | * clock | ||
35 | * 16000000 - 32000000 | ||
36 | */ | ||
37 | u32 clock; | ||
38 | |||
39 | /* | ||
40 | * RF loop-through | ||
41 | */ | ||
42 | u8 loop_through:1; | ||
43 | |||
44 | /* | ||
45 | * clock output | ||
46 | */ | ||
47 | #define M88TS2022_CLOCK_OUT_DISABLED 0 | ||
48 | #define M88TS2022_CLOCK_OUT_ENABLED 1 | ||
49 | #define M88TS2022_CLOCK_OUT_ENABLED_XTALOUT 2 | ||
50 | u8 clock_out:2; | ||
51 | |||
52 | /* | ||
53 | * clock output divider | ||
54 | * 1 - 31 | ||
55 | */ | ||
56 | u8 clock_out_div:5; | ||
57 | }; | ||
58 | |||
59 | #if defined(CONFIG_MEDIA_TUNER_M88TS2022) || \ | ||
60 | (defined(CONFIG_MEDIA_TUNER_M88TS2022_MODULE) && defined(MODULE)) | ||
61 | extern struct dvb_frontend *m88ts2022_attach(struct dvb_frontend *fe, | ||
62 | struct i2c_adapter *i2c, const struct m88ts2022_config *cfg); | ||
63 | #else | ||
64 | static inline struct dvb_frontend *m88ts2022_attach(struct dvb_frontend *fe, | ||
65 | struct i2c_adapter *i2c, const struct m88ts2022_config *cfg) | ||
66 | { | ||
67 | pr_warn("%s: driver disabled by Kconfig\n", __func__); | ||
68 | return NULL; | ||
69 | } | ||
70 | #endif | ||
71 | |||
72 | #endif | ||
diff --git a/drivers/media/tuners/m88ts2022_priv.h b/drivers/media/tuners/m88ts2022_priv.h new file mode 100644 index 000000000000..190299a95ce9 --- /dev/null +++ b/drivers/media/tuners/m88ts2022_priv.h | |||
@@ -0,0 +1,38 @@ | |||
1 | /* | ||
2 | * Montage M88TS2022 silicon tuner driver | ||
3 | * | ||
4 | * Copyright (C) 2013 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 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | */ | ||
20 | |||
21 | #ifndef M88TS2022_PRIV_H | ||
22 | #define M88TS2022_PRIV_H | ||
23 | |||
24 | #include "m88ts2022.h" | ||
25 | |||
26 | struct m88ts2022_priv { | ||
27 | const struct m88ts2022_config *cfg; | ||
28 | struct i2c_adapter *i2c; | ||
29 | struct dvb_frontend *fe; | ||
30 | u32 frequency_khz; | ||
31 | }; | ||
32 | |||
33 | struct m88ts2022_reg_val { | ||
34 | u8 reg; | ||
35 | u8 val; | ||
36 | }; | ||
37 | |||
38 | #endif | ||