]>
Commit | Line | Data |
---|---|---|
f69a7cf7 | 1 | /* |
2eedcbfc | 2 | * MFD core driver for Rockchip RK808/RK818 |
f69a7cf7 CZ |
3 | * |
4 | * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd | |
5 | * | |
6 | * Author: Chris Zhong <zyw@rock-chips.com> | |
7 | * Author: Zhang Qing <zhangqing@rock-chips.com> | |
8 | * | |
2eedcbfc WE |
9 | * Copyright (C) 2016 PHYTEC Messtechnik GmbH |
10 | * | |
11 | * Author: Wadim Egorov <w.egorov@phytec.de> | |
12 | * | |
f69a7cf7 CZ |
13 | * This program is free software; you can redistribute it and/or modify it |
14 | * under the terms and conditions of the GNU General Public License, | |
15 | * version 2, as published by the Free Software Foundation. | |
16 | * | |
17 | * This program is distributed in the hope it will be useful, but WITHOUT | |
18 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
19 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
20 | * more details. | |
21 | */ | |
22 | ||
23 | #include <linux/i2c.h> | |
24 | #include <linux/interrupt.h> | |
25 | #include <linux/mfd/rk808.h> | |
26 | #include <linux/mfd/core.h> | |
27 | #include <linux/module.h> | |
2eedcbfc | 28 | #include <linux/of_device.h> |
f69a7cf7 CZ |
29 | #include <linux/regmap.h> |
30 | ||
31 | struct rk808_reg_data { | |
32 | int addr; | |
33 | int mask; | |
34 | int value; | |
35 | }; | |
36 | ||
2adb3b8e DA |
37 | static bool rk808_is_volatile_reg(struct device *dev, unsigned int reg) |
38 | { | |
39 | /* | |
40 | * Notes: | |
41 | * - Technically the ROUND_30s bit makes RTC_CTRL_REG volatile, but | |
42 | * we don't use that feature. It's better to cache. | |
43 | * - It's unlikely we care that RK808_DEVCTRL_REG is volatile since | |
44 | * bits are cleared in case when we shutoff anyway, but better safe. | |
45 | */ | |
46 | ||
47 | switch (reg) { | |
48 | case RK808_SECONDS_REG ... RK808_WEEKS_REG: | |
49 | case RK808_RTC_STATUS_REG: | |
50 | case RK808_VB_MON_REG: | |
51 | case RK808_THERMAL_REG: | |
52 | case RK808_DCDC_UV_STS_REG: | |
53 | case RK808_LDO_UV_STS_REG: | |
54 | case RK808_DCDC_PG_REG: | |
55 | case RK808_LDO_PG_REG: | |
56 | case RK808_DEVCTRL_REG: | |
57 | case RK808_INT_STS_REG1: | |
58 | case RK808_INT_STS_REG2: | |
59 | return true; | |
60 | } | |
61 | ||
62 | return false; | |
63 | } | |
64 | ||
2eedcbfc WE |
65 | static const struct regmap_config rk818_regmap_config = { |
66 | .reg_bits = 8, | |
67 | .val_bits = 8, | |
68 | .max_register = RK818_USB_CTRL_REG, | |
69 | .cache_type = REGCACHE_RBTREE, | |
70 | .volatile_reg = rk808_is_volatile_reg, | |
71 | }; | |
72 | ||
f69a7cf7 CZ |
73 | static const struct regmap_config rk808_regmap_config = { |
74 | .reg_bits = 8, | |
75 | .val_bits = 8, | |
76 | .max_register = RK808_IO_POL_REG, | |
2adb3b8e DA |
77 | .cache_type = REGCACHE_RBTREE, |
78 | .volatile_reg = rk808_is_volatile_reg, | |
f69a7cf7 CZ |
79 | }; |
80 | ||
81 | static struct resource rtc_resources[] = { | |
82 | { | |
83 | .start = RK808_IRQ_RTC_ALARM, | |
84 | .end = RK808_IRQ_RTC_ALARM, | |
85 | .flags = IORESOURCE_IRQ, | |
86 | } | |
87 | }; | |
88 | ||
89 | static const struct mfd_cell rk808s[] = { | |
90 | { .name = "rk808-clkout", }, | |
91 | { .name = "rk808-regulator", }, | |
92 | { | |
93 | .name = "rk808-rtc", | |
94 | .num_resources = ARRAY_SIZE(rtc_resources), | |
2eedcbfc | 95 | .resources = rtc_resources, |
f69a7cf7 CZ |
96 | }, |
97 | }; | |
98 | ||
2eedcbfc WE |
99 | static const struct mfd_cell rk818s[] = { |
100 | { .name = "rk808-clkout", }, | |
101 | { .name = "rk808-regulator", }, | |
102 | { | |
103 | .name = "rk808-rtc", | |
104 | .num_resources = ARRAY_SIZE(rtc_resources), | |
105 | .resources = rtc_resources, | |
106 | }, | |
107 | }; | |
108 | ||
109 | static const struct rk808_reg_data rk808_pre_init_reg[] = { | |
f69a7cf7 CZ |
110 | { RK808_BUCK3_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_150MA }, |
111 | { RK808_BUCK4_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_200MA }, | |
112 | { RK808_BOOST_CONFIG_REG, BOOST_ILMIN_MASK, BOOST_ILMIN_100MA }, | |
113 | { RK808_BUCK1_CONFIG_REG, BUCK1_RATE_MASK, BUCK_ILMIN_200MA }, | |
114 | { RK808_BUCK2_CONFIG_REG, BUCK2_RATE_MASK, BUCK_ILMIN_200MA }, | |
e19f7428 | 115 | { RK808_DCDC_UV_ACT_REG, BUCK_UV_ACT_MASK, BUCK_UV_ACT_DISABLE}, |
f69a7cf7 CZ |
116 | { RK808_VB_MON_REG, MASK_ALL, VB_LO_ACT | |
117 | VB_LO_SEL_3500MV }, | |
118 | }; | |
119 | ||
2eedcbfc WE |
120 | static const struct rk808_reg_data rk818_pre_init_reg[] = { |
121 | /* improve efficiency */ | |
122 | { RK818_BUCK2_CONFIG_REG, BUCK2_RATE_MASK, BUCK_ILMIN_250MA }, | |
123 | { RK818_BUCK4_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_250MA }, | |
124 | { RK818_BOOST_CONFIG_REG, BOOST_ILMIN_MASK, BOOST_ILMIN_100MA }, | |
125 | { RK818_USB_CTRL_REG, RK818_USB_ILIM_SEL_MASK, | |
126 | RK818_USB_ILMIN_2000MA }, | |
127 | /* close charger when usb lower then 3.4V */ | |
128 | { RK818_USB_CTRL_REG, RK818_USB_CHG_SD_VSEL_MASK, | |
129 | (0x7 << 4) }, | |
130 | /* no action when vref */ | |
131 | { RK818_H5V_EN_REG, BIT(1), RK818_REF_RDY_CTRL }, | |
132 | /* enable HDMI 5V */ | |
133 | { RK818_H5V_EN_REG, BIT(0), RK818_H5V_EN }, | |
134 | { RK808_VB_MON_REG, MASK_ALL, VB_LO_ACT | | |
135 | VB_LO_SEL_3500MV }, | |
136 | }; | |
137 | ||
f69a7cf7 CZ |
138 | static const struct regmap_irq rk808_irqs[] = { |
139 | /* INT_STS */ | |
140 | [RK808_IRQ_VOUT_LO] = { | |
141 | .mask = RK808_IRQ_VOUT_LO_MSK, | |
142 | .reg_offset = 0, | |
143 | }, | |
144 | [RK808_IRQ_VB_LO] = { | |
145 | .mask = RK808_IRQ_VB_LO_MSK, | |
146 | .reg_offset = 0, | |
147 | }, | |
148 | [RK808_IRQ_PWRON] = { | |
149 | .mask = RK808_IRQ_PWRON_MSK, | |
150 | .reg_offset = 0, | |
151 | }, | |
152 | [RK808_IRQ_PWRON_LP] = { | |
153 | .mask = RK808_IRQ_PWRON_LP_MSK, | |
154 | .reg_offset = 0, | |
155 | }, | |
156 | [RK808_IRQ_HOTDIE] = { | |
157 | .mask = RK808_IRQ_HOTDIE_MSK, | |
158 | .reg_offset = 0, | |
159 | }, | |
160 | [RK808_IRQ_RTC_ALARM] = { | |
161 | .mask = RK808_IRQ_RTC_ALARM_MSK, | |
162 | .reg_offset = 0, | |
163 | }, | |
164 | [RK808_IRQ_RTC_PERIOD] = { | |
165 | .mask = RK808_IRQ_RTC_PERIOD_MSK, | |
166 | .reg_offset = 0, | |
167 | }, | |
168 | ||
169 | /* INT_STS2 */ | |
170 | [RK808_IRQ_PLUG_IN_INT] = { | |
171 | .mask = RK808_IRQ_PLUG_IN_INT_MSK, | |
172 | .reg_offset = 1, | |
173 | }, | |
174 | [RK808_IRQ_PLUG_OUT_INT] = { | |
175 | .mask = RK808_IRQ_PLUG_OUT_INT_MSK, | |
176 | .reg_offset = 1, | |
177 | }, | |
178 | }; | |
179 | ||
2eedcbfc WE |
180 | static const struct regmap_irq rk818_irqs[] = { |
181 | /* INT_STS */ | |
182 | [RK818_IRQ_VOUT_LO] = { | |
183 | .mask = RK818_IRQ_VOUT_LO_MSK, | |
184 | .reg_offset = 0, | |
185 | }, | |
186 | [RK818_IRQ_VB_LO] = { | |
187 | .mask = RK818_IRQ_VB_LO_MSK, | |
188 | .reg_offset = 0, | |
189 | }, | |
190 | [RK818_IRQ_PWRON] = { | |
191 | .mask = RK818_IRQ_PWRON_MSK, | |
192 | .reg_offset = 0, | |
193 | }, | |
194 | [RK818_IRQ_PWRON_LP] = { | |
195 | .mask = RK818_IRQ_PWRON_LP_MSK, | |
196 | .reg_offset = 0, | |
197 | }, | |
198 | [RK818_IRQ_HOTDIE] = { | |
199 | .mask = RK818_IRQ_HOTDIE_MSK, | |
200 | .reg_offset = 0, | |
201 | }, | |
202 | [RK818_IRQ_RTC_ALARM] = { | |
203 | .mask = RK818_IRQ_RTC_ALARM_MSK, | |
204 | .reg_offset = 0, | |
205 | }, | |
206 | [RK818_IRQ_RTC_PERIOD] = { | |
207 | .mask = RK818_IRQ_RTC_PERIOD_MSK, | |
208 | .reg_offset = 0, | |
209 | }, | |
210 | [RK818_IRQ_USB_OV] = { | |
211 | .mask = RK818_IRQ_USB_OV_MSK, | |
212 | .reg_offset = 0, | |
213 | }, | |
214 | ||
215 | /* INT_STS2 */ | |
216 | [RK818_IRQ_PLUG_IN] = { | |
217 | .mask = RK818_IRQ_PLUG_IN_MSK, | |
218 | .reg_offset = 1, | |
219 | }, | |
220 | [RK818_IRQ_PLUG_OUT] = { | |
221 | .mask = RK818_IRQ_PLUG_OUT_MSK, | |
222 | .reg_offset = 1, | |
223 | }, | |
224 | [RK818_IRQ_CHG_OK] = { | |
225 | .mask = RK818_IRQ_CHG_OK_MSK, | |
226 | .reg_offset = 1, | |
227 | }, | |
228 | [RK818_IRQ_CHG_TE] = { | |
229 | .mask = RK818_IRQ_CHG_TE_MSK, | |
230 | .reg_offset = 1, | |
231 | }, | |
232 | [RK818_IRQ_CHG_TS1] = { | |
233 | .mask = RK818_IRQ_CHG_TS1_MSK, | |
234 | .reg_offset = 1, | |
235 | }, | |
236 | [RK818_IRQ_TS2] = { | |
237 | .mask = RK818_IRQ_TS2_MSK, | |
238 | .reg_offset = 1, | |
239 | }, | |
240 | [RK818_IRQ_CHG_CVTLIM] = { | |
241 | .mask = RK818_IRQ_CHG_CVTLIM_MSK, | |
242 | .reg_offset = 1, | |
243 | }, | |
244 | [RK818_IRQ_DISCHG_ILIM] = { | |
245 | .mask = RK818_IRQ_DISCHG_ILIM_MSK, | |
246 | .reg_offset = 1, | |
247 | }, | |
248 | }; | |
249 | ||
24e34b9d | 250 | static const struct regmap_irq_chip rk808_irq_chip = { |
f69a7cf7 CZ |
251 | .name = "rk808", |
252 | .irqs = rk808_irqs, | |
253 | .num_irqs = ARRAY_SIZE(rk808_irqs), | |
254 | .num_regs = 2, | |
255 | .irq_reg_stride = 2, | |
256 | .status_base = RK808_INT_STS_REG1, | |
257 | .mask_base = RK808_INT_STS_MSK_REG1, | |
258 | .ack_base = RK808_INT_STS_REG1, | |
259 | .init_ack_masked = true, | |
260 | }; | |
261 | ||
24e34b9d | 262 | static const struct regmap_irq_chip rk818_irq_chip = { |
2eedcbfc WE |
263 | .name = "rk818", |
264 | .irqs = rk818_irqs, | |
265 | .num_irqs = ARRAY_SIZE(rk818_irqs), | |
266 | .num_regs = 2, | |
267 | .irq_reg_stride = 2, | |
268 | .status_base = RK818_INT_STS_REG1, | |
269 | .mask_base = RK818_INT_STS_MSK_REG1, | |
270 | .ack_base = RK818_INT_STS_REG1, | |
271 | .init_ack_masked = true, | |
272 | }; | |
273 | ||
f69a7cf7 CZ |
274 | static struct i2c_client *rk808_i2c_client; |
275 | static void rk808_device_shutdown(void) | |
276 | { | |
277 | int ret; | |
278 | struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client); | |
279 | ||
280 | if (!rk808) { | |
281 | dev_warn(&rk808_i2c_client->dev, | |
282 | "have no rk808, so do nothing here\n"); | |
283 | return; | |
284 | } | |
285 | ||
286 | ret = regmap_update_bits(rk808->regmap, | |
287 | RK808_DEVCTRL_REG, | |
288 | DEV_OFF_RST, DEV_OFF_RST); | |
289 | if (ret) | |
290 | dev_err(&rk808_i2c_client->dev, "power off error!\n"); | |
291 | } | |
292 | ||
b2e2c850 JC |
293 | static void rk818_device_shutdown(void) |
294 | { | |
295 | int ret; | |
296 | struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client); | |
297 | ||
298 | if (!rk808) { | |
299 | dev_warn(&rk808_i2c_client->dev, | |
300 | "have no rk818, so do nothing here\n"); | |
301 | return; | |
302 | } | |
303 | ||
304 | ret = regmap_update_bits(rk808->regmap, | |
305 | RK818_DEVCTRL_REG, | |
306 | DEV_OFF, DEV_OFF); | |
307 | if (ret) | |
308 | dev_err(&rk808_i2c_client->dev, "power off error!\n"); | |
309 | } | |
310 | ||
2eedcbfc WE |
311 | static const struct of_device_id rk808_of_match[] = { |
312 | { .compatible = "rockchip,rk808" }, | |
313 | { .compatible = "rockchip,rk818" }, | |
314 | { }, | |
315 | }; | |
316 | MODULE_DEVICE_TABLE(of, rk808_of_match); | |
317 | ||
f69a7cf7 CZ |
318 | static int rk808_probe(struct i2c_client *client, |
319 | const struct i2c_device_id *id) | |
320 | { | |
321 | struct device_node *np = client->dev.of_node; | |
322 | struct rk808 *rk808; | |
2eedcbfc WE |
323 | const struct rk808_reg_data *pre_init_reg; |
324 | const struct mfd_cell *cells; | |
b2e2c850 | 325 | void (*pm_pwroff_fn)(void); |
2eedcbfc WE |
326 | int nr_pre_init_regs; |
327 | int nr_cells; | |
f69a7cf7 CZ |
328 | int pm_off = 0; |
329 | int ret; | |
330 | int i; | |
331 | ||
f69a7cf7 CZ |
332 | rk808 = devm_kzalloc(&client->dev, sizeof(*rk808), GFP_KERNEL); |
333 | if (!rk808) | |
334 | return -ENOMEM; | |
335 | ||
2eedcbfc WE |
336 | rk808->variant = i2c_smbus_read_word_data(client, RK808_ID_MSB); |
337 | if (rk808->variant < 0) { | |
338 | dev_err(&client->dev, "Failed to read the chip id at 0x%02x\n", | |
339 | RK808_ID_MSB); | |
340 | return rk808->variant; | |
341 | } | |
342 | ||
343 | dev_dbg(&client->dev, "Chip id: 0x%x\n", (unsigned int)rk808->variant); | |
344 | ||
345 | switch (rk808->variant) { | |
346 | case RK808_ID: | |
347 | rk808->regmap_cfg = &rk808_regmap_config; | |
348 | rk808->regmap_irq_chip = &rk808_irq_chip; | |
349 | pre_init_reg = rk808_pre_init_reg; | |
350 | nr_pre_init_regs = ARRAY_SIZE(rk808_pre_init_reg); | |
351 | cells = rk808s; | |
352 | nr_cells = ARRAY_SIZE(rk808s); | |
b2e2c850 | 353 | pm_pwroff_fn = rk808_device_shutdown; |
2eedcbfc WE |
354 | break; |
355 | case RK818_ID: | |
356 | rk808->regmap_cfg = &rk818_regmap_config; | |
357 | rk808->regmap_irq_chip = &rk818_irq_chip; | |
358 | pre_init_reg = rk818_pre_init_reg; | |
359 | nr_pre_init_regs = ARRAY_SIZE(rk818_pre_init_reg); | |
360 | cells = rk818s; | |
361 | nr_cells = ARRAY_SIZE(rk818s); | |
b2e2c850 | 362 | pm_pwroff_fn = rk818_device_shutdown; |
2eedcbfc WE |
363 | break; |
364 | default: | |
365 | dev_err(&client->dev, "Unsupported RK8XX ID %lu\n", | |
366 | rk808->variant); | |
367 | return -EINVAL; | |
368 | } | |
369 | ||
370 | rk808->i2c = client; | |
371 | i2c_set_clientdata(client, rk808); | |
372 | ||
373 | rk808->regmap = devm_regmap_init_i2c(client, rk808->regmap_cfg); | |
f69a7cf7 CZ |
374 | if (IS_ERR(rk808->regmap)) { |
375 | dev_err(&client->dev, "regmap initialization failed\n"); | |
376 | return PTR_ERR(rk808->regmap); | |
377 | } | |
378 | ||
2eedcbfc WE |
379 | if (!client->irq) { |
380 | dev_err(&client->dev, "No interrupt support, no core IRQ\n"); | |
381 | return -EINVAL; | |
f69a7cf7 CZ |
382 | } |
383 | ||
384 | ret = regmap_add_irq_chip(rk808->regmap, client->irq, | |
385 | IRQF_ONESHOT, -1, | |
2eedcbfc | 386 | rk808->regmap_irq_chip, &rk808->irq_data); |
f69a7cf7 CZ |
387 | if (ret) { |
388 | dev_err(&client->dev, "Failed to add irq_chip %d\n", ret); | |
389 | return ret; | |
390 | } | |
391 | ||
2eedcbfc WE |
392 | for (i = 0; i < nr_pre_init_regs; i++) { |
393 | ret = regmap_update_bits(rk808->regmap, | |
394 | pre_init_reg[i].addr, | |
395 | pre_init_reg[i].mask, | |
396 | pre_init_reg[i].value); | |
397 | if (ret) { | |
398 | dev_err(&client->dev, | |
399 | "0x%x write err\n", | |
400 | pre_init_reg[i].addr); | |
401 | return ret; | |
402 | } | |
403 | } | |
f69a7cf7 | 404 | |
2eedcbfc WE |
405 | ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, |
406 | cells, nr_cells, NULL, 0, | |
407 | regmap_irq_get_domain(rk808->irq_data)); | |
f69a7cf7 CZ |
408 | if (ret) { |
409 | dev_err(&client->dev, "failed to add MFD devices %d\n", ret); | |
410 | goto err_irq; | |
411 | } | |
412 | ||
413 | pm_off = of_property_read_bool(np, | |
414 | "rockchip,system-power-controller"); | |
415 | if (pm_off && !pm_power_off) { | |
416 | rk808_i2c_client = client; | |
b2e2c850 | 417 | pm_power_off = pm_pwroff_fn; |
f69a7cf7 CZ |
418 | } |
419 | ||
420 | return 0; | |
421 | ||
422 | err_irq: | |
423 | regmap_del_irq_chip(client->irq, rk808->irq_data); | |
424 | return ret; | |
425 | } | |
426 | ||
427 | static int rk808_remove(struct i2c_client *client) | |
428 | { | |
429 | struct rk808 *rk808 = i2c_get_clientdata(client); | |
430 | ||
431 | regmap_del_irq_chip(client->irq, rk808->irq_data); | |
f69a7cf7 CZ |
432 | pm_power_off = NULL; |
433 | ||
434 | return 0; | |
435 | } | |
436 | ||
f69a7cf7 CZ |
437 | static const struct i2c_device_id rk808_ids[] = { |
438 | { "rk808" }, | |
2eedcbfc | 439 | { "rk818" }, |
f69a7cf7 CZ |
440 | { }, |
441 | }; | |
442 | MODULE_DEVICE_TABLE(i2c, rk808_ids); | |
443 | ||
444 | static struct i2c_driver rk808_i2c_driver = { | |
445 | .driver = { | |
446 | .name = "rk808", | |
447 | .of_match_table = rk808_of_match, | |
448 | }, | |
449 | .probe = rk808_probe, | |
450 | .remove = rk808_remove, | |
451 | .id_table = rk808_ids, | |
452 | }; | |
453 | ||
454 | module_i2c_driver(rk808_i2c_driver); | |
455 | ||
456 | MODULE_LICENSE("GPL"); | |
457 | MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); | |
458 | MODULE_AUTHOR("Zhang Qing <zhangqing@rock-chips.com>"); | |
2eedcbfc WE |
459 | MODULE_AUTHOR("Wadim Egorov <w.egorov@phytec.de>"); |
460 | MODULE_DESCRIPTION("RK808/RK818 PMIC driver"); |