diff mbox series

[2/2] power: supply: max77976: add Maxim MAX77976 charger driver

Message ID 20211004130732.950512-2-luca@lucaceresoli.net (mailing list archive)
State Not Applicable, archived
Headers show
Series [1/2] dt-bindings: power: supply: add Maxim MAX77976 battery charger | expand

Commit Message

Luca Ceresoli Oct. 4, 2021, 1:07 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>
---
 MAINTAINERS                             |   1 +
 drivers/power/supply/Kconfig            |  11 +
 drivers/power/supply/Makefile           |   1 +
 drivers/power/supply/max77976_charger.c | 508 ++++++++++++++++++++++++
 4 files changed, 521 insertions(+)
 create mode 100644 drivers/power/supply/max77976_charger.c

Comments

Randy Dunlap Oct. 4, 2021, 3:28 p.m. UTC | #1
On 10/4/21 6:07 AM, Luca Ceresoli wrote:
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index ad93b3550d6d..622d690c883a 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -557,6 +557,17 @@ 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 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.

REGMAP_I2C is not a user-settable config option, so drivers should not
"depend on" it. This should be more like:

	depends on I2C
	select REGMAP_I2C
Luca Ceresoli Oct. 5, 2021, 1:15 p.m. UTC | #2
Hi Randy,

On 04/10/21 17:28, Randy Dunlap wrote:
> On 10/4/21 6:07 AM, Luca Ceresoli wrote:
>> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
>> index ad93b3550d6d..622d690c883a 100644
>> --- a/drivers/power/supply/Kconfig
>> +++ b/drivers/power/supply/Kconfig
>> @@ -557,6 +557,17 @@ 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 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.
> 
> REGMAP_I2C is not a user-settable config option, so drivers should not
> "depend on" it. This should be more like:
> 
>     depends on I2C
>     select REGMAP_I2C

Ouch, thanks for spotting, will fix in v2.
Sebastian Reichel Oct. 25, 2021, 10:02 p.m. UTC | #3
Hi,

On Mon, Oct 04, 2021 at 03:07:32PM +0200, 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>
> ---
>  MAINTAINERS                             |   1 +
>  drivers/power/supply/Kconfig            |  11 +
>  drivers/power/supply/Makefile           |   1 +
>  drivers/power/supply/max77976_charger.c | 508 ++++++++++++++++++++++++
>  4 files changed, 521 insertions(+)
>  create mode 100644 drivers/power/supply/max77976_charger.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b3a3667cef46..064c0560b810 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11392,6 +11392,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 ad93b3550d6d..622d690c883a 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -557,6 +557,17 @@ 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 REGMAP_I2C

needs to be selected as pointed out by Randy.

> +	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..2ff900b1843a
> --- /dev/null
> +++ b/drivers/power/supply/max77976_charger.c
> @@ -0,0 +1,508 @@
> +// 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_DEAD;
> +		break;

I suppose the charger is still able to power the system when
there is no battery, so it's not dead. I think this should
either report POWER_SUPPLY_HEALTH_GOOD or introduce a new
POWER_SUPPLY_HEALTH_NO_BATTERY.

> +	case MAX77976_BATTERY_PREQUALIFICATION:
> +	case MAX77976_BATTERY_LOW_VOLTAGE:

Not sure what prequalification is, but at least low voltage seems
like a candidate to either introduce a new health property, or
report an unspecified failure instead of HEALTH_GOOD.

> +	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_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_BATTERY,

Incorrect type. TYPE_BATTERY is for fuel gauges. Looks like max77976
is supposed to be used with USB, so:

.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;
> +}

missing newline

> +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");

Otherwise looks good!

-- Sebastian
Luca Ceresoli Oct. 26, 2021, 11:50 a.m. UTC | #4
Hi Sebastian,

On 26/10/21 00:02, Sebastian Reichel wrote:
[...]
>> +config CHARGER_MAX77976
>> +	tristate "Maxim MAX77976 battery charger driver"
>> +	depends on REGMAP_I2C
> 
> needs to be selected as pointed out by Randy.

Yes, fix ready for v2.

>> +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_DEAD;
>> +		break;
> 
> I suppose the charger is still able to power the system when
> there is no battery, so it's not dead.

Correct.

> I think this should
> either report POWER_SUPPLY_HEALTH_GOOD or introduce a new
> POWER_SUPPLY_HEALTH_NO_BATTERY.

Introducing POWER_SUPPLY_HEALTH_NO_BATTERY seems to be the correct way.
I'll add a patch for that.

>> +	case MAX77976_BATTERY_PREQUALIFICATION:
>> +	case MAX77976_BATTERY_LOW_VOLTAGE:
> 
> Not sure what prequalification is, but at least low voltage seems
> like a candidate to either introduce a new health property, or
> report an unspecified failure instead of HEALTH_GOOD.

Prequalification is defined in the datasheet [0] as: "A valid adapter is
present and the battery voltage is low: V_BATT < V_TRICKLE" (page 57).
The battery is charged at a very low current (300 mA) until voltage goes
above V_trickle (3.1 V).

[0] https://datasheets.maximintegrated.com/en/ds/MAX77975-MAX77976.pdf

This state probably very short-lived as I don't normally observe it even
when connecting a completely discharged battery.

To the best of my understanding, in this state the charger is charging
slowly to ensure the battery is good while staying safe. Perhaps this
should return POWER_SUPPLY_HEALTH_UNKNOWN then.

Low voltage is defined as: "A valid adapter is present and the battery
voltage is lower than the minimum system regulation level but higher
than prequalification voltage: V_TRICKLE < V_BATT < V_SYSMIN. V_SYS is
regulated at least equal to V_SYSMIN".

In this state the battery is not fully charged but it is OK, as
confirmed by the definition of bit BAT_OK in the CHG_INT_OK register
(0x12): "The battery is okay. BAT_DTLS = 0x03,0x04 or 0x07", value 0x04
being MAX77976_BATTERY_LOW_VOLTAGE.

So I think I should change PREQUALIFICATION to report
POWER_SUPPLY_HEALTH_UNKNOWN and leave LOW_VOLTAGE as is. Does it sound
correct?

>> +static const struct power_supply_desc max77976_psy_desc = {
>> +	.name			= MAX77976_DRIVER_NAME,
>> +	.type			= POWER_SUPPLY_TYPE_BATTERY,
> 
> Incorrect type. TYPE_BATTERY is for fuel gauges. Looks like max77976
> is supposed to be used with USB, so:
> 
> .type = POWER_SUPPLY_TYPE_USB,

Even though this charger is definitely a USB-oriented one, it is not
restricted to USB. E.g. I'm using it without any USB. Should I use
POWER_SUPPLY_TYPE_USB anyway?

>> +/* --------------------------------------------------------------------------
>> + * 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;
>> +}
> 
> missing newline

Ouch.
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index b3a3667cef46..064c0560b810 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11392,6 +11392,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 ad93b3550d6d..622d690c883a 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -557,6 +557,17 @@  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 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..2ff900b1843a
--- /dev/null
+++ b/drivers/power/supply/max77976_charger.c
@@ -0,0 +1,508 @@ 
+// 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_DEAD;
+		break;
+	case MAX77976_BATTERY_PREQUALIFICATION:
+	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_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_BATTERY,
+	.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");