// SPDX-License-Identifier: GPL-2.0-or-later /* * max8903_charger.c - Maxim 8903 USB/Adapter Charger Driver * * Copyright (C) 2011 Samsung Electronics * MyungJoo Ham */ #include #include #include #include #include #include #include #include #include /* * IUSB pin: hardcoded by silicon to 100 mA (low) / 500 mA (high). * MAX8903A/B/C/D/E/F/G/H/I datasheet, "Pin Description" table: * "USB Current-Limit Set Input. Drive IUSB logic-low to set the * USB current limit to 100mA. Drive IUSB logic-high to set the * USB current limit to 500mA." * Not a board parameter - never DT-configurable. */ #define MAX8903_USB_CURRENT_LIMIT_LOW_UA 100000 #define MAX8903_USB_CURRENT_LIMIT_HIGH_UA 500000 struct max8903_current_limit_mapping { u32 limit_ua; /* Current limit in microamps */ u32 gpio_value; /* GPIO bit pattern */ }; struct max8903_data { struct device *dev; struct power_supply *psy; struct power_supply_desc psy_desc; /* * GPIOs * chg, flt, dcm and usus are optional. * dok or uok must be present. * If dok is present, cen must be present. */ struct gpio_desc *cen; /* Charger Enable input */ struct gpio_desc *dok; /* DC (Adapter) Power OK output */ struct gpio_desc *uok; /* USB Power OK output */ struct gpio_desc *chg; /* Charger status output */ struct gpio_desc *flt; /* Fault output */ struct gpio_desc *dcm; /* Current-Limit Mode input (1: DC, 2: USB) */ struct gpio_desc *usus; /* USB Suspend Input (1: suspended) */ /* DC current limit control (ISET pins) */ struct gpio_descs *dc_current_limit_gpios; struct max8903_current_limit_mapping *dc_current_limit_map; u32 dc_current_limit_map_size; u32 dc_current_limit_ua; /* Current setting in uA */ /* USB current limit control (IUSB pin) */ struct gpio_desc *usb_current_limit_gpio; u32 usb_current_limit_ua; /* Current setting in uA */ /* * Serialises ta_in / usb_in updates against * max8903_set_property() which steers the current-limit write to * the DC or USB path based on which source is currently online. * The IRQ handlers are requested with IRQF_ONESHOT (threaded), so * a sleepable mutex is the right primitive in both contexts. */ struct mutex source_lock; bool fault; bool usb_in; bool ta_in; }; static enum power_supply_property max8903_charger_props[] = { POWER_SUPPLY_PROP_STATUS, /* Charger status output */ POWER_SUPPLY_PROP_ONLINE, /* External power source */ POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, /* Input current limit */ }; static int max8903_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct max8903_data *data = power_supply_get_drvdata(psy); bool ta_in, usb_in; u32 dc_limit, usb_limit; /* * Snapshot the source flags and current-limit settings under the * source_lock that the IRQs (max8903_dcin / max8903_usbin) and * max8903_set_property() take when updating them, so we never * observe a torn pair of (source-online flag, current-limit ua). * The gpiod_get_value() reads further down deliberately stay * outside the lock — they hit the GPIO controller, not driver * state, and the IRQs do not touch them under the lock either. */ mutex_lock(&data->source_lock); ta_in = data->ta_in; usb_in = data->usb_in; dc_limit = data->dc_current_limit_ua; usb_limit = data->usb_current_limit_ua; mutex_unlock(&data->source_lock); switch (psp) { case POWER_SUPPLY_PROP_STATUS: val->intval = POWER_SUPPLY_STATUS_UNKNOWN; if (data->chg) { if (gpiod_get_value(data->chg)) /* CHG asserted */ val->intval = POWER_SUPPLY_STATUS_CHARGING; else if (usb_in || ta_in) val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; else val->intval = POWER_SUPPLY_STATUS_DISCHARGING; } break; case POWER_SUPPLY_PROP_ONLINE: val->intval = (ta_in || usb_in) ? 1 : 0; break; case POWER_SUPPLY_PROP_HEALTH: /* * data->fault is a single bool toggled from one IRQ * handler, so a torn read is not possible; no need to * extend source_lock coverage here. */ val->intval = data->fault ? POWER_SUPPLY_HEALTH_UNSPEC_FAILURE : POWER_SUPPLY_HEALTH_GOOD; break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: /* * Hardware prioritises DC over USB - when ta_in is asserted * the part draws from the DC input regardless of USB state. * So always report the DC-side limit when DC is online, and * refuse rather than silently fall back to the USB cap if * the DC GPIOs are not configured - that would mis-describe * the active source. Same policy applies in the set path. */ if (ta_in) { if (!data->dc_current_limit_gpios) return -ENODATA; val->intval = dc_limit; } else if (usb_in && data->usb_current_limit_gpio) { val->intval = usb_limit; } else { return -ENODATA; } break; default: return -EINVAL; } return 0; } static int max8903_set_dc_current_limit(struct max8903_data *data, u32 limit_ua) { int i, best_idx = -1; /* * The mapping's gpio_value fits in the lowest ndescs bits of one * unsigned long (parse_dc_current_limit enforces ndescs < 32 and * gpio_value < BIT(ndescs)); a single-word bitmap is sufficient * on both 32- and 64-bit builds. Don't use bitmap_from_arr32() - * that macro reinterprets its source pointer as unsigned long on * 64-bit and would read past the on-stack u32. */ DECLARE_BITMAP(values, BITS_PER_TYPE(u32)); if (!data->dc_current_limit_gpios) return -EOPNOTSUPP; /* * Find the highest supported current <= requested. Use a -1 * "not found" sentinel rather than tracking best_limit > 0 so * that a 0 uA entry (used to disable charging) can be selected * by a 0 uA request. */ for (i = 0; i < data->dc_current_limit_map_size; i++) { if (data->dc_current_limit_map[i].limit_ua > limit_ua) continue; if (best_idx < 0 || data->dc_current_limit_map[i].limit_ua > data->dc_current_limit_map[best_idx].limit_ua) best_idx = i; } if (best_idx < 0) return -EINVAL; bitmap_zero(values, BITS_PER_TYPE(u32)); values[0] = data->dc_current_limit_map[best_idx].gpio_value; gpiod_set_array_value_cansleep(data->dc_current_limit_gpios->ndescs, data->dc_current_limit_gpios->desc, data->dc_current_limit_gpios->info, values); data->dc_current_limit_ua = data->dc_current_limit_map[best_idx].limit_ua; dev_dbg(data->dev, "DC current limit set to %u uA\n", data->dc_current_limit_ua); return 0; } static int max8903_set_usb_current_limit(struct max8903_data *data, u32 limit_ua) { u32 selected; int gpio_val; if (!data->usb_current_limit_gpio) return -EOPNOTSUPP; /* * IUSB is a single-bit input with two silicon-fixed settings; * pick HIGH (500 mA) iff the caller's cap can absorb it, else * LOW (100 mA), else refuse rather than program a higher current * than the request allows. */ if (limit_ua >= MAX8903_USB_CURRENT_LIMIT_HIGH_UA) { selected = MAX8903_USB_CURRENT_LIMIT_HIGH_UA; gpio_val = 1; } else if (limit_ua >= MAX8903_USB_CURRENT_LIMIT_LOW_UA) { selected = MAX8903_USB_CURRENT_LIMIT_LOW_UA; gpio_val = 0; } else { return -EINVAL; } gpiod_set_value_cansleep(data->usb_current_limit_gpio, gpio_val); data->usb_current_limit_ua = selected; dev_dbg(data->dev, "USB current limit set to %u uA\n", data->usb_current_limit_ua); return 0; } static int max8903_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct max8903_data *data = power_supply_get_drvdata(psy); int ret; switch (psp) { case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: /* * val->intval is signed; the set_*_current_limit() helpers * take a u32. Reject negatives explicitly so a negative * request cannot widen into a huge unsigned value, bypass * the "limit <= cap" bounds check inside the helper, and * silently program the maximum permitted current. */ if (val->intval < 0) return -EINVAL; /* * Hold source_lock across the source check and the * resulting hardware write so the IRQ handler cannot * flip ta_in/usb_in between them and have us program the * limit for a source that has just gone offline. Mirror * the DC-priority policy of the get path: if DC is online * route to the DC helper (refuse if DC GPIOs aren't * configured) rather than fall through to USB. */ mutex_lock(&data->source_lock); if (data->ta_in) ret = data->dc_current_limit_gpios ? max8903_set_dc_current_limit(data, val->intval) : -ENODEV; else if (data->usb_in && data->usb_current_limit_gpio) ret = max8903_set_usb_current_limit(data, val->intval); else ret = -EINVAL; mutex_unlock(&data->source_lock); return ret; default: return -EINVAL; } } static int max8903_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { struct max8903_data *data = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return data->dc_current_limit_gpios || data->usb_current_limit_gpio; default: return 0; } } static irqreturn_t max8903_dcin(int irq, void *_data) { struct max8903_data *data = _data; bool ta_in; enum power_supply_type old_type; /* * This means the line is asserted. * * The signal is active low, but the inversion is handled in the GPIO * library as the line should be flagged GPIO_ACTIVE_LOW in the device * tree. */ /* * Hold source_lock across the full read-modify-evaluate block: * - so a concurrent max8903_set_property() sees a consistent * state (lock release would otherwise expose a window where * data->ta_in is updated but the cen/dcm writes still pend); * - so the cen enable calculation reads a stable data->usb_in * rather than racing with max8903_usbin() and writing the * wrong enable state. */ mutex_lock(&data->source_lock); ta_in = gpiod_get_value(data->dok); if (ta_in == data->ta_in) { mutex_unlock(&data->source_lock); return IRQ_HANDLED; } data->ta_in = ta_in; /* Set Current-Limit-Mode 1:DC 0:USB */ if (data->dcm) gpiod_set_value(data->dcm, ta_in); /* Charger Enable / Disable */ if (data->cen) { int val; if (ta_in) /* Certainly enable if DOK is asserted */ val = 1; else if (data->usb_in) /* Enable if the USB charger is enabled */ val = 1; else /* Else default-disable */ val = 0; gpiod_set_value(data->cen, val); } old_type = data->psy_desc.type; if (data->ta_in) data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; else if (data->usb_in) data->psy_desc.type = POWER_SUPPLY_TYPE_USB; else data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; mutex_unlock(&data->source_lock); dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? "Connected" : "Disconnected"); if (old_type != data->psy_desc.type) power_supply_changed(data->psy); return IRQ_HANDLED; } static irqreturn_t max8903_usbin(int irq, void *_data) { struct max8903_data *data = _data; bool usb_in; enum power_supply_type old_type; /* * This means the line is asserted. * * The signal is active low, but the inversion is handled in the GPIO * library as the line should be flagged GPIO_ACTIVE_LOW in the device * tree. */ /* See max8903_dcin(): hold the lock across the full update. */ mutex_lock(&data->source_lock); usb_in = gpiod_get_value(data->uok); if (usb_in == data->usb_in) { mutex_unlock(&data->source_lock); return IRQ_HANDLED; } data->usb_in = usb_in; /* Do not touch Current-Limit-Mode */ /* Charger Enable / Disable */ if (data->cen) { int val; if (usb_in) /* Certainly enable if UOK is asserted */ val = 1; else if (data->ta_in) /* Enable if the DC charger is enabled */ val = 1; else /* Else default-disable */ val = 0; gpiod_set_value(data->cen, val); } old_type = data->psy_desc.type; if (data->ta_in) data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; else if (data->usb_in) data->psy_desc.type = POWER_SUPPLY_TYPE_USB; else data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; mutex_unlock(&data->source_lock); dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? "Connected" : "Disconnected"); if (old_type != data->psy_desc.type) power_supply_changed(data->psy); return IRQ_HANDLED; } static irqreturn_t max8903_fault(int irq, void *_data) { struct max8903_data *data = _data; bool fault; /* * This means the line is asserted. * * The signal is active low, but the inversion is handled in the GPIO * library as the line should be flagged GPIO_ACTIVE_LOW in the device * tree. */ fault = gpiod_get_value(data->flt); if (fault == data->fault) return IRQ_HANDLED; data->fault = fault; if (fault) dev_err(data->dev, "Charger suffers a fault and stops.\n"); else dev_err(data->dev, "Charger recovered from a fault.\n"); return IRQ_HANDLED; } static int max8903_parse_dc_current_limit(struct platform_device *pdev, struct max8903_data *data) { struct device *dev = &pdev->dev; int ret, i, map_size; u32 *map; data->dc_current_limit_gpios = devm_gpiod_get_array_optional(dev, "dc-current-limit", GPIOD_OUT_LOW); if (IS_ERR(data->dc_current_limit_gpios)) return dev_err_probe(dev, PTR_ERR(data->dc_current_limit_gpios), "failed to get DC current limit GPIOs"); if (!data->dc_current_limit_gpios) return 0; /* Optional feature not present */ /* * gpio_value entries below are bit patterns indexed into the * dc-current-limit GPIO array. The driver represents them in * a single unsigned long for gpiod_set_array_value_cansleep(), * and BIT(ndescs) further down assumes ndescs fits in a u32 * shift; reject pathological DTs at parse time instead of * relying on undefined-behaviour-free dtschema. The binding * already caps maxItems at 4 so this is purely defensive. */ if (data->dc_current_limit_gpios->ndescs >= BITS_PER_TYPE(u32)) { dev_err(dev, "dc-current-limit-gpios: %u GPIOs exceeds %u-bit cap\n", data->dc_current_limit_gpios->ndescs, (unsigned int)BITS_PER_TYPE(u32)); return -EINVAL; } /* Parse mapping: pairs of (current_ua, gpio_value) */ map_size = device_property_count_u32(dev, "dc-current-limit-mapping"); if (map_size <= 0 || map_size % 2) { dev_err(dev, "invalid dc-current-limit-mapping\n"); return -EINVAL; } /* * map[] is a scratch buffer used only inside this function to * read the property and unpack it into data->dc_current_limit_map. * Use a plain kmalloc + kfree rather than devm_*: there is no * reason to keep the raw mirror around for the lifetime of the * device. */ map = kmalloc_array(map_size, sizeof(*map), GFP_KERNEL); if (!map) return -ENOMEM; ret = device_property_read_u32_array(dev, "dc-current-limit-mapping", map, map_size); if (ret) { dev_err(dev, "failed to read dc-current-limit-mapping\n"); kfree(map); return ret; } data->dc_current_limit_map_size = map_size / 2; data->dc_current_limit_map = devm_kcalloc(dev, data->dc_current_limit_map_size, sizeof(*data->dc_current_limit_map), GFP_KERNEL); if (!data->dc_current_limit_map) { kfree(map); return -ENOMEM; } for (i = 0; i < data->dc_current_limit_map_size; i++) { u32 gpio_value = map[i * 2 + 1]; /* * gpio_value is the bitmap programmed across the * dc-current-limit GPIOs, so it cannot represent more * bits than the GPIO array width. A larger value would * be silently truncated by gpiod_set_array_value() and * select the wrong limit; reject it at parse time so * the bogus DT is visible to the integrator. */ if (gpio_value >= BIT(data->dc_current_limit_gpios->ndescs)) { dev_err(dev, "dc-current-limit-mapping entry %d: gpio_value 0x%x exceeds %u-GPIO range\n", i, gpio_value, data->dc_current_limit_gpios->ndescs); kfree(map); return -EINVAL; } data->dc_current_limit_map[i].limit_ua = map[i * 2]; data->dc_current_limit_map[i].gpio_value = gpio_value; } kfree(map); /* * devm_gpiod_get_array_optional() above asked for GPIOD_OUT_LOW, * so the hardware mux starts at gpio_value 0. Require the DT * mapping to include a gpio_value=0 entry so the software * current-limit state has a definite initial value matching the * hardware. Without this entry we would have to guess and the * reported INPUT_CURRENT_LIMIT could disagree with what the * mux is actually wired to until a set_property write picks a * real value. */ for (i = 0; i < data->dc_current_limit_map_size; i++) if (data->dc_current_limit_map[i].gpio_value == 0) break; if (i == data->dc_current_limit_map_size) { dev_err(dev, "dc-current-limit-mapping must include a gpio_value=0 entry to describe the boot-time mux state\n"); return -EINVAL; } data->dc_current_limit_ua = data->dc_current_limit_map[i].limit_ua; dev_dbg(dev, "DC current limit control: %d levels available, initial %u uA\n", data->dc_current_limit_map_size, data->dc_current_limit_ua); return 0; } static int max8903_parse_usb_current_limit(struct platform_device *pdev, struct max8903_data *data) { struct device *dev = &pdev->dev; data->usb_current_limit_gpio = devm_gpiod_get_optional(dev, "usb-current-limit", GPIOD_OUT_LOW); if (IS_ERR(data->usb_current_limit_gpio)) return dev_err_probe(dev, PTR_ERR(data->usb_current_limit_gpio), "failed to get USB current limit GPIO"); if (!data->usb_current_limit_gpio) return 0; /* Optional feature not present */ /* Start at low current (IUSB low = 100 mA) for safety */ data->usb_current_limit_ua = MAX8903_USB_CURRENT_LIMIT_LOW_UA; return 0; } static int max8903_setup_gpios(struct platform_device *pdev) { struct max8903_data *data = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; bool ta_in = false; bool usb_in = false; enum gpiod_flags flags; data->dok = devm_gpiod_get_optional(dev, "dok", GPIOD_IN); if (IS_ERR(data->dok)) return dev_err_probe(dev, PTR_ERR(data->dok), "failed to get DOK GPIO"); if (data->dok) { gpiod_set_consumer_name(data->dok, data->psy_desc.name); /* * The DC OK is pulled up to 1 and goes low when a charger * is plugged in (active low) but in the device tree the * line is marked as GPIO_ACTIVE_LOW so we get a 1 (asserted) * here if the DC charger is plugged in. */ ta_in = gpiod_get_value(data->dok); } data->uok = devm_gpiod_get_optional(dev, "uok", GPIOD_IN); if (IS_ERR(data->uok)) return dev_err_probe(dev, PTR_ERR(data->uok), "failed to get UOK GPIO"); if (data->uok) { gpiod_set_consumer_name(data->uok, data->psy_desc.name); /* * The USB OK is pulled up to 1 and goes low when a USB charger * is plugged in (active low) but in the device tree the * line is marked as GPIO_ACTIVE_LOW so we get a 1 (asserted) * here if the USB charger is plugged in. */ usb_in = gpiod_get_value(data->uok); } /* Either DC OK or USB OK must be provided */ if (!data->dok && !data->uok) { dev_err(dev, "no valid power source\n"); return -EINVAL; } /* * If either charger is already connected at this point, * assert the CEN line and enable charging from the start. * * The line is active low but also marked with GPIO_ACTIVE_LOW * in the device tree, so when we assert the line with * GPIOD_OUT_HIGH the line will be driven low. */ flags = (ta_in || usb_in) ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW; /* * If DC OK is provided, Charger Enable CEN is compulsory * so this is not optional here. */ data->cen = devm_gpiod_get(dev, "cen", flags); if (IS_ERR(data->cen)) return dev_err_probe(dev, PTR_ERR(data->cen), "failed to get CEN GPIO"); gpiod_set_consumer_name(data->cen, data->psy_desc.name); /* * If the DC charger is connected, then select it. * * The DCM line should be marked GPIO_ACTIVE_HIGH in the * device tree. Driving it high will enable the DC charger * input over the USB charger input. */ flags = ta_in ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW; data->dcm = devm_gpiod_get_optional(dev, "dcm", flags); if (IS_ERR(data->dcm)) return dev_err_probe(dev, PTR_ERR(data->dcm), "failed to get DCM GPIO"); gpiod_set_consumer_name(data->dcm, data->psy_desc.name); data->chg = devm_gpiod_get_optional(dev, "chg", GPIOD_IN); if (IS_ERR(data->chg)) return dev_err_probe(dev, PTR_ERR(data->chg), "failed to get CHG GPIO"); gpiod_set_consumer_name(data->chg, data->psy_desc.name); data->flt = devm_gpiod_get_optional(dev, "flt", GPIOD_IN); if (IS_ERR(data->flt)) return dev_err_probe(dev, PTR_ERR(data->flt), "failed to get FLT GPIO"); gpiod_set_consumer_name(data->flt, data->psy_desc.name); data->usus = devm_gpiod_get_optional(dev, "usus", GPIOD_IN); if (IS_ERR(data->usus)) return dev_err_probe(dev, PTR_ERR(data->usus), "failed to get USUS GPIO"); gpiod_set_consumer_name(data->usus, data->psy_desc.name); data->fault = false; data->ta_in = ta_in; data->usb_in = usb_in; return 0; } static int max8903_probe(struct platform_device *pdev) { struct max8903_data *data; struct device *dev = &pdev->dev; struct power_supply_config psy_cfg = {}; int ret = 0; data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL); if (!data) return -ENOMEM; data->dev = dev; mutex_init(&data->source_lock); platform_set_drvdata(pdev, data); ret = max8903_setup_gpios(pdev); if (ret) return ret; ret = max8903_parse_dc_current_limit(pdev, data); if (ret) return ret; ret = max8903_parse_usb_current_limit(pdev, data); if (ret) return ret; data->psy_desc.name = "max8903_charger"; data->psy_desc.type = (data->ta_in) ? POWER_SUPPLY_TYPE_MAINS : ((data->usb_in) ? POWER_SUPPLY_TYPE_USB : POWER_SUPPLY_TYPE_BATTERY); data->psy_desc.get_property = max8903_get_property; data->psy_desc.set_property = max8903_set_property; data->psy_desc.property_is_writeable = max8903_property_is_writeable; data->psy_desc.properties = max8903_charger_props; data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props); psy_cfg.fwnode = dev_fwnode(dev); psy_cfg.drv_data = data; data->psy = devm_power_supply_register(dev, &data->psy_desc, &psy_cfg); if (IS_ERR(data->psy)) { dev_err(dev, "failed: power supply register.\n"); return PTR_ERR(data->psy); } if (data->dok) { ret = devm_request_threaded_irq(dev, gpiod_to_irq(data->dok), NULL, max8903_dcin, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 DC IN", data); if (ret) return ret; } if (data->uok) { ret = devm_request_threaded_irq(dev, gpiod_to_irq(data->uok), NULL, max8903_usbin, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 USB IN", data); if (ret) return ret; } if (data->flt) { ret = devm_request_threaded_irq(dev, gpiod_to_irq(data->flt), NULL, max8903_fault, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 Fault", data); if (ret) return ret; } return 0; } static const struct of_device_id max8903_match_ids[] = { { .compatible = "maxim,max8903", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, max8903_match_ids); static struct platform_driver max8903_driver = { .probe = max8903_probe, .driver = { .name = "max8903-charger", .of_match_table = max8903_match_ids }, }; module_platform_driver(max8903_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("MAX8903 Charger Driver"); MODULE_AUTHOR("MyungJoo Ham "); MODULE_ALIAS("platform:max8903-charger");