1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Driver for Microtune MT2266 "Direct conversion low power broadband tuner" |
4 | * |
5 | * Copyright (c) 2007 Olivier DANET <odanet@caramail.com> |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/dvb/frontend.h> |
11 | #include <linux/i2c.h> |
12 | #include <linux/slab.h> |
13 | |
14 | #include <media/dvb_frontend.h> |
15 | #include "mt2266.h" |
16 | |
17 | #define I2C_ADDRESS 0x60 |
18 | |
19 | #define REG_PART_REV 0 |
20 | #define REG_TUNE 1 |
21 | #define REG_BAND 6 |
22 | #define REG_BANDWIDTH 8 |
23 | #define REG_LOCK 0x12 |
24 | |
25 | #define PART_REV 0x85 |
26 | |
27 | struct mt2266_priv { |
28 | struct mt2266_config *cfg; |
29 | struct i2c_adapter *i2c; |
30 | |
31 | u32 frequency; |
32 | u32 bandwidth; |
33 | u8 band; |
34 | }; |
35 | |
36 | #define MT2266_VHF 1 |
37 | #define MT2266_UHF 0 |
38 | |
39 | /* Here, frequencies are expressed in kiloHertz to avoid 32 bits overflows */ |
40 | |
41 | static int debug; |
42 | module_param(debug, int, 0644); |
43 | MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)." ); |
44 | |
45 | #define dprintk(args...) do { if (debug) {printk(KERN_DEBUG "MT2266: " args); printk("\n"); }} while (0) |
46 | |
47 | // Reads a single register |
48 | static int mt2266_readreg(struct mt2266_priv *priv, u8 reg, u8 *val) |
49 | { |
50 | struct i2c_msg msg[2] = { |
51 | { .addr = priv->cfg->i2c_address, .flags = 0, .buf = ®, .len = 1 }, |
52 | { .addr = priv->cfg->i2c_address, .flags = I2C_M_RD, .buf = val, .len = 1 }, |
53 | }; |
54 | if (i2c_transfer(adap: priv->i2c, msgs: msg, num: 2) != 2) { |
55 | printk(KERN_WARNING "MT2266 I2C read failed\n" ); |
56 | return -EREMOTEIO; |
57 | } |
58 | return 0; |
59 | } |
60 | |
61 | // Writes a single register |
62 | static int mt2266_writereg(struct mt2266_priv *priv, u8 reg, u8 val) |
63 | { |
64 | u8 buf[2] = { reg, val }; |
65 | struct i2c_msg msg = { |
66 | .addr = priv->cfg->i2c_address, .flags = 0, .buf = buf, .len = 2 |
67 | }; |
68 | if (i2c_transfer(adap: priv->i2c, msgs: &msg, num: 1) != 1) { |
69 | printk(KERN_WARNING "MT2266 I2C write failed\n" ); |
70 | return -EREMOTEIO; |
71 | } |
72 | return 0; |
73 | } |
74 | |
75 | // Writes a set of consecutive registers |
76 | static int mt2266_writeregs(struct mt2266_priv *priv,u8 *buf, u8 len) |
77 | { |
78 | struct i2c_msg msg = { |
79 | .addr = priv->cfg->i2c_address, .flags = 0, .buf = buf, .len = len |
80 | }; |
81 | if (i2c_transfer(adap: priv->i2c, msgs: &msg, num: 1) != 1) { |
82 | printk(KERN_WARNING "MT2266 I2C write failed (len=%i)\n" ,(int)len); |
83 | return -EREMOTEIO; |
84 | } |
85 | return 0; |
86 | } |
87 | |
88 | // Initialisation sequences |
89 | static u8 mt2266_init1[] = { REG_TUNE, 0x00, 0x00, 0x28, |
90 | 0x00, 0x52, 0x99, 0x3f }; |
91 | |
92 | static u8 mt2266_init2[] = { |
93 | 0x17, 0x6d, 0x71, 0x61, 0xc0, 0xbf, 0xff, 0xdc, 0x00, 0x0a, 0xd4, |
94 | 0x03, 0x64, 0x64, 0x64, 0x64, 0x22, 0xaa, 0xf2, 0x1e, 0x80, 0x14, |
95 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x7f, 0x5e, 0x3f, 0xff, 0xff, |
96 | 0xff, 0x00, 0x77, 0x0f, 0x2d |
97 | }; |
98 | |
99 | static u8 mt2266_init_8mhz[] = { REG_BANDWIDTH, 0x22, 0x22, 0x22, 0x22, |
100 | 0x22, 0x22, 0x22, 0x22 }; |
101 | |
102 | static u8 mt2266_init_7mhz[] = { REG_BANDWIDTH, 0x32, 0x32, 0x32, 0x32, |
103 | 0x32, 0x32, 0x32, 0x32 }; |
104 | |
105 | static u8 mt2266_init_6mhz[] = { REG_BANDWIDTH, 0xa7, 0xa7, 0xa7, 0xa7, |
106 | 0xa7, 0xa7, 0xa7, 0xa7 }; |
107 | |
108 | static u8 mt2266_uhf[] = { 0x1d, 0xdc, 0x00, 0x0a, 0xd4, 0x03, 0x64, 0x64, |
109 | 0x64, 0x64, 0x22, 0xaa, 0xf2, 0x1e, 0x80, 0x14 }; |
110 | |
111 | static u8 mt2266_vhf[] = { 0x1d, 0xfe, 0x00, 0x00, 0xb4, 0x03, 0xa5, 0xa5, |
112 | 0xa5, 0xa5, 0x82, 0xaa, 0xf1, 0x17, 0x80, 0x1f }; |
113 | |
114 | #define FREF 30000 // Quartz oscillator 30 MHz |
115 | |
116 | static int mt2266_set_params(struct dvb_frontend *fe) |
117 | { |
118 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; |
119 | struct mt2266_priv *priv; |
120 | int ret=0; |
121 | u32 freq; |
122 | u32 tune; |
123 | u8 lnaband; |
124 | u8 b[10]; |
125 | int i; |
126 | u8 band; |
127 | |
128 | priv = fe->tuner_priv; |
129 | |
130 | freq = priv->frequency / 1000; /* Hz -> kHz */ |
131 | if (freq < 470000 && freq > 230000) |
132 | return -EINVAL; /* Gap between VHF and UHF bands */ |
133 | |
134 | priv->frequency = c->frequency; |
135 | tune = 2 * freq * (8192/16) / (FREF/16); |
136 | band = (freq < 300000) ? MT2266_VHF : MT2266_UHF; |
137 | if (band == MT2266_VHF) |
138 | tune *= 2; |
139 | |
140 | switch (c->bandwidth_hz) { |
141 | case 6000000: |
142 | mt2266_writeregs(priv, buf: mt2266_init_6mhz, |
143 | len: sizeof(mt2266_init_6mhz)); |
144 | break; |
145 | case 8000000: |
146 | mt2266_writeregs(priv, buf: mt2266_init_8mhz, |
147 | len: sizeof(mt2266_init_8mhz)); |
148 | break; |
149 | case 7000000: |
150 | default: |
151 | mt2266_writeregs(priv, buf: mt2266_init_7mhz, |
152 | len: sizeof(mt2266_init_7mhz)); |
153 | break; |
154 | } |
155 | priv->bandwidth = c->bandwidth_hz; |
156 | |
157 | if (band == MT2266_VHF && priv->band == MT2266_UHF) { |
158 | dprintk("Switch from UHF to VHF" ); |
159 | mt2266_writereg(priv, reg: 0x05, val: 0x04); |
160 | mt2266_writereg(priv, reg: 0x19, val: 0x61); |
161 | mt2266_writeregs(priv, buf: mt2266_vhf, len: sizeof(mt2266_vhf)); |
162 | } else if (band == MT2266_UHF && priv->band == MT2266_VHF) { |
163 | dprintk("Switch from VHF to UHF" ); |
164 | mt2266_writereg(priv, reg: 0x05, val: 0x52); |
165 | mt2266_writereg(priv, reg: 0x19, val: 0x61); |
166 | mt2266_writeregs(priv, buf: mt2266_uhf, len: sizeof(mt2266_uhf)); |
167 | } |
168 | msleep(msecs: 10); |
169 | |
170 | if (freq <= 495000) |
171 | lnaband = 0xEE; |
172 | else if (freq <= 525000) |
173 | lnaband = 0xDD; |
174 | else if (freq <= 550000) |
175 | lnaband = 0xCC; |
176 | else if (freq <= 580000) |
177 | lnaband = 0xBB; |
178 | else if (freq <= 605000) |
179 | lnaband = 0xAA; |
180 | else if (freq <= 630000) |
181 | lnaband = 0x99; |
182 | else if (freq <= 655000) |
183 | lnaband = 0x88; |
184 | else if (freq <= 685000) |
185 | lnaband = 0x77; |
186 | else if (freq <= 710000) |
187 | lnaband = 0x66; |
188 | else if (freq <= 735000) |
189 | lnaband = 0x55; |
190 | else if (freq <= 765000) |
191 | lnaband = 0x44; |
192 | else if (freq <= 802000) |
193 | lnaband = 0x33; |
194 | else if (freq <= 840000) |
195 | lnaband = 0x22; |
196 | else |
197 | lnaband = 0x11; |
198 | |
199 | b[0] = REG_TUNE; |
200 | b[1] = (tune >> 8) & 0x1F; |
201 | b[2] = tune & 0xFF; |
202 | b[3] = tune >> 13; |
203 | mt2266_writeregs(priv,buf: b,len: 4); |
204 | |
205 | dprintk("set_parms: tune=%d band=%d %s" , |
206 | (int) tune, (int) lnaband, |
207 | (band == MT2266_UHF) ? "UHF" : "VHF" ); |
208 | dprintk("set_parms: [1..3]: %2x %2x %2x" , |
209 | (int) b[1], (int) b[2], (int)b[3]); |
210 | |
211 | if (band == MT2266_UHF) { |
212 | b[0] = 0x05; |
213 | b[1] = (priv->band == MT2266_VHF) ? 0x52 : 0x62; |
214 | b[2] = lnaband; |
215 | mt2266_writeregs(priv, buf: b, len: 3); |
216 | } |
217 | |
218 | /* Wait for pll lock or timeout */ |
219 | i = 0; |
220 | do { |
221 | mt2266_readreg(priv,REG_LOCK,val: b); |
222 | if (b[0] & 0x40) |
223 | break; |
224 | msleep(msecs: 10); |
225 | i++; |
226 | } while (i<10); |
227 | dprintk("Lock when i=%i" ,(int)i); |
228 | |
229 | if (band == MT2266_UHF && priv->band == MT2266_VHF) |
230 | mt2266_writereg(priv, reg: 0x05, val: 0x62); |
231 | |
232 | priv->band = band; |
233 | |
234 | return ret; |
235 | } |
236 | |
237 | static void mt2266_calibrate(struct mt2266_priv *priv) |
238 | { |
239 | mt2266_writereg(priv, reg: 0x11, val: 0x03); |
240 | mt2266_writereg(priv, reg: 0x11, val: 0x01); |
241 | mt2266_writeregs(priv, buf: mt2266_init1, len: sizeof(mt2266_init1)); |
242 | mt2266_writeregs(priv, buf: mt2266_init2, len: sizeof(mt2266_init2)); |
243 | mt2266_writereg(priv, reg: 0x33, val: 0x5e); |
244 | mt2266_writereg(priv, reg: 0x10, val: 0x10); |
245 | mt2266_writereg(priv, reg: 0x10, val: 0x00); |
246 | mt2266_writeregs(priv, buf: mt2266_init_8mhz, len: sizeof(mt2266_init_8mhz)); |
247 | msleep(msecs: 25); |
248 | mt2266_writereg(priv, reg: 0x17, val: 0x6d); |
249 | mt2266_writereg(priv, reg: 0x1c, val: 0x00); |
250 | msleep(msecs: 75); |
251 | mt2266_writereg(priv, reg: 0x17, val: 0x6d); |
252 | mt2266_writereg(priv, reg: 0x1c, val: 0xff); |
253 | } |
254 | |
255 | static int mt2266_get_frequency(struct dvb_frontend *fe, u32 *frequency) |
256 | { |
257 | struct mt2266_priv *priv = fe->tuner_priv; |
258 | *frequency = priv->frequency; |
259 | return 0; |
260 | } |
261 | |
262 | static int mt2266_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth) |
263 | { |
264 | struct mt2266_priv *priv = fe->tuner_priv; |
265 | *bandwidth = priv->bandwidth; |
266 | return 0; |
267 | } |
268 | |
269 | static int mt2266_init(struct dvb_frontend *fe) |
270 | { |
271 | int ret; |
272 | struct mt2266_priv *priv = fe->tuner_priv; |
273 | ret = mt2266_writereg(priv, reg: 0x17, val: 0x6d); |
274 | if (ret < 0) |
275 | return ret; |
276 | ret = mt2266_writereg(priv, reg: 0x1c, val: 0xff); |
277 | if (ret < 0) |
278 | return ret; |
279 | return 0; |
280 | } |
281 | |
282 | static int mt2266_sleep(struct dvb_frontend *fe) |
283 | { |
284 | struct mt2266_priv *priv = fe->tuner_priv; |
285 | mt2266_writereg(priv, reg: 0x17, val: 0x6d); |
286 | mt2266_writereg(priv, reg: 0x1c, val: 0x00); |
287 | return 0; |
288 | } |
289 | |
290 | static void mt2266_release(struct dvb_frontend *fe) |
291 | { |
292 | kfree(objp: fe->tuner_priv); |
293 | fe->tuner_priv = NULL; |
294 | } |
295 | |
296 | static const struct dvb_tuner_ops mt2266_tuner_ops = { |
297 | .info = { |
298 | .name = "Microtune MT2266" , |
299 | .frequency_min_hz = 174 * MHz, |
300 | .frequency_max_hz = 862 * MHz, |
301 | .frequency_step_hz = 50 * kHz, |
302 | }, |
303 | .release = mt2266_release, |
304 | .init = mt2266_init, |
305 | .sleep = mt2266_sleep, |
306 | .set_params = mt2266_set_params, |
307 | .get_frequency = mt2266_get_frequency, |
308 | .get_bandwidth = mt2266_get_bandwidth |
309 | }; |
310 | |
311 | struct dvb_frontend * mt2266_attach(struct dvb_frontend *fe, struct i2c_adapter *i2c, struct mt2266_config *cfg) |
312 | { |
313 | struct mt2266_priv *priv = NULL; |
314 | u8 id = 0; |
315 | |
316 | priv = kzalloc(size: sizeof(struct mt2266_priv), GFP_KERNEL); |
317 | if (priv == NULL) |
318 | return NULL; |
319 | |
320 | priv->cfg = cfg; |
321 | priv->i2c = i2c; |
322 | priv->band = MT2266_UHF; |
323 | |
324 | if (mt2266_readreg(priv, reg: 0, val: &id)) { |
325 | kfree(objp: priv); |
326 | return NULL; |
327 | } |
328 | if (id != PART_REV) { |
329 | kfree(objp: priv); |
330 | return NULL; |
331 | } |
332 | printk(KERN_INFO "MT2266: successfully identified\n" ); |
333 | memcpy(&fe->ops.tuner_ops, &mt2266_tuner_ops, sizeof(struct dvb_tuner_ops)); |
334 | |
335 | fe->tuner_priv = priv; |
336 | mt2266_calibrate(priv); |
337 | return fe; |
338 | } |
339 | EXPORT_SYMBOL_GPL(mt2266_attach); |
340 | |
341 | MODULE_AUTHOR("Olivier DANET" ); |
342 | MODULE_DESCRIPTION("Microtune MT2266 silicon tuner driver" ); |
343 | MODULE_LICENSE("GPL" ); |
344 | |