1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * MP2629 battery charger driver |
4 | * |
5 | * Copyright 2020 Monolithic Power Systems, Inc |
6 | * |
7 | * Author: Saravanan Sekar <sravanhome@gmail.com> |
8 | */ |
9 | |
10 | #include <linux/bits.h> |
11 | #include <linux/iio/consumer.h> |
12 | #include <linux/iio/types.h> |
13 | #include <linux/interrupt.h> |
14 | #include <linux/mfd/mp2629.h> |
15 | #include <linux/module.h> |
16 | #include <linux/mod_devicetable.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/power_supply.h> |
19 | #include <linux/regmap.h> |
20 | |
21 | #define MP2629_REG_INPUT_ILIM 0x00 |
22 | #define MP2629_REG_INPUT_VLIM 0x01 |
23 | #define MP2629_REG_CHARGE_CTRL 0x04 |
24 | #define MP2629_REG_CHARGE_ILIM 0x05 |
25 | #define MP2629_REG_PRECHARGE 0x06 |
26 | #define MP2629_REG_TERM_CURRENT 0x06 |
27 | #define MP2629_REG_CHARGE_VLIM 0x07 |
28 | #define MP2629_REG_TIMER_CTRL 0x08 |
29 | #define MP2629_REG_IMPEDANCE_COMP 0x09 |
30 | #define MP2629_REG_INTERRUPT 0x0b |
31 | #define MP2629_REG_STATUS 0x0c |
32 | #define MP2629_REG_FAULT 0x0d |
33 | |
34 | #define MP2629_MASK_INPUT_TYPE GENMASK(7, 5) |
35 | #define MP2629_MASK_CHARGE_TYPE GENMASK(4, 3) |
36 | #define MP2629_MASK_CHARGE_CTRL GENMASK(5, 4) |
37 | #define MP2629_MASK_WDOG_CTRL GENMASK(5, 4) |
38 | #define MP2629_MASK_IMPEDANCE GENMASK(7, 4) |
39 | |
40 | #define MP2629_INPUTSOURCE_CHANGE GENMASK(7, 5) |
41 | #define MP2629_CHARGING_CHANGE GENMASK(4, 3) |
42 | #define MP2629_FAULT_BATTERY BIT(3) |
43 | #define MP2629_FAULT_THERMAL BIT(4) |
44 | #define MP2629_FAULT_INPUT BIT(5) |
45 | #define MP2629_FAULT_OTG BIT(6) |
46 | |
47 | #define MP2629_MAX_BATT_CAPACITY 100 |
48 | |
49 | #define MP2629_PROPS(_idx, _min, _max, _step) \ |
50 | [_idx] = { \ |
51 | .min = _min, \ |
52 | .max = _max, \ |
53 | .step = _step, \ |
54 | } |
55 | |
56 | enum mp2629_source_type { |
57 | MP2629_SOURCE_TYPE_NO_INPUT, |
58 | MP2629_SOURCE_TYPE_NON_STD, |
59 | MP2629_SOURCE_TYPE_SDP, |
60 | MP2629_SOURCE_TYPE_CDP, |
61 | MP2629_SOURCE_TYPE_DCP, |
62 | MP2629_SOURCE_TYPE_OTG = 7, |
63 | }; |
64 | |
65 | enum mp2629_field { |
66 | INPUT_ILIM, |
67 | INPUT_VLIM, |
68 | CHARGE_ILIM, |
69 | CHARGE_VLIM, |
70 | PRECHARGE, |
71 | TERM_CURRENT, |
72 | MP2629_MAX_FIELD |
73 | }; |
74 | |
75 | struct mp2629_charger { |
76 | struct device *dev; |
77 | int status; |
78 | int fault; |
79 | |
80 | struct regmap *regmap; |
81 | struct regmap_field *regmap_fields[MP2629_MAX_FIELD]; |
82 | struct mutex lock; |
83 | struct power_supply *usb; |
84 | struct power_supply *battery; |
85 | struct iio_channel *iiochan[MP2629_ADC_CHAN_END]; |
86 | }; |
87 | |
88 | struct mp2629_prop { |
89 | int reg; |
90 | int mask; |
91 | int min; |
92 | int max; |
93 | int step; |
94 | int shift; |
95 | }; |
96 | |
97 | static enum power_supply_usb_type mp2629_usb_types[] = { |
98 | POWER_SUPPLY_USB_TYPE_SDP, |
99 | POWER_SUPPLY_USB_TYPE_DCP, |
100 | POWER_SUPPLY_USB_TYPE_CDP, |
101 | POWER_SUPPLY_USB_TYPE_PD_DRP, |
102 | POWER_SUPPLY_USB_TYPE_UNKNOWN |
103 | }; |
104 | |
105 | static enum power_supply_property mp2629_charger_usb_props[] = { |
106 | POWER_SUPPLY_PROP_ONLINE, |
107 | POWER_SUPPLY_PROP_USB_TYPE, |
108 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
109 | POWER_SUPPLY_PROP_CURRENT_NOW, |
110 | POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, |
111 | POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, |
112 | }; |
113 | |
114 | static enum power_supply_property mp2629_charger_bat_props[] = { |
115 | POWER_SUPPLY_PROP_STATUS, |
116 | POWER_SUPPLY_PROP_HEALTH, |
117 | POWER_SUPPLY_PROP_CHARGE_TYPE, |
118 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
119 | POWER_SUPPLY_PROP_CURRENT_NOW, |
120 | POWER_SUPPLY_PROP_CAPACITY, |
121 | POWER_SUPPLY_PROP_PRECHARGE_CURRENT, |
122 | POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, |
123 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, |
124 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, |
125 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, |
126 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, |
127 | }; |
128 | |
129 | static struct mp2629_prop props[] = { |
130 | MP2629_PROPS(INPUT_ILIM, 100000, 3250000, 50000), |
131 | MP2629_PROPS(INPUT_VLIM, 3800000, 5300000, 100000), |
132 | MP2629_PROPS(CHARGE_ILIM, 320000, 4520000, 40000), |
133 | MP2629_PROPS(CHARGE_VLIM, 3400000, 4670000, 10000), |
134 | MP2629_PROPS(PRECHARGE, 120000, 720000, 40000), |
135 | MP2629_PROPS(TERM_CURRENT, 80000, 680000, 40000), |
136 | }; |
137 | |
138 | static const struct reg_field mp2629_reg_fields[] = { |
139 | [INPUT_ILIM] = REG_FIELD(MP2629_REG_INPUT_ILIM, 0, 5), |
140 | [INPUT_VLIM] = REG_FIELD(MP2629_REG_INPUT_VLIM, 0, 3), |
141 | [CHARGE_ILIM] = REG_FIELD(MP2629_REG_CHARGE_ILIM, 0, 6), |
142 | [CHARGE_VLIM] = REG_FIELD(MP2629_REG_CHARGE_VLIM, 1, 7), |
143 | [PRECHARGE] = REG_FIELD(MP2629_REG_PRECHARGE, 4, 7), |
144 | [TERM_CURRENT] = REG_FIELD(MP2629_REG_TERM_CURRENT, 0, 3), |
145 | }; |
146 | |
147 | static char *adc_chan_name[] = { |
148 | "mp2629-batt-volt" , |
149 | "mp2629-system-volt" , |
150 | "mp2629-input-volt" , |
151 | "mp2629-batt-current" , |
152 | "mp2629-input-current" , |
153 | }; |
154 | |
155 | static int mp2629_read_adc(struct mp2629_charger *charger, |
156 | enum mp2629_adc_chan ch, |
157 | union power_supply_propval *val) |
158 | { |
159 | int ret; |
160 | int chval; |
161 | |
162 | ret = iio_read_channel_processed(chan: charger->iiochan[ch], val: &chval); |
163 | if (ret) |
164 | return ret; |
165 | |
166 | val->intval = chval * 1000; |
167 | |
168 | return 0; |
169 | } |
170 | |
171 | static int mp2629_get_prop(struct mp2629_charger *charger, |
172 | enum mp2629_field fld, |
173 | union power_supply_propval *val) |
174 | { |
175 | int ret; |
176 | unsigned int rval; |
177 | |
178 | ret = regmap_field_read(field: charger->regmap_fields[fld], val: &rval); |
179 | if (ret) |
180 | return ret; |
181 | |
182 | val->intval = rval * props[fld].step + props[fld].min; |
183 | |
184 | return 0; |
185 | } |
186 | |
187 | static int mp2629_set_prop(struct mp2629_charger *charger, |
188 | enum mp2629_field fld, |
189 | const union power_supply_propval *val) |
190 | { |
191 | unsigned int rval; |
192 | |
193 | if (val->intval < props[fld].min || val->intval > props[fld].max) |
194 | return -EINVAL; |
195 | |
196 | rval = (val->intval - props[fld].min) / props[fld].step; |
197 | return regmap_field_write(field: charger->regmap_fields[fld], val: rval); |
198 | } |
199 | |
200 | static int mp2629_get_battery_capacity(struct mp2629_charger *charger, |
201 | union power_supply_propval *val) |
202 | { |
203 | union power_supply_propval vnow, vlim; |
204 | int ret; |
205 | |
206 | ret = mp2629_read_adc(charger, ch: MP2629_BATT_VOLT, val: &vnow); |
207 | if (ret) |
208 | return ret; |
209 | |
210 | ret = mp2629_get_prop(charger, fld: CHARGE_VLIM, val: &vlim); |
211 | if (ret) |
212 | return ret; |
213 | |
214 | val->intval = (vnow.intval * 100) / vlim.intval; |
215 | val->intval = min(val->intval, MP2629_MAX_BATT_CAPACITY); |
216 | |
217 | return 0; |
218 | } |
219 | |
220 | static int mp2629_charger_battery_get_prop(struct power_supply *psy, |
221 | enum power_supply_property psp, |
222 | union power_supply_propval *val) |
223 | { |
224 | struct mp2629_charger *charger = dev_get_drvdata(dev: psy->dev.parent); |
225 | unsigned int rval; |
226 | int ret = 0; |
227 | |
228 | switch (psp) { |
229 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
230 | ret = mp2629_read_adc(charger, ch: MP2629_BATT_VOLT, val); |
231 | break; |
232 | |
233 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
234 | ret = mp2629_read_adc(charger, ch: MP2629_BATT_CURRENT, val); |
235 | break; |
236 | |
237 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: |
238 | val->intval = 4520000; |
239 | break; |
240 | |
241 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: |
242 | val->intval = 4670000; |
243 | break; |
244 | |
245 | case POWER_SUPPLY_PROP_CAPACITY: |
246 | ret = mp2629_get_battery_capacity(charger, val); |
247 | break; |
248 | |
249 | case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
250 | ret = mp2629_get_prop(charger, fld: TERM_CURRENT, val); |
251 | break; |
252 | |
253 | case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: |
254 | ret = mp2629_get_prop(charger, fld: PRECHARGE, val); |
255 | break; |
256 | |
257 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: |
258 | ret = mp2629_get_prop(charger, fld: CHARGE_VLIM, val); |
259 | break; |
260 | |
261 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: |
262 | ret = mp2629_get_prop(charger, fld: CHARGE_ILIM, val); |
263 | break; |
264 | |
265 | case POWER_SUPPLY_PROP_HEALTH: |
266 | if (!charger->fault) |
267 | val->intval = POWER_SUPPLY_HEALTH_GOOD; |
268 | if (MP2629_FAULT_BATTERY & charger->fault) |
269 | val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
270 | else if (MP2629_FAULT_THERMAL & charger->fault) |
271 | val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; |
272 | else if (MP2629_FAULT_INPUT & charger->fault) |
273 | val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
274 | break; |
275 | |
276 | case POWER_SUPPLY_PROP_STATUS: |
277 | ret = regmap_read(map: charger->regmap, MP2629_REG_STATUS, val: &rval); |
278 | if (ret) |
279 | break; |
280 | |
281 | rval = (rval & MP2629_MASK_CHARGE_TYPE) >> 3; |
282 | switch (rval) { |
283 | case 0x00: |
284 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; |
285 | break; |
286 | case 0x01: |
287 | case 0x10: |
288 | val->intval = POWER_SUPPLY_STATUS_CHARGING; |
289 | break; |
290 | case 0x11: |
291 | val->intval = POWER_SUPPLY_STATUS_FULL; |
292 | } |
293 | break; |
294 | |
295 | case POWER_SUPPLY_PROP_CHARGE_TYPE: |
296 | ret = regmap_read(map: charger->regmap, MP2629_REG_STATUS, val: &rval); |
297 | if (ret) |
298 | break; |
299 | |
300 | rval = (rval & MP2629_MASK_CHARGE_TYPE) >> 3; |
301 | switch (rval) { |
302 | case 0x00: |
303 | val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; |
304 | break; |
305 | case 0x01: |
306 | val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
307 | break; |
308 | case 0x10: |
309 | val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; |
310 | break; |
311 | default: |
312 | val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; |
313 | } |
314 | break; |
315 | |
316 | default: |
317 | return -EINVAL; |
318 | } |
319 | |
320 | return ret; |
321 | } |
322 | |
323 | static int mp2629_charger_battery_set_prop(struct power_supply *psy, |
324 | enum power_supply_property psp, |
325 | const union power_supply_propval *val) |
326 | { |
327 | struct mp2629_charger *charger = dev_get_drvdata(dev: psy->dev.parent); |
328 | |
329 | switch (psp) { |
330 | case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
331 | return mp2629_set_prop(charger, fld: TERM_CURRENT, val); |
332 | |
333 | case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: |
334 | return mp2629_set_prop(charger, fld: PRECHARGE, val); |
335 | |
336 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: |
337 | return mp2629_set_prop(charger, fld: CHARGE_VLIM, val); |
338 | |
339 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: |
340 | return mp2629_set_prop(charger, fld: CHARGE_ILIM, val); |
341 | |
342 | default: |
343 | return -EINVAL; |
344 | } |
345 | } |
346 | |
347 | static int mp2629_charger_usb_get_prop(struct power_supply *psy, |
348 | enum power_supply_property psp, |
349 | union power_supply_propval *val) |
350 | { |
351 | struct mp2629_charger *charger = dev_get_drvdata(dev: psy->dev.parent); |
352 | unsigned int rval; |
353 | int ret; |
354 | |
355 | switch (psp) { |
356 | case POWER_SUPPLY_PROP_ONLINE: |
357 | ret = regmap_read(map: charger->regmap, MP2629_REG_STATUS, val: &rval); |
358 | if (ret) |
359 | break; |
360 | |
361 | val->intval = !!(rval & MP2629_MASK_INPUT_TYPE); |
362 | break; |
363 | |
364 | case POWER_SUPPLY_PROP_USB_TYPE: |
365 | ret = regmap_read(map: charger->regmap, MP2629_REG_STATUS, val: &rval); |
366 | if (ret) |
367 | break; |
368 | |
369 | rval = (rval & MP2629_MASK_INPUT_TYPE) >> 5; |
370 | switch (rval) { |
371 | case MP2629_SOURCE_TYPE_SDP: |
372 | val->intval = POWER_SUPPLY_USB_TYPE_SDP; |
373 | break; |
374 | case MP2629_SOURCE_TYPE_CDP: |
375 | val->intval = POWER_SUPPLY_USB_TYPE_CDP; |
376 | break; |
377 | case MP2629_SOURCE_TYPE_DCP: |
378 | val->intval = POWER_SUPPLY_USB_TYPE_DCP; |
379 | break; |
380 | case MP2629_SOURCE_TYPE_OTG: |
381 | val->intval = POWER_SUPPLY_USB_TYPE_PD_DRP; |
382 | break; |
383 | default: |
384 | val->intval = POWER_SUPPLY_USB_TYPE_UNKNOWN; |
385 | break; |
386 | } |
387 | break; |
388 | |
389 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
390 | ret = mp2629_read_adc(charger, ch: MP2629_INPUT_VOLT, val); |
391 | break; |
392 | |
393 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
394 | ret = mp2629_read_adc(charger, ch: MP2629_INPUT_CURRENT, val); |
395 | break; |
396 | |
397 | case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: |
398 | ret = mp2629_get_prop(charger, fld: INPUT_VLIM, val); |
399 | break; |
400 | |
401 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
402 | ret = mp2629_get_prop(charger, fld: INPUT_ILIM, val); |
403 | break; |
404 | |
405 | default: |
406 | return -EINVAL; |
407 | } |
408 | |
409 | return ret; |
410 | } |
411 | |
412 | static int mp2629_charger_usb_set_prop(struct power_supply *psy, |
413 | enum power_supply_property psp, |
414 | const union power_supply_propval *val) |
415 | { |
416 | struct mp2629_charger *charger = dev_get_drvdata(dev: psy->dev.parent); |
417 | |
418 | switch (psp) { |
419 | case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: |
420 | return mp2629_set_prop(charger, fld: INPUT_VLIM, val); |
421 | |
422 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
423 | return mp2629_set_prop(charger, fld: INPUT_ILIM, val); |
424 | |
425 | default: |
426 | return -EINVAL; |
427 | } |
428 | } |
429 | |
430 | static int mp2629_charger_battery_prop_writeable(struct power_supply *psy, |
431 | enum power_supply_property psp) |
432 | { |
433 | return (psp == POWER_SUPPLY_PROP_PRECHARGE_CURRENT) || |
434 | (psp == POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT) || |
435 | (psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT) || |
436 | (psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE); |
437 | } |
438 | |
439 | static int mp2629_charger_usb_prop_writeable(struct power_supply *psy, |
440 | enum power_supply_property psp) |
441 | { |
442 | return (psp == POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT) || |
443 | (psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT); |
444 | } |
445 | |
446 | static irqreturn_t mp2629_irq_handler(int irq, void *dev_id) |
447 | { |
448 | struct mp2629_charger *charger = dev_id; |
449 | unsigned int rval; |
450 | int ret; |
451 | |
452 | mutex_lock(&charger->lock); |
453 | |
454 | ret = regmap_read(map: charger->regmap, MP2629_REG_FAULT, val: &rval); |
455 | if (ret) |
456 | goto unlock; |
457 | |
458 | if (rval) { |
459 | charger->fault = rval; |
460 | if (MP2629_FAULT_BATTERY & rval) |
461 | dev_err(charger->dev, "Battery fault OVP\n" ); |
462 | else if (MP2629_FAULT_THERMAL & rval) |
463 | dev_err(charger->dev, "Thermal shutdown fault\n" ); |
464 | else if (MP2629_FAULT_INPUT & rval) |
465 | dev_err(charger->dev, "no input or input OVP\n" ); |
466 | else if (MP2629_FAULT_OTG & rval) |
467 | dev_err(charger->dev, "VIN overloaded\n" ); |
468 | |
469 | goto unlock; |
470 | } |
471 | |
472 | ret = regmap_read(map: charger->regmap, MP2629_REG_STATUS, val: &rval); |
473 | if (ret) |
474 | goto unlock; |
475 | |
476 | if (rval & MP2629_INPUTSOURCE_CHANGE) |
477 | power_supply_changed(psy: charger->usb); |
478 | else if (rval & MP2629_CHARGING_CHANGE) |
479 | power_supply_changed(psy: charger->battery); |
480 | |
481 | unlock: |
482 | mutex_unlock(lock: &charger->lock); |
483 | |
484 | return IRQ_HANDLED; |
485 | } |
486 | |
487 | static const struct power_supply_desc mp2629_usb_desc = { |
488 | .name = "mp2629_usb" , |
489 | .type = POWER_SUPPLY_TYPE_USB, |
490 | .usb_types = mp2629_usb_types, |
491 | .num_usb_types = ARRAY_SIZE(mp2629_usb_types), |
492 | .properties = mp2629_charger_usb_props, |
493 | .num_properties = ARRAY_SIZE(mp2629_charger_usb_props), |
494 | .get_property = mp2629_charger_usb_get_prop, |
495 | .set_property = mp2629_charger_usb_set_prop, |
496 | .property_is_writeable = mp2629_charger_usb_prop_writeable, |
497 | }; |
498 | |
499 | static const struct power_supply_desc mp2629_battery_desc = { |
500 | .name = "mp2629_battery" , |
501 | .type = POWER_SUPPLY_TYPE_BATTERY, |
502 | .properties = mp2629_charger_bat_props, |
503 | .num_properties = ARRAY_SIZE(mp2629_charger_bat_props), |
504 | .get_property = mp2629_charger_battery_get_prop, |
505 | .set_property = mp2629_charger_battery_set_prop, |
506 | .property_is_writeable = mp2629_charger_battery_prop_writeable, |
507 | }; |
508 | |
509 | static ssize_t batt_impedance_compensation_show(struct device *dev, |
510 | struct device_attribute *attr, |
511 | char *buf) |
512 | { |
513 | struct mp2629_charger *charger = dev_get_drvdata(dev: dev->parent); |
514 | unsigned int rval; |
515 | int ret; |
516 | |
517 | ret = regmap_read(map: charger->regmap, MP2629_REG_IMPEDANCE_COMP, val: &rval); |
518 | if (ret) |
519 | return ret; |
520 | |
521 | rval = (rval >> 4) * 10; |
522 | return sysfs_emit(buf, fmt: "%d mohm\n" , rval); |
523 | } |
524 | |
525 | static ssize_t batt_impedance_compensation_store(struct device *dev, |
526 | struct device_attribute *attr, |
527 | const char *buf, |
528 | size_t count) |
529 | { |
530 | struct mp2629_charger *charger = dev_get_drvdata(dev: dev->parent); |
531 | unsigned int val; |
532 | int ret; |
533 | |
534 | ret = kstrtouint(s: buf, base: 10, res: &val); |
535 | if (ret) |
536 | return ret; |
537 | |
538 | if (val > 140) |
539 | return -ERANGE; |
540 | |
541 | /* multiples of 10 mohm so round off */ |
542 | val = val / 10; |
543 | ret = regmap_update_bits(map: charger->regmap, MP2629_REG_IMPEDANCE_COMP, |
544 | MP2629_MASK_IMPEDANCE, val: val << 4); |
545 | if (ret) |
546 | return ret; |
547 | |
548 | return count; |
549 | } |
550 | |
551 | static DEVICE_ATTR_RW(batt_impedance_compensation); |
552 | |
553 | static struct attribute *mp2629_charger_sysfs_attrs[] = { |
554 | &dev_attr_batt_impedance_compensation.attr, |
555 | NULL |
556 | }; |
557 | ATTRIBUTE_GROUPS(mp2629_charger_sysfs); |
558 | |
559 | static void mp2629_charger_disable(void *data) |
560 | { |
561 | struct mp2629_charger *charger = data; |
562 | |
563 | regmap_update_bits(map: charger->regmap, MP2629_REG_CHARGE_CTRL, |
564 | MP2629_MASK_CHARGE_CTRL, val: 0); |
565 | } |
566 | |
567 | static int mp2629_charger_probe(struct platform_device *pdev) |
568 | { |
569 | struct device *dev = &pdev->dev; |
570 | struct mp2629_data *ddata = dev_get_drvdata(dev: dev->parent); |
571 | struct mp2629_charger *charger; |
572 | struct power_supply_config psy_cfg = {}; |
573 | int ret, i, irq; |
574 | |
575 | charger = devm_kzalloc(dev, size: sizeof(*charger), GFP_KERNEL); |
576 | if (!charger) |
577 | return -ENOMEM; |
578 | |
579 | charger->regmap = ddata->regmap; |
580 | charger->dev = dev; |
581 | platform_set_drvdata(pdev, data: charger); |
582 | |
583 | irq = platform_get_irq(to_platform_device(dev->parent), 0); |
584 | if (irq < 0) |
585 | return irq; |
586 | |
587 | for (i = 0; i < MP2629_MAX_FIELD; i++) { |
588 | charger->regmap_fields[i] = devm_regmap_field_alloc(dev, |
589 | regmap: charger->regmap, reg_field: mp2629_reg_fields[i]); |
590 | if (IS_ERR(ptr: charger->regmap_fields[i])) { |
591 | dev_err(dev, "regmap field alloc fail %d\n" , i); |
592 | return PTR_ERR(ptr: charger->regmap_fields[i]); |
593 | } |
594 | } |
595 | |
596 | for (i = 0; i < MP2629_ADC_CHAN_END; i++) { |
597 | charger->iiochan[i] = devm_iio_channel_get(dev, |
598 | consumer_channel: adc_chan_name[i]); |
599 | if (IS_ERR(ptr: charger->iiochan[i])) { |
600 | dev_err(dev, "iio chan get %s err\n" , adc_chan_name[i]); |
601 | return PTR_ERR(ptr: charger->iiochan[i]); |
602 | } |
603 | } |
604 | |
605 | ret = devm_add_action_or_reset(dev, mp2629_charger_disable, charger); |
606 | if (ret) |
607 | return ret; |
608 | |
609 | charger->usb = devm_power_supply_register(parent: dev, desc: &mp2629_usb_desc, NULL); |
610 | if (IS_ERR(ptr: charger->usb)) { |
611 | dev_err(dev, "power supply register usb failed\n" ); |
612 | return PTR_ERR(ptr: charger->usb); |
613 | } |
614 | |
615 | psy_cfg.drv_data = charger; |
616 | psy_cfg.attr_grp = mp2629_charger_sysfs_groups; |
617 | charger->battery = devm_power_supply_register(parent: dev, |
618 | desc: &mp2629_battery_desc, cfg: &psy_cfg); |
619 | if (IS_ERR(ptr: charger->battery)) { |
620 | dev_err(dev, "power supply register battery failed\n" ); |
621 | return PTR_ERR(ptr: charger->battery); |
622 | } |
623 | |
624 | ret = regmap_update_bits(map: charger->regmap, MP2629_REG_CHARGE_CTRL, |
625 | MP2629_MASK_CHARGE_CTRL, BIT(4)); |
626 | if (ret) { |
627 | dev_err(dev, "enable charge fail: %d\n" , ret); |
628 | return ret; |
629 | } |
630 | |
631 | regmap_update_bits(map: charger->regmap, MP2629_REG_TIMER_CTRL, |
632 | MP2629_MASK_WDOG_CTRL, val: 0); |
633 | |
634 | mutex_init(&charger->lock); |
635 | |
636 | ret = devm_request_threaded_irq(dev, irq, NULL, thread_fn: mp2629_irq_handler, |
637 | IRQF_ONESHOT | IRQF_TRIGGER_RISING, |
638 | devname: "mp2629-charger" , dev_id: charger); |
639 | if (ret) { |
640 | dev_err(dev, "failed to request gpio IRQ\n" ); |
641 | return ret; |
642 | } |
643 | |
644 | regmap_update_bits(map: charger->regmap, MP2629_REG_INTERRUPT, |
645 | GENMASK(6, 5), BIT(6) | BIT(5)); |
646 | |
647 | return 0; |
648 | } |
649 | |
650 | static const struct of_device_id mp2629_charger_of_match[] = { |
651 | { .compatible = "mps,mp2629_charger" }, |
652 | {} |
653 | }; |
654 | MODULE_DEVICE_TABLE(of, mp2629_charger_of_match); |
655 | |
656 | static struct platform_driver mp2629_charger_driver = { |
657 | .driver = { |
658 | .name = "mp2629_charger" , |
659 | .of_match_table = mp2629_charger_of_match, |
660 | }, |
661 | .probe = mp2629_charger_probe, |
662 | }; |
663 | module_platform_driver(mp2629_charger_driver); |
664 | |
665 | MODULE_AUTHOR("Saravanan Sekar <sravanhome@gmail.com>" ); |
666 | MODULE_DESCRIPTION("MP2629 Charger driver" ); |
667 | MODULE_LICENSE("GPL" ); |
668 | |