1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * NXP TDA18212HN silicon tuner driver |
4 | * |
5 | * Copyright (C) 2011 Antti Palosaari <crope@iki.fi> |
6 | */ |
7 | |
8 | #include "tda18212.h" |
9 | #include <linux/regmap.h> |
10 | |
11 | struct tda18212_dev { |
12 | struct tda18212_config cfg; |
13 | struct i2c_client *client; |
14 | struct regmap *regmap; |
15 | |
16 | u32 if_frequency; |
17 | }; |
18 | |
19 | static int tda18212_set_params(struct dvb_frontend *fe) |
20 | { |
21 | struct tda18212_dev *dev = fe->tuner_priv; |
22 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; |
23 | int ret, i; |
24 | u32 if_khz; |
25 | u8 buf[9]; |
26 | #define DVBT_6 0 |
27 | #define DVBT_7 1 |
28 | #define DVBT_8 2 |
29 | #define DVBT2_6 3 |
30 | #define DVBT2_7 4 |
31 | #define DVBT2_8 5 |
32 | #define DVBC_6 6 |
33 | #define DVBC_8 7 |
34 | #define ATSC_VSB 8 |
35 | #define ATSC_QAM 9 |
36 | static const u8 bw_params[][3] = { |
37 | /* reg: 0f 13 23 */ |
38 | [DVBT_6] = { 0xb3, 0x20, 0x03 }, |
39 | [DVBT_7] = { 0xb3, 0x31, 0x01 }, |
40 | [DVBT_8] = { 0xb3, 0x22, 0x01 }, |
41 | [DVBT2_6] = { 0xbc, 0x20, 0x03 }, |
42 | [DVBT2_7] = { 0xbc, 0x72, 0x03 }, |
43 | [DVBT2_8] = { 0xbc, 0x22, 0x01 }, |
44 | [DVBC_6] = { 0x92, 0x50, 0x03 }, |
45 | [DVBC_8] = { 0x92, 0x53, 0x03 }, |
46 | [ATSC_VSB] = { 0x7d, 0x20, 0x63 }, |
47 | [ATSC_QAM] = { 0x7d, 0x20, 0x63 }, |
48 | }; |
49 | |
50 | dev_dbg(&dev->client->dev, |
51 | "delivery_system=%d frequency=%d bandwidth_hz=%d\n" , |
52 | c->delivery_system, c->frequency, |
53 | c->bandwidth_hz); |
54 | |
55 | if (fe->ops.i2c_gate_ctrl) |
56 | fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */ |
57 | |
58 | switch (c->delivery_system) { |
59 | case SYS_ATSC: |
60 | if_khz = dev->cfg.if_atsc_vsb; |
61 | i = ATSC_VSB; |
62 | break; |
63 | case SYS_DVBC_ANNEX_B: |
64 | if_khz = dev->cfg.if_atsc_qam; |
65 | i = ATSC_QAM; |
66 | break; |
67 | case SYS_DVBT: |
68 | switch (c->bandwidth_hz) { |
69 | case 6000000: |
70 | if_khz = dev->cfg.if_dvbt_6; |
71 | i = DVBT_6; |
72 | break; |
73 | case 7000000: |
74 | if_khz = dev->cfg.if_dvbt_7; |
75 | i = DVBT_7; |
76 | break; |
77 | case 8000000: |
78 | if_khz = dev->cfg.if_dvbt_8; |
79 | i = DVBT_8; |
80 | break; |
81 | default: |
82 | ret = -EINVAL; |
83 | goto error; |
84 | } |
85 | break; |
86 | case SYS_DVBT2: |
87 | switch (c->bandwidth_hz) { |
88 | case 6000000: |
89 | if_khz = dev->cfg.if_dvbt2_6; |
90 | i = DVBT2_6; |
91 | break; |
92 | case 7000000: |
93 | if_khz = dev->cfg.if_dvbt2_7; |
94 | i = DVBT2_7; |
95 | break; |
96 | case 8000000: |
97 | if_khz = dev->cfg.if_dvbt2_8; |
98 | i = DVBT2_8; |
99 | break; |
100 | default: |
101 | ret = -EINVAL; |
102 | goto error; |
103 | } |
104 | break; |
105 | case SYS_DVBC_ANNEX_A: |
106 | case SYS_DVBC_ANNEX_C: |
107 | if_khz = dev->cfg.if_dvbc; |
108 | i = DVBC_8; |
109 | break; |
110 | default: |
111 | ret = -EINVAL; |
112 | goto error; |
113 | } |
114 | |
115 | ret = regmap_write(map: dev->regmap, reg: 0x23, val: bw_params[i][2]); |
116 | if (ret) |
117 | goto error; |
118 | |
119 | ret = regmap_write(map: dev->regmap, reg: 0x06, val: 0x00); |
120 | if (ret) |
121 | goto error; |
122 | |
123 | ret = regmap_write(map: dev->regmap, reg: 0x0f, val: bw_params[i][0]); |
124 | if (ret) |
125 | goto error; |
126 | |
127 | buf[0] = 0x02; |
128 | buf[1] = bw_params[i][1]; |
129 | buf[2] = 0x03; /* default value */ |
130 | buf[3] = DIV_ROUND_CLOSEST(if_khz, 50); |
131 | buf[4] = ((c->frequency / 1000) >> 16) & 0xff; |
132 | buf[5] = ((c->frequency / 1000) >> 8) & 0xff; |
133 | buf[6] = ((c->frequency / 1000) >> 0) & 0xff; |
134 | buf[7] = 0xc1; |
135 | buf[8] = 0x01; |
136 | ret = regmap_bulk_write(map: dev->regmap, reg: 0x12, val: buf, val_count: sizeof(buf)); |
137 | if (ret) |
138 | goto error; |
139 | |
140 | /* actual IF rounded as it is on register */ |
141 | dev->if_frequency = buf[3] * 50 * 1000; |
142 | |
143 | exit: |
144 | if (fe->ops.i2c_gate_ctrl) |
145 | fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */ |
146 | |
147 | return ret; |
148 | |
149 | error: |
150 | dev_dbg(&dev->client->dev, "failed=%d\n" , ret); |
151 | goto exit; |
152 | } |
153 | |
154 | static int tda18212_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) |
155 | { |
156 | struct tda18212_dev *dev = fe->tuner_priv; |
157 | |
158 | *frequency = dev->if_frequency; |
159 | |
160 | return 0; |
161 | } |
162 | |
163 | static const struct dvb_tuner_ops tda18212_tuner_ops = { |
164 | .info = { |
165 | .name = "NXP TDA18212" , |
166 | |
167 | .frequency_min_hz = 48 * MHz, |
168 | .frequency_max_hz = 864 * MHz, |
169 | .frequency_step_hz = 1 * kHz, |
170 | }, |
171 | |
172 | .set_params = tda18212_set_params, |
173 | .get_if_frequency = tda18212_get_if_frequency, |
174 | }; |
175 | |
176 | static int tda18212_probe(struct i2c_client *client) |
177 | { |
178 | struct tda18212_config *cfg = client->dev.platform_data; |
179 | struct dvb_frontend *fe = cfg->fe; |
180 | struct tda18212_dev *dev; |
181 | int ret; |
182 | unsigned int chip_id; |
183 | char *version; |
184 | static const struct regmap_config regmap_config = { |
185 | .reg_bits = 8, |
186 | .val_bits = 8, |
187 | }; |
188 | |
189 | dev = kzalloc(size: sizeof(*dev), GFP_KERNEL); |
190 | if (dev == NULL) { |
191 | ret = -ENOMEM; |
192 | dev_err(&client->dev, "kzalloc() failed\n" ); |
193 | goto err; |
194 | } |
195 | |
196 | memcpy(&dev->cfg, cfg, sizeof(struct tda18212_config)); |
197 | dev->client = client; |
198 | dev->regmap = devm_regmap_init_i2c(client, ®map_config); |
199 | if (IS_ERR(ptr: dev->regmap)) { |
200 | ret = PTR_ERR(ptr: dev->regmap); |
201 | goto err; |
202 | } |
203 | |
204 | /* check if the tuner is there */ |
205 | if (fe->ops.i2c_gate_ctrl) |
206 | fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */ |
207 | |
208 | ret = regmap_read(map: dev->regmap, reg: 0x00, val: &chip_id); |
209 | dev_dbg(&dev->client->dev, "chip_id=%02x\n" , chip_id); |
210 | |
211 | if (fe->ops.i2c_gate_ctrl) |
212 | fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */ |
213 | |
214 | if (ret) |
215 | goto err; |
216 | |
217 | switch (chip_id) { |
218 | case 0xc7: |
219 | version = "M" ; /* master */ |
220 | break; |
221 | case 0x47: |
222 | version = "S" ; /* slave */ |
223 | break; |
224 | default: |
225 | ret = -ENODEV; |
226 | goto err; |
227 | } |
228 | |
229 | dev_info(&dev->client->dev, |
230 | "NXP TDA18212HN/%s successfully identified\n" , version); |
231 | |
232 | fe->tuner_priv = dev; |
233 | memcpy(&fe->ops.tuner_ops, &tda18212_tuner_ops, |
234 | sizeof(struct dvb_tuner_ops)); |
235 | i2c_set_clientdata(client, data: dev); |
236 | |
237 | return 0; |
238 | err: |
239 | dev_dbg(&client->dev, "failed=%d\n" , ret); |
240 | kfree(objp: dev); |
241 | return ret; |
242 | } |
243 | |
244 | static void tda18212_remove(struct i2c_client *client) |
245 | { |
246 | struct tda18212_dev *dev = i2c_get_clientdata(client); |
247 | struct dvb_frontend *fe = dev->cfg.fe; |
248 | |
249 | dev_dbg(&client->dev, "\n" ); |
250 | |
251 | memset(&fe->ops.tuner_ops, 0, sizeof(struct dvb_tuner_ops)); |
252 | fe->tuner_priv = NULL; |
253 | kfree(objp: dev); |
254 | } |
255 | |
256 | static const struct i2c_device_id tda18212_id[] = { |
257 | {"tda18212" , 0}, |
258 | {} |
259 | }; |
260 | MODULE_DEVICE_TABLE(i2c, tda18212_id); |
261 | |
262 | static struct i2c_driver tda18212_driver = { |
263 | .driver = { |
264 | .name = "tda18212" , |
265 | }, |
266 | .probe = tda18212_probe, |
267 | .remove = tda18212_remove, |
268 | .id_table = tda18212_id, |
269 | }; |
270 | |
271 | module_i2c_driver(tda18212_driver); |
272 | |
273 | MODULE_DESCRIPTION("NXP TDA18212HN silicon tuner driver" ); |
274 | MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>" ); |
275 | MODULE_LICENSE("GPL" ); |
276 | |