diff mbox series

[v2,3/3] power: supply: max77976: add Maxim MAX77976 charger driver

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

Commit Message

Luca Ceresoli Nov. 8, 2021, 4:27 p.m. UTC
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

Comments

Sebastian Reichel Nov. 16, 2021, 3:21 p.m. UTC | #1
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], &regval);
> +	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], &regval);
> +	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], &regval);
> +	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], &regval);
> +	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], &regval);
> +	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
>
Luca Ceresoli Nov. 16, 2021, 5:35 p.m. UTC | #2
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 mbox series

Patch

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], &regval);
+	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], &regval);
+	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], &regval);
+	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], &regval);
+	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], &regval);
+	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");