diff mbox series

[3/6] drivers: power: supply: add MAX77659 charger support

Message ID 20221220132250.19383-4-Zeynep.Arslanbenzer@analog.com
State New
Headers show
Series [1/6] drivers: mfd: add MAX77659 PMIC support | expand

Commit Message

Arslanbenzer, Zeynep Dec. 20, 2022, 1:22 p.m. UTC
This patch adds battery charger driver for MAX77659.

Signed-off-by: Nurettin Bolucu <Nurettin.Bolucu@analog.com>
Signed-off-by: Zeynep Arslanbenzer <Zeynep.Arslanbenzer@analog.com>
---
 MAINTAINERS                             |   1 +
 drivers/power/supply/Kconfig            |   7 +
 drivers/power/supply/Makefile           |   1 +
 drivers/power/supply/max77659-charger.c | 547 ++++++++++++++++++++++++
 4 files changed, 556 insertions(+)
 create mode 100644 drivers/power/supply/max77659-charger.c

Comments

Sebastian Reichel Jan. 2, 2023, 8:44 p.m. UTC | #1
Hi,

On Tue, Dec 20, 2022 at 04:22:47PM +0300, Zeynep Arslanbenzer wrote:
> This patch adds battery charger driver for MAX77659.
> 
> Signed-off-by: Nurettin Bolucu <Nurettin.Bolucu@analog.com>
> Signed-off-by: Zeynep Arslanbenzer <Zeynep.Arslanbenzer@analog.com>
> ---

Some additional feedback.

>  MAINTAINERS                             |   1 +
>  drivers/power/supply/Kconfig            |   7 +
>  drivers/power/supply/Makefile           |   1 +
>  drivers/power/supply/max77659-charger.c | 547 ++++++++++++++++++++++++
>  4 files changed, 556 insertions(+)
>  create mode 100644 drivers/power/supply/max77659-charger.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e7a9cadf0ff2..b3e307163063 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12647,6 +12647,7 @@ L:	linux-kernel@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/devicetree/bindings/mfd/adi,max77659.yaml
>  F:	drivers/mfd/max77659.c
> +F:	drivers/power/supply/max77659-charger.c
>  F:	include/linux/mfd/max77659.h
>  
>  MAXIM MAX77714 PMIC MFD DRIVER
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index 0bbfe6a7ce4d..d38ef40ae9ee 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -565,6 +565,13 @@ config CHARGER_MAX77650
>  	  Say Y to enable support for the battery charger control of MAX77650
>  	  PMICs.
>  
> +config CHARGER_MAX77659
> +	tristate "Analog Devices MAX77659 battery charger driver"
> +	depends on MFD_MAX77659
> +	help
> +	  Say Y to enable support for the battery charger control of MAX77659
> +	  PMICs.
> +
>  config CHARGER_MAX77693
>  	tristate "Maxim MAX77693 battery charger driver"
>  	depends on MFD_MAX77693
> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
> index 0ee8653e882e..e8646896bcd2 100644
> --- a/drivers/power/supply/Makefile
> +++ b/drivers/power/supply/Makefile
> @@ -76,6 +76,7 @@ obj-$(CONFIG_CHARGER_LTC4162L)	+= ltc4162-l-charger.o
>  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_MAX77659)	+= max77659-charger.o
>  obj-$(CONFIG_CHARGER_MAX77693)	+= max77693_charger.o
>  obj-$(CONFIG_CHARGER_MAX77976)	+= max77976_charger.o
>  obj-$(CONFIG_CHARGER_MAX8997)	+= max8997_charger.o
> diff --git a/drivers/power/supply/max77659-charger.c b/drivers/power/supply/max77659-charger.c
> new file mode 100644
> index 000000000000..dfdbd484f7cd
> --- /dev/null
> +++ b/drivers/power/supply/max77659-charger.c
> @@ -0,0 +1,547 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +#include <linux/bitfield.h>
> +#include <linux/interrupt.h>
> +#include <linux/mfd/max77659.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/power_supply.h>
> +#include <linux/regmap.h>
> +
> +#define MAX77659_IRQ_WORK_DELAY 0
> +#define MAX77659_CHARGER_CURRENT_MAX 300000
> +#define MAX77659_CHARGER_CURRENT_MIN 7500
> +#define MAX77659_CHARGER_CURRENT_STEP 7500
> +#define MAX77659_CHARGER_TOPOFF_TIMER_STEP 5
> +
> +#define MAX77659_REG_STAT_CHG_A 0x02
> +#define MAX77659_REG_STAT_CHG_B 0x03
> +#define MAX77659_REG_CNFG_CHG_A 0x20
> +#define MAX77659_REG_CNFG_CHG_B 0x21
> +#define MAX77659_REG_CNFG_CHG_C 0x22
> +#define MAX77659_REG_CNFG_CHG_D 0x23
> +#define MAX77659_REG_CNFG_CHG_E 0x24
> +#define MAX77659_REG_CNFG_CHG_F 0x25
> +#define MAX77659_REG_CNFG_CHG_G 0x26
> +#define MAX77659_REG_CNFG_CHG_H 0x27
> +#define MAX77659_REG_CNFG_CHG_I 0x28
> +
> +#define MAX77659_BIT_STAT_A_VSYSY_MIN_STAT	BIT(4)
> +#define MAX77659_BIT_STAT_A_TJ_REG_STAT		BIT(3)
> +#define MAX77659_BIT_STAT_A_THM_DTLS		GENMASK(2, 0)
> +#define MAX77659_BIT_STAT_B_CHG_DTLS		GENMASK(7, 4)
> +#define MAX77659_BIT_STAT_B_CHGIN_DTSL		GENMASK(3, 2)
> +#define MAX77659_BIT_STAT_B_CHG			BIT(1)
> +#define MAX77659_BIT_CNFG_B_CHG_EN		BIT(0)
> +#define MAX77659_BIT_CNFG_C_TOPOFFTIMER		GENMASK(2, 0)
> +#define MAX77659_BIT_CNFG_E_CC			GENMASK(7, 2)
> +#define MAX77659_BIT_CNFG_E_TFASTCHG		GENMASK(1, 0)
> +#define MAX77659_BIT_CNFG_G_CHG_CV		GENMASK(7, 2)
> +
> +enum {
> +	MAX77659_CHG_DTLS_OFF,
> +	MAX77659_CHG_DTLS_PREQUAL,
> +	MAX77659_CHG_DTLS_FASTCHARGE_CC,
> +	MAX77659_CHG_DTLS_JEITA_FASTCHARGE_CC,
> +	MAX77659_CHG_DTLS_FASTCHARGE_CV,
> +	MAX77659_CHG_DTLS_JEITA_FASTCHARGE_CV,
> +	MAX77659_CHG_DTLS_TOPOFF,
> +	MAX77659_CHG_DTLS_JEITA_TOPOFF,
> +	MAX77659_CHG_DTLS_DONE,
> +	MAX77659_CHG_DTLS_JEITA_DONE,
> +	MAX77659_CHG_DTLS_OFF_TIMER_FAULT,
> +	MAX77659_CHG_DTLS_OFF_CHARGE_TIMER_FAULT,
> +	MAX77659_CHG_DTLS_OFF_BATTERY_TEMP_FAULT,
> +	MAX77659_CHG_DTLS_RESERVED_13,
> +};
> +
> +enum {
> +	MAX77659_CHG_IRQ_THM_I,
> +	MAX77659_CHG_IRQ_CHG_I,
> +	MAX77659_CHG_IRQ_CHGIN_I,
> +	MAX77659_CHG_IRQ_TJ_REG_I,
> +	MAX77659_CHG_IRQ_SYS_CTRL_I,
> +
> +	MAX77659_CHG_IRQ_MAX,
> +};
> +
> +struct max77659_charger_dev {
> +	struct device *dev;
> +	struct max77659_dev *max77659;
> +	struct regmap *regmap;
> +
> +	struct power_supply *psy_chg;
> +	struct power_supply_desc psy_chg_d;
> +
> +	int irq;
> +	int irq_arr[MAX77659_CHG_IRQ_MAX];
> +	int irq_mask;
> +
> +	struct delayed_work irq_work;
> +	/* mutex for charger operations*/
> +	struct mutex lock;
> +
> +	int present;
> +	int health;
> +	int status;
> +	int charge_type;
> +
> +	unsigned int fast_charge_current_ua;
> +	unsigned int fast_charge_timer_hr;
> +	unsigned int topoff_timer_min;
> +};
> +
> +static char *chg_supplied_to[] = {
> +	MAX77659_CHARGER_NAME,
> +};
> +
> +static int max77659_set_charge_current(struct max77659_charger_dev *charger,
> +				       int fast_charge_current_ua)
> +{
> +	unsigned int reg_data;
> +
> +	fast_charge_current_ua = clamp_val(fast_charge_current_ua, MAX77659_CHARGER_CURRENT_MIN,
> +					   MAX77659_CHARGER_CURRENT_MAX);
> +	reg_data = fast_charge_current_ua / MAX77659_CHARGER_CURRENT_STEP - 1;
> +	reg_data = FIELD_PREP(MAX77659_BIT_CNFG_E_CC, reg_data);
> +
> +	return regmap_update_bits(charger->regmap, MAX77659_REG_CNFG_CHG_E,
> +				  MAX77659_BIT_CNFG_E_CC, reg_data);
> +}
> +
> +static int max77659_get_charge_current(struct max77659_charger_dev *charger,
> +				       int *get_current)
> +{
> +	struct regmap *regmap = charger->regmap;
> +	unsigned int reg_data, current_val;
> +	int ret;
> +
> +	ret = regmap_read(regmap, MAX77659_REG_CNFG_CHG_E, &reg_data);
> +	if (ret)
> +		return ret;
> +
> +	reg_data = FIELD_GET(MAX77659_BIT_CNFG_E_CC, reg_data);
> +	current_val = (reg_data + 1) * MAX77659_CHARGER_CURRENT_STEP;
> +
> +	*get_current = clamp_val(current_val, MAX77659_CHARGER_CURRENT_MIN,
> +				 MAX77659_CHARGER_CURRENT_MAX);
> +
> +	return 0;
> +}
> +
> +static int max77659_set_topoff_timer(struct max77659_charger_dev *charger, int termination_time_min)
> +{
> +	unsigned int reg_data;
> +
> +	termination_time_min = clamp_val(termination_time_min, 0, MAX77659_BIT_CNFG_C_TOPOFFTIMER
> +					 * MAX77659_CHARGER_TOPOFF_TIMER_STEP);
> +	reg_data = FIELD_PREP(MAX77659_BIT_CNFG_C_TOPOFFTIMER,
> +			      termination_time_min / MAX77659_CHARGER_TOPOFF_TIMER_STEP);
> +
> +	return regmap_update_bits(charger->regmap, MAX77659_REG_CNFG_CHG_C,
> +				  MAX77659_BIT_CNFG_C_TOPOFFTIMER, reg_data);
> +}
> +
> +static int max77659_charger_initialize(struct max77659_charger_dev *charger)
> +{
> +	int ret;
> +	unsigned int val;
> +
> +	val = (MAX77659_BIT_INT_SYS_CTRL | MAX77659_BIT_INT_TJ_REG | MAX77659_BIT_INT_CHGIN |
> +	       MAX77659_BIT_INT_CHG | MAX77659_BIT_INT_THM);
> +
> +	ret = regmap_write(charger->regmap, MAX77659_REG_INT_M_CHG, val);
> +	if (ret)
> +		return dev_err_probe(charger->dev, ret, "Error in writing register INT_M_CHG\n");
> +
> +	ret = max77659_set_charge_current(charger, charger->fast_charge_current_ua);
> +	if (ret)
> +		return dev_err_probe(charger->dev, ret, "Error in writing register CNFG_CHG_C\n");
> +
> +	if (charger->fast_charge_timer_hr == 0 || charger->fast_charge_timer_hr > 7)
> +		val = 0x00;
> +	else if (charger->fast_charge_timer_hr < 3)
> +		val = 0x01;
> +	else
> +		val = DIV_ROUND_UP(charger->fast_charge_timer_hr - 1, 2);
> +
> +	ret = regmap_update_bits(charger->regmap, MAX77659_REG_CNFG_CHG_E,
> +				 MAX77659_BIT_CNFG_E_TFASTCHG, val);
> +	if (ret)
> +		return dev_err_probe(charger->dev, ret, "Error in writing register CNFG_CHG_E\n");
> +
> +	return max77659_set_topoff_timer(charger, charger->topoff_timer_min);
> +}
> +
> +static void max77659_charger_parse_dt(struct max77659_charger_dev *charger)
> +{
> +	int ret;
> +
> +	ret = device_property_read_u32(charger->dev, "adi,fast-charge-timer",
> +				       &charger->fast_charge_timer_hr);
> +	if (ret)
> +		dev_dbg(charger->dev, "Could not read adi,fast-charge-timer DT property\n");
> +
> +	ret = device_property_read_u32(charger->dev, "adi,fast-charge-microamp",
> +				       &charger->fast_charge_current_ua);
> +	if (ret)
> +		dev_dbg(charger->dev, "Could not read adi,fast-charge-microamp DT property\n");

As far as I can tell this is power_supply_battery_info.constant_charge_current_max_ua
from power_supply_get_battery_info().

> +	ret = device_property_read_u32(charger->dev, "adi,topoff-timer",
> +				       &charger->topoff_timer_min);
> +	if (ret)
> +		dev_dbg(charger->dev, "Could not read adi,topoff-timer DT property\n");
> +}
> +
> +struct max77659_charger_status_map {
> +	int health;
> +	int status;
> +	int charge_type;
> +};
> +
> +#define STATUS_MAP(_MAX77659_CHG_DTLS, _health, _status, _charge_type) \
> +	[MAX77659_CHG_DTLS_##_MAX77659_CHG_DTLS] = {\
> +		.health = POWER_SUPPLY_HEALTH_##_health,\
> +		.status = POWER_SUPPLY_STATUS_##_status,\
> +		.charge_type = POWER_SUPPLY_CHARGE_TYPE_##_charge_type,\
> +	}
> +
> +static struct max77659_charger_status_map max77659_charger_status_map[] = {
> +	/* chg_details_xx, health, status, charge_type */
> +	STATUS_MAP(OFF, UNKNOWN, NOT_CHARGING, NONE),
> +	STATUS_MAP(PREQUAL, GOOD, CHARGING, TRICKLE),
> +	STATUS_MAP(FASTCHARGE_CC, GOOD, CHARGING, FAST),
> +	STATUS_MAP(JEITA_FASTCHARGE_CC, GOOD, CHARGING, FAST),
> +	STATUS_MAP(FASTCHARGE_CV, GOOD, CHARGING, FAST),
> +	STATUS_MAP(JEITA_FASTCHARGE_CV, GOOD, CHARGING, FAST),
> +	STATUS_MAP(TOPOFF, GOOD, CHARGING, FAST),
> +	STATUS_MAP(JEITA_TOPOFF, GOOD, CHARGING, FAST),
> +	STATUS_MAP(DONE, GOOD, FULL, NONE),
> +	STATUS_MAP(JEITA_DONE, GOOD, FULL, NONE),
> +	STATUS_MAP(OFF_TIMER_FAULT, SAFETY_TIMER_EXPIRE, NOT_CHARGING, NONE),
> +	STATUS_MAP(OFF_CHARGE_TIMER_FAULT, UNKNOWN, NOT_CHARGING, NONE),
> +	STATUS_MAP(OFF_BATTERY_TEMP_FAULT, HOT, NOT_CHARGING, NONE),
> +};
> +
> +static int max77659_charger_update(struct max77659_charger_dev *charger)
> +{
> +	unsigned int stat_chg_b, chg_dtls;
> +	int ret;
> +
> +	ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_B, &stat_chg_b);
> +	if (ret) {
> +		charger->health = POWER_SUPPLY_HEALTH_UNKNOWN;
> +		charger->status = POWER_SUPPLY_STATUS_UNKNOWN;
> +		charger->charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
> +		return ret;
> +	}
> +
> +	charger->present = stat_chg_b & MAX77659_BIT_STAT_B_CHG;
> +	if (!charger->present) {
> +		charger->health = POWER_SUPPLY_HEALTH_UNKNOWN;
> +		charger->status = POWER_SUPPLY_STATUS_DISCHARGING;
> +		charger->charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
> +		return 0;
> +	}
> +
> +	chg_dtls = FIELD_GET(MAX77659_BIT_STAT_B_CHG_DTLS, stat_chg_b);
> +
> +	charger->health = max77659_charger_status_map[chg_dtls].health;
> +	charger->status = max77659_charger_status_map[chg_dtls].status;
> +	charger->charge_type = max77659_charger_status_map[chg_dtls].charge_type;
> +
> +	return 0;
> +}
> +
> +static enum power_supply_property max77659_charger_props[] = {
> +	POWER_SUPPLY_PROP_ONLINE,
> +	POWER_SUPPLY_PROP_HEALTH,
> +	POWER_SUPPLY_PROP_STATUS,
> +	POWER_SUPPLY_PROP_CHARGE_TYPE,
> +	POWER_SUPPLY_PROP_CURRENT_NOW
> +};
> +
> +static int max77659_property_is_writeable(struct power_supply *psy, enum power_supply_property psp)
> +{
> +	switch (psp) {
> +	case POWER_SUPPLY_PROP_ONLINE:
> +	case POWER_SUPPLY_PROP_CURRENT_NOW:
> +		return 1;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static int max77659_charger_get_property(struct power_supply *psy,
> +					 enum power_supply_property psp,
> +					 union power_supply_propval *val)
> +{
> +	struct max77659_charger_dev *charger = power_supply_get_drvdata(psy);
> +	int ret;
> +
> +	mutex_lock(&charger->lock);
> +
> +	ret = max77659_charger_update(charger);
> +	if (ret)
> +		goto out;
> +
> +	switch (psp) {
> +	case POWER_SUPPLY_PROP_ONLINE:
> +		val->intval = charger->present;
> +		break;
> +	case POWER_SUPPLY_PROP_HEALTH:
> +		val->intval = charger->health;
> +		break;
> +	case POWER_SUPPLY_PROP_STATUS:
> +		val->intval = charger->status;
> +		break;
> +	case POWER_SUPPLY_PROP_CHARGE_TYPE:
> +		val->intval = charger->charge_type;
> +		break;
> +	case POWER_SUPPLY_PROP_CURRENT_NOW:
> +		ret = max77659_get_charge_current(charger, &val->intval);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +out:
> +	mutex_unlock(&charger->lock);
> +
> +	return ret;
> +}
> +
> +static int max77659_charger_set_property(struct power_supply *psy, enum power_supply_property psp,
> +					 const union power_supply_propval *val)
> +{
> +	struct max77659_charger_dev *charger = power_supply_get_drvdata(psy);
> +	int ret;
> +
> +	mutex_lock(&charger->lock);
> +
> +	switch (psp) {
> +	case POWER_SUPPLY_PROP_ONLINE:
> +		ret = regmap_update_bits(charger->regmap, MAX77659_REG_CNFG_CHG_B,
> +					 MAX77659_BIT_CNFG_B_CHG_EN, !!val->intval);
> +		if (ret)
> +			goto out;
> +
> +		ret = max77659_set_charge_current(charger, charger->fast_charge_current_ua);
> +		break;
> +	case POWER_SUPPLY_PROP_CURRENT_NOW:
> +		/* val->intval - uA */
> +		ret = max77659_set_charge_current(charger, val->intval);
> +		if (ret)
> +			goto out;
> +
> +		charger->fast_charge_current_ua = val->intval;
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +out:
> +	mutex_unlock(&charger->lock);
> +
> +	return ret;
> +}
> +
> +static void max77659_charger_irq_handler(struct max77659_charger_dev *charger, int irq)
> +{
> +	unsigned int val, stat_chg_b;
> +	int chg_present;
> +	int ret;
> +
> +	switch (irq) {
> +	case MAX77659_CHG_IRQ_THM_I:
> +		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_A, &val);
> +		if (ret) {
> +			dev_err(charger->dev, "Failed to read STAT_CHG_A\n");
> +			return;
> +		}
> +
> +		val = FIELD_GET(MAX77659_BIT_STAT_A_THM_DTLS, val);
> +		dev_dbg(charger->dev, "CHG_INT_THM: thm_dtls = %02Xh\n", val);
> +		break;
> +
> +	case MAX77659_CHG_IRQ_CHG_I:
> +		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_B, &val);
> +		if (ret) {
> +			dev_err(charger->dev, "Failed to read STAT_CHG_B\n");
> +			return;
> +		}
> +
> +		val = FIELD_GET(MAX77659_BIT_STAT_B_CHG_DTLS, val);
> +		dev_dbg(charger->dev, "CHG_INT_CHG: MAX77659_CHG_DTLS = %02Xh\n", val);
> +		break;
> +
> +	case MAX77659_CHG_IRQ_CHGIN_I:
> +		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_B, &val);
> +		if (ret) {
> +			dev_err(charger->dev, "Failed to read STAT_CHG_B\n");
> +			return;
> +		}
> +
> +		val = FIELD_GET(MAX77659_BIT_STAT_B_CHG_DTLS, val);
> +		dev_dbg(charger->dev, "CHG_INT_CHG: MAX77659_CHG_DTLS = %02Xh\n", val);
> +
> +		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_B, &stat_chg_b);
> +		if (ret) {
> +			dev_err(charger->dev, "Failed to read STAT_CHG_B\n");
> +			return;
> +		}
> +
> +		chg_present = stat_chg_b & MAX77659_BIT_STAT_B_CHG;
> +
> +		regmap_update_bits(charger->regmap, MAX77659_REG_CNFG_CHG_B,
> +				   MAX77659_BIT_CNFG_B_CHG_EN, !!chg_present);
> +		if (!chg_present)
> +			max77659_set_charge_current(charger, charger->fast_charge_current_ua);
> +
> +		dev_dbg(charger->dev, "CHG_INT_CHGIN: Charger input %s\n",
> +			chg_present ? "inserted" : "removed");
> +		break;
> +
> +	case MAX77659_CHG_IRQ_TJ_REG_I:
> +		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_A, &val);
> +		if (ret) {
> +			dev_err(charger->dev, "Failed to read STAT_CHG_A\n");
> +			return;
> +		}
> +
> +		val = FIELD_GET(MAX77659_BIT_STAT_A_TJ_REG_STAT, val);
> +		dev_dbg(charger->dev, "CHG_INT_TJ_REG: tj_reg_stat = %02Xh\n", val);
> +		dev_dbg(charger->dev, "CHG_INT_TJ_REG: Die temperature %s Tj-reg\n",
> +			val ? "has exceeded" : "has not exceeded");
> +		break;
> +
> +	case MAX77659_CHG_IRQ_SYS_CTRL_I:
> +		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_A, &val);
> +		if (ret) {
> +			dev_err(charger->dev, "Failed to read STAT_CHG_A\n");
> +			return;
> +		}
> +
> +		val = FIELD_GET(MAX77659_BIT_STAT_A_VSYSY_MIN_STAT, val);
> +		dev_dbg(charger->dev,
> +			"CHG_INT_SYS_CTRL: The minimum system voltage regulation loop %s\n",
> +			 val ? "has engaged" : "has not engaged");
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	power_supply_changed(charger->psy_chg);
> +}
> +
> +static irqreturn_t max77659_charger_isr(int irq, void *data)
> +{
> +	struct max77659_charger_dev *charger = data;
> +
> +	charger->irq = irq;
> +	max77659_charger_update(charger);
> +	max77659_charger_irq_handler(charger, charger->irq - charger->irq_arr[0]);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static const char * const max77659_irq_descs[] = {
> +	"charger-thm",
> +	"charger-chg",
> +	"charger-chgin",
> +	"charger-tj-reg",
> +	"charger-sys-ctrl",
> +};
> +
> +static int max77659_charger_probe(struct platform_device *pdev)
> +{
> +	struct max77659_dev *max77659 = dev_get_drvdata(pdev->dev.parent);
> +	struct max77659_charger_dev *charger;
> +	struct device *dev = &pdev->dev;
> +	int i, ret;
> +	struct power_supply_config charger_cfg = {};
> +
> +	charger = devm_kzalloc(dev, sizeof(*charger), GFP_KERNEL);
> +	if (!charger)
> +		return -ENOMEM;
> +
> +	mutex_init(&charger->lock);
> +
> +	charger->dev = dev;
> +	charger->regmap = dev_get_regmap(charger->dev->parent, NULL);
> +	charger->fast_charge_current_ua = 15000;
> +	charger->topoff_timer_min = 30;
> +
> +	max77659_charger_parse_dt(charger);
> +
> +	charger->psy_chg_d.name = MAX77659_CHARGER_NAME;
> +	charger->psy_chg_d.type = POWER_SUPPLY_TYPE_UNKNOWN;

POWER_SUPPLY_TYPE_USB

> +	charger->psy_chg_d.get_property	= max77659_charger_get_property;
> +	charger->psy_chg_d.set_property	= max77659_charger_set_property;
> +	charger->psy_chg_d.properties = max77659_charger_props;
> +	charger->psy_chg_d.property_is_writeable = max77659_property_is_writeable;
> +	charger->psy_chg_d.num_properties = ARRAY_SIZE(max77659_charger_props);
> +	charger_cfg.drv_data = charger;
> +	charger_cfg.supplied_to = chg_supplied_to;
> +	charger_cfg.of_node = dev->of_node;
> +	charger_cfg.num_supplicants = ARRAY_SIZE(chg_supplied_to);

It does not make sense that the charger supplies itself.

> +	ret = max77659_charger_initialize(charger);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to initialize charger\n");
> +
> +	for (i = 0; i < MAX77659_CHG_IRQ_MAX; i++) {
> +		charger->irq_arr[i] = regmap_irq_get_virq(max77659->irqc_chg, i);
> +
> +		if (charger->irq_arr[i] < 0)
> +			return dev_err_probe(dev, -EINVAL,
> +					     "Invalid IRQ for MAX77659_CHG_IRQ %d\n", i);
> +
> +		ret = devm_request_threaded_irq(dev, charger->irq_arr[i],
> +						NULL, max77659_charger_isr, IRQF_TRIGGER_FALLING,
> +						max77659_irq_descs[i], charger);
> +		if (ret)
> +			return dev_err_probe(dev, ret, "Failed to request irq: %d\n",
> +					     charger->irq_arr[i]);
> +	}
> +
> +	charger->psy_chg = devm_power_supply_register(dev, &charger->psy_chg_d, &charger_cfg);
> +	if (IS_ERR(charger->psy_chg))
> +		return dev_err_probe(dev, PTR_ERR(charger->psy_chg),
> +				     "Failed to register power supply\n");
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id max77659_charger_of_id[] = {
> +	{ .compatible = "adi,max77659-charger" },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, max77659_charger_of_id);
> +
> +static const struct platform_device_id max77659_charger_id[] = {
> +	{ MAX77659_CHARGER_NAME, 0, },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(platform, max77659_charger_id);
> +
> +static struct platform_driver max77659_charger_driver = {
> +	.driver = {
> +		.name = MAX77659_CHARGER_NAME,
> +		.of_match_table = of_match_ptr(max77659_charger_of_id),
> +	},
> +	.probe = max77659_charger_probe,
> +	.id_table = max77659_charger_id,
> +};
> +
> +module_platform_driver(max77659_charger_driver);
> +
> +MODULE_DESCRIPTION("MAX77659 Charger Driver");
> +MODULE_AUTHOR("Nurettin.Bolucu@analog.com, Zeynep.Arslanbenzer@analog.com");
> +MODULE_LICENSE("GPL");
> +MODULE_VERSION("1.0");

-- Sebastian
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index e7a9cadf0ff2..b3e307163063 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12647,6 +12647,7 @@  L:	linux-kernel@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/mfd/adi,max77659.yaml
 F:	drivers/mfd/max77659.c
+F:	drivers/power/supply/max77659-charger.c
 F:	include/linux/mfd/max77659.h
 
 MAXIM MAX77714 PMIC MFD DRIVER
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 0bbfe6a7ce4d..d38ef40ae9ee 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -565,6 +565,13 @@  config CHARGER_MAX77650
 	  Say Y to enable support for the battery charger control of MAX77650
 	  PMICs.
 
+config CHARGER_MAX77659
+	tristate "Analog Devices MAX77659 battery charger driver"
+	depends on MFD_MAX77659
+	help
+	  Say Y to enable support for the battery charger control of MAX77659
+	  PMICs.
+
 config CHARGER_MAX77693
 	tristate "Maxim MAX77693 battery charger driver"
 	depends on MFD_MAX77693
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 0ee8653e882e..e8646896bcd2 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -76,6 +76,7 @@  obj-$(CONFIG_CHARGER_LTC4162L)	+= ltc4162-l-charger.o
 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_MAX77659)	+= max77659-charger.o
 obj-$(CONFIG_CHARGER_MAX77693)	+= max77693_charger.o
 obj-$(CONFIG_CHARGER_MAX77976)	+= max77976_charger.o
 obj-$(CONFIG_CHARGER_MAX8997)	+= max8997_charger.o
diff --git a/drivers/power/supply/max77659-charger.c b/drivers/power/supply/max77659-charger.c
new file mode 100644
index 000000000000..dfdbd484f7cd
--- /dev/null
+++ b/drivers/power/supply/max77659-charger.c
@@ -0,0 +1,547 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/bitfield.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max77659.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+
+#define MAX77659_IRQ_WORK_DELAY 0
+#define MAX77659_CHARGER_CURRENT_MAX 300000
+#define MAX77659_CHARGER_CURRENT_MIN 7500
+#define MAX77659_CHARGER_CURRENT_STEP 7500
+#define MAX77659_CHARGER_TOPOFF_TIMER_STEP 5
+
+#define MAX77659_REG_STAT_CHG_A 0x02
+#define MAX77659_REG_STAT_CHG_B 0x03
+#define MAX77659_REG_CNFG_CHG_A 0x20
+#define MAX77659_REG_CNFG_CHG_B 0x21
+#define MAX77659_REG_CNFG_CHG_C 0x22
+#define MAX77659_REG_CNFG_CHG_D 0x23
+#define MAX77659_REG_CNFG_CHG_E 0x24
+#define MAX77659_REG_CNFG_CHG_F 0x25
+#define MAX77659_REG_CNFG_CHG_G 0x26
+#define MAX77659_REG_CNFG_CHG_H 0x27
+#define MAX77659_REG_CNFG_CHG_I 0x28
+
+#define MAX77659_BIT_STAT_A_VSYSY_MIN_STAT	BIT(4)
+#define MAX77659_BIT_STAT_A_TJ_REG_STAT		BIT(3)
+#define MAX77659_BIT_STAT_A_THM_DTLS		GENMASK(2, 0)
+#define MAX77659_BIT_STAT_B_CHG_DTLS		GENMASK(7, 4)
+#define MAX77659_BIT_STAT_B_CHGIN_DTSL		GENMASK(3, 2)
+#define MAX77659_BIT_STAT_B_CHG			BIT(1)
+#define MAX77659_BIT_CNFG_B_CHG_EN		BIT(0)
+#define MAX77659_BIT_CNFG_C_TOPOFFTIMER		GENMASK(2, 0)
+#define MAX77659_BIT_CNFG_E_CC			GENMASK(7, 2)
+#define MAX77659_BIT_CNFG_E_TFASTCHG		GENMASK(1, 0)
+#define MAX77659_BIT_CNFG_G_CHG_CV		GENMASK(7, 2)
+
+enum {
+	MAX77659_CHG_DTLS_OFF,
+	MAX77659_CHG_DTLS_PREQUAL,
+	MAX77659_CHG_DTLS_FASTCHARGE_CC,
+	MAX77659_CHG_DTLS_JEITA_FASTCHARGE_CC,
+	MAX77659_CHG_DTLS_FASTCHARGE_CV,
+	MAX77659_CHG_DTLS_JEITA_FASTCHARGE_CV,
+	MAX77659_CHG_DTLS_TOPOFF,
+	MAX77659_CHG_DTLS_JEITA_TOPOFF,
+	MAX77659_CHG_DTLS_DONE,
+	MAX77659_CHG_DTLS_JEITA_DONE,
+	MAX77659_CHG_DTLS_OFF_TIMER_FAULT,
+	MAX77659_CHG_DTLS_OFF_CHARGE_TIMER_FAULT,
+	MAX77659_CHG_DTLS_OFF_BATTERY_TEMP_FAULT,
+	MAX77659_CHG_DTLS_RESERVED_13,
+};
+
+enum {
+	MAX77659_CHG_IRQ_THM_I,
+	MAX77659_CHG_IRQ_CHG_I,
+	MAX77659_CHG_IRQ_CHGIN_I,
+	MAX77659_CHG_IRQ_TJ_REG_I,
+	MAX77659_CHG_IRQ_SYS_CTRL_I,
+
+	MAX77659_CHG_IRQ_MAX,
+};
+
+struct max77659_charger_dev {
+	struct device *dev;
+	struct max77659_dev *max77659;
+	struct regmap *regmap;
+
+	struct power_supply *psy_chg;
+	struct power_supply_desc psy_chg_d;
+
+	int irq;
+	int irq_arr[MAX77659_CHG_IRQ_MAX];
+	int irq_mask;
+
+	struct delayed_work irq_work;
+	/* mutex for charger operations*/
+	struct mutex lock;
+
+	int present;
+	int health;
+	int status;
+	int charge_type;
+
+	unsigned int fast_charge_current_ua;
+	unsigned int fast_charge_timer_hr;
+	unsigned int topoff_timer_min;
+};
+
+static char *chg_supplied_to[] = {
+	MAX77659_CHARGER_NAME,
+};
+
+static int max77659_set_charge_current(struct max77659_charger_dev *charger,
+				       int fast_charge_current_ua)
+{
+	unsigned int reg_data;
+
+	fast_charge_current_ua = clamp_val(fast_charge_current_ua, MAX77659_CHARGER_CURRENT_MIN,
+					   MAX77659_CHARGER_CURRENT_MAX);
+	reg_data = fast_charge_current_ua / MAX77659_CHARGER_CURRENT_STEP - 1;
+	reg_data = FIELD_PREP(MAX77659_BIT_CNFG_E_CC, reg_data);
+
+	return regmap_update_bits(charger->regmap, MAX77659_REG_CNFG_CHG_E,
+				  MAX77659_BIT_CNFG_E_CC, reg_data);
+}
+
+static int max77659_get_charge_current(struct max77659_charger_dev *charger,
+				       int *get_current)
+{
+	struct regmap *regmap = charger->regmap;
+	unsigned int reg_data, current_val;
+	int ret;
+
+	ret = regmap_read(regmap, MAX77659_REG_CNFG_CHG_E, &reg_data);
+	if (ret)
+		return ret;
+
+	reg_data = FIELD_GET(MAX77659_BIT_CNFG_E_CC, reg_data);
+	current_val = (reg_data + 1) * MAX77659_CHARGER_CURRENT_STEP;
+
+	*get_current = clamp_val(current_val, MAX77659_CHARGER_CURRENT_MIN,
+				 MAX77659_CHARGER_CURRENT_MAX);
+
+	return 0;
+}
+
+static int max77659_set_topoff_timer(struct max77659_charger_dev *charger, int termination_time_min)
+{
+	unsigned int reg_data;
+
+	termination_time_min = clamp_val(termination_time_min, 0, MAX77659_BIT_CNFG_C_TOPOFFTIMER
+					 * MAX77659_CHARGER_TOPOFF_TIMER_STEP);
+	reg_data = FIELD_PREP(MAX77659_BIT_CNFG_C_TOPOFFTIMER,
+			      termination_time_min / MAX77659_CHARGER_TOPOFF_TIMER_STEP);
+
+	return regmap_update_bits(charger->regmap, MAX77659_REG_CNFG_CHG_C,
+				  MAX77659_BIT_CNFG_C_TOPOFFTIMER, reg_data);
+}
+
+static int max77659_charger_initialize(struct max77659_charger_dev *charger)
+{
+	int ret;
+	unsigned int val;
+
+	val = (MAX77659_BIT_INT_SYS_CTRL | MAX77659_BIT_INT_TJ_REG | MAX77659_BIT_INT_CHGIN |
+	       MAX77659_BIT_INT_CHG | MAX77659_BIT_INT_THM);
+
+	ret = regmap_write(charger->regmap, MAX77659_REG_INT_M_CHG, val);
+	if (ret)
+		return dev_err_probe(charger->dev, ret, "Error in writing register INT_M_CHG\n");
+
+	ret = max77659_set_charge_current(charger, charger->fast_charge_current_ua);
+	if (ret)
+		return dev_err_probe(charger->dev, ret, "Error in writing register CNFG_CHG_C\n");
+
+	if (charger->fast_charge_timer_hr == 0 || charger->fast_charge_timer_hr > 7)
+		val = 0x00;
+	else if (charger->fast_charge_timer_hr < 3)
+		val = 0x01;
+	else
+		val = DIV_ROUND_UP(charger->fast_charge_timer_hr - 1, 2);
+
+	ret = regmap_update_bits(charger->regmap, MAX77659_REG_CNFG_CHG_E,
+				 MAX77659_BIT_CNFG_E_TFASTCHG, val);
+	if (ret)
+		return dev_err_probe(charger->dev, ret, "Error in writing register CNFG_CHG_E\n");
+
+	return max77659_set_topoff_timer(charger, charger->topoff_timer_min);
+}
+
+static void max77659_charger_parse_dt(struct max77659_charger_dev *charger)
+{
+	int ret;
+
+	ret = device_property_read_u32(charger->dev, "adi,fast-charge-timer",
+				       &charger->fast_charge_timer_hr);
+	if (ret)
+		dev_dbg(charger->dev, "Could not read adi,fast-charge-timer DT property\n");
+
+	ret = device_property_read_u32(charger->dev, "adi,fast-charge-microamp",
+				       &charger->fast_charge_current_ua);
+	if (ret)
+		dev_dbg(charger->dev, "Could not read adi,fast-charge-microamp DT property\n");
+
+	ret = device_property_read_u32(charger->dev, "adi,topoff-timer",
+				       &charger->topoff_timer_min);
+	if (ret)
+		dev_dbg(charger->dev, "Could not read adi,topoff-timer DT property\n");
+}
+
+struct max77659_charger_status_map {
+	int health;
+	int status;
+	int charge_type;
+};
+
+#define STATUS_MAP(_MAX77659_CHG_DTLS, _health, _status, _charge_type) \
+	[MAX77659_CHG_DTLS_##_MAX77659_CHG_DTLS] = {\
+		.health = POWER_SUPPLY_HEALTH_##_health,\
+		.status = POWER_SUPPLY_STATUS_##_status,\
+		.charge_type = POWER_SUPPLY_CHARGE_TYPE_##_charge_type,\
+	}
+
+static struct max77659_charger_status_map max77659_charger_status_map[] = {
+	/* chg_details_xx, health, status, charge_type */
+	STATUS_MAP(OFF, UNKNOWN, NOT_CHARGING, NONE),
+	STATUS_MAP(PREQUAL, GOOD, CHARGING, TRICKLE),
+	STATUS_MAP(FASTCHARGE_CC, GOOD, CHARGING, FAST),
+	STATUS_MAP(JEITA_FASTCHARGE_CC, GOOD, CHARGING, FAST),
+	STATUS_MAP(FASTCHARGE_CV, GOOD, CHARGING, FAST),
+	STATUS_MAP(JEITA_FASTCHARGE_CV, GOOD, CHARGING, FAST),
+	STATUS_MAP(TOPOFF, GOOD, CHARGING, FAST),
+	STATUS_MAP(JEITA_TOPOFF, GOOD, CHARGING, FAST),
+	STATUS_MAP(DONE, GOOD, FULL, NONE),
+	STATUS_MAP(JEITA_DONE, GOOD, FULL, NONE),
+	STATUS_MAP(OFF_TIMER_FAULT, SAFETY_TIMER_EXPIRE, NOT_CHARGING, NONE),
+	STATUS_MAP(OFF_CHARGE_TIMER_FAULT, UNKNOWN, NOT_CHARGING, NONE),
+	STATUS_MAP(OFF_BATTERY_TEMP_FAULT, HOT, NOT_CHARGING, NONE),
+};
+
+static int max77659_charger_update(struct max77659_charger_dev *charger)
+{
+	unsigned int stat_chg_b, chg_dtls;
+	int ret;
+
+	ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_B, &stat_chg_b);
+	if (ret) {
+		charger->health = POWER_SUPPLY_HEALTH_UNKNOWN;
+		charger->status = POWER_SUPPLY_STATUS_UNKNOWN;
+		charger->charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+		return ret;
+	}
+
+	charger->present = stat_chg_b & MAX77659_BIT_STAT_B_CHG;
+	if (!charger->present) {
+		charger->health = POWER_SUPPLY_HEALTH_UNKNOWN;
+		charger->status = POWER_SUPPLY_STATUS_DISCHARGING;
+		charger->charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+		return 0;
+	}
+
+	chg_dtls = FIELD_GET(MAX77659_BIT_STAT_B_CHG_DTLS, stat_chg_b);
+
+	charger->health = max77659_charger_status_map[chg_dtls].health;
+	charger->status = max77659_charger_status_map[chg_dtls].status;
+	charger->charge_type = max77659_charger_status_map[chg_dtls].charge_type;
+
+	return 0;
+}
+
+static enum power_supply_property max77659_charger_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+	POWER_SUPPLY_PROP_CURRENT_NOW
+};
+
+static int max77659_property_is_writeable(struct power_supply *psy, enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static int max77659_charger_get_property(struct power_supply *psy,
+					 enum power_supply_property psp,
+					 union power_supply_propval *val)
+{
+	struct max77659_charger_dev *charger = power_supply_get_drvdata(psy);
+	int ret;
+
+	mutex_lock(&charger->lock);
+
+	ret = max77659_charger_update(charger);
+	if (ret)
+		goto out;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = charger->present;
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		val->intval = charger->health;
+		break;
+	case POWER_SUPPLY_PROP_STATUS:
+		val->intval = charger->status;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		val->intval = charger->charge_type;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		ret = max77659_get_charge_current(charger, &val->intval);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+out:
+	mutex_unlock(&charger->lock);
+
+	return ret;
+}
+
+static int max77659_charger_set_property(struct power_supply *psy, enum power_supply_property psp,
+					 const union power_supply_propval *val)
+{
+	struct max77659_charger_dev *charger = power_supply_get_drvdata(psy);
+	int ret;
+
+	mutex_lock(&charger->lock);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = regmap_update_bits(charger->regmap, MAX77659_REG_CNFG_CHG_B,
+					 MAX77659_BIT_CNFG_B_CHG_EN, !!val->intval);
+		if (ret)
+			goto out;
+
+		ret = max77659_set_charge_current(charger, charger->fast_charge_current_ua);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		/* val->intval - uA */
+		ret = max77659_set_charge_current(charger, val->intval);
+		if (ret)
+			goto out;
+
+		charger->fast_charge_current_ua = val->intval;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+out:
+	mutex_unlock(&charger->lock);
+
+	return ret;
+}
+
+static void max77659_charger_irq_handler(struct max77659_charger_dev *charger, int irq)
+{
+	unsigned int val, stat_chg_b;
+	int chg_present;
+	int ret;
+
+	switch (irq) {
+	case MAX77659_CHG_IRQ_THM_I:
+		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_A, &val);
+		if (ret) {
+			dev_err(charger->dev, "Failed to read STAT_CHG_A\n");
+			return;
+		}
+
+		val = FIELD_GET(MAX77659_BIT_STAT_A_THM_DTLS, val);
+		dev_dbg(charger->dev, "CHG_INT_THM: thm_dtls = %02Xh\n", val);
+		break;
+
+	case MAX77659_CHG_IRQ_CHG_I:
+		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_B, &val);
+		if (ret) {
+			dev_err(charger->dev, "Failed to read STAT_CHG_B\n");
+			return;
+		}
+
+		val = FIELD_GET(MAX77659_BIT_STAT_B_CHG_DTLS, val);
+		dev_dbg(charger->dev, "CHG_INT_CHG: MAX77659_CHG_DTLS = %02Xh\n", val);
+		break;
+
+	case MAX77659_CHG_IRQ_CHGIN_I:
+		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_B, &val);
+		if (ret) {
+			dev_err(charger->dev, "Failed to read STAT_CHG_B\n");
+			return;
+		}
+
+		val = FIELD_GET(MAX77659_BIT_STAT_B_CHG_DTLS, val);
+		dev_dbg(charger->dev, "CHG_INT_CHG: MAX77659_CHG_DTLS = %02Xh\n", val);
+
+		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_B, &stat_chg_b);
+		if (ret) {
+			dev_err(charger->dev, "Failed to read STAT_CHG_B\n");
+			return;
+		}
+
+		chg_present = stat_chg_b & MAX77659_BIT_STAT_B_CHG;
+
+		regmap_update_bits(charger->regmap, MAX77659_REG_CNFG_CHG_B,
+				   MAX77659_BIT_CNFG_B_CHG_EN, !!chg_present);
+		if (!chg_present)
+			max77659_set_charge_current(charger, charger->fast_charge_current_ua);
+
+		dev_dbg(charger->dev, "CHG_INT_CHGIN: Charger input %s\n",
+			chg_present ? "inserted" : "removed");
+		break;
+
+	case MAX77659_CHG_IRQ_TJ_REG_I:
+		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_A, &val);
+		if (ret) {
+			dev_err(charger->dev, "Failed to read STAT_CHG_A\n");
+			return;
+		}
+
+		val = FIELD_GET(MAX77659_BIT_STAT_A_TJ_REG_STAT, val);
+		dev_dbg(charger->dev, "CHG_INT_TJ_REG: tj_reg_stat = %02Xh\n", val);
+		dev_dbg(charger->dev, "CHG_INT_TJ_REG: Die temperature %s Tj-reg\n",
+			val ? "has exceeded" : "has not exceeded");
+		break;
+
+	case MAX77659_CHG_IRQ_SYS_CTRL_I:
+		ret = regmap_read(charger->regmap, MAX77659_REG_STAT_CHG_A, &val);
+		if (ret) {
+			dev_err(charger->dev, "Failed to read STAT_CHG_A\n");
+			return;
+		}
+
+		val = FIELD_GET(MAX77659_BIT_STAT_A_VSYSY_MIN_STAT, val);
+		dev_dbg(charger->dev,
+			"CHG_INT_SYS_CTRL: The minimum system voltage regulation loop %s\n",
+			 val ? "has engaged" : "has not engaged");
+		break;
+
+	default:
+		break;
+	}
+
+	power_supply_changed(charger->psy_chg);
+}
+
+static irqreturn_t max77659_charger_isr(int irq, void *data)
+{
+	struct max77659_charger_dev *charger = data;
+
+	charger->irq = irq;
+	max77659_charger_update(charger);
+	max77659_charger_irq_handler(charger, charger->irq - charger->irq_arr[0]);
+
+	return IRQ_HANDLED;
+}
+
+static const char * const max77659_irq_descs[] = {
+	"charger-thm",
+	"charger-chg",
+	"charger-chgin",
+	"charger-tj-reg",
+	"charger-sys-ctrl",
+};
+
+static int max77659_charger_probe(struct platform_device *pdev)
+{
+	struct max77659_dev *max77659 = dev_get_drvdata(pdev->dev.parent);
+	struct max77659_charger_dev *charger;
+	struct device *dev = &pdev->dev;
+	int i, ret;
+	struct power_supply_config charger_cfg = {};
+
+	charger = devm_kzalloc(dev, sizeof(*charger), GFP_KERNEL);
+	if (!charger)
+		return -ENOMEM;
+
+	mutex_init(&charger->lock);
+
+	charger->dev = dev;
+	charger->regmap = dev_get_regmap(charger->dev->parent, NULL);
+	charger->fast_charge_current_ua = 15000;
+	charger->topoff_timer_min = 30;
+
+	max77659_charger_parse_dt(charger);
+
+	charger->psy_chg_d.name = MAX77659_CHARGER_NAME;
+	charger->psy_chg_d.type = POWER_SUPPLY_TYPE_UNKNOWN;
+	charger->psy_chg_d.get_property	= max77659_charger_get_property;
+	charger->psy_chg_d.set_property	= max77659_charger_set_property;
+	charger->psy_chg_d.properties = max77659_charger_props;
+	charger->psy_chg_d.property_is_writeable = max77659_property_is_writeable;
+	charger->psy_chg_d.num_properties = ARRAY_SIZE(max77659_charger_props);
+	charger_cfg.drv_data = charger;
+	charger_cfg.supplied_to = chg_supplied_to;
+	charger_cfg.of_node = dev->of_node;
+	charger_cfg.num_supplicants = ARRAY_SIZE(chg_supplied_to);
+
+	ret = max77659_charger_initialize(charger);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to initialize charger\n");
+
+	for (i = 0; i < MAX77659_CHG_IRQ_MAX; i++) {
+		charger->irq_arr[i] = regmap_irq_get_virq(max77659->irqc_chg, i);
+
+		if (charger->irq_arr[i] < 0)
+			return dev_err_probe(dev, -EINVAL,
+					     "Invalid IRQ for MAX77659_CHG_IRQ %d\n", i);
+
+		ret = devm_request_threaded_irq(dev, charger->irq_arr[i],
+						NULL, max77659_charger_isr, IRQF_TRIGGER_FALLING,
+						max77659_irq_descs[i], charger);
+		if (ret)
+			return dev_err_probe(dev, ret, "Failed to request irq: %d\n",
+					     charger->irq_arr[i]);
+	}
+
+	charger->psy_chg = devm_power_supply_register(dev, &charger->psy_chg_d, &charger_cfg);
+	if (IS_ERR(charger->psy_chg))
+		return dev_err_probe(dev, PTR_ERR(charger->psy_chg),
+				     "Failed to register power supply\n");
+
+	return 0;
+}
+
+static const struct of_device_id max77659_charger_of_id[] = {
+	{ .compatible = "adi,max77659-charger" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, max77659_charger_of_id);
+
+static const struct platform_device_id max77659_charger_id[] = {
+	{ MAX77659_CHARGER_NAME, 0, },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, max77659_charger_id);
+
+static struct platform_driver max77659_charger_driver = {
+	.driver = {
+		.name = MAX77659_CHARGER_NAME,
+		.of_match_table = of_match_ptr(max77659_charger_of_id),
+	},
+	.probe = max77659_charger_probe,
+	.id_table = max77659_charger_id,
+};
+
+module_platform_driver(max77659_charger_driver);
+
+MODULE_DESCRIPTION("MAX77659 Charger Driver");
+MODULE_AUTHOR("Nurettin.Bolucu@analog.com, Zeynep.Arslanbenzer@analog.com");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");