1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* NXP PCF50633 Main Battery Charger Driver |
3 | * |
4 | * (C) 2006-2008 by Openmoko, Inc. |
5 | * Author: Balaji Rao <balajirrao@openmoko.org> |
6 | * All rights reserved. |
7 | * |
8 | * Broken down from monstrous PCF50633 driver mainly by |
9 | * Harald Welte, Andy Green and Werner Almesberger |
10 | */ |
11 | |
12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/init.h> |
16 | #include <linux/types.h> |
17 | #include <linux/device.h> |
18 | #include <linux/sysfs.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/power_supply.h> |
21 | |
22 | #include <linux/mfd/pcf50633/core.h> |
23 | #include <linux/mfd/pcf50633/mbc.h> |
24 | |
25 | struct pcf50633_mbc { |
26 | struct pcf50633 *pcf; |
27 | |
28 | int adapter_online; |
29 | int usb_online; |
30 | |
31 | struct power_supply *usb; |
32 | struct power_supply *adapter; |
33 | struct power_supply *ac; |
34 | }; |
35 | |
36 | int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) |
37 | { |
38 | struct pcf50633_mbc *mbc = platform_get_drvdata(pdev: pcf->mbc_pdev); |
39 | int ret = 0; |
40 | u8 bits; |
41 | u8 mbcs2, chgmod; |
42 | unsigned int mbcc5; |
43 | |
44 | if (ma >= 1000) { |
45 | bits = PCF50633_MBCC7_USB_1000mA; |
46 | ma = 1000; |
47 | } else if (ma >= 500) { |
48 | bits = PCF50633_MBCC7_USB_500mA; |
49 | ma = 500; |
50 | } else if (ma >= 100) { |
51 | bits = PCF50633_MBCC7_USB_100mA; |
52 | ma = 100; |
53 | } else { |
54 | bits = PCF50633_MBCC7_USB_SUSPEND; |
55 | ma = 0; |
56 | } |
57 | |
58 | ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, |
59 | PCF50633_MBCC7_USB_MASK, val: bits); |
60 | if (ret) |
61 | dev_err(pcf->dev, "error setting usb curlim to %d mA\n" , ma); |
62 | else |
63 | dev_info(pcf->dev, "usb curlim to %d mA\n" , ma); |
64 | |
65 | /* |
66 | * We limit the charging current to be the USB current limit. |
67 | * The reason is that on pcf50633, when it enters PMU Standby mode, |
68 | * which it does when the device goes "off", the USB current limit |
69 | * reverts to the variant default. In at least one common case, that |
70 | * default is 500mA. By setting the charging current to be the same |
71 | * as the USB limit we set here before PMU standby, we enforce it only |
72 | * using the correct amount of current even when the USB current limit |
73 | * gets reset to the wrong thing |
74 | */ |
75 | |
76 | if (mbc->pcf->pdata->charger_reference_current_ma) { |
77 | mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma; |
78 | if (mbcc5 > 255) |
79 | mbcc5 = 255; |
80 | pcf50633_reg_write(pcf: mbc->pcf, PCF50633_REG_MBCC5, val: mbcc5); |
81 | } |
82 | |
83 | mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); |
84 | chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); |
85 | |
86 | /* If chgmod == BATFULL, setting chgena has no effect. |
87 | * Datasheet says we need to set resume instead but when autoresume is |
88 | * used resume doesn't work. Clear and set chgena instead. |
89 | */ |
90 | if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL) |
91 | pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, |
92 | mask: PCF50633_MBCC1_CHGENA, val: PCF50633_MBCC1_CHGENA); |
93 | else { |
94 | pcf50633_reg_clear_bits(pcf, PCF50633_REG_MBCC1, |
95 | bits: PCF50633_MBCC1_CHGENA); |
96 | pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, |
97 | mask: PCF50633_MBCC1_CHGENA, val: PCF50633_MBCC1_CHGENA); |
98 | } |
99 | |
100 | power_supply_changed(psy: mbc->usb); |
101 | |
102 | return ret; |
103 | } |
104 | EXPORT_SYMBOL_GPL(pcf50633_mbc_usb_curlim_set); |
105 | |
106 | int pcf50633_mbc_get_status(struct pcf50633 *pcf) |
107 | { |
108 | struct pcf50633_mbc *mbc = platform_get_drvdata(pdev: pcf->mbc_pdev); |
109 | int status = 0; |
110 | u8 chgmod; |
111 | |
112 | if (!mbc) |
113 | return 0; |
114 | |
115 | chgmod = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2) |
116 | & PCF50633_MBCS2_MBC_MASK; |
117 | |
118 | if (mbc->usb_online) |
119 | status |= PCF50633_MBC_USB_ONLINE; |
120 | if (chgmod == PCF50633_MBCS2_MBC_USB_PRE || |
121 | chgmod == PCF50633_MBCS2_MBC_USB_PRE_WAIT || |
122 | chgmod == PCF50633_MBCS2_MBC_USB_FAST || |
123 | chgmod == PCF50633_MBCS2_MBC_USB_FAST_WAIT) |
124 | status |= PCF50633_MBC_USB_ACTIVE; |
125 | if (mbc->adapter_online) |
126 | status |= PCF50633_MBC_ADAPTER_ONLINE; |
127 | if (chgmod == PCF50633_MBCS2_MBC_ADP_PRE || |
128 | chgmod == PCF50633_MBCS2_MBC_ADP_PRE_WAIT || |
129 | chgmod == PCF50633_MBCS2_MBC_ADP_FAST || |
130 | chgmod == PCF50633_MBCS2_MBC_ADP_FAST_WAIT) |
131 | status |= PCF50633_MBC_ADAPTER_ACTIVE; |
132 | |
133 | return status; |
134 | } |
135 | EXPORT_SYMBOL_GPL(pcf50633_mbc_get_status); |
136 | |
137 | int pcf50633_mbc_get_usb_online_status(struct pcf50633 *pcf) |
138 | { |
139 | struct pcf50633_mbc *mbc = platform_get_drvdata(pdev: pcf->mbc_pdev); |
140 | |
141 | if (!mbc) |
142 | return 0; |
143 | |
144 | return mbc->usb_online; |
145 | } |
146 | EXPORT_SYMBOL_GPL(pcf50633_mbc_get_usb_online_status); |
147 | |
148 | static ssize_t |
149 | show_chgmode(struct device *dev, struct device_attribute *attr, char *buf) |
150 | { |
151 | struct pcf50633_mbc *mbc = dev_get_drvdata(dev); |
152 | |
153 | u8 mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); |
154 | u8 chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); |
155 | |
156 | return sysfs_emit(buf, fmt: "%d\n" , chgmod); |
157 | } |
158 | static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, NULL); |
159 | |
160 | static ssize_t |
161 | show_usblim(struct device *dev, struct device_attribute *attr, char *buf) |
162 | { |
163 | struct pcf50633_mbc *mbc = dev_get_drvdata(dev); |
164 | u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & |
165 | PCF50633_MBCC7_USB_MASK; |
166 | unsigned int ma; |
167 | |
168 | if (usblim == PCF50633_MBCC7_USB_1000mA) |
169 | ma = 1000; |
170 | else if (usblim == PCF50633_MBCC7_USB_500mA) |
171 | ma = 500; |
172 | else if (usblim == PCF50633_MBCC7_USB_100mA) |
173 | ma = 100; |
174 | else |
175 | ma = 0; |
176 | |
177 | return sysfs_emit(buf, fmt: "%u\n" , ma); |
178 | } |
179 | |
180 | static ssize_t set_usblim(struct device *dev, |
181 | struct device_attribute *attr, const char *buf, size_t count) |
182 | { |
183 | struct pcf50633_mbc *mbc = dev_get_drvdata(dev); |
184 | unsigned long ma; |
185 | int ret; |
186 | |
187 | ret = kstrtoul(s: buf, base: 10, res: &ma); |
188 | if (ret) |
189 | return ret; |
190 | |
191 | pcf50633_mbc_usb_curlim_set(mbc->pcf, ma); |
192 | |
193 | return count; |
194 | } |
195 | |
196 | static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim); |
197 | |
198 | static ssize_t |
199 | show_chglim(struct device *dev, struct device_attribute *attr, char *buf) |
200 | { |
201 | struct pcf50633_mbc *mbc = dev_get_drvdata(dev); |
202 | u8 mbcc5 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC5); |
203 | unsigned int ma; |
204 | |
205 | if (!mbc->pcf->pdata->charger_reference_current_ma) |
206 | return -ENODEV; |
207 | |
208 | ma = (mbc->pcf->pdata->charger_reference_current_ma * mbcc5) >> 8; |
209 | |
210 | return sysfs_emit(buf, fmt: "%u\n" , ma); |
211 | } |
212 | |
213 | static ssize_t set_chglim(struct device *dev, |
214 | struct device_attribute *attr, const char *buf, size_t count) |
215 | { |
216 | struct pcf50633_mbc *mbc = dev_get_drvdata(dev); |
217 | unsigned long ma; |
218 | unsigned int mbcc5; |
219 | int ret; |
220 | |
221 | if (!mbc->pcf->pdata->charger_reference_current_ma) |
222 | return -ENODEV; |
223 | |
224 | ret = kstrtoul(s: buf, base: 10, res: &ma); |
225 | if (ret) |
226 | return ret; |
227 | |
228 | mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma; |
229 | if (mbcc5 > 255) |
230 | mbcc5 = 255; |
231 | pcf50633_reg_write(pcf: mbc->pcf, PCF50633_REG_MBCC5, val: mbcc5); |
232 | |
233 | return count; |
234 | } |
235 | |
236 | /* |
237 | * This attribute allows to change MBC charging limit on the fly |
238 | * independently of usb current limit. It also gets set automatically every |
239 | * time usb current limit is changed. |
240 | */ |
241 | static DEVICE_ATTR(chg_curlim, S_IRUGO | S_IWUSR, show_chglim, set_chglim); |
242 | |
243 | static struct attribute *pcf50633_mbc_sysfs_attrs[] = { |
244 | &dev_attr_chgmode.attr, |
245 | &dev_attr_usb_curlim.attr, |
246 | &dev_attr_chg_curlim.attr, |
247 | NULL, |
248 | }; |
249 | |
250 | ATTRIBUTE_GROUPS(pcf50633_mbc_sysfs); |
251 | |
252 | static void |
253 | pcf50633_mbc_irq_handler(int irq, void *data) |
254 | { |
255 | struct pcf50633_mbc *mbc = data; |
256 | |
257 | /* USB */ |
258 | if (irq == PCF50633_IRQ_USBINS) { |
259 | mbc->usb_online = 1; |
260 | } else if (irq == PCF50633_IRQ_USBREM) { |
261 | mbc->usb_online = 0; |
262 | pcf50633_mbc_usb_curlim_set(mbc->pcf, 0); |
263 | } |
264 | |
265 | /* Adapter */ |
266 | if (irq == PCF50633_IRQ_ADPINS) |
267 | mbc->adapter_online = 1; |
268 | else if (irq == PCF50633_IRQ_ADPREM) |
269 | mbc->adapter_online = 0; |
270 | |
271 | power_supply_changed(psy: mbc->ac); |
272 | power_supply_changed(psy: mbc->usb); |
273 | power_supply_changed(psy: mbc->adapter); |
274 | |
275 | if (mbc->pcf->pdata->mbc_event_callback) |
276 | mbc->pcf->pdata->mbc_event_callback(mbc->pcf, irq); |
277 | } |
278 | |
279 | static int adapter_get_property(struct power_supply *psy, |
280 | enum power_supply_property psp, |
281 | union power_supply_propval *val) |
282 | { |
283 | struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); |
284 | int ret = 0; |
285 | |
286 | switch (psp) { |
287 | case POWER_SUPPLY_PROP_ONLINE: |
288 | val->intval = mbc->adapter_online; |
289 | break; |
290 | default: |
291 | ret = -EINVAL; |
292 | break; |
293 | } |
294 | return ret; |
295 | } |
296 | |
297 | static int usb_get_property(struct power_supply *psy, |
298 | enum power_supply_property psp, |
299 | union power_supply_propval *val) |
300 | { |
301 | struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); |
302 | int ret = 0; |
303 | u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & |
304 | PCF50633_MBCC7_USB_MASK; |
305 | |
306 | switch (psp) { |
307 | case POWER_SUPPLY_PROP_ONLINE: |
308 | val->intval = mbc->usb_online && |
309 | (usblim <= PCF50633_MBCC7_USB_500mA); |
310 | break; |
311 | default: |
312 | ret = -EINVAL; |
313 | break; |
314 | } |
315 | return ret; |
316 | } |
317 | |
318 | static int ac_get_property(struct power_supply *psy, |
319 | enum power_supply_property psp, |
320 | union power_supply_propval *val) |
321 | { |
322 | struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); |
323 | int ret = 0; |
324 | u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & |
325 | PCF50633_MBCC7_USB_MASK; |
326 | |
327 | switch (psp) { |
328 | case POWER_SUPPLY_PROP_ONLINE: |
329 | val->intval = mbc->usb_online && |
330 | (usblim == PCF50633_MBCC7_USB_1000mA); |
331 | break; |
332 | default: |
333 | ret = -EINVAL; |
334 | break; |
335 | } |
336 | return ret; |
337 | } |
338 | |
339 | static enum power_supply_property power_props[] = { |
340 | POWER_SUPPLY_PROP_ONLINE, |
341 | }; |
342 | |
343 | static const u8 mbc_irq_handlers[] = { |
344 | PCF50633_IRQ_ADPINS, |
345 | PCF50633_IRQ_ADPREM, |
346 | PCF50633_IRQ_USBINS, |
347 | PCF50633_IRQ_USBREM, |
348 | PCF50633_IRQ_BATFULL, |
349 | PCF50633_IRQ_CHGHALT, |
350 | PCF50633_IRQ_THLIMON, |
351 | PCF50633_IRQ_THLIMOFF, |
352 | PCF50633_IRQ_USBLIMON, |
353 | PCF50633_IRQ_USBLIMOFF, |
354 | PCF50633_IRQ_LOWSYS, |
355 | PCF50633_IRQ_LOWBAT, |
356 | }; |
357 | |
358 | static const struct power_supply_desc pcf50633_mbc_adapter_desc = { |
359 | .name = "adapter" , |
360 | .type = POWER_SUPPLY_TYPE_MAINS, |
361 | .properties = power_props, |
362 | .num_properties = ARRAY_SIZE(power_props), |
363 | .get_property = &adapter_get_property, |
364 | }; |
365 | |
366 | static const struct power_supply_desc pcf50633_mbc_usb_desc = { |
367 | .name = "usb" , |
368 | .type = POWER_SUPPLY_TYPE_USB, |
369 | .properties = power_props, |
370 | .num_properties = ARRAY_SIZE(power_props), |
371 | .get_property = usb_get_property, |
372 | }; |
373 | |
374 | static const struct power_supply_desc pcf50633_mbc_ac_desc = { |
375 | .name = "ac" , |
376 | .type = POWER_SUPPLY_TYPE_MAINS, |
377 | .properties = power_props, |
378 | .num_properties = ARRAY_SIZE(power_props), |
379 | .get_property = ac_get_property, |
380 | }; |
381 | |
382 | static int pcf50633_mbc_probe(struct platform_device *pdev) |
383 | { |
384 | struct power_supply_config psy_cfg = {}; |
385 | struct power_supply_config usb_psy_cfg; |
386 | struct pcf50633_mbc *mbc; |
387 | int i; |
388 | u8 mbcs1; |
389 | |
390 | mbc = devm_kzalloc(dev: &pdev->dev, size: sizeof(*mbc), GFP_KERNEL); |
391 | if (!mbc) |
392 | return -ENOMEM; |
393 | |
394 | platform_set_drvdata(pdev, data: mbc); |
395 | mbc->pcf = dev_to_pcf50633(dev: pdev->dev.parent); |
396 | |
397 | /* Set up IRQ handlers */ |
398 | for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) |
399 | pcf50633_register_irq(pcf: mbc->pcf, irq: mbc_irq_handlers[i], |
400 | handler: pcf50633_mbc_irq_handler, data: mbc); |
401 | |
402 | psy_cfg.supplied_to = mbc->pcf->pdata->batteries; |
403 | psy_cfg.num_supplicants = mbc->pcf->pdata->num_batteries; |
404 | psy_cfg.drv_data = mbc; |
405 | |
406 | /* Create power supplies */ |
407 | mbc->adapter = power_supply_register(parent: &pdev->dev, |
408 | desc: &pcf50633_mbc_adapter_desc, |
409 | cfg: &psy_cfg); |
410 | if (IS_ERR(ptr: mbc->adapter)) { |
411 | dev_err(mbc->pcf->dev, "failed to register adapter\n" ); |
412 | return PTR_ERR(ptr: mbc->adapter); |
413 | } |
414 | |
415 | usb_psy_cfg = psy_cfg; |
416 | usb_psy_cfg.attr_grp = pcf50633_mbc_sysfs_groups; |
417 | |
418 | mbc->usb = power_supply_register(parent: &pdev->dev, desc: &pcf50633_mbc_usb_desc, |
419 | cfg: &usb_psy_cfg); |
420 | if (IS_ERR(ptr: mbc->usb)) { |
421 | dev_err(mbc->pcf->dev, "failed to register usb\n" ); |
422 | power_supply_unregister(psy: mbc->adapter); |
423 | return PTR_ERR(ptr: mbc->usb); |
424 | } |
425 | |
426 | mbc->ac = power_supply_register(parent: &pdev->dev, desc: &pcf50633_mbc_ac_desc, |
427 | cfg: &psy_cfg); |
428 | if (IS_ERR(ptr: mbc->ac)) { |
429 | dev_err(mbc->pcf->dev, "failed to register ac\n" ); |
430 | power_supply_unregister(psy: mbc->adapter); |
431 | power_supply_unregister(psy: mbc->usb); |
432 | return PTR_ERR(ptr: mbc->ac); |
433 | } |
434 | |
435 | mbcs1 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS1); |
436 | if (mbcs1 & PCF50633_MBCS1_USBPRES) |
437 | pcf50633_mbc_irq_handler(irq: PCF50633_IRQ_USBINS, data: mbc); |
438 | if (mbcs1 & PCF50633_MBCS1_ADAPTPRES) |
439 | pcf50633_mbc_irq_handler(irq: PCF50633_IRQ_ADPINS, data: mbc); |
440 | |
441 | return 0; |
442 | } |
443 | |
444 | static void pcf50633_mbc_remove(struct platform_device *pdev) |
445 | { |
446 | struct pcf50633_mbc *mbc = platform_get_drvdata(pdev); |
447 | int i; |
448 | |
449 | /* Remove IRQ handlers */ |
450 | for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) |
451 | pcf50633_free_irq(pcf: mbc->pcf, irq: mbc_irq_handlers[i]); |
452 | |
453 | power_supply_unregister(psy: mbc->usb); |
454 | power_supply_unregister(psy: mbc->adapter); |
455 | power_supply_unregister(psy: mbc->ac); |
456 | } |
457 | |
458 | static struct platform_driver pcf50633_mbc_driver = { |
459 | .driver = { |
460 | .name = "pcf50633-mbc" , |
461 | }, |
462 | .probe = pcf50633_mbc_probe, |
463 | .remove_new = pcf50633_mbc_remove, |
464 | }; |
465 | |
466 | module_platform_driver(pcf50633_mbc_driver); |
467 | |
468 | MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>" ); |
469 | MODULE_DESCRIPTION("PCF50633 mbc driver" ); |
470 | MODULE_LICENSE("GPL" ); |
471 | MODULE_ALIAS("platform:pcf50633-mbc" ); |
472 | |