1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Copyright (C) 2018 BayLibre SAS |
4 | // Author: Bartosz Golaszewski <bgolaszewski@baylibre.com> |
5 | // |
6 | // Battery charger driver for MAXIM 77650/77651 charger/power-supply. |
7 | |
8 | #include <linux/i2c.h> |
9 | #include <linux/interrupt.h> |
10 | #include <linux/mfd/max77650.h> |
11 | #include <linux/module.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/power_supply.h> |
14 | #include <linux/regmap.h> |
15 | |
16 | #define MAX77650_CHARGER_ENABLED BIT(0) |
17 | #define MAX77650_CHARGER_DISABLED 0x00 |
18 | #define MAX77650_CHARGER_CHG_EN_MASK BIT(0) |
19 | |
20 | #define MAX77650_CHG_DETAILS_MASK GENMASK(7, 4) |
21 | #define MAX77650_CHG_DETAILS_BITS(_reg) \ |
22 | (((_reg) & MAX77650_CHG_DETAILS_MASK) >> 4) |
23 | |
24 | /* Charger is OFF. */ |
25 | #define MAX77650_CHG_OFF 0x00 |
26 | /* Charger is in prequalification mode. */ |
27 | #define MAX77650_CHG_PREQ 0x01 |
28 | /* Charger is in fast-charge constant current mode. */ |
29 | #define MAX77650_CHG_ON_CURR 0x02 |
30 | /* Charger is in JEITA modified fast-charge constant-current mode. */ |
31 | #define MAX77650_CHG_ON_CURR_JEITA 0x03 |
32 | /* Charger is in fast-charge constant-voltage mode. */ |
33 | #define MAX77650_CHG_ON_VOLT 0x04 |
34 | /* Charger is in JEITA modified fast-charge constant-voltage mode. */ |
35 | #define MAX77650_CHG_ON_VOLT_JEITA 0x05 |
36 | /* Charger is in top-off mode. */ |
37 | #define MAX77650_CHG_ON_TOPOFF 0x06 |
38 | /* Charger is in JEITA modified top-off mode. */ |
39 | #define MAX77650_CHG_ON_TOPOFF_JEITA 0x07 |
40 | /* Charger is done. */ |
41 | #define MAX77650_CHG_DONE 0x08 |
42 | /* Charger is JEITA modified done. */ |
43 | #define MAX77650_CHG_DONE_JEITA 0x09 |
44 | /* Charger is suspended due to a prequalification timer fault. */ |
45 | #define MAX77650_CHG_SUSP_PREQ_TIM_FAULT 0x0a |
46 | /* Charger is suspended due to a fast-charge timer fault. */ |
47 | #define MAX77650_CHG_SUSP_FAST_CHG_TIM_FAULT 0x0b |
48 | /* Charger is suspended due to a battery temperature fault. */ |
49 | #define MAX77650_CHG_SUSP_BATT_TEMP_FAULT 0x0c |
50 | |
51 | #define MAX77650_CHGIN_DETAILS_MASK GENMASK(3, 2) |
52 | #define MAX77650_CHGIN_DETAILS_BITS(_reg) \ |
53 | (((_reg) & MAX77650_CHGIN_DETAILS_MASK) >> 2) |
54 | |
55 | #define MAX77650_CHGIN_UNDERVOLTAGE_LOCKOUT 0x00 |
56 | #define MAX77650_CHGIN_OVERVOLTAGE_LOCKOUT 0x01 |
57 | #define MAX77650_CHGIN_OKAY 0x11 |
58 | |
59 | #define MAX77650_CHARGER_CHG_MASK BIT(1) |
60 | #define MAX77650_CHARGER_CHG_CHARGING(_reg) \ |
61 | (((_reg) & MAX77650_CHARGER_CHG_MASK) > 1) |
62 | |
63 | #define MAX77650_CHARGER_VCHGIN_MIN_MASK 0xc0 |
64 | #define MAX77650_CHARGER_VCHGIN_MIN_SHIFT(_val) ((_val) << 5) |
65 | |
66 | #define MAX77650_CHARGER_ICHGIN_LIM_MASK 0x1c |
67 | #define MAX77650_CHARGER_ICHGIN_LIM_SHIFT(_val) ((_val) << 2) |
68 | |
69 | struct max77650_charger_data { |
70 | struct regmap *map; |
71 | struct device *dev; |
72 | }; |
73 | |
74 | static enum power_supply_property max77650_charger_properties[] = { |
75 | POWER_SUPPLY_PROP_STATUS, |
76 | POWER_SUPPLY_PROP_ONLINE, |
77 | POWER_SUPPLY_PROP_CHARGE_TYPE |
78 | }; |
79 | |
80 | static const unsigned int max77650_charger_vchgin_min_table[] = { |
81 | 4000000, 4100000, 4200000, 4300000, 4400000, 4500000, 4600000, 4700000 |
82 | }; |
83 | |
84 | static const unsigned int max77650_charger_ichgin_lim_table[] = { |
85 | 95000, 190000, 285000, 380000, 475000 |
86 | }; |
87 | |
88 | static int max77650_charger_set_vchgin_min(struct max77650_charger_data *chg, |
89 | unsigned int val) |
90 | { |
91 | int i, rv; |
92 | |
93 | for (i = 0; i < ARRAY_SIZE(max77650_charger_vchgin_min_table); i++) { |
94 | if (val == max77650_charger_vchgin_min_table[i]) { |
95 | rv = regmap_update_bits(map: chg->map, |
96 | MAX77650_REG_CNFG_CHG_B, |
97 | MAX77650_CHARGER_VCHGIN_MIN_MASK, |
98 | MAX77650_CHARGER_VCHGIN_MIN_SHIFT(i)); |
99 | if (rv) |
100 | return rv; |
101 | |
102 | return 0; |
103 | } |
104 | } |
105 | |
106 | return -EINVAL; |
107 | } |
108 | |
109 | static int max77650_charger_set_ichgin_lim(struct max77650_charger_data *chg, |
110 | unsigned int val) |
111 | { |
112 | int i, rv; |
113 | |
114 | for (i = 0; i < ARRAY_SIZE(max77650_charger_ichgin_lim_table); i++) { |
115 | if (val == max77650_charger_ichgin_lim_table[i]) { |
116 | rv = regmap_update_bits(map: chg->map, |
117 | MAX77650_REG_CNFG_CHG_B, |
118 | MAX77650_CHARGER_ICHGIN_LIM_MASK, |
119 | MAX77650_CHARGER_ICHGIN_LIM_SHIFT(i)); |
120 | if (rv) |
121 | return rv; |
122 | |
123 | return 0; |
124 | } |
125 | } |
126 | |
127 | return -EINVAL; |
128 | } |
129 | |
130 | static int max77650_charger_enable(struct max77650_charger_data *chg) |
131 | { |
132 | int rv; |
133 | |
134 | rv = regmap_update_bits(map: chg->map, |
135 | MAX77650_REG_CNFG_CHG_B, |
136 | MAX77650_CHARGER_CHG_EN_MASK, |
137 | MAX77650_CHARGER_ENABLED); |
138 | if (rv) |
139 | dev_err(chg->dev, "unable to enable the charger: %d\n" , rv); |
140 | |
141 | return rv; |
142 | } |
143 | |
144 | static void max77650_charger_disable(struct max77650_charger_data *chg) |
145 | { |
146 | int rv; |
147 | |
148 | rv = regmap_update_bits(map: chg->map, |
149 | MAX77650_REG_CNFG_CHG_B, |
150 | MAX77650_CHARGER_CHG_EN_MASK, |
151 | MAX77650_CHARGER_DISABLED); |
152 | if (rv) |
153 | dev_err(chg->dev, "unable to disable the charger: %d\n" , rv); |
154 | } |
155 | |
156 | static irqreturn_t max77650_charger_check_status(int irq, void *data) |
157 | { |
158 | struct max77650_charger_data *chg = data; |
159 | int rv, reg; |
160 | |
161 | rv = regmap_read(map: chg->map, MAX77650_REG_STAT_CHG_B, val: ®); |
162 | if (rv) { |
163 | dev_err(chg->dev, |
164 | "unable to read the charger status: %d\n" , rv); |
165 | return IRQ_HANDLED; |
166 | } |
167 | |
168 | switch (MAX77650_CHGIN_DETAILS_BITS(reg)) { |
169 | case MAX77650_CHGIN_UNDERVOLTAGE_LOCKOUT: |
170 | dev_err(chg->dev, "undervoltage lockout detected, disabling charger\n" ); |
171 | max77650_charger_disable(chg); |
172 | break; |
173 | case MAX77650_CHGIN_OVERVOLTAGE_LOCKOUT: |
174 | dev_err(chg->dev, "overvoltage lockout detected, disabling charger\n" ); |
175 | max77650_charger_disable(chg); |
176 | break; |
177 | case MAX77650_CHGIN_OKAY: |
178 | max77650_charger_enable(chg); |
179 | break; |
180 | default: |
181 | /* May be 0x10 - debouncing */ |
182 | break; |
183 | } |
184 | |
185 | return IRQ_HANDLED; |
186 | } |
187 | |
188 | static int max77650_charger_get_property(struct power_supply *psy, |
189 | enum power_supply_property psp, |
190 | union power_supply_propval *val) |
191 | { |
192 | struct max77650_charger_data *chg = power_supply_get_drvdata(psy); |
193 | int rv, reg; |
194 | |
195 | switch (psp) { |
196 | case POWER_SUPPLY_PROP_STATUS: |
197 | rv = regmap_read(map: chg->map, MAX77650_REG_STAT_CHG_B, val: ®); |
198 | if (rv) |
199 | return rv; |
200 | |
201 | if (MAX77650_CHARGER_CHG_CHARGING(reg)) { |
202 | val->intval = POWER_SUPPLY_STATUS_CHARGING; |
203 | break; |
204 | } |
205 | |
206 | switch (MAX77650_CHG_DETAILS_BITS(reg)) { |
207 | case MAX77650_CHG_OFF: |
208 | case MAX77650_CHG_SUSP_PREQ_TIM_FAULT: |
209 | case MAX77650_CHG_SUSP_FAST_CHG_TIM_FAULT: |
210 | case MAX77650_CHG_SUSP_BATT_TEMP_FAULT: |
211 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; |
212 | break; |
213 | case MAX77650_CHG_PREQ: |
214 | case MAX77650_CHG_ON_CURR: |
215 | case MAX77650_CHG_ON_CURR_JEITA: |
216 | case MAX77650_CHG_ON_VOLT: |
217 | case MAX77650_CHG_ON_VOLT_JEITA: |
218 | case MAX77650_CHG_ON_TOPOFF: |
219 | case MAX77650_CHG_ON_TOPOFF_JEITA: |
220 | val->intval = POWER_SUPPLY_STATUS_CHARGING; |
221 | break; |
222 | case MAX77650_CHG_DONE: |
223 | val->intval = POWER_SUPPLY_STATUS_FULL; |
224 | break; |
225 | default: |
226 | val->intval = POWER_SUPPLY_STATUS_UNKNOWN; |
227 | } |
228 | break; |
229 | case POWER_SUPPLY_PROP_ONLINE: |
230 | rv = regmap_read(map: chg->map, MAX77650_REG_STAT_CHG_B, val: ®); |
231 | if (rv) |
232 | return rv; |
233 | |
234 | val->intval = MAX77650_CHARGER_CHG_CHARGING(reg); |
235 | break; |
236 | case POWER_SUPPLY_PROP_CHARGE_TYPE: |
237 | rv = regmap_read(map: chg->map, MAX77650_REG_STAT_CHG_B, val: ®); |
238 | if (rv) |
239 | return rv; |
240 | |
241 | if (!MAX77650_CHARGER_CHG_CHARGING(reg)) { |
242 | val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; |
243 | break; |
244 | } |
245 | |
246 | switch (MAX77650_CHG_DETAILS_BITS(reg)) { |
247 | case MAX77650_CHG_PREQ: |
248 | case MAX77650_CHG_ON_CURR: |
249 | case MAX77650_CHG_ON_CURR_JEITA: |
250 | case MAX77650_CHG_ON_VOLT: |
251 | case MAX77650_CHG_ON_VOLT_JEITA: |
252 | val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; |
253 | break; |
254 | case MAX77650_CHG_ON_TOPOFF: |
255 | case MAX77650_CHG_ON_TOPOFF_JEITA: |
256 | val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
257 | break; |
258 | default: |
259 | val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; |
260 | } |
261 | break; |
262 | default: |
263 | return -EINVAL; |
264 | } |
265 | |
266 | return 0; |
267 | } |
268 | |
269 | static const struct power_supply_desc max77650_battery_desc = { |
270 | .name = "max77650" , |
271 | .type = POWER_SUPPLY_TYPE_USB, |
272 | .get_property = max77650_charger_get_property, |
273 | .properties = max77650_charger_properties, |
274 | .num_properties = ARRAY_SIZE(max77650_charger_properties), |
275 | }; |
276 | |
277 | static int max77650_charger_probe(struct platform_device *pdev) |
278 | { |
279 | struct power_supply_config pscfg = {}; |
280 | struct max77650_charger_data *chg; |
281 | struct power_supply *battery; |
282 | struct device *dev, *parent; |
283 | int rv, chg_irq, chgin_irq; |
284 | unsigned int prop; |
285 | |
286 | dev = &pdev->dev; |
287 | parent = dev->parent; |
288 | |
289 | chg = devm_kzalloc(dev, size: sizeof(*chg), GFP_KERNEL); |
290 | if (!chg) |
291 | return -ENOMEM; |
292 | |
293 | platform_set_drvdata(pdev, data: chg); |
294 | |
295 | chg->map = dev_get_regmap(dev: parent, NULL); |
296 | if (!chg->map) |
297 | return -ENODEV; |
298 | |
299 | chg->dev = dev; |
300 | |
301 | pscfg.of_node = dev->of_node; |
302 | pscfg.drv_data = chg; |
303 | |
304 | chg_irq = platform_get_irq_byname(pdev, "CHG" ); |
305 | if (chg_irq < 0) |
306 | return chg_irq; |
307 | |
308 | chgin_irq = platform_get_irq_byname(pdev, "CHGIN" ); |
309 | if (chgin_irq < 0) |
310 | return chgin_irq; |
311 | |
312 | rv = devm_request_any_context_irq(dev, irq: chg_irq, |
313 | handler: max77650_charger_check_status, |
314 | IRQF_ONESHOT, devname: "chg" , dev_id: chg); |
315 | if (rv < 0) |
316 | return rv; |
317 | |
318 | rv = devm_request_any_context_irq(dev, irq: chgin_irq, |
319 | handler: max77650_charger_check_status, |
320 | IRQF_ONESHOT, devname: "chgin" , dev_id: chg); |
321 | if (rv < 0) |
322 | return rv; |
323 | |
324 | battery = devm_power_supply_register(parent: dev, |
325 | desc: &max77650_battery_desc, cfg: &pscfg); |
326 | if (IS_ERR(ptr: battery)) |
327 | return PTR_ERR(ptr: battery); |
328 | |
329 | rv = of_property_read_u32(np: dev->of_node, |
330 | propname: "input-voltage-min-microvolt" , out_value: &prop); |
331 | if (rv == 0) { |
332 | rv = max77650_charger_set_vchgin_min(chg, val: prop); |
333 | if (rv) |
334 | return rv; |
335 | } |
336 | |
337 | rv = of_property_read_u32(np: dev->of_node, |
338 | propname: "input-current-limit-microamp" , out_value: &prop); |
339 | if (rv == 0) { |
340 | rv = max77650_charger_set_ichgin_lim(chg, val: prop); |
341 | if (rv) |
342 | return rv; |
343 | } |
344 | |
345 | return max77650_charger_enable(chg); |
346 | } |
347 | |
348 | static void max77650_charger_remove(struct platform_device *pdev) |
349 | { |
350 | struct max77650_charger_data *chg = platform_get_drvdata(pdev); |
351 | |
352 | max77650_charger_disable(chg); |
353 | } |
354 | |
355 | static const struct of_device_id max77650_charger_of_match[] = { |
356 | { .compatible = "maxim,max77650-charger" }, |
357 | { } |
358 | }; |
359 | MODULE_DEVICE_TABLE(of, max77650_charger_of_match); |
360 | |
361 | static struct platform_driver max77650_charger_driver = { |
362 | .driver = { |
363 | .name = "max77650-charger" , |
364 | .of_match_table = max77650_charger_of_match, |
365 | }, |
366 | .probe = max77650_charger_probe, |
367 | .remove_new = max77650_charger_remove, |
368 | }; |
369 | module_platform_driver(max77650_charger_driver); |
370 | |
371 | MODULE_DESCRIPTION("MAXIM 77650/77651 charger driver" ); |
372 | MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>" ); |
373 | MODULE_LICENSE("GPL v2" ); |
374 | MODULE_ALIAS("platform:max77650-charger" ); |
375 | |