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