diff options
author | Adam Thomson <Adam.Thomson.Opensource@diasemi.com> | 2014-12-22 11:51:06 -0500 |
---|---|---|
committer | Lee Jones <lee.jones@linaro.org> | 2015-01-22 10:55:54 -0500 |
commit | b8fce55c09d3327014191247957d875c9fe5b7ce (patch) | |
tree | e61bb0fa8078418bce1b1ddf8512c555dab02cf5 /drivers | |
parent | 1ae68f95de1e18751ff2830c361c173e8ae677d8 (diff) |
mfd: Add support for DA9150 combined charger & fuel-gauge device
DA9150 is a combined Charger and Fuel-Gauge IC, with additional
GPIO and GPADC functionality.
Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
Signed-off-by: Lee Jones <lee.jones@linaro.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mfd/Kconfig | 12 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 2 | ||||
-rw-r--r-- | drivers/mfd/da9150-core.c | 413 |
3 files changed, 426 insertions, 1 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 9cd2af660cc5..c47b2da7986e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig | |||
@@ -195,6 +195,18 @@ config MFD_DA9063 | |||
195 | Additional drivers must be enabled in order to use the functionality | 195 | Additional drivers must be enabled in order to use the functionality |
196 | of the device. | 196 | of the device. |
197 | 197 | ||
198 | config MFD_DA9150 | ||
199 | tristate "Dialog Semiconductor DA9150 Charger Fuel-Gauge chip" | ||
200 | depends on I2C=y | ||
201 | select MFD_CORE | ||
202 | select REGMAP_I2C | ||
203 | select REGMAP_IRQ | ||
204 | help | ||
205 | This adds support for the DA9150 integrated charger and fuel-gauge | ||
206 | chip. This driver provides common support for accessing the device. | ||
207 | Additional drivers must be enabled in order to use the specific | ||
208 | features of the device. | ||
209 | |||
198 | config MFD_DLN2 | 210 | config MFD_DLN2 |
199 | tristate "Diolan DLN2 support" | 211 | tristate "Diolan DLN2 support" |
200 | select MFD_CORE | 212 | select MFD_CORE |
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 4059c247c59a..02c2f2c69b94 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile | |||
@@ -113,7 +113,7 @@ obj-$(CONFIG_MFD_DA9055) += da9055.o | |||
113 | 113 | ||
114 | da9063-objs := da9063-core.o da9063-irq.o da9063-i2c.o | 114 | da9063-objs := da9063-core.o da9063-irq.o da9063-i2c.o |
115 | obj-$(CONFIG_MFD_DA9063) += da9063.o | 115 | obj-$(CONFIG_MFD_DA9063) += da9063.o |
116 | 116 | obj-$(CONFIG_MFD_DA9150) += da9150-core.o | |
117 | obj-$(CONFIG_MFD_MAX14577) += max14577.o | 117 | obj-$(CONFIG_MFD_MAX14577) += max14577.o |
118 | obj-$(CONFIG_MFD_MAX77686) += max77686.o | 118 | obj-$(CONFIG_MFD_MAX77686) += max77686.o |
119 | obj-$(CONFIG_MFD_MAX77693) += max77693.o | 119 | obj-$(CONFIG_MFD_MAX77693) += max77693.o |
diff --git a/drivers/mfd/da9150-core.c b/drivers/mfd/da9150-core.c new file mode 100644 index 000000000000..4d757b97ef9a --- /dev/null +++ b/drivers/mfd/da9150-core.c | |||
@@ -0,0 +1,413 @@ | |||
1 | /* | ||
2 | * DA9150 Core MFD Driver | ||
3 | * | ||
4 | * Copyright (c) 2014 Dialog Semiconductor | ||
5 | * | ||
6 | * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms of the GNU General Public License as published by the | ||
10 | * Free Software Foundation; either version 2 of the License, or (at your | ||
11 | * option) any later version. | ||
12 | */ | ||
13 | |||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/module.h> | ||
16 | #include <linux/platform_device.h> | ||
17 | #include <linux/i2c.h> | ||
18 | #include <linux/regmap.h> | ||
19 | #include <linux/slab.h> | ||
20 | #include <linux/irq.h> | ||
21 | #include <linux/interrupt.h> | ||
22 | #include <linux/mfd/core.h> | ||
23 | #include <linux/mfd/da9150/core.h> | ||
24 | #include <linux/mfd/da9150/registers.h> | ||
25 | |||
26 | static bool da9150_volatile_reg(struct device *dev, unsigned int reg) | ||
27 | { | ||
28 | switch (reg) { | ||
29 | case DA9150_PAGE_CON: | ||
30 | case DA9150_STATUS_A: | ||
31 | case DA9150_STATUS_B: | ||
32 | case DA9150_STATUS_C: | ||
33 | case DA9150_STATUS_D: | ||
34 | case DA9150_STATUS_E: | ||
35 | case DA9150_STATUS_F: | ||
36 | case DA9150_STATUS_G: | ||
37 | case DA9150_STATUS_H: | ||
38 | case DA9150_STATUS_I: | ||
39 | case DA9150_STATUS_J: | ||
40 | case DA9150_STATUS_K: | ||
41 | case DA9150_STATUS_L: | ||
42 | case DA9150_STATUS_N: | ||
43 | case DA9150_FAULT_LOG_A: | ||
44 | case DA9150_FAULT_LOG_B: | ||
45 | case DA9150_EVENT_E: | ||
46 | case DA9150_EVENT_F: | ||
47 | case DA9150_EVENT_G: | ||
48 | case DA9150_EVENT_H: | ||
49 | case DA9150_CONTROL_B: | ||
50 | case DA9150_CONTROL_C: | ||
51 | case DA9150_GPADC_MAN: | ||
52 | case DA9150_GPADC_RES_A: | ||
53 | case DA9150_GPADC_RES_B: | ||
54 | case DA9150_ADETVB_CFG_C: | ||
55 | case DA9150_ADETD_STAT: | ||
56 | case DA9150_ADET_CMPSTAT: | ||
57 | case DA9150_ADET_CTRL_A: | ||
58 | case DA9150_PPR_TCTR_B: | ||
59 | case DA9150_COREBTLD_STAT_A: | ||
60 | case DA9150_CORE_DATA_A: | ||
61 | case DA9150_CORE_DATA_B: | ||
62 | case DA9150_CORE_DATA_C: | ||
63 | case DA9150_CORE_DATA_D: | ||
64 | case DA9150_CORE2WIRE_STAT_A: | ||
65 | case DA9150_FW_CTRL_C: | ||
66 | case DA9150_FG_CTRL_B: | ||
67 | case DA9150_FW_CTRL_B: | ||
68 | case DA9150_GPADC_CMAN: | ||
69 | case DA9150_GPADC_CRES_A: | ||
70 | case DA9150_GPADC_CRES_B: | ||
71 | case DA9150_CC_ICHG_RES_A: | ||
72 | case DA9150_CC_ICHG_RES_B: | ||
73 | case DA9150_CC_IAVG_RES_A: | ||
74 | case DA9150_CC_IAVG_RES_B: | ||
75 | case DA9150_TAUX_CTRL_A: | ||
76 | case DA9150_TAUX_VALUE_H: | ||
77 | case DA9150_TAUX_VALUE_L: | ||
78 | case DA9150_TBAT_RES_A: | ||
79 | case DA9150_TBAT_RES_B: | ||
80 | return true; | ||
81 | default: | ||
82 | return false; | ||
83 | } | ||
84 | } | ||
85 | |||
86 | static const struct regmap_range_cfg da9150_range_cfg[] = { | ||
87 | { | ||
88 | .range_min = DA9150_PAGE_CON, | ||
89 | .range_max = DA9150_TBAT_RES_B, | ||
90 | .selector_reg = DA9150_PAGE_CON, | ||
91 | .selector_mask = DA9150_I2C_PAGE_MASK, | ||
92 | .selector_shift = DA9150_I2C_PAGE_SHIFT, | ||
93 | .window_start = 0, | ||
94 | .window_len = 256, | ||
95 | }, | ||
96 | }; | ||
97 | |||
98 | static struct regmap_config da9150_regmap_config = { | ||
99 | .reg_bits = 8, | ||
100 | .val_bits = 8, | ||
101 | .ranges = da9150_range_cfg, | ||
102 | .num_ranges = ARRAY_SIZE(da9150_range_cfg), | ||
103 | .max_register = DA9150_TBAT_RES_B, | ||
104 | |||
105 | .cache_type = REGCACHE_RBTREE, | ||
106 | |||
107 | .volatile_reg = da9150_volatile_reg, | ||
108 | }; | ||
109 | |||
110 | u8 da9150_reg_read(struct da9150 *da9150, u16 reg) | ||
111 | { | ||
112 | int val, ret; | ||
113 | |||
114 | ret = regmap_read(da9150->regmap, reg, &val); | ||
115 | if (ret) | ||
116 | dev_err(da9150->dev, "Failed to read from reg 0x%x: %d\n", | ||
117 | reg, ret); | ||
118 | |||
119 | return (u8) val; | ||
120 | } | ||
121 | EXPORT_SYMBOL_GPL(da9150_reg_read); | ||
122 | |||
123 | void da9150_reg_write(struct da9150 *da9150, u16 reg, u8 val) | ||
124 | { | ||
125 | int ret; | ||
126 | |||
127 | ret = regmap_write(da9150->regmap, reg, val); | ||
128 | if (ret) | ||
129 | dev_err(da9150->dev, "Failed to write to reg 0x%x: %d\n", | ||
130 | reg, ret); | ||
131 | } | ||
132 | EXPORT_SYMBOL_GPL(da9150_reg_write); | ||
133 | |||
134 | void da9150_set_bits(struct da9150 *da9150, u16 reg, u8 mask, u8 val) | ||
135 | { | ||
136 | int ret; | ||
137 | |||
138 | ret = regmap_update_bits(da9150->regmap, reg, mask, val); | ||
139 | if (ret) | ||
140 | dev_err(da9150->dev, "Failed to set bits in reg 0x%x: %d\n", | ||
141 | reg, ret); | ||
142 | } | ||
143 | EXPORT_SYMBOL_GPL(da9150_set_bits); | ||
144 | |||
145 | void da9150_bulk_read(struct da9150 *da9150, u16 reg, int count, u8 *buf) | ||
146 | { | ||
147 | int ret; | ||
148 | |||
149 | ret = regmap_bulk_read(da9150->regmap, reg, buf, count); | ||
150 | if (ret) | ||
151 | dev_err(da9150->dev, "Failed to bulk read from reg 0x%x: %d\n", | ||
152 | reg, ret); | ||
153 | } | ||
154 | EXPORT_SYMBOL_GPL(da9150_bulk_read); | ||
155 | |||
156 | void da9150_bulk_write(struct da9150 *da9150, u16 reg, int count, const u8 *buf) | ||
157 | { | ||
158 | int ret; | ||
159 | |||
160 | ret = regmap_raw_write(da9150->regmap, reg, buf, count); | ||
161 | if (ret) | ||
162 | dev_err(da9150->dev, "Failed to bulk write to reg 0x%x %d\n", | ||
163 | reg, ret); | ||
164 | } | ||
165 | EXPORT_SYMBOL_GPL(da9150_bulk_write); | ||
166 | |||
167 | static struct regmap_irq da9150_irqs[] = { | ||
168 | [DA9150_IRQ_VBUS] = { | ||
169 | .reg_offset = 0, | ||
170 | .mask = DA9150_E_VBUS_MASK, | ||
171 | }, | ||
172 | [DA9150_IRQ_CHG] = { | ||
173 | .reg_offset = 0, | ||
174 | .mask = DA9150_E_CHG_MASK, | ||
175 | }, | ||
176 | [DA9150_IRQ_TCLASS] = { | ||
177 | .reg_offset = 0, | ||
178 | .mask = DA9150_E_TCLASS_MASK, | ||
179 | }, | ||
180 | [DA9150_IRQ_TJUNC] = { | ||
181 | .reg_offset = 0, | ||
182 | .mask = DA9150_E_TJUNC_MASK, | ||
183 | }, | ||
184 | [DA9150_IRQ_VFAULT] = { | ||
185 | .reg_offset = 0, | ||
186 | .mask = DA9150_E_VFAULT_MASK, | ||
187 | }, | ||
188 | [DA9150_IRQ_CONF] = { | ||
189 | .reg_offset = 1, | ||
190 | .mask = DA9150_E_CONF_MASK, | ||
191 | }, | ||
192 | [DA9150_IRQ_DAT] = { | ||
193 | .reg_offset = 1, | ||
194 | .mask = DA9150_E_DAT_MASK, | ||
195 | }, | ||
196 | [DA9150_IRQ_DTYPE] = { | ||
197 | .reg_offset = 1, | ||
198 | .mask = DA9150_E_DTYPE_MASK, | ||
199 | }, | ||
200 | [DA9150_IRQ_ID] = { | ||
201 | .reg_offset = 1, | ||
202 | .mask = DA9150_E_ID_MASK, | ||
203 | }, | ||
204 | [DA9150_IRQ_ADP] = { | ||
205 | .reg_offset = 1, | ||
206 | .mask = DA9150_E_ADP_MASK, | ||
207 | }, | ||
208 | [DA9150_IRQ_SESS_END] = { | ||
209 | .reg_offset = 1, | ||
210 | .mask = DA9150_E_SESS_END_MASK, | ||
211 | }, | ||
212 | [DA9150_IRQ_SESS_VLD] = { | ||
213 | .reg_offset = 1, | ||
214 | .mask = DA9150_E_SESS_VLD_MASK, | ||
215 | }, | ||
216 | [DA9150_IRQ_FG] = { | ||
217 | .reg_offset = 2, | ||
218 | .mask = DA9150_E_FG_MASK, | ||
219 | }, | ||
220 | [DA9150_IRQ_GP] = { | ||
221 | .reg_offset = 2, | ||
222 | .mask = DA9150_E_GP_MASK, | ||
223 | }, | ||
224 | [DA9150_IRQ_TBAT] = { | ||
225 | .reg_offset = 2, | ||
226 | .mask = DA9150_E_TBAT_MASK, | ||
227 | }, | ||
228 | [DA9150_IRQ_GPIOA] = { | ||
229 | .reg_offset = 2, | ||
230 | .mask = DA9150_E_GPIOA_MASK, | ||
231 | }, | ||
232 | [DA9150_IRQ_GPIOB] = { | ||
233 | .reg_offset = 2, | ||
234 | .mask = DA9150_E_GPIOB_MASK, | ||
235 | }, | ||
236 | [DA9150_IRQ_GPIOC] = { | ||
237 | .reg_offset = 2, | ||
238 | .mask = DA9150_E_GPIOC_MASK, | ||
239 | }, | ||
240 | [DA9150_IRQ_GPIOD] = { | ||
241 | .reg_offset = 2, | ||
242 | .mask = DA9150_E_GPIOD_MASK, | ||
243 | }, | ||
244 | [DA9150_IRQ_GPADC] = { | ||
245 | .reg_offset = 2, | ||
246 | .mask = DA9150_E_GPADC_MASK, | ||
247 | }, | ||
248 | [DA9150_IRQ_WKUP] = { | ||
249 | .reg_offset = 3, | ||
250 | .mask = DA9150_E_WKUP_MASK, | ||
251 | }, | ||
252 | }; | ||
253 | |||
254 | static struct regmap_irq_chip da9150_regmap_irq_chip = { | ||
255 | .name = "da9150_irq", | ||
256 | .status_base = DA9150_EVENT_E, | ||
257 | .mask_base = DA9150_IRQ_MASK_E, | ||
258 | .ack_base = DA9150_EVENT_E, | ||
259 | .num_regs = DA9150_NUM_IRQ_REGS, | ||
260 | .irqs = da9150_irqs, | ||
261 | .num_irqs = ARRAY_SIZE(da9150_irqs), | ||
262 | }; | ||
263 | |||
264 | static struct resource da9150_gpadc_resources[] = { | ||
265 | { | ||
266 | .name = "GPADC", | ||
267 | .start = DA9150_IRQ_GPADC, | ||
268 | .end = DA9150_IRQ_GPADC, | ||
269 | .flags = IORESOURCE_IRQ, | ||
270 | }, | ||
271 | }; | ||
272 | |||
273 | static struct resource da9150_charger_resources[] = { | ||
274 | { | ||
275 | .name = "CHG_STATUS", | ||
276 | .start = DA9150_IRQ_CHG, | ||
277 | .end = DA9150_IRQ_CHG, | ||
278 | .flags = IORESOURCE_IRQ, | ||
279 | }, | ||
280 | { | ||
281 | .name = "CHG_TJUNC", | ||
282 | .start = DA9150_IRQ_TJUNC, | ||
283 | .end = DA9150_IRQ_TJUNC, | ||
284 | .flags = IORESOURCE_IRQ, | ||
285 | }, | ||
286 | { | ||
287 | .name = "CHG_VFAULT", | ||
288 | .start = DA9150_IRQ_VFAULT, | ||
289 | .end = DA9150_IRQ_VFAULT, | ||
290 | .flags = IORESOURCE_IRQ, | ||
291 | }, | ||
292 | { | ||
293 | .name = "CHG_VBUS", | ||
294 | .start = DA9150_IRQ_VBUS, | ||
295 | .end = DA9150_IRQ_VBUS, | ||
296 | .flags = IORESOURCE_IRQ, | ||
297 | }, | ||
298 | }; | ||
299 | |||
300 | static struct mfd_cell da9150_devs[] = { | ||
301 | { | ||
302 | .name = "da9150-gpadc", | ||
303 | .of_compatible = "dlg,da9150-gpadc", | ||
304 | .resources = da9150_gpadc_resources, | ||
305 | .num_resources = ARRAY_SIZE(da9150_gpadc_resources), | ||
306 | }, | ||
307 | { | ||
308 | .name = "da9150-charger", | ||
309 | .of_compatible = "dlg,da9150-charger", | ||
310 | .resources = da9150_charger_resources, | ||
311 | .num_resources = ARRAY_SIZE(da9150_charger_resources), | ||
312 | }, | ||
313 | }; | ||
314 | |||
315 | static int da9150_probe(struct i2c_client *client, | ||
316 | const struct i2c_device_id *id) | ||
317 | { | ||
318 | struct da9150 *da9150; | ||
319 | struct da9150_pdata *pdata = dev_get_platdata(&client->dev); | ||
320 | int ret; | ||
321 | |||
322 | da9150 = devm_kzalloc(&client->dev, sizeof(*da9150), GFP_KERNEL); | ||
323 | if (!da9150) | ||
324 | return -ENOMEM; | ||
325 | |||
326 | da9150->dev = &client->dev; | ||
327 | da9150->irq = client->irq; | ||
328 | i2c_set_clientdata(client, da9150); | ||
329 | |||
330 | da9150->regmap = devm_regmap_init_i2c(client, &da9150_regmap_config); | ||
331 | if (IS_ERR(da9150->regmap)) { | ||
332 | ret = PTR_ERR(da9150->regmap); | ||
333 | dev_err(da9150->dev, "Failed to allocate register map: %d\n", | ||
334 | ret); | ||
335 | return ret; | ||
336 | } | ||
337 | |||
338 | da9150->irq_base = pdata ? pdata->irq_base : -1; | ||
339 | |||
340 | ret = regmap_add_irq_chip(da9150->regmap, da9150->irq, | ||
341 | IRQF_TRIGGER_LOW | IRQF_ONESHOT, | ||
342 | da9150->irq_base, &da9150_regmap_irq_chip, | ||
343 | &da9150->regmap_irq_data); | ||
344 | if (ret) | ||
345 | return ret; | ||
346 | |||
347 | da9150->irq_base = regmap_irq_chip_get_base(da9150->regmap_irq_data); | ||
348 | enable_irq_wake(da9150->irq); | ||
349 | |||
350 | ret = mfd_add_devices(da9150->dev, -1, da9150_devs, | ||
351 | ARRAY_SIZE(da9150_devs), NULL, | ||
352 | da9150->irq_base, NULL); | ||
353 | if (ret) { | ||
354 | dev_err(da9150->dev, "Failed to add child devices: %d\n", ret); | ||
355 | regmap_del_irq_chip(da9150->irq, da9150->regmap_irq_data); | ||
356 | return ret; | ||
357 | } | ||
358 | |||
359 | return 0; | ||
360 | } | ||
361 | |||
362 | static int da9150_remove(struct i2c_client *client) | ||
363 | { | ||
364 | struct da9150 *da9150 = i2c_get_clientdata(client); | ||
365 | |||
366 | regmap_del_irq_chip(da9150->irq, da9150->regmap_irq_data); | ||
367 | mfd_remove_devices(da9150->dev); | ||
368 | |||
369 | return 0; | ||
370 | } | ||
371 | |||
372 | static void da9150_shutdown(struct i2c_client *client) | ||
373 | { | ||
374 | struct da9150 *da9150 = i2c_get_clientdata(client); | ||
375 | |||
376 | /* Make sure we have a wakup source for the device */ | ||
377 | da9150_set_bits(da9150, DA9150_CONFIG_D, | ||
378 | DA9150_WKUP_PM_EN_MASK, | ||
379 | DA9150_WKUP_PM_EN_MASK); | ||
380 | |||
381 | /* Set device to DISABLED mode */ | ||
382 | da9150_set_bits(da9150, DA9150_CONTROL_C, | ||
383 | DA9150_DISABLE_MASK, DA9150_DISABLE_MASK); | ||
384 | } | ||
385 | |||
386 | static const struct i2c_device_id da9150_i2c_id[] = { | ||
387 | { "da9150", }, | ||
388 | { } | ||
389 | }; | ||
390 | MODULE_DEVICE_TABLE(i2c, da9150_i2c_id); | ||
391 | |||
392 | static const struct of_device_id da9150_of_match[] = { | ||
393 | { .compatible = "dlg,da9150", }, | ||
394 | { } | ||
395 | }; | ||
396 | MODULE_DEVICE_TABLE(of, da9150_of_match); | ||
397 | |||
398 | static struct i2c_driver da9150_driver = { | ||
399 | .driver = { | ||
400 | .name = "da9150", | ||
401 | .of_match_table = of_match_ptr(da9150_of_match), | ||
402 | }, | ||
403 | .probe = da9150_probe, | ||
404 | .remove = da9150_remove, | ||
405 | .shutdown = da9150_shutdown, | ||
406 | .id_table = da9150_i2c_id, | ||
407 | }; | ||
408 | |||
409 | module_i2c_driver(da9150_driver); | ||
410 | |||
411 | MODULE_DESCRIPTION("MFD Core Driver for DA9150"); | ||
412 | MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>"); | ||
413 | MODULE_LICENSE("GPL"); | ||