Message ID | 20211108162706.2816454-3-luca@lucaceresoli.net (mailing list archive) |
---|---|
State | Not Applicable, archived |
Headers | show |
Series | [v2,1/3] dt-bindings: power: supply: add Maxim MAX77976 battery charger | expand |
Hi, On Mon, Nov 08, 2021 at 05:27:06PM +0100, Luca Ceresoli wrote: > Add support for the MAX77976 3.5/5.5A 1-Cell Li+ Battery Charger. > > This is a simple implementation enough to be used as a simple battery > charger without OTG and boost. > > Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net> > > --- Thanks, I queued this to power-supply's for-next branch together with the other 2 patches with one small change: The of_match_ptr() is not needed and actually should result in a warning when building the driver with CONFIG_OF disabled. > > Changes in v2: > - don't depend on REGMAP_I2C, select it and depend on I2C (Randy Dunlap) > - use the newly-introduced POWER_SUPPLY_HEALTH_NO_BATTERY (Sebastian Reichel) > - report "unknown" battery health when BAT_DTLS reports prequalification > state (as discussed with Sebastian Reichel) > - change type to POWER_SUPPLY_TYPE_USB (Sebastian Reichel) > - add missing empty line (Sebastian Reichel) > --- > MAINTAINERS | 1 + > drivers/power/supply/Kconfig | 12 + > drivers/power/supply/Makefile | 1 + > drivers/power/supply/max77976_charger.c | 509 ++++++++++++++++++++++++ > 4 files changed, 523 insertions(+) > create mode 100644 drivers/power/supply/max77976_charger.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 21b38ada178c..94a1f2d03a43 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -11413,6 +11413,7 @@ MAXIM MAX77976 BATTERY CHARGER > M: Luca Ceresoli <luca@lucaceresoli.net> > S: Supported > F: Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml > +F: drivers/power/supply/max77976_charger.c > > MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS > M: Krzysztof Kozlowski <krzysztof.kozlowski@canonical.com> > diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig > index 5cf5bb56d2e3..b366e2fd8e97 100644 > --- a/drivers/power/supply/Kconfig > +++ b/drivers/power/supply/Kconfig > @@ -557,6 +557,18 @@ config CHARGER_MAX77693 > help > Say Y to enable support for the Maxim MAX77693 battery charger. > > +config CHARGER_MAX77976 > + tristate "Maxim MAX77976 battery charger driver" > + depends on I2C > + select REGMAP_I2C > + help > + The Maxim MAX77976 is a 19 Vin, 5.5A 1-Cell Li+ Battery Charger > + USB OTG support. It has an I2C interface for configuration. > + > + Say Y to enable support for the Maxim MAX77976 battery charger. > + This driver can also be built as a module. If so, the module will be > + called max77976_charger. > + > config CHARGER_MAX8997 > tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" > depends on MFD_MAX8997 && REGULATOR_MAX8997 > diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile > index 4e55a11aab79..2c1b264b2046 100644 > --- a/drivers/power/supply/Makefile > +++ b/drivers/power/supply/Makefile > @@ -75,6 +75,7 @@ obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o > obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o > obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o > obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o > +obj-$(CONFIG_CHARGER_MAX77976) += max77976_charger.o > obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o > obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o > obj-$(CONFIG_CHARGER_MP2629) += mp2629_charger.o > diff --git a/drivers/power/supply/max77976_charger.c b/drivers/power/supply/max77976_charger.c > new file mode 100644 > index 000000000000..1ff2042071c3 > --- /dev/null > +++ b/drivers/power/supply/max77976_charger.c > @@ -0,0 +1,509 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * max77976_charger.c - Driver for the Maxim MAX77976 battery charger > + * > + * Copyright (C) 2021 Luca Ceresoli > + * Author: Luca Ceresoli <luca@lucaceresoli.net> > + */ > + > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/power_supply.h> > +#include <linux/regmap.h> > + > +#define MAX77976_DRIVER_NAME "max77976-charger" > +#define MAX77976_CHIP_ID 0x76 > + > +static const char *max77976_manufacturer = "Maxim Integrated"; > +static const char *max77976_model = "MAX77976"; > + > +/* -------------------------------------------------------------------------- > + * Register map > + */ > + > +#define MAX77976_REG_CHIP_ID 0x00 > +#define MAX77976_REG_CHIP_REVISION 0x01 > +#define MAX77976_REG_CHG_INT_OK 0x12 > +#define MAX77976_REG_CHG_DETAILS_01 0x14 > +#define MAX77976_REG_CHG_CNFG_00 0x16 > +#define MAX77976_REG_CHG_CNFG_02 0x18 > +#define MAX77976_REG_CHG_CNFG_06 0x1c > +#define MAX77976_REG_CHG_CNFG_09 0x1f > + > +/* CHG_DETAILS_01.CHG_DTLS values */ > +enum max77976_charging_state { > + MAX77976_CHARGING_PREQUALIFICATION = 0x0, > + MAX77976_CHARGING_FAST_CONST_CURRENT, > + MAX77976_CHARGING_FAST_CONST_VOLTAGE, > + MAX77976_CHARGING_TOP_OFF, > + MAX77976_CHARGING_DONE, > + MAX77976_CHARGING_RESERVED_05, > + MAX77976_CHARGING_TIMER_FAULT, > + MAX77976_CHARGING_SUSPENDED_QBATT_OFF, > + MAX77976_CHARGING_OFF, > + MAX77976_CHARGING_RESERVED_09, > + MAX77976_CHARGING_THERMAL_SHUTDOWN, > + MAX77976_CHARGING_WATCHDOG_EXPIRED, > + MAX77976_CHARGING_SUSPENDED_JEITA, > + MAX77976_CHARGING_SUSPENDED_THM_REMOVAL, > + MAX77976_CHARGING_SUSPENDED_PIN, > + MAX77976_CHARGING_RESERVED_0F, > +}; > + > +/* CHG_DETAILS_01.BAT_DTLS values */ > +enum max77976_battery_state { > + MAX77976_BATTERY_BATTERY_REMOVAL = 0x0, > + MAX77976_BATTERY_PREQUALIFICATION, > + MAX77976_BATTERY_TIMER_FAULT, > + MAX77976_BATTERY_REGULAR_VOLTAGE, > + MAX77976_BATTERY_LOW_VOLTAGE, > + MAX77976_BATTERY_OVERVOLTAGE, > + MAX77976_BATTERY_RESERVED, > + MAX77976_BATTERY_BATTERY_ONLY, // No valid adapter is present > +}; > + > +/* CHG_CNFG_00.MODE values */ > +enum max77976_mode { > + MAX77976_MODE_CHARGER_BUCK = 0x5, > + MAX77976_MODE_BOOST = 0x9, > +}; > + > +/* CHG_CNFG_02.CHG_CC: charge current limit, 100..5500 mA, 50 mA steps */ > +#define MAX77976_CHG_CC_STEP 50000U > +#define MAX77976_CHG_CC_MIN 100000U > +#define MAX77976_CHG_CC_MAX 5500000U > + > +/* CHG_CNFG_09.CHGIN_ILIM: input current limit, 100..3200 mA, 100 mA steps */ > +#define MAX77976_CHGIN_ILIM_STEP 100000U > +#define MAX77976_CHGIN_ILIM_MIN 100000U > +#define MAX77976_CHGIN_ILIM_MAX 3200000U > + > +enum max77976_field_idx { > + VERSION, REVISION, /* CHIP_REVISION */ > + CHGIN_OK, /* CHG_INT_OK */ > + BAT_DTLS, CHG_DTLS, /* CHG_DETAILS_01 */ > + MODE, /* CHG_CNFG_00 */ > + CHG_CC, /* CHG_CNFG_02 */ > + CHGPROT, /* CHG_CNFG_06 */ > + CHGIN_ILIM, /* CHG_CNFG_09 */ > + MAX77976_N_REGMAP_FIELDS > +}; > + > +static const struct reg_field max77976_reg_field[MAX77976_N_REGMAP_FIELDS] = { > + [VERSION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 4, 7), > + [REVISION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 0, 3), > + [CHGIN_OK] = REG_FIELD(MAX77976_REG_CHG_INT_OK, 6, 6), > + [CHG_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 0, 3), > + [BAT_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 4, 6), > + [MODE] = REG_FIELD(MAX77976_REG_CHG_CNFG_00, 0, 3), > + [CHG_CC] = REG_FIELD(MAX77976_REG_CHG_CNFG_02, 0, 6), > + [CHGPROT] = REG_FIELD(MAX77976_REG_CHG_CNFG_06, 2, 3), > + [CHGIN_ILIM] = REG_FIELD(MAX77976_REG_CHG_CNFG_09, 0, 5), > +}; > + > +static const struct regmap_config max77976_regmap_config = { > + .reg_bits = 8, > + .val_bits = 8, > + .max_register = 0x24, > +}; > + > +/* -------------------------------------------------------------------------- > + * Data structures > + */ > + > +struct max77976 { > + struct i2c_client *client; > + struct regmap *regmap; > + struct regmap_field *rfield[MAX77976_N_REGMAP_FIELDS]; > +}; > + > +/* -------------------------------------------------------------------------- > + * power_supply properties > + */ > + > +static int max77976_get_status(struct max77976 *chg, int *val) > +{ > + unsigned int regval; > + int err; > + > + err = regmap_field_read(chg->rfield[CHG_DTLS], ®val); > + if (err < 0) > + return err; > + > + switch (regval) { > + case MAX77976_CHARGING_PREQUALIFICATION: > + case MAX77976_CHARGING_FAST_CONST_CURRENT: > + case MAX77976_CHARGING_FAST_CONST_VOLTAGE: > + case MAX77976_CHARGING_TOP_OFF: > + *val = POWER_SUPPLY_STATUS_CHARGING; > + break; > + case MAX77976_CHARGING_DONE: > + *val = POWER_SUPPLY_STATUS_FULL; > + break; > + case MAX77976_CHARGING_TIMER_FAULT: > + case MAX77976_CHARGING_SUSPENDED_QBATT_OFF: > + case MAX77976_CHARGING_SUSPENDED_JEITA: > + case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL: > + case MAX77976_CHARGING_SUSPENDED_PIN: > + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; > + break; > + case MAX77976_CHARGING_OFF: > + case MAX77976_CHARGING_THERMAL_SHUTDOWN: > + case MAX77976_CHARGING_WATCHDOG_EXPIRED: > + *val = POWER_SUPPLY_STATUS_DISCHARGING; > + break; > + default: > + *val = POWER_SUPPLY_STATUS_UNKNOWN; > + } > + > + return 0; > +} > + > +static int max77976_get_charge_type(struct max77976 *chg, int *val) > +{ > + unsigned int regval; > + int err; > + > + err = regmap_field_read(chg->rfield[CHG_DTLS], ®val); > + if (err < 0) > + return err; > + > + switch (regval) { > + case MAX77976_CHARGING_PREQUALIFICATION: > + *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; > + break; > + case MAX77976_CHARGING_FAST_CONST_CURRENT: > + case MAX77976_CHARGING_FAST_CONST_VOLTAGE: > + *val = POWER_SUPPLY_CHARGE_TYPE_FAST; > + break; > + case MAX77976_CHARGING_TOP_OFF: > + *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD; > + break; > + case MAX77976_CHARGING_DONE: > + case MAX77976_CHARGING_TIMER_FAULT: > + case MAX77976_CHARGING_SUSPENDED_QBATT_OFF: > + case MAX77976_CHARGING_OFF: > + case MAX77976_CHARGING_THERMAL_SHUTDOWN: > + case MAX77976_CHARGING_WATCHDOG_EXPIRED: > + case MAX77976_CHARGING_SUSPENDED_JEITA: > + case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL: > + case MAX77976_CHARGING_SUSPENDED_PIN: > + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; > + break; > + default: > + *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; > + } > + > + return 0; > +} > + > +static int max77976_get_health(struct max77976 *chg, int *val) > +{ > + unsigned int regval; > + int err; > + > + err = regmap_field_read(chg->rfield[BAT_DTLS], ®val); > + if (err < 0) > + return err; > + > + switch (regval) { > + case MAX77976_BATTERY_BATTERY_REMOVAL: > + *val = POWER_SUPPLY_HEALTH_NO_BATTERY; > + break; > + case MAX77976_BATTERY_LOW_VOLTAGE: > + case MAX77976_BATTERY_REGULAR_VOLTAGE: > + *val = POWER_SUPPLY_HEALTH_GOOD; > + break; > + case MAX77976_BATTERY_TIMER_FAULT: > + *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; > + break; > + case MAX77976_BATTERY_OVERVOLTAGE: > + *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; > + break; > + case MAX77976_BATTERY_PREQUALIFICATION: > + case MAX77976_BATTERY_BATTERY_ONLY: > + *val = POWER_SUPPLY_HEALTH_UNKNOWN; > + break; > + default: > + *val = POWER_SUPPLY_HEALTH_UNKNOWN; > + } > + > + return 0; > +} > + > +static int max77976_get_online(struct max77976 *chg, int *val) > +{ > + unsigned int regval; > + int err; > + > + err = regmap_field_read(chg->rfield[CHGIN_OK], ®val); > + if (err < 0) > + return err; > + > + *val = (regval ? 1 : 0); > + > + return 0; > +} > + > +static int max77976_get_integer(struct max77976 *chg, enum max77976_field_idx fidx, > + unsigned int clamp_min, unsigned int clamp_max, > + unsigned int mult, int *val) > +{ > + unsigned int regval; > + int err; > + > + err = regmap_field_read(chg->rfield[fidx], ®val); > + if (err < 0) > + return err; > + > + *val = clamp_val(regval * mult, clamp_min, clamp_max); > + > + return 0; > +} > + > +static int max77976_set_integer(struct max77976 *chg, enum max77976_field_idx fidx, > + unsigned int clamp_min, unsigned int clamp_max, > + unsigned int div, int val) > +{ > + unsigned int regval; > + > + regval = clamp_val(val, clamp_min, clamp_max) / div; > + > + return regmap_field_write(chg->rfield[fidx], regval); > +} > + > +static int max77976_get_property(struct power_supply *psy, > + enum power_supply_property psp, > + union power_supply_propval *val) > +{ > + struct max77976 *chg = power_supply_get_drvdata(psy); > + int err = 0; > + > + switch (psp) { > + case POWER_SUPPLY_PROP_STATUS: > + err = max77976_get_status(chg, &val->intval); > + break; > + case POWER_SUPPLY_PROP_CHARGE_TYPE: > + err = max77976_get_charge_type(chg, &val->intval); > + break; > + case POWER_SUPPLY_PROP_HEALTH: > + err = max77976_get_health(chg, &val->intval); > + break; > + case POWER_SUPPLY_PROP_ONLINE: > + err = max77976_get_online(chg, &val->intval); > + break; > + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: > + val->intval = MAX77976_CHG_CC_MAX; > + break; > + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: > + err = max77976_get_integer(chg, CHG_CC, > + MAX77976_CHG_CC_MIN, > + MAX77976_CHG_CC_MAX, > + MAX77976_CHG_CC_STEP, > + &val->intval); > + break; > + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: > + err = max77976_get_integer(chg, CHGIN_ILIM, > + MAX77976_CHGIN_ILIM_MIN, > + MAX77976_CHGIN_ILIM_MAX, > + MAX77976_CHGIN_ILIM_STEP, > + &val->intval); > + break; > + case POWER_SUPPLY_PROP_MODEL_NAME: > + val->strval = max77976_model; > + break; > + case POWER_SUPPLY_PROP_MANUFACTURER: > + val->strval = max77976_manufacturer; > + break; > + default: > + err = -EINVAL; > + } > + > + return err; > +} > + > +static int max77976_set_property(struct power_supply *psy, > + enum power_supply_property psp, > + const union power_supply_propval *val) > +{ > + struct max77976 *chg = power_supply_get_drvdata(psy); > + int err = 0; > + > + switch (psp) { > + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: > + err = max77976_set_integer(chg, CHG_CC, > + MAX77976_CHG_CC_MIN, > + MAX77976_CHG_CC_MAX, > + MAX77976_CHG_CC_STEP, > + val->intval); > + break; > + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: > + err = max77976_set_integer(chg, CHGIN_ILIM, > + MAX77976_CHGIN_ILIM_MIN, > + MAX77976_CHGIN_ILIM_MAX, > + MAX77976_CHGIN_ILIM_STEP, > + val->intval); > + break; > + default: > + err = -EINVAL; > + } > + > + return err; > +}; > + > +static int max77976_property_is_writeable(struct power_supply *psy, > + enum power_supply_property psp) > +{ > + switch (psp) { > + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: > + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: > + return true; > + default: > + return false; > + } > +} > + > +static enum power_supply_property max77976_psy_props[] = { > + POWER_SUPPLY_PROP_STATUS, > + POWER_SUPPLY_PROP_CHARGE_TYPE, > + POWER_SUPPLY_PROP_HEALTH, > + POWER_SUPPLY_PROP_ONLINE, > + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, > + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, > + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, > + POWER_SUPPLY_PROP_MODEL_NAME, > + POWER_SUPPLY_PROP_MANUFACTURER, > +}; > + > +static const struct power_supply_desc max77976_psy_desc = { > + .name = MAX77976_DRIVER_NAME, > + .type = POWER_SUPPLY_TYPE_USB, > + .properties = max77976_psy_props, > + .num_properties = ARRAY_SIZE(max77976_psy_props), > + .get_property = max77976_get_property, > + .set_property = max77976_set_property, > + .property_is_writeable = max77976_property_is_writeable, > +}; > + > +/* -------------------------------------------------------------------------- > + * Entry point > + */ > + > +static int max77976_detect(struct max77976 *chg) > +{ > + struct device *dev = &chg->client->dev; > + unsigned int id, ver, rev; > + int err; > + > + err = regmap_read(chg->regmap, MAX77976_REG_CHIP_ID, &id); > + if (err) > + return dev_err_probe(dev, err, "cannot read chip ID\n"); > + > + if (id != MAX77976_CHIP_ID) > + return dev_err_probe(dev, -ENXIO, "unknown model ID 0x%02x\n", id); > + > + err = regmap_field_read(chg->rfield[VERSION], &ver); > + if (!err) > + err = regmap_field_read(chg->rfield[REVISION], &rev); > + if (err) > + return dev_err_probe(dev, -ENXIO, "cannot read version/revision\n"); > + > + dev_info(dev, "detected model MAX779%02x ver %u rev %u", id, ver, rev); > + > + return 0; > +} > + > +static int max77976_configure(struct max77976 *chg) > +{ > + struct device *dev = &chg->client->dev; > + int err; > + > + /* Magic value to unlock writing to some registers */ > + err = regmap_field_write(chg->rfield[CHGPROT], 0x3); > + if (err) > + goto err; > + > + /* > + * Mode 5 = Charger ON, OTG OFF, buck ON, boost OFF. > + * Other modes are not implemented by this driver. > + */ > + err = regmap_field_write(chg->rfield[MODE], MAX77976_MODE_CHARGER_BUCK); > + if (err) > + goto err; > + > + return 0; > + > +err: > + return dev_err_probe(dev, err, "error while configuring"); > +} > + > +static int max77976_probe(struct i2c_client *client) > +{ > + struct device *dev = &client->dev; > + struct power_supply_config psy_cfg = {}; > + struct power_supply *psy; > + struct max77976 *chg; > + int err; > + int i; > + > + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); > + if (!chg) > + return -ENOMEM; > + > + i2c_set_clientdata(client, chg); > + psy_cfg.drv_data = chg; > + chg->client = client; > + > + chg->regmap = devm_regmap_init_i2c(client, &max77976_regmap_config); > + if (IS_ERR(chg->regmap)) > + return dev_err_probe(dev, PTR_ERR(chg->regmap), > + "cannot allocate regmap\n"); > + > + for (i = 0; i < MAX77976_N_REGMAP_FIELDS; i++) { > + chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap, > + max77976_reg_field[i]); > + if (IS_ERR(chg->rfield[i])) > + return dev_err_probe(dev, PTR_ERR(chg->rfield[i]), > + "cannot allocate regmap field\n"); > + } > + > + err = max77976_detect(chg); > + if (err) > + return err; > + > + err = max77976_configure(chg); > + if (err) > + return err; > + > + psy = devm_power_supply_register_no_ws(dev, &max77976_psy_desc, &psy_cfg); > + if (IS_ERR(psy)) > + return dev_err_probe(dev, PTR_ERR(psy), "cannot register\n"); > + > + return 0; > +} > + > +static const struct i2c_device_id max77976_i2c_id[] = { > + { MAX77976_DRIVER_NAME, 0 }, > + { }, > +}; > +MODULE_DEVICE_TABLE(i2c, max77976_i2c_id); > + > +static const struct of_device_id max77976_of_id[] = { > + { .compatible = "maxim,max77976" }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, max77976_of_id); > + > +static struct i2c_driver max77976_driver = { > + .driver = { > + .name = MAX77976_DRIVER_NAME, > + .of_match_table = of_match_ptr(max77976_of_id), > + }, > + .probe_new = max77976_probe, > + .id_table = max77976_i2c_id, > +}; > +module_i2c_driver(max77976_driver); > + > +MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>"); > +MODULE_DESCRIPTION("Maxim MAX77976 charger driver"); > +MODULE_LICENSE("GPL v2"); > -- > 2.25.1 >
Hi Sebastian, On 16/11/21 16:21, Sebastian Reichel wrote: > Hi, > > On Mon, Nov 08, 2021 at 05:27:06PM +0100, Luca Ceresoli wrote: >> Add support for the MAX77976 3.5/5.5A 1-Cell Li+ Battery Charger. >> >> This is a simple implementation enough to be used as a simple battery >> charger without OTG and boost. >> >> Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net> >> >> --- > > Thanks, I queued this to power-supply's for-next branch together > with the other 2 patches Thanks! > with one small change: The of_match_ptr() > is not needed and actually should result in a warning when building > the driver with CONFIG_OF disabled. Ouch. I made the same mistake in the past and the kernel test robot noticed, I wonder why it didn't this time but hopefully I will remember in the future! Regards,
diff --git a/MAINTAINERS b/MAINTAINERS index 21b38ada178c..94a1f2d03a43 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11413,6 +11413,7 @@ MAXIM MAX77976 BATTERY CHARGER M: Luca Ceresoli <luca@lucaceresoli.net> S: Supported F: Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml +F: drivers/power/supply/max77976_charger.c MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS M: Krzysztof Kozlowski <krzysztof.kozlowski@canonical.com> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 5cf5bb56d2e3..b366e2fd8e97 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -557,6 +557,18 @@ config CHARGER_MAX77693 help Say Y to enable support for the Maxim MAX77693 battery charger. +config CHARGER_MAX77976 + tristate "Maxim MAX77976 battery charger driver" + depends on I2C + select REGMAP_I2C + help + The Maxim MAX77976 is a 19 Vin, 5.5A 1-Cell Li+ Battery Charger + USB OTG support. It has an I2C interface for configuration. + + Say Y to enable support for the Maxim MAX77976 battery charger. + This driver can also be built as a module. If so, the module will be + called max77976_charger. + config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" depends on MFD_MAX8997 && REGULATOR_MAX8997 diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 4e55a11aab79..2c1b264b2046 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o +obj-$(CONFIG_CHARGER_MAX77976) += max77976_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o obj-$(CONFIG_CHARGER_MP2629) += mp2629_charger.o diff --git a/drivers/power/supply/max77976_charger.c b/drivers/power/supply/max77976_charger.c new file mode 100644 index 000000000000..1ff2042071c3 --- /dev/null +++ b/drivers/power/supply/max77976_charger.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * max77976_charger.c - Driver for the Maxim MAX77976 battery charger + * + * Copyright (C) 2021 Luca Ceresoli + * Author: Luca Ceresoli <luca@lucaceresoli.net> + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> + +#define MAX77976_DRIVER_NAME "max77976-charger" +#define MAX77976_CHIP_ID 0x76 + +static const char *max77976_manufacturer = "Maxim Integrated"; +static const char *max77976_model = "MAX77976"; + +/* -------------------------------------------------------------------------- + * Register map + */ + +#define MAX77976_REG_CHIP_ID 0x00 +#define MAX77976_REG_CHIP_REVISION 0x01 +#define MAX77976_REG_CHG_INT_OK 0x12 +#define MAX77976_REG_CHG_DETAILS_01 0x14 +#define MAX77976_REG_CHG_CNFG_00 0x16 +#define MAX77976_REG_CHG_CNFG_02 0x18 +#define MAX77976_REG_CHG_CNFG_06 0x1c +#define MAX77976_REG_CHG_CNFG_09 0x1f + +/* CHG_DETAILS_01.CHG_DTLS values */ +enum max77976_charging_state { + MAX77976_CHARGING_PREQUALIFICATION = 0x0, + MAX77976_CHARGING_FAST_CONST_CURRENT, + MAX77976_CHARGING_FAST_CONST_VOLTAGE, + MAX77976_CHARGING_TOP_OFF, + MAX77976_CHARGING_DONE, + MAX77976_CHARGING_RESERVED_05, + MAX77976_CHARGING_TIMER_FAULT, + MAX77976_CHARGING_SUSPENDED_QBATT_OFF, + MAX77976_CHARGING_OFF, + MAX77976_CHARGING_RESERVED_09, + MAX77976_CHARGING_THERMAL_SHUTDOWN, + MAX77976_CHARGING_WATCHDOG_EXPIRED, + MAX77976_CHARGING_SUSPENDED_JEITA, + MAX77976_CHARGING_SUSPENDED_THM_REMOVAL, + MAX77976_CHARGING_SUSPENDED_PIN, + MAX77976_CHARGING_RESERVED_0F, +}; + +/* CHG_DETAILS_01.BAT_DTLS values */ +enum max77976_battery_state { + MAX77976_BATTERY_BATTERY_REMOVAL = 0x0, + MAX77976_BATTERY_PREQUALIFICATION, + MAX77976_BATTERY_TIMER_FAULT, + MAX77976_BATTERY_REGULAR_VOLTAGE, + MAX77976_BATTERY_LOW_VOLTAGE, + MAX77976_BATTERY_OVERVOLTAGE, + MAX77976_BATTERY_RESERVED, + MAX77976_BATTERY_BATTERY_ONLY, // No valid adapter is present +}; + +/* CHG_CNFG_00.MODE values */ +enum max77976_mode { + MAX77976_MODE_CHARGER_BUCK = 0x5, + MAX77976_MODE_BOOST = 0x9, +}; + +/* CHG_CNFG_02.CHG_CC: charge current limit, 100..5500 mA, 50 mA steps */ +#define MAX77976_CHG_CC_STEP 50000U +#define MAX77976_CHG_CC_MIN 100000U +#define MAX77976_CHG_CC_MAX 5500000U + +/* CHG_CNFG_09.CHGIN_ILIM: input current limit, 100..3200 mA, 100 mA steps */ +#define MAX77976_CHGIN_ILIM_STEP 100000U +#define MAX77976_CHGIN_ILIM_MIN 100000U +#define MAX77976_CHGIN_ILIM_MAX 3200000U + +enum max77976_field_idx { + VERSION, REVISION, /* CHIP_REVISION */ + CHGIN_OK, /* CHG_INT_OK */ + BAT_DTLS, CHG_DTLS, /* CHG_DETAILS_01 */ + MODE, /* CHG_CNFG_00 */ + CHG_CC, /* CHG_CNFG_02 */ + CHGPROT, /* CHG_CNFG_06 */ + CHGIN_ILIM, /* CHG_CNFG_09 */ + MAX77976_N_REGMAP_FIELDS +}; + +static const struct reg_field max77976_reg_field[MAX77976_N_REGMAP_FIELDS] = { + [VERSION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 4, 7), + [REVISION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 0, 3), + [CHGIN_OK] = REG_FIELD(MAX77976_REG_CHG_INT_OK, 6, 6), + [CHG_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 0, 3), + [BAT_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 4, 6), + [MODE] = REG_FIELD(MAX77976_REG_CHG_CNFG_00, 0, 3), + [CHG_CC] = REG_FIELD(MAX77976_REG_CHG_CNFG_02, 0, 6), + [CHGPROT] = REG_FIELD(MAX77976_REG_CHG_CNFG_06, 2, 3), + [CHGIN_ILIM] = REG_FIELD(MAX77976_REG_CHG_CNFG_09, 0, 5), +}; + +static const struct regmap_config max77976_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x24, +}; + +/* -------------------------------------------------------------------------- + * Data structures + */ + +struct max77976 { + struct i2c_client *client; + struct regmap *regmap; + struct regmap_field *rfield[MAX77976_N_REGMAP_FIELDS]; +}; + +/* -------------------------------------------------------------------------- + * power_supply properties + */ + +static int max77976_get_status(struct max77976 *chg, int *val) +{ + unsigned int regval; + int err; + + err = regmap_field_read(chg->rfield[CHG_DTLS], ®val); + if (err < 0) + return err; + + switch (regval) { + case MAX77976_CHARGING_PREQUALIFICATION: + case MAX77976_CHARGING_FAST_CONST_CURRENT: + case MAX77976_CHARGING_FAST_CONST_VOLTAGE: + case MAX77976_CHARGING_TOP_OFF: + *val = POWER_SUPPLY_STATUS_CHARGING; + break; + case MAX77976_CHARGING_DONE: + *val = POWER_SUPPLY_STATUS_FULL; + break; + case MAX77976_CHARGING_TIMER_FAULT: + case MAX77976_CHARGING_SUSPENDED_QBATT_OFF: + case MAX77976_CHARGING_SUSPENDED_JEITA: + case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL: + case MAX77976_CHARGING_SUSPENDED_PIN: + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case MAX77976_CHARGING_OFF: + case MAX77976_CHARGING_THERMAL_SHUTDOWN: + case MAX77976_CHARGING_WATCHDOG_EXPIRED: + *val = POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + *val = POWER_SUPPLY_STATUS_UNKNOWN; + } + + return 0; +} + +static int max77976_get_charge_type(struct max77976 *chg, int *val) +{ + unsigned int regval; + int err; + + err = regmap_field_read(chg->rfield[CHG_DTLS], ®val); + if (err < 0) + return err; + + switch (regval) { + case MAX77976_CHARGING_PREQUALIFICATION: + *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case MAX77976_CHARGING_FAST_CONST_CURRENT: + case MAX77976_CHARGING_FAST_CONST_VOLTAGE: + *val = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case MAX77976_CHARGING_TOP_OFF: + *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + break; + case MAX77976_CHARGING_DONE: + case MAX77976_CHARGING_TIMER_FAULT: + case MAX77976_CHARGING_SUSPENDED_QBATT_OFF: + case MAX77976_CHARGING_OFF: + case MAX77976_CHARGING_THERMAL_SHUTDOWN: + case MAX77976_CHARGING_WATCHDOG_EXPIRED: + case MAX77976_CHARGING_SUSPENDED_JEITA: + case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL: + case MAX77976_CHARGING_SUSPENDED_PIN: + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + default: + *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + + return 0; +} + +static int max77976_get_health(struct max77976 *chg, int *val) +{ + unsigned int regval; + int err; + + err = regmap_field_read(chg->rfield[BAT_DTLS], ®val); + if (err < 0) + return err; + + switch (regval) { + case MAX77976_BATTERY_BATTERY_REMOVAL: + *val = POWER_SUPPLY_HEALTH_NO_BATTERY; + break; + case MAX77976_BATTERY_LOW_VOLTAGE: + case MAX77976_BATTERY_REGULAR_VOLTAGE: + *val = POWER_SUPPLY_HEALTH_GOOD; + break; + case MAX77976_BATTERY_TIMER_FAULT: + *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + break; + case MAX77976_BATTERY_OVERVOLTAGE: + *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + case MAX77976_BATTERY_PREQUALIFICATION: + case MAX77976_BATTERY_BATTERY_ONLY: + *val = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + default: + *val = POWER_SUPPLY_HEALTH_UNKNOWN; + } + + return 0; +} + +static int max77976_get_online(struct max77976 *chg, int *val) +{ + unsigned int regval; + int err; + + err = regmap_field_read(chg->rfield[CHGIN_OK], ®val); + if (err < 0) + return err; + + *val = (regval ? 1 : 0); + + return 0; +} + +static int max77976_get_integer(struct max77976 *chg, enum max77976_field_idx fidx, + unsigned int clamp_min, unsigned int clamp_max, + unsigned int mult, int *val) +{ + unsigned int regval; + int err; + + err = regmap_field_read(chg->rfield[fidx], ®val); + if (err < 0) + return err; + + *val = clamp_val(regval * mult, clamp_min, clamp_max); + + return 0; +} + +static int max77976_set_integer(struct max77976 *chg, enum max77976_field_idx fidx, + unsigned int clamp_min, unsigned int clamp_max, + unsigned int div, int val) +{ + unsigned int regval; + + regval = clamp_val(val, clamp_min, clamp_max) / div; + + return regmap_field_write(chg->rfield[fidx], regval); +} + +static int max77976_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77976 *chg = power_supply_get_drvdata(psy); + int err = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + err = max77976_get_status(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + err = max77976_get_charge_type(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_HEALTH: + err = max77976_get_health(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + err = max77976_get_online(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + val->intval = MAX77976_CHG_CC_MAX; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + err = max77976_get_integer(chg, CHG_CC, + MAX77976_CHG_CC_MIN, + MAX77976_CHG_CC_MAX, + MAX77976_CHG_CC_STEP, + &val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + err = max77976_get_integer(chg, CHGIN_ILIM, + MAX77976_CHGIN_ILIM_MIN, + MAX77976_CHGIN_ILIM_MAX, + MAX77976_CHGIN_ILIM_STEP, + &val->intval); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = max77976_model; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = max77976_manufacturer; + break; + default: + err = -EINVAL; + } + + return err; +} + +static int max77976_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77976 *chg = power_supply_get_drvdata(psy); + int err = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + err = max77976_set_integer(chg, CHG_CC, + MAX77976_CHG_CC_MIN, + MAX77976_CHG_CC_MAX, + MAX77976_CHG_CC_STEP, + val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + err = max77976_set_integer(chg, CHGIN_ILIM, + MAX77976_CHGIN_ILIM_MIN, + MAX77976_CHGIN_ILIM_MAX, + MAX77976_CHGIN_ILIM_STEP, + val->intval); + break; + default: + err = -EINVAL; + } + + return err; +}; + +static int max77976_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return true; + default: + return false; + } +} + +static enum power_supply_property max77976_psy_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static const struct power_supply_desc max77976_psy_desc = { + .name = MAX77976_DRIVER_NAME, + .type = POWER_SUPPLY_TYPE_USB, + .properties = max77976_psy_props, + .num_properties = ARRAY_SIZE(max77976_psy_props), + .get_property = max77976_get_property, + .set_property = max77976_set_property, + .property_is_writeable = max77976_property_is_writeable, +}; + +/* -------------------------------------------------------------------------- + * Entry point + */ + +static int max77976_detect(struct max77976 *chg) +{ + struct device *dev = &chg->client->dev; + unsigned int id, ver, rev; + int err; + + err = regmap_read(chg->regmap, MAX77976_REG_CHIP_ID, &id); + if (err) + return dev_err_probe(dev, err, "cannot read chip ID\n"); + + if (id != MAX77976_CHIP_ID) + return dev_err_probe(dev, -ENXIO, "unknown model ID 0x%02x\n", id); + + err = regmap_field_read(chg->rfield[VERSION], &ver); + if (!err) + err = regmap_field_read(chg->rfield[REVISION], &rev); + if (err) + return dev_err_probe(dev, -ENXIO, "cannot read version/revision\n"); + + dev_info(dev, "detected model MAX779%02x ver %u rev %u", id, ver, rev); + + return 0; +} + +static int max77976_configure(struct max77976 *chg) +{ + struct device *dev = &chg->client->dev; + int err; + + /* Magic value to unlock writing to some registers */ + err = regmap_field_write(chg->rfield[CHGPROT], 0x3); + if (err) + goto err; + + /* + * Mode 5 = Charger ON, OTG OFF, buck ON, boost OFF. + * Other modes are not implemented by this driver. + */ + err = regmap_field_write(chg->rfield[MODE], MAX77976_MODE_CHARGER_BUCK); + if (err) + goto err; + + return 0; + +err: + return dev_err_probe(dev, err, "error while configuring"); +} + +static int max77976_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct power_supply_config psy_cfg = {}; + struct power_supply *psy; + struct max77976 *chg; + int err; + int i; + + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + i2c_set_clientdata(client, chg); + psy_cfg.drv_data = chg; + chg->client = client; + + chg->regmap = devm_regmap_init_i2c(client, &max77976_regmap_config); + if (IS_ERR(chg->regmap)) + return dev_err_probe(dev, PTR_ERR(chg->regmap), + "cannot allocate regmap\n"); + + for (i = 0; i < MAX77976_N_REGMAP_FIELDS; i++) { + chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap, + max77976_reg_field[i]); + if (IS_ERR(chg->rfield[i])) + return dev_err_probe(dev, PTR_ERR(chg->rfield[i]), + "cannot allocate regmap field\n"); + } + + err = max77976_detect(chg); + if (err) + return err; + + err = max77976_configure(chg); + if (err) + return err; + + psy = devm_power_supply_register_no_ws(dev, &max77976_psy_desc, &psy_cfg); + if (IS_ERR(psy)) + return dev_err_probe(dev, PTR_ERR(psy), "cannot register\n"); + + return 0; +} + +static const struct i2c_device_id max77976_i2c_id[] = { + { MAX77976_DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max77976_i2c_id); + +static const struct of_device_id max77976_of_id[] = { + { .compatible = "maxim,max77976" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max77976_of_id); + +static struct i2c_driver max77976_driver = { + .driver = { + .name = MAX77976_DRIVER_NAME, + .of_match_table = of_match_ptr(max77976_of_id), + }, + .probe_new = max77976_probe, + .id_table = max77976_i2c_id, +}; +module_i2c_driver(max77976_driver); + +MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>"); +MODULE_DESCRIPTION("Maxim MAX77976 charger driver"); +MODULE_LICENSE("GPL v2");
Add support for the MAX77976 3.5/5.5A 1-Cell Li+ Battery Charger. This is a simple implementation enough to be used as a simple battery charger without OTG and boost. Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net> --- Changes in v2: - don't depend on REGMAP_I2C, select it and depend on I2C (Randy Dunlap) - use the newly-introduced POWER_SUPPLY_HEALTH_NO_BATTERY (Sebastian Reichel) - report "unknown" battery health when BAT_DTLS reports prequalification state (as discussed with Sebastian Reichel) - change type to POWER_SUPPLY_TYPE_USB (Sebastian Reichel) - add missing empty line (Sebastian Reichel) --- MAINTAINERS | 1 + drivers/power/supply/Kconfig | 12 + drivers/power/supply/Makefile | 1 + drivers/power/supply/max77976_charger.c | 509 ++++++++++++++++++++++++ 4 files changed, 523 insertions(+) create mode 100644 drivers/power/supply/max77976_charger.c