From 66628d24867e70c525acdc9dd4d1a5e1a357deba Mon Sep 17 00:00:00 2001 From: joan-na-good Date: Sun, 8 Jun 2025 18:56:20 +0900 Subject: [PATCH 1/4] dt-bindings: mfd: Add bindings for MAX77840 PMIC and submodules Add device tree bindings to support the MAX77840 PMIC and its submodules, enabling configuration and control through device tree. Signed-off-by: joan-na-good --- .../bindings/mfd/maxim,max77840.yaml | 91 +++++++++++++++++++ .../supply/maxim,max77840-charger-detect.yaml | 26 ++++++ .../power/supply/maxim,max77840-charger.yaml | 60 ++++++++++++ .../supply/maxim,max77840-fuelgauge.yaml | 25 +++++ .../regulator/maxim,max77840-regulator.yaml | 45 +++++++++ 5 files changed, 247 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/maxim,max77840.yaml create mode 100644 Documentation/devicetree/bindings/power/supply/maxim,max77840-charger-detect.yaml create mode 100644 Documentation/devicetree/bindings/power/supply/maxim,max77840-charger.yaml create mode 100644 Documentation/devicetree/bindings/power/supply/maxim,max77840-fuelgauge.yaml create mode 100644 Documentation/devicetree/bindings/regulator/maxim,max77840-regulator.yaml diff --git a/Documentation/devicetree/bindings/mfd/maxim,max77840.yaml b/Documentation/devicetree/bindings/mfd/maxim,max77840.yaml new file mode 100644 index 00000000000000..44b5f7cc5d9bcd --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/maxim,max77840.yaml @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/maxim,max77840.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim MAX77840 PMIC + +maintainers: + - Joan Na + + +properties: + compatible: + const: maxim,max77840 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + regulator: + type: object + $ref: ../regulator/maxim,max77840-regulator.yaml# + + charger: + type: object + $ref: ../power/supply/maxim,max77840-charger.yaml# + + fuelgauge: + type: object + $ref: ../power/supply/maxim,max77840-fuelgauge.yaml# + + charger-detect: + type: object + properties: + compatible: + const: maxim,max77840-charger-detect + + additionalProperties: false + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + max77840@66 { + compatible = "maxim,max77840"; + reg = <0x66>; + interrupt-parent = <&gpio>; + interrupts = <16 IRQ_TYPE_LEVEL_LOW>; + status = "okay"; + + regulator { + SAFEOUT1 { + regulator-boot-on; + regulator-always-on; + }; + }; + + charger { + compatible = "maxim,max77840-charger"; + fast_charge_timer = <0>; // disable + fast_charge_current = <1500>; // mA + charge_termination_voltage = <4350>; // mV + topoff_timer = <30>; // min + topoff_current = <150>; // mA + restart_threshold = <150>; // mV + input_current_limit = <500>; // mA + }; + + charger-detect { + compatible = "maxim,max77840-charger-detect"; + }; + + fuelgauge { + compatible = "maxim,max77840-fuelgauge"; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max77840-charger-detect.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max77840-charger-detect.yaml new file mode 100644 index 00000000000000..9ec365d8d7d129 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/maxim,max77840-charger-detect.yaml @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/maxim,max77840-charger-detect.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim MAX77840 Charger Detect + +maintainers: + - Joan Na + +properties: + compatible: + const: maxim,max77840-charger-detect + +required: + - compatible + +additionalProperties: false + +examples: + - | + charger { + compatible = "maxim,max77840-charger-detect"; + }; + diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max77840-charger.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max77840-charger.yaml new file mode 100644 index 00000000000000..cc58dad5706699 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/maxim,max77840-charger.yaml @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/maxim,max77840-charger.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim MAX77840 Charger + +maintainers: + - Joan Na + +properties: + compatible: + const: maxim,max77840-charger + + fast_charge_timer: + description: Fast charge timer in hours + $ref: /schemas/types.yaml#/definitions/uint32 + + fast_charge_current: + description: Fast charge current in milliamps + $ref: /schemas/types.yaml#/definitions/uint32 + + charge_termination_voltage: + description: Charge termination (CV) voltage in millivolts + $ref: /schemas/types.yaml#/definitions/uint32 + + topoff_timer: + description: Top-off timer in minutes + $ref: /schemas/types.yaml#/definitions/uint32 + + topoff_current: + description: Top-off current in milliamps (mA) + $ref: /schemas/types.yaml#/definitions/uint32 + + restart_threshold: + description: Restart threshold in millivolts (mV) + $ref: /schemas/types.yaml#/definitions/uint32 + + input_current_limit: + description: Input current limit in milliamps (mA) + $ref: /schemas/types.yaml#/definitions/uint32 + +required: + - compatible + +additionalProperties: false + +examples: + - | + charger { + compatible = "maxim,max77840-charger"; + fast_charge_timer = <5>; + fast_charge_current = <1500>; + charge_termination_voltage = <4200>; + topoff_timer = <20>; + topoff_current = <100>; + restart_threshold = <100>; + input_current_limit = <500>; + }; diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max77840-fuelgauge.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max77840-fuelgauge.yaml new file mode 100644 index 00000000000000..b3c3a3f0cec7f0 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/maxim,max77840-fuelgauge.yaml @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/maxim,max77840-fuelgauge.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim MAX77840 Fuel Gauge + +maintainers: + - Joan Na + +properties: + compatible: + const: maxim,max77840-fuelgauge + +required: + - compatible + +additionalProperties: false + +examples: + - | + fuelgauge { + compatible = "maxim,max77840-fuelgauge"; + }; diff --git a/Documentation/devicetree/bindings/regulator/maxim,max77840-regulator.yaml b/Documentation/devicetree/bindings/regulator/maxim,max77840-regulator.yaml new file mode 100644 index 00000000000000..90b5dd765821c8 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/maxim,max77840-regulator.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/maxim,max77840-regulator.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim MAX77840 Regulator + +maintainers: + - Joan Na + +properties: + compatible: + const: maxim,max77840-regulator + + reg: + maxItems: 1 + + regulators: + type: object + + properties: + SAFEOUT1: + $ref: regulator.yaml# + + additionalProperties: false + +required: + - compatible + - reg + - regulators + +additionalProperties: false + +examples: + - | + + regulators { + SAFEOUT1 { + regulator-name = "SAFEOUT1"; + regulator-boot-on; + regulator-always-on; + }; + }; + From c323c8ba93ce49e10b866d240e962f25c5c0fdc6 Mon Sep 17 00:00:00 2001 From: joan-na-good Date: Sun, 8 Jun 2025 18:56:30 +0900 Subject: [PATCH 2/4] mfd: max77840: Add support for MAX77840 PMIC core Implement basic support for the MAX77840 PMIC core, including initialization, power management, and core functions necessary for submodules. Signed-off-by: joan-na-good --- Kconfig.adi | 3 + drivers/mfd/Kconfig | 13 + drivers/mfd/Makefile | 1 + drivers/mfd/max77840.c | 644 +++++++++++++++++++++++++++++++++++ include/linux/mfd/max77840.h | 196 +++++++++++ 5 files changed, 857 insertions(+) create mode 100644 drivers/mfd/max77840.c create mode 100644 include/linux/mfd/max77840.h diff --git a/Kconfig.adi b/Kconfig.adi index ebf771fe4169ae..cc4409c09b2304 100644 --- a/Kconfig.adi +++ b/Kconfig.adi @@ -96,6 +96,9 @@ config KERNEL_ALL_ADI_DRIVERS imply REGULATOR_MAX77857 imply REGULATOR_MAX77541 imply MFD_MAX77541 + imply REGULATOR_MAX77840 + imply CHARGER_MAX77840 + imply MFD_MAX77840 imply REGULATOR_ADP5055 source "drivers/clk/Kconfig.adi" diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 90ce58fd629e5f..f4a0c77e672d35 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -894,6 +894,19 @@ config MFD_MAX77714 drivers must be enabled in order to use each functionality of the device. +config MFD_MAX77840 + tristate "Maxim Integrated MAX77840 Charger Support" + depends on I2C + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + Say yes here to add support for Maxim Integrated MAX77840. + This is a 3A Switch Mode Charger with Fuel Gauge. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the functionality + of the device. + config MFD_MAX77843 bool "Maxim Semiconductor MAX77843 PMIC Support" depends on I2C=y diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index c66f07edcd0e62..08965b62fb6f61 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -163,6 +163,7 @@ obj-$(CONFIG_MFD_MAX77650) += max77650.o obj-$(CONFIG_MFD_MAX77686) += max77686.o obj-$(CONFIG_MFD_MAX77693) += max77693.o obj-$(CONFIG_MFD_MAX77714) += max77714.o +obj-$(CONFIG_MFD_MAX77840) += max77840.o obj-$(CONFIG_MFD_MAX77843) += max77843.o obj-$(CONFIG_MFD_MAX8907) += max8907.o max8925-objs := max8925-core.o max8925-i2c.o diff --git a/drivers/mfd/max77840.c b/drivers/mfd/max77840.c new file mode 100644 index 00000000000000..b8193b27b64e9d --- /dev/null +++ b/drivers/mfd/max77840.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Maxim Integrated Products, Inc. + * Copyright (C) 2024 Analog Devices, Inc. + * + * Author : Analog Devices + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* for Regmap */ +#include + +/* for Device Tree */ +#include +#include +#include +#include + +#include +#include +#include +#include + +#define DRIVER_VERSION "1.0" + +#define I2C_ADDR_PMIC (0xCC >> 1) /* PMIC (CLOGIC/SAFELDOs) */ +#define I2C_ADDR_CHARGER (0xD2 >> 1) /* Charger */ +#define I2C_ADDR_CHARGER_DETECT (0x4A >> 1) /* Charger Detect */ +#define I2C_ADDR_FUEL_GAUGE (0x6C >> 1) /* Fuel Gauge */ + +#define __lock(_me) mutex_lock(&(_me)->lock) +#define __unlock(_me) mutex_unlock(&(_me)->lock) + +static const struct regmap_config max77840_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, +}; + +static const struct regmap_config max77840_regmap_config_fuelgauge = { + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_NONE, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +/******************************************************************************* + * Chip IO + ******************************************************************************/ +int max77840_read(struct regmap *regmap, u8 addr, u8 *val) +{ + unsigned int buf = 0; + int rc; + + rc = regmap_read(regmap, (unsigned int)addr, &buf); + if (rc < 0) + return rc; + + *val = (u8)buf; + + return 0; +} +EXPORT_SYMBOL(max77840_read); + +int max77840_write(struct regmap *regmap, u8 addr, u8 val) +{ + unsigned int buf = (unsigned int)val; + + return regmap_write(regmap, (unsigned int)addr, buf); +} +EXPORT_SYMBOL(max77840_write); + +int max77840_fg_read(struct regmap *regmap, u8 addr, u16 *val) +{ + unsigned int buf = 0; + int rc; + + rc = regmap_read(regmap, (unsigned int)addr, &buf); + if (rc < 0) + return rc; + + *val = (u16)buf; + + return 0; +} +EXPORT_SYMBOL(max77840_fg_read); + +int max77840_fg_write(struct regmap *regmap, u8 addr, u16 val) +{ + unsigned int buf = (unsigned int)val; + + return regmap_write(regmap, (unsigned int)addr, buf); +} +EXPORT_SYMBOL(max77840_fg_write); + +int max77840_bulk_read(struct regmap *regmap, u8 addr, u8 *dst, u16 len) +{ + return regmap_bulk_read(regmap, (unsigned int)addr, dst, (size_t)len); +} +EXPORT_SYMBOL(max77840_bulk_read); + +int max77840_bulk_write(struct regmap *regmap, u8 addr, const u8 *src, u16 len) +{ + return regmap_bulk_write(regmap, (unsigned int)addr, src, (size_t)len); +} +EXPORT_SYMBOL(max77840_bulk_write); + +/******************************************************************************* + * device + ******************************************************************************/ +static int max77840_add_devices(struct max77840_dev *me, + struct mfd_cell *cells, int n_devs) +{ + struct device *dev = me->dev; + int rc; + + rc = mfd_add_devices(dev, -1, cells, n_devs, NULL, 0, NULL); + + return rc; +} + +/******************************************************************************* + *** MAX77840 PMIC + ******************************************************************************/ + +/* PMIC */ +#define REG_PMICID 0x00 + +/* Declare Interrupt */ +static const struct regmap_irq max77840_intsrc_irqs[] = { + { .reg_offset = 0, .mask = BIT_CHGR_INT,}, /* CHGR_INT */ + { .reg_offset = 0, .mask = BIT_SYS_INT,}, /* SYS_INT */ + { .reg_offset = 0, .mask = BIT_FG_INT,}, /* FG_INT */ + { .reg_offset = 0, .mask = BIT_CHGDET_INT,}, /* CHGDET_INT */ + { .reg_offset = 0, .mask = BIT_B2SOVRC_INT,}, /* B2SOVRC_INT */ +}; + +static const struct regmap_irq_chip max77840_intsrc_irq_chip = { + .name = "max77840 intsrc", + .status_base = REG_INTSRC, + .mask_base = REG_INTSRCMASK, + .num_regs = 1, + .irqs = max77840_intsrc_irqs, + .num_irqs = ARRAY_SIZE(max77840_intsrc_irqs), +}; + +static const struct regmap_irq max77840_sys_irqs[] = { + { .reg_offset = 0, .mask = BIT_T120C_INT,}, /* T120C_INT */ + { .reg_offset = 0, .mask = BIT_T140C_INT,}, /* T140C_INT */ + { .reg_offset = 0, .mask = BIT_LOWSYS_INT,}, /* LOWSYS_INT */ + { .reg_offset = 0, .mask = BIT_SYSUVLO_INT,}, /* SYSUVLO_INT */ + { .reg_offset = 0, .mask = BIT_SYSOVLO_INT,}, /* SYSOVLO_INT */ + { .reg_offset = 0, .mask = BIT_TSHDN_INT,}, /* TSHDN_INT */ +}; + +static const struct regmap_irq_chip max77840_sys_irq_chip = { + .name = "max77840 system", + .status_base = REG_SYSINTSRC, + .mask_base = REG_SYSINTMASK, + .num_regs = 1, + .irqs = max77840_sys_irqs, + .num_irqs = ARRAY_SIZE(max77840_sys_irqs), +}; + +static const struct regmap_irq max77840_chgdet_irqs[] = { + { .reg_offset = 0, .mask = BIT_CHGDET_CHGTYPE_I,}, /* CHGTYPE_I */ + { .reg_offset = 0, .mask = BIT_CHGDET_CHGDETRUN_I,}, /* CHGDETRUN_I */ + { .reg_offset = 0, .mask = BIT_CHGDET_DCDTMR_I,}, /* DCDTMR_I */ + { .reg_offset = 0, .mask = BIT_CHGDET_DXOVP_I,}, /* DxOVP_I */ + { .reg_offset = 0, .mask = BIT_CHGDET_VDNMON_I,}, /* VDCNMON_I */ +}; + +static const struct regmap_irq_chip max77840_chgdet_irq_chip = { + .name = "max77840 chgdet", + .status_base = REG_CHGDET_INT, + .mask_base = REG_CHGDET_INT_MASK, + .num_regs = 1, + .irqs = max77840_chgdet_irqs, + .num_irqs = ARRAY_SIZE(max77840_chgdet_irqs), +}; + +static const struct regmap_irq max77840_chg_irqs[] = { + { .reg_offset = 0, .mask = BIT_CHG_BYP_I,}, /* BYP_I */ + { .reg_offset = 0, .mask = BIT_CHG_BAT2SOC_I,}, /* BAT2SOC_I */ + { .reg_offset = 0, .mask = BIT_CHG_BATP_I,}, /* BATP_I */ + { .reg_offset = 0, .mask = BIT_CHG_BAT_I,}, /* BAT_I */ + { .reg_offset = 0, .mask = BIT_CHG_CHG_I,}, /* CHG_I */ + { .reg_offset = 0, .mask = BIT_CHG_TOPOFF_I,}, /* TOPOFF_I */ + { .reg_offset = 0, .mask = BIT_CHG_CHGIN_I,}, /* CHGIN_I */ + { .reg_offset = 0, .mask = BIT_CHG_AICL_I,}, /* AICL_I */ +}; + +static const struct regmap_irq_chip max77840_chg_irq_chip = { + .name = "max77840 chg", + .status_base = REG_CHARGER_INT, + .mask_base = REG_CHARGER_INT_MASK, + .num_regs = 1, + .irqs = max77840_chg_irqs, + .num_irqs = ARRAY_SIZE(max77840_chg_irqs), +}; + +int max77840_map_irq(struct max77840_dev *max77840, int irq) +{ + return regmap_irq_get_virq(max77840->irqc_intsrc, irq); +} +EXPORT_SYMBOL_GPL(max77840_map_irq); + +int max77840_map_sys_irq(struct max77840_dev *max77840, int irq) +{ + return regmap_irq_get_virq(max77840->irqc_sys, irq); +} +EXPORT_SYMBOL_GPL(max77840_map_sys_irq); + +int max77840_map_chg_irq(struct max77840_dev *max77840, int irq) +{ + return regmap_irq_get_virq(max77840->irqc_chg, irq); +} +EXPORT_SYMBOL_GPL(max77840_map_chg_irq); + +int max77840_map_chg_det_irq(struct max77840_dev *max77840, int irq) +{ + return regmap_irq_get_virq(max77840->irqc_chgdet, irq); +} +EXPORT_SYMBOL_GPL(max77840_map_chg_det_irq); + +static int max77840_pmic_irq_int(struct max77840_dev *me) +{ + struct device *dev = me->dev; + struct i2c_client *client = to_i2c_client(dev); + int rc = 0; + + /* disable all interrupt source */ + max77840_write(me->regmap_pmic, REG_INTSRCMASK, 0xFF); + + /* interrupt source */ + rc = regmap_add_irq_chip(me->regmap_pmic, me->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED, -1, + &max77840_intsrc_irq_chip, + &me->irqc_intsrc); + if (rc != 0) { + dev_err(&client->dev, "failed to add insrc irq chip: %d\n", rc); + goto out; + } + + /* system interrupt */ + rc = regmap_add_irq_chip(me->regmap_pmic, me->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED, -1, + &max77840_sys_irq_chip, + &me->irqc_sys); + if (rc != 0) { + dev_err(&client->dev, "failed to add system irq chip: %d\n", + rc); + goto err_irqc_sys; + } + + /* charger interrupt */ + rc = regmap_add_irq_chip(me->regmap_chg, me->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED, -1, + &max77840_chg_irq_chip, + &me->irqc_chg); + if (rc != 0) { + dev_err(&client->dev, "failed to add chg irq chip: %d\n", rc); + goto err_irqc_chg; + } + + /* charger detect interrupt */ + rc = regmap_add_irq_chip(me->regmap_chg_det, me->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED, -1, + &max77840_chgdet_irq_chip, + &me->irqc_chgdet); + if (rc != 0) { + dev_err(&client->dev, "failed to add chgdet irq chip: %d\n", rc); + goto err_irqc_chgdet; + } + + pr_err("<%s> IRQ initialize done\n", client->name); + return 0; + +err_irqc_chgdet: + regmap_del_irq_chip(me->irq, me->irqc_chg); +err_irqc_chg: + regmap_del_irq_chip(me->irq, me->irqc_sys); +err_irqc_sys: + regmap_del_irq_chip(me->irq, me->irqc_intsrc); +out: + return rc; +} + +static int max77840_pre_init_data(struct max77840_dev *pmic) +{ +#ifdef CONFIG_OF + int size, cnt; + const int *list; + struct device *dev = pmic->dev; + struct device_node *np = dev->of_node; + + u8 *init_data; + + list = of_get_property(np, "max77840,pmic-init", &size); + if (list) { + init_data = kmalloc(size, GFP_KERNEL); + of_property_read_u8_array(np, "max77840,pmic-init", init_data, size); + for (cnt = 0; cnt < size; cnt += 2) { + max77840_write(pmic->regmap_pmic, + init_data[cnt], init_data[cnt + 1]); + } + kfree(init_data); + } + + list = of_get_property(np, "max77840,chg-init", &size); + if (list) { + init_data = kmalloc(size, GFP_KERNEL); + of_property_read_u8_array(np, "max77840,chg-init", init_data, size); + for (cnt = 0; cnt < size; cnt += 2) { + max77840_write(pmic->regmap_chg, + init_data[cnt], init_data[cnt + 1]); + } + kfree(init_data); + } + + list = of_get_property(np, "max77840,chgdet-init", &size); + if (list) { + init_data = kmalloc(size, GFP_KERNEL); + of_property_read_u8_array(np, "max77840,chgdet-init", init_data, size); + for (cnt = 0; cnt < size; cnt += 2) { + max77840_write(pmic->regmap_chg_det, + init_data[cnt], init_data[cnt + 1]); + } + kfree(init_data); + } + + list = of_get_property(np, "max77840,fg-init", &size); + if (list) { + init_data = kmalloc(size, GFP_KERNEL); + of_property_read_u8_array(np, "max77840,fg-init", init_data, size); + for (cnt = 0; cnt < size; cnt += 3) { + max77840_fg_write(pmic->regmap_fuel, init_data[cnt], + (init_data[cnt + 1] << 8) | init_data[cnt + 2]); + } + kfree(init_data); + } +#endif + return 0; +} + +static struct mfd_cell max77840_devices[] = { + { .name = MAX77840_REGULATOR_NAME, }, + { .name = MAX77840_CHARGER_NAME, }, + { .name = MAX77840_CHARGER_DETECT_NAME, }, + { .name = MAX77840_FUELGAUGE_NAME, }, +}; + +static int max77840_pmic_setup(struct max77840_dev *me) +{ + struct device *dev = me->dev; + struct i2c_client *client = to_i2c_client(dev); + int rc = 0; + u8 chip_id; + u8 val = 0; + + /* IRQ init */ + pr_info("<%s> property:IRQ %d\n", client->name, client->irq); + me->irq = client->irq; + + pr_err("%s: max77840_pmic_irq_int\n", __func__); + rc = max77840_pmic_irq_int(me); + if (rc != 0) { + dev_err(&client->dev, "failed to initialize irq: %d\n", rc); + goto err_irq_init; + } + + pr_err("%s: max77840_add_devices\n", __func__); + rc = max77840_add_devices(me, max77840_devices, + ARRAY_SIZE(max77840_devices)); + if (rc < 0) { + pr_err("<%s> failed to add sub-devices [%d]\n", + client->name, rc); + goto err_add_devices; + } + + pr_err("<%s> driver core " DRIVER_VERSION " installed\n", client->name); + + chip_id = 0; + + max77840_read(me->regmap_pmic, REG_PMICID, &chip_id); + pr_err("<%s> pmic id %Xh\n", client->name, chip_id); + + /* clear IRQ */ + max77840_read(me->regmap_pmic, REG_INTSRC, &val); + pr_err("<%s> intsrc %Xh\n", client->name, val); + + max77840_read(me->regmap_pmic, REG_INTSRCMASK, &val); + pr_err("<%s> intsrc_mask %Xh\n", client->name, val); + + /* set device able to wake up system */ + device_init_wakeup(dev, true); + if (likely(me->irq > 0)) + enable_irq_wake((unsigned int)me->irq); + + return 0; + +err_add_devices: + regmap_del_irq_chip(me->irq, me->irqc_intsrc); +err_irq_init: + return rc; +} + +/******************************************************************************* + *** MAX77840 MFD Core + ******************************************************************************/ + +static __always_inline void max77840_destroy(struct max77840_dev *me) +{ + struct device *dev = me->dev; + + mfd_remove_devices(me->dev); + + if (likely(me->irq > 0)) + regmap_del_irq_chip(me->irq, me->irqc_intsrc); + + if (likely(me->irq_gpio >= 0)) + gpio_free((unsigned int)me->irq_gpio); + + if (likely(me->regmap_pmic)) + regmap_exit(me->regmap_pmic); + + if (likely(me->regmap_chg_det)) + regmap_exit(me->regmap_chg_det); + + if (likely(me->regmap_chg)) + regmap_exit(me->regmap_chg); + + if (likely(me->regmap_fuel)) + regmap_exit(me->regmap_fuel); + + mutex_destroy(&me->lock); + devm_kfree(dev, me); +} + +static int max77840_i2c_probe(struct i2c_client *client) +{ + struct max77840_dev *me; + int rc; + + pr_err("<%s> attached\n", client->name); + pr_err("%s: Max77840 I2C Driver Loading\n", __func__); + + me = devm_kzalloc(&client->dev, sizeof(*me), GFP_KERNEL); + if (unlikely(!me)) { + pr_err("<%s> out of memory (%uB requested)\n", client->name, + (unsigned int)sizeof(*me)); + return -ENOMEM; + } + + i2c_set_clientdata(client, me); + + mutex_init(&me->lock); + me->dev = &client->dev; + me->irq = -1; + me->irq_gpio = -1; + + me->pmic = client; + + me->regmap_pmic = devm_regmap_init_i2c(client, &max77840_regmap_config); + if (IS_ERR(me->regmap_pmic)) { + rc = PTR_ERR(me->regmap_pmic); + me->regmap_pmic = NULL; + pr_err("<%s> failed to initialize i2c\n", + client->name); + pr_err("<%s> regmap pmic [%d]\n", + client->name, rc); + goto abort; + } + + me->chg = i2c_new_dummy_device(client->adapter, I2C_ADDR_CHARGER); + if (!me->chg) { + rc = -ENOMEM; + goto abort; + } + i2c_set_clientdata(me->chg, me); + me->regmap_chg = devm_regmap_init_i2c(me->chg, &max77840_regmap_config); + if (IS_ERR(me->regmap_chg)) { + rc = PTR_ERR(me->regmap_chg); + me->regmap_chg = NULL; + pr_err("<%s> failed to initialize i2c\n", + client->name); + pr_err("<%s> regmap chg [%d]\n", + client->name, rc); + goto abort; + } + + me->chg_det = i2c_new_dummy_device(client->adapter, I2C_ADDR_CHARGER_DETECT); + if (!me->chg_det) { + rc = -ENOMEM; + goto abort; + } + i2c_set_clientdata(me->chg_det, me); + me->regmap_chg_det = devm_regmap_init_i2c(me->chg_det, &max77840_regmap_config); + if (IS_ERR(me->regmap_chg_det)) { + rc = PTR_ERR(me->regmap_chg_det); + me->regmap_chg_det = NULL; + pr_err("<%s> failed to initialize i2c\n", + client->name); + pr_err("<%s> regmap chgdet [%d]\n", + client->name, rc); + goto abort; + } + + me->fuel = i2c_new_dummy_device(client->adapter, I2C_ADDR_FUEL_GAUGE); + if (!me->fuel) { + rc = -ENOMEM; + goto abort; + } + i2c_set_clientdata(me->fuel, me); + me->regmap_fuel = devm_regmap_init_i2c(me->fuel, &max77840_regmap_config_fuelgauge); + if (IS_ERR(me->regmap_fuel)) { + rc = PTR_ERR(me->regmap_fuel); + me->regmap_fuel = NULL; + pr_err("<%s> failed to initialize i2c\n", client->name); + pr_err("<%s> regmap fuelgauge [%d]\n", client->name, rc); + goto abort; + } + + max77840_pre_init_data(me); + rc = max77840_pmic_setup(me); + if (rc != 0) { + pr_err("<%s> failed to set up interrupt\n", + client->name); + pr_err("<%s> and add sub-device [%d]\n", + client->name, rc); + goto abort; + } + + return 0; + +abort: + pr_err("%s: Failed to probe max77840\n", __func__); + i2c_set_clientdata(client, NULL); + max77840_destroy(me); + return rc; +} + +static void max77840_i2c_remove(struct i2c_client *client) +{ + struct max77840_dev *me = i2c_get_clientdata(client); + + i2c_set_clientdata(client, NULL); + max77840_destroy(me); +} + +#ifdef CONFIG_PM_SLEEP +static int max77840_suspend(struct device *dev) +{ + struct max77840_dev *me = dev_get_drvdata(dev); + struct i2c_client *client = to_i2c_client(dev); + + __lock(me); + + pr_info("<%s> suspending\n", client->name); + + __unlock(me); + return 0; +} + +static int max77840_resume(struct device *dev) +{ + struct max77840_dev *me = dev_get_drvdata(dev); + struct i2c_client *client = to_i2c_client(dev); + + __lock(me); + + pr_info("<%s> resuming\n", client->name); + + __unlock(me); + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(max77840_pm, max77840_suspend, max77840_resume); + +#ifdef CONFIG_OF +static const struct of_device_id max77840_of_id[] = { + { .compatible = "maxim,max77840"}, + { }, +}; +MODULE_DEVICE_TABLE(of, max77840_of_id); +#endif /* CONFIG_OF */ + +static const struct i2c_device_id max77840_i2c_id[] = { + { MAX77840_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max77840_i2c_id); + +static struct i2c_driver max77840_i2c_driver = { + .driver.name = MAX77840_NAME, + .driver.owner = THIS_MODULE, + .driver.pm = &max77840_pm, +#ifdef CONFIG_OF + .driver.of_match_table = max77840_of_id, +#endif /* CONFIG_OF */ + .id_table = max77840_i2c_id, + .probe = max77840_i2c_probe, + .remove = max77840_i2c_remove, +}; + +static __init int max77840_init(void) +{ + int rc = -ENODEV; + + rc = i2c_add_driver(&max77840_i2c_driver); + if (rc != 0) + pr_err("Failed to register I2C driver: %d\n", rc); + pr_err("%s: Added I2C Driver\n", __func__); + return rc; +} + +static __exit void max77840_exit(void) +{ + i2c_del_driver(&max77840_i2c_driver); +} + +module_init(max77840_init); +module_exit(max77840_exit); + +MODULE_DESCRIPTION("MAX77840 MFD Gauge"); +MODULE_AUTHOR("joan.na@analog.com"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/include/linux/mfd/max77840.h b/include/linux/mfd/max77840.h new file mode 100644 index 00000000000000..b41f34076c2b9d --- /dev/null +++ b/include/linux/mfd/max77840.h @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020 Maxim Integrated Products, Inc. + * Copyright (C) 2024 Analog Devices, Inc. + * + * Author : Analog Devices + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __MAX77840_MFD_H__ +#define __MAX77840_MFD_H__ + +#define __TEST_DEVICE_NODE__ + +#include +#include + +/* MAX77840 Top Devices */ +#define MAX77840_NAME "max77840" + +/* MAX77840 PMIC Devices */ +#define MAX77840_REGULATOR_NAME "max77840-regulator" +#define MAX77840_CHARGER_NAME "max77840-charger" +#define MAX77840_CHARGER_DETECT_NAME "max77840-charger-detect" +#define MAX77840_FUELGAUGE_NAME "max77840-fuelgauge" + +/* Register map */ +/* Interrupt register & mask bit */ +#define REG_INTSRC 0x22 +#define REG_INTSRCMASK 0x23 +#define BIT_CHGR_INT BIT(0) +#define BIT_SYS_INT BIT(1) +#define BIT_FG_INT BIT(2) +#define BIT_CHGDET_INT BIT(3) +#define BIT_B2SOVRC_INT BIT(5) + +#define REG_SYSINTSRC 0x24 +#define REG_SYSINTMASK 0x26 +#define BIT_T120C_INT BIT(0) +#define BIT_T140C_INT BIT(1) +#define BIT_LOWSYS_INT BIT(3) +#define BIT_SYSUVLO_INT BIT(4) +#define BIT_SYSOVLO_INT BIT(5) +#define BIT_TSHDN_INT BIT(6) + +#define REG_CHGDET_INT 0x01 +#define REG_CHGDET_INT_MASK 0x03 +#define BIT_CHGDET_CHGTYPE_I BIT(0) +#define BIT_CHGDET_CHGDETRUN_I BIT(1) +#define BIT_CHGDET_DCDTMR_I BIT(2) +#define BIT_CHGDET_DXOVP_I BIT(3) +#define BIT_CHGDET_VDNMON_I BIT(4) + +#define REG_CHARGER_INT 0xB0 +#define REG_CHARGER_INT_MASK 0xB1 +#define BIT_CHG_BYP_I BIT(0) +#define BIT_CHG_BAT2SOC_I BIT(1) +#define BIT_CHG_BATP_I BIT(2) +#define BIT_CHG_BAT_I BIT(3) +#define BIT_CHG_CHG_I BIT(4) +#define BIT_CHG_TOPOFF_I BIT(5) +#define BIT_CHG_CHGIN_I BIT(6) +#define BIT_CHG_AICL_I BIT(7) + +/* Chip Interrupts */ +enum { + MAX77840_CHGR_INT = 0, + MAX77840_SYS_INT, + MAX77840_FG_INT, + MAX77840_CHGDET_INT, + MAX77840_B2SOVRC_INT, + + MAX77840_SYS_IRQ_START, + MAX77840_SYS_IRQ_T120C = MAX77840_SYS_IRQ_START, + MAX77840_SYS_IRQ_T140C, + MAX77840_SYS_IRQ_LOWSYS, + MAX77840_SYS_IRQ_UVLO, + MAX77840_SYS_IRQ_OVLO, + MAX77840_SYS_IRQ_TSHDN, + + MAX77840_CHGDET_IRQ_START, + MAX77840_CHGDET_IRQ_CHGTYPE_I = MAX77840_CHGDET_IRQ_START, + MAX77840_CHGDET_IRQ_CHGDETRUN_I, + MAX77840_CHGDET_IRQ_DCDTMR_I, + MAX77840_CHGDET_IRQ_DXOVP_I, + MAX77840_CHGDET_IRQ_VDCNMON_I, + + MAX77840_CHG_IRQ_START, + MAX77840_CHG_IRQ_BYP_I = MAX77840_CHG_IRQ_START, + MAX77840_CHG_IRQ_BAT2SOC_I, + MAX77840_CHG_IRQ_BATP_I, + MAX77840_CHG_IRQ_BAT_I, + MAX77840_CHG_IRQ_CHG_I, + MAX77840_CHG_IRQ_WCIN_I, + MAX77840_CHG_IRQ_CHGIN_I, + MAX77840_CHG_IRQ_AICL_I, + + MAX77840_NUM_OF_INTS, +}; + +enum{ + SYS_IRQ_T120C = 0, + SYS_IRQ_T140C, + SYS_IRQ_LOWSYS, + SYS_IRQ_UVLO, + SYS_IRQ_OVLO, + SYS_IRQ_TSHDN, + + CHGDET_IRQ_CHGTYPE_I = 0, + CHGDET_IRQ_CHGDETRUN_I, + CHGDET_IRQ_DCDTMR_I, + CHGDET_IRQ_DxOVP_I, + CHGDET_IRQ_VDCNMON_I, + + CHG_IRQ_BYP_I = 0, + CHG_IRQ_BAT2SOC_I, + CHG_IRQ_BATP_I, + CHG_IRQ_BAT_I, + CHG_IRQ_CHG_I, + CHG_IRQ_TOPOFF_I, + CHG_IRQ_CHGIN_I, + CHG_IRQ_AICL_I, + + FG_IRQ_ALERT = 0, + + B2SOVRC_IRQ_ALERT = 0, + +}; + +/******************************************************************************* + * Useful Macros + ******************************************************************************/ +#undef BIT_RSVD +#define BIT_RSVD 0 + +#undef BITS +#define BITS(_end, _start) GENMASK((_end), (_start)) + +/******************************************************************************* + * Sub Modules Support + ******************************************************************************/ +enum { + MAX77840_DEV_REGULATOR = 0, + MAX77840_DEV_CHARGER, + MAX77840_DEV_CHARGER_DETECT, + MAX77840_DEV_FUELGAUGE, + MAX77840_DEV_NUM_OF_DEVICES, +}; + +struct max77840_dev { + void *pdata; + struct mutex lock; /* mutext */ + struct device *dev; + + int irq; + int irq_gpio; + + struct regmap_irq_chip_data *irqc_intsrc; + struct regmap_irq_chip_data *irqc_sys; + struct regmap_irq_chip_data *irqc_chg; + struct regmap_irq_chip_data *irqc_chgdet; + + struct i2c_client *pmic; /* 0xCC , CLOGIC/SAFELDOS */ + struct i2c_client *chg; /* 0xD2, CHARGER */ + struct i2c_client *chg_det; /* 0x4A, CHARGER DETECT */ + struct i2c_client *fuel; /* 0x6C, FUEL GAUGE */ + + struct regmap *regmap_pmic; /* CLOGIC/SAFELDOS */ + struct regmap *regmap_chg; /* CHARGER */ + struct regmap *regmap_chg_det; /* CHARGER DETECT */ + struct regmap *regmap_fuel; /* FUEL GAUGE */ +}; + +/******************************************************************************* + * Chip IO + ******************************************************************************/ +int max77840_read(struct regmap *regmap, u8 addr, u8 *val); +int max77840_write(struct regmap *regmap, u8 addr, u8 val); +int max77840_fg_read(struct regmap *regmap, u8 addr, u16 *val); +int max77840_fg_write(struct regmap *regmap, u8 addr, u16 val); +int max77840_bulk_read(struct regmap *regmap, u8 addr, u8 *dst, u16 len); +int max77840_bulk_write(struct regmap *regmap, u8 addr, const u8 *src, u16 len); + +/******************************************************************************* + * Interrupt + ******************************************************************************/ +int max77840_irq_init(struct max77840_dev *max77840); +void max77840_irq_exit(struct max77840_dev *max77840); +int max77840_irq_resume(struct max77840_dev *max77840); + +int max77840_map_irq(struct max77840_dev *max77840, int irq); + +#endif /* !__MAX77840_MFD_H__ */ From e34ccdff6aca561ae2fed59d603db7edb787922d Mon Sep 17 00:00:00 2001 From: joan-na-good Date: Sun, 8 Jun 2025 18:56:43 +0900 Subject: [PATCH 3/4] power: supply: Add support for MAX77840 charger, charger-detect and fuel gauge This patch adds driver support for the MAX77840 charger, charger-detect, and fuel gauge functionalities, enabling proper battery management and monitoring on supported platforms. Signed-off-by: joan-na-good --- drivers/power/supply/Kconfig | 13 + drivers/power/supply/Makefile | 1 + drivers/power/supply/max77840-battery.c | 574 ++++++++++ .../power/supply/max77840-charger-detect.c | 490 ++++++++ drivers/power/supply/max77840-charger.c | 1014 +++++++++++++++++ include/linux/power/max77840-battery.h | 52 + include/linux/power/max77840-charger-detect.h | 98 ++ include/linux/power/max77840-charger.h | 174 +++ 8 files changed, 2416 insertions(+) create mode 100644 drivers/power/supply/max77840-battery.c create mode 100644 drivers/power/supply/max77840-charger-detect.c create mode 100644 drivers/power/supply/max77840-charger.c create mode 100644 include/linux/power/max77840-battery.h create mode 100644 include/linux/power/max77840-charger-detect.h create mode 100644 include/linux/power/max77840-charger.h diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index a5f48ccd67eb6c..9b4afc98f6a264 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -552,6 +552,19 @@ config CHARGER_MAX77693 help Say Y to enable support for the Maxim MAX77693 battery charger. +config CHARGER_MAX77840 + tristate "Maxim Integrated MAX77840 Charger with Fuel Gauge" + depends on MFD_MAX77840 + help + Say Y here to enable support for the Maxim Integrated MAX77840 charger + with integrated fuel gauge functionality. This driver provides support + for battery charging, battery capacity monitoring, and handles various + charger-related interrupts such as charging state changes or faults. + + The MAX77840 communicates over the I2C bus and is typically part of a + PMIC used in portable devices. This driver interfaces with the Linux + power supply subsystem. + config CHARGER_MAX77976 tristate "Maxim MAX77976 battery charger driver" depends on I2C diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index a8a9fa6de1e9a0..ceec2f7a7ff16a 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -77,6 +77,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_MAX77840) += max77840-charger.o max77840-charger-detect.o max77840-battery.o obj-$(CONFIG_CHARGER_MAX77976) += max77976_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o diff --git a/drivers/power/supply/max77840-battery.c b/drivers/power/supply/max77840-battery.c new file mode 100644 index 00000000000000..ecabe204c17b31 --- /dev/null +++ b/drivers/power/supply/max77840-battery.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Maxim Integrated Products, Inc. + * Copyright (C) 2024 Analog Devices, Inc. + * + * Author : Analog Devices + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* for Regmap */ +#include + +/* for Device Tree */ +#include +#include +#include + +#ifdef __TEST_DEVICE_NODE__ +#include +#include +#endif + +#define MAX77840_FG_DELAY 1000 +#define MAX77840_BATTERY_FULL 100 +#define MAX77840_BATTERY_LOW 15 + +#define MAX77840_VERSION_NO 0x20B0 + +static char *batt_supplied_to[] = { + "max77840-fuelgauge", +}; + +struct max77840_chip { + struct device *dev; + struct max77840_dev *max77840; + struct regmap *regmap; + + int fg_irq; + struct delayed_work work; + struct power_supply *battery; + struct power_supply_desc psy_batt_d; + + struct mutex lock; /* mutext */ + + /* alert */ + int alert_threshold; + + /* State Of Connect */ + int ac_online; + int usb_online; + + /* battery voltage */ + int vcell; + + /* battery capacity */ + int soc; + + /* State Of Charge */ + int status; + + /* battery health */ + int health; + + /* battery capacity */ + int capacity_level; + + int lasttime_vcell; + int lasttime_soc; + int lasttime_status; + + struct max77840_fg_platform_data *pdata; +}; + +static int max77840_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77840_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = chip->status; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = chip->vcell; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = chip->soc; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = chip->health; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = chip->capacity_level; + break; + default: + return -EINVAL; + } + return 0; +} + +static void max77840_fg_get_vcell(struct max77840_chip *max77840_fg) +{ + u16 vcell; + int rc; + + rc = max77840_fg_read(max77840_fg->regmap, REG_VCELL, &vcell); + if (rc < 0) { + dev_err(max77840_fg->dev, "%s: err %d\n", __func__, rc); + } else { + pr_info("%s: vcell raw value(0x%4x)\n", __func__, vcell); + max77840_fg->vcell = (vcell >> 3) * 625; + } +} + +static void max77840_fg_get_soc(struct max77840_chip *max77840_fg) +{ + u16 soc; + int rc; + + rc = max77840_fg_read(max77840_fg->regmap, REG_VFSOC, &soc); + pr_info("%s fg read REG_VFSOC %d, rc=%d", __func__, soc, rc); + + if (rc < 0) + dev_err(max77840_fg->dev, "%s: err %d\n", __func__, rc); + else + max77840_fg->soc = (u16)soc >> 8; + + if (max77840_fg->soc > MAX77840_BATTERY_FULL) { + max77840_fg->soc = MAX77840_BATTERY_FULL; + max77840_fg->status = POWER_SUPPLY_STATUS_FULL; + max77840_fg->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + max77840_fg->health = POWER_SUPPLY_HEALTH_GOOD; + } else if (max77840_fg->soc < MAX77840_BATTERY_LOW) { + max77840_fg->health = POWER_SUPPLY_HEALTH_DEAD; + max77840_fg->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + } else { + max77840_fg->health = POWER_SUPPLY_HEALTH_GOOD; + max77840_fg->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + } +} + +static u16 max77840_fg_get_version(struct max77840_chip *max77840_fg) +{ + u16 version; + int rc; + + rc = max77840_fg_read(max77840_fg->regmap, REG_VERSION, &version); + if (rc < 0) + dev_err(max77840_fg->dev, "%s: err %d\n", __func__, rc); + + return version; +} + +static bool max77840_fg_check_status(struct max77840_chip *max77840_fg) +{ + u16 data; + bool ret = false; + int fg_write_rt; + + /* check if Smn was generated */ + if (max77840_fg_read(max77840_fg->regmap, REG_STATUS, &data) < 0) + return ret; + + pr_info("%s: status_reg(0x%4x)\n", __func__, data); + + /* minimum SOC threshold exceeded. */ + if (data & BIT_SMN) + ret = true; + + /* check 1% SOC change happened */ + if (data & BIT_DSOCI) { + max77840_fg_get_vcell(max77840_fg); + max77840_fg_get_soc(max77840_fg); + power_supply_changed(max77840_fg->battery); + + pr_info("soc changed, SOC=%d, VCELL=%d\n", max77840_fg->soc, max77840_fg->vcell); + } + + /* clear status reg */ + if (!ret) { + data = data & 0x007F; + fg_write_rt = max77840_fg_write(max77840_fg->regmap, REG_STATUS, data); + if (fg_write_rt < 0) + return ret; + } + + return ret; +} + +static void max77840_fg_work(struct work_struct *work) +{ + struct max77840_chip *chip; + + chip = container_of(work, struct max77840_chip, work.work); + + max77840_fg_get_vcell(chip); + max77840_fg_get_soc(chip); + + if (chip->vcell != chip->lasttime_vcell || + chip->soc != chip->lasttime_soc || + chip->status != chip->lasttime_status) { + chip->lasttime_vcell = chip->vcell; + chip->lasttime_soc = chip->soc; + + power_supply_changed(chip->battery); + } + schedule_delayed_work(&chip->work, MAX77840_FG_DELAY); +} + +static irqreturn_t max77840_fg_irq_thread(int irq, void *irq_data) +{ + struct max77840_chip *fuelgauge = irq_data; + bool fuel_alerted; + + if (fuelgauge->pdata->soc_alert_threshold >= 0) { + fuel_alerted = max77840_fg_check_status(fuelgauge); + pr_info("%s: Fuel-alert %salerted!\n", + __func__, fuel_alerted ? "" : "NOT "); + + /* schedule_delayed_work(&fuelgauge->work, 0); */ + } + + return IRQ_HANDLED; +} + +static enum power_supply_property max77840_fg_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, +}; + +static int max77840_fg_initialize(struct max77840_chip *chip) +{ + u16 config, val; + u8 data[2]; + int ret; + + max77840_fg_get_vcell(chip); + pr_info("<%s> vcell %d\n", __func__, chip->vcell); + + max77840_fg_get_soc(chip); + pr_info("<%s> soc %d\n", __func__, chip->soc); + + /* 1. set fuel gauge alert configuration */ + /* SALRT Threshold setting */ + data[0] = chip->pdata->soc_alert_threshold; + data[1] = 0xff; + val = (data[1] << 8) | data[0]; + max77840_fg_write(chip->regmap, REG_SALRT_TH, val); + + /* VALRT Threshold setting */ + data[0] = 0x00; + data[1] = 0xff; + val = (data[1] << 8) | data[0]; + max77840_fg_write(chip->regmap, REG_VALRT_TH, val); + + /* TALRT Threshold setting */ + data[0] = 0x80; + data[1] = 0x7f; + val = (data[1] << 8) | data[0]; + max77840_fg_write(chip->regmap, REG_TALRT_TH, val); + + ret = max77840_fg_read(chip->regmap, REG_CONFIG, &val); + if (ret < 0) + return ret; + + /*Enable Alert (Aen = 1) */ + config = val | (0x01 << 2); + ret = max77840_fg_write(chip->regmap, REG_CONFIG, config); + if (ret < 0) + return ret; + + /* 2. set SOC 1% change alert */ + ret = max77840_fg_read(chip->regmap, REG_CONFIG2, &val); + if (ret < 0) + return ret; + config = val | BIT_DSOCEN; + ret = max77840_fg_write(chip->regmap, REG_CONFIG2, config); + if (ret < 0) + return ret; + + return 0; +} + +#ifdef CONFIG_OF +static int max77840_fg_parse_dt(struct max77840_chip *fuelgauge) +{ + struct device *dev = fuelgauge->dev; + struct device_node *np = of_find_node_by_name(NULL, "fuelgauge"); + struct max77840_fg_platform_data *pdata; + int ret = 0; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (unlikely(!pdata)) + return -ENOMEM; + + /* reset, irq gpio info */ + if (!np) { + pr_err("%s np NULL\n", __func__); + return -EINVAL; + } + ret |= of_property_read_u32(np, "fuel_alert_soc", &pdata->soc_alert_threshold); + if (ret < 0) + pr_err("%s error reading pdata->fuel_alert_soc %d\n", __func__, ret); + + if (ret < 0) + return ret; + fuelgauge->pdata = pdata; + return 0; +} +#endif + +#ifdef __TEST_DEVICE_NODE__ +static struct max77840_chip *test_if; +static ssize_t max77840_test_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret, reg; + unsigned long strval; + u16 val; + char *args[3]; + char *sep = ",\t"; + char *buff = (char *)buf; + struct regmap *if_regmap = test_if->regmap; + + args[0] = strsep(&buff, sep); + if (!args[0]) + return -2; + + // register read + args[1] = strsep(&buff, sep); + if (strncmp("read", args[0], 4) == 0) { + ret = kstrtoul(args[1], 0, &strval); + reg = (int)strval; + ret = max77840_fg_read(if_regmap, reg, &val); + if (ret < 0) + dev_err(test_if->dev, "failed to read i2c: %d @ function %s\n", + ret, __func__); + else + dev_err(test_if->dev, "read [0x%x] = 0x%x\n", reg, val); + return size; + } + // register write + else if (strncmp("write", args[0], 5) == 0) { + ret = kstrtoul(args[1], 0, &strval); + reg = (int)strval; + args[2] = strsep(&buff, sep); + ret = kstrtoul(args[2], 0, &strval); + val = (int)strval; + ret = max77840_fg_write(if_regmap, reg, val); + if (ret < 0) + dev_err(test_if->dev, "failed to write i2c: %d @ function %s\n", + ret, __func__); + else + dev_err(test_if->dev, "write [0x%x] = 0x%x\n", reg, val); + return size; + } + + dev_err(test_if->dev, "Command not supported.\n"); + + return 0; +} + +static struct device_attribute max77840_attribute = { + .attr = { + .name = "max77840_test_node", + .mode = 0x0666, + }, + .show = NULL, + .store = max77840_test_store, +}; + +static int max77840_test_node(struct max77840_chip *pchip) +{ + int ret; + + ret = sysfs_create_file(&pchip->battery->dev.kobj, &max77840_attribute.attr); + test_if = pchip; + return ret; +} +#else +static int max77840_test_node(struct max77840_chip *pchip) +{ + return 0; +} +#endif + +static int max77840_fg_probe(struct platform_device *pdev) +{ + struct max77840_dev *max77840 = dev_get_drvdata(pdev->dev.parent); + struct max77840_fg_platform_data *pdata = + dev_get_platdata(max77840->dev); + struct max77840_chip *chip; + u16 version; + int ret = 0; + u8 val; + struct power_supply_config fg_cfg; + + pr_info("%s: MAX77840 Fuelgauge Driver Loading\n", __func__); + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (unlikely(!pdata)) { + pr_err("%s: out of memory\n", __func__); + pdata = ERR_PTR(-ENOMEM); + return -ENOMEM; + } + + mutex_init(&chip->lock); + + chip->dev = &pdev->dev; + chip->pdata = pdata; + chip->regmap = max77840->regmap_fuel; + +#if defined(CONFIG_OF) + ret = max77840_fg_parse_dt(chip); + if (ret < 0) + pr_err("%s not found fuelgauge dt! ret[%d]\n", __func__, ret); +#else + pdata = dev_get_platdata(&pdev->dev); +#endif + + version = max77840_fg_get_version(chip); + dev_info(&pdev->dev, "MAX77840 Fuelgauge Ver 0x%x\n", version); + if (version != MAX77840_VERSION_NO) { + ret = -ENODEV; + goto error; + } + chip->psy_batt_d.name = "battery"; + chip->psy_batt_d.type = POWER_SUPPLY_TYPE_BATTERY; + chip->psy_batt_d.get_property = max77840_fg_get_property; + chip->psy_batt_d.properties = max77840_fg_battery_props; + chip->psy_batt_d.num_properties = ARRAY_SIZE(max77840_fg_battery_props); + + fg_cfg.drv_data = chip; + fg_cfg.supplied_to = batt_supplied_to; + fg_cfg.of_node = max77840->dev->of_node; + fg_cfg.num_supplicants = ARRAY_SIZE(batt_supplied_to); + + chip->battery = + devm_power_supply_register(max77840->dev, &chip->psy_batt_d, &fg_cfg); + if (IS_ERR(chip->battery)) { + pr_err("Couldn't register battery rc=%ld\n", PTR_ERR(chip->battery)); + goto error; + } + + chip->fg_irq = regmap_irq_get_virq(max77840->irqc_intsrc, MAX77840_FG_INT); + dev_info(&pdev->dev, "MAX77840 Fuel-Gauge irq %d\n", chip->fg_irq); + + if (chip->fg_irq > 0) { + INIT_DELAYED_WORK(&chip->work, max77840_fg_work); + ret = request_threaded_irq(chip->fg_irq, NULL, + max77840_fg_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, "fuelgauge-irq", chip); + if (ret) { + pr_err("%s: Failed to Request IRQ\n", __func__); + goto error1; + } + } + + ret = max77840_fg_initialize(chip); + if (ret < 0) { + dev_err(&pdev->dev, "Error: Initializing fuel-gauge\n"); + goto error2; + } + + max77840_read(max77840->regmap_pmic, REG_INTSRCMASK, &val); + pr_info("<%s> intsrc_mask %Xh\n", pdev->name, val); + + max77840_test_node(chip); + return 0; + +error2: + if (chip->fg_irq) + free_irq(chip->fg_irq, chip); +error1: + power_supply_unregister(chip->battery); +error: + kfree(chip); + return ret; +} + +static int max77840_fg_remove(struct platform_device *pdev) +{ + struct max77840_chip *chip = platform_get_drvdata(pdev); + + power_supply_unregister(chip->battery); + cancel_delayed_work(&chip->work); + kfree(chip); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max77840_fg_suspend(struct device *dev) +{ + struct max77840_chip *chip = dev_get_drvdata(dev); + + cancel_delayed_work(&chip->work); + return 0; +} + +static int max77840_fg_resume(struct device *dev) +{ + struct max77840_chip *chip = dev_get_drvdata(dev); + + schedule_delayed_work(&chip->work, MAX77840_FG_DELAY); + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(max77840_fg_pm_ops, max77840_fg_suspend, + max77840_fg_resume); + +#ifdef CONFIG_OF +static const struct of_device_id max77840_fg_dt_ids[] = { + { .compatible = "maxim,max77840-fuelgauge" }, + { } +}; +MODULE_DEVICE_TABLE(of, max77840_fg_dt_ids); +#endif /* CONFIG_OF */ + +static struct platform_driver max77840_fg_driver = { + .driver = { + .name = MAX77840_FUELGAUGE_NAME, + .pm = &max77840_fg_pm_ops, +#ifdef CONFIG_OF + .of_match_table = max77840_fg_dt_ids, +#endif /* CONFIG_OF */ + }, + .probe = max77840_fg_probe, + .remove = max77840_fg_remove, +}; + +static int __init max77840_fg_init(void) +{ + return platform_driver_register(&max77840_fg_driver); +} + +static void __exit max77840_fg_exit(void) +{ + platform_driver_unregister(&max77840_fg_driver); +} + +module_init(max77840_fg_init); +module_exit(max77840_fg_exit); + +MODULE_DESCRIPTION("MAX77840 Fuel Gauge Driver"); +MODULE_AUTHOR("joan.na@analog.com"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); diff --git a/drivers/power/supply/max77840-charger-detect.c b/drivers/power/supply/max77840-charger-detect.c new file mode 100644 index 00000000000000..943a6f4724e883 --- /dev/null +++ b/drivers/power/supply/max77840-charger-detect.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Maxim Integrated Products, Inc. + * Copyright (C) 2024 Analog Devices, Inc. + * + * Author : Analog Devices + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* for Regmap */ +#include + +/* for Device Tree */ +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __TEST_DEVICE_NODE__ +#include +#include +#endif + +#define M2SH __ffs + +#define IRQ_WORK_DELAY 0 + +static char *chgdet_supplied_to[] = { + "max77840-charger-detect", +}; + +#define __lock(_me) mutex_lock(&(_me)->lock) +#define __unlock(_me) mutex_unlock(&(_me)->lock) + +static const struct max77840_chg_type_props { + enum power_supply_type type; +} chg_type_props[] = { + { POWER_SUPPLY_TYPE_UNKNOWN }, + { POWER_SUPPLY_TYPE_USB }, + { POWER_SUPPLY_TYPE_USB_CDP }, + { POWER_SUPPLY_TYPE_USB_DCP }, + { POWER_SUPPLY_TYPE_USB_DCP }, + { POWER_SUPPLY_TYPE_USB_DCP }, + { POWER_SUPPLY_TYPE_USB_DCP }, + { POWER_SUPPLY_TYPE_USB }, +}; + +struct max77840_chgdet_data { + struct device *dev; + struct max77840_dev *max77840; + struct regmap *regmap; + struct power_supply *psy_chgdet; + struct power_supply_desc psy_chgdet_d; + + int chgtyp_irq; + + int status; + + int irq; + int irq_mask; + struct delayed_work irq_work; + + /* mutex */ + struct mutex lock; + + struct max77840_chgdet_platform_data *pdata; +}; + +static enum power_supply_property max77840_chgdet_props[] = { + POWER_SUPPLY_PROP_CURRENT_MAX +}; + +static void max77840_chgdet_initialize(struct max77840_chgdet_data *chgdet); + +static inline int max77840_ffs(unsigned int x) +{ + return x ? __ffs(x) : 0; +} + +/* charger detect API function */ +static int max77840_chgdet_get_input_current(struct max77840_chgdet_data *chgdet) +{ + u8 reg_data = 0; + int steps[3] = { 0, 33, 67 }; /* mA */ + int get_current, quotient, remainder; + + pr_info("max77840 charger detect get charger current"); + + max77840_read(chgdet->regmap, REG_CHGDET_CHGIN_ILIM, ®_data); + + quotient = reg_data / 3; + remainder = reg_data % 3; + + if ((reg_data & BIT_CHGIN_ILIM) < 3) + get_current = 100; /* 100mA */ + else if ((reg_data & BIT_CHGIN_ILIM) > 0x78) + get_current = 4000; /* 4000mA */ + else + get_current = quotient * 100 + steps[remainder]; + + return get_current; +} + +/* interrupt handler and workqueu*/ +static void max77840_do_irq(struct max77840_chgdet_data *chgdet, int irq) +{ + u8 val; + + switch (irq) { + case CHGDET_INT_CHGTYPE_I: + val = (chgdet->status) & BIT_CHGTYPE; + if (val < CHGDET_CHGTYP_CHARGER_LAST) + chgdet->psy_chgdet_d.type = chg_type_props[val].type; + else + chgdet->psy_chgdet_d.type = POWER_SUPPLY_TYPE_UNKNOWN; + pr_debug("CHGDET_INT_CHGTYP: ChgTyp = %02Xh\n", val); + break; + + case CHGDET_INT_CHGDETRUN_I: + val = ((chgdet->status & BIT_CHGDETRUN) >> max77840_ffs(BIT_CHGDETRUN)); + pr_debug("CHGDET_INT_CHGDETRUN: ChgDetRun = %02Xh\n", val); + break; + + case CHGDET_INT_DCDTMR_I: + val = ((chgdet->status & BIT_DCDTMR) >> max77840_ffs(BIT_DCDTMR)); + pr_debug("CHGDET_INT_DCDTMR: DCDTmr = %02Xh\n", val); + break; + + case CHGDET_INT_DXOVP_I: + val = ((chgdet->status & BIT_DXOVP) >> max77840_ffs(BIT_DXOVP)); + pr_debug("CHGDET_INT_DXOVP: DxOVP = %02Xh\n", val); + break; + + case CHGDET_INT_VDNMON_I: + val = ((chgdet->status & BIT_VDNMON) >> max77840_ffs(BIT_VDNMON)); + pr_debug("CHGDET_INT_VDNMON: VDNMon = %02Xh\n", val); + break; + + default: + break; + } + /* notify psy changed */ + power_supply_changed(chgdet->psy_chgdet); +} + +static irqreturn_t max77840_chgdet_chgin_isr(int irq, void *data) +{ + struct max77840_chgdet_data *me = data; + u8 reg_data; + + me->irq = irq; + + max77840_read(me->regmap, REG_CHGDET_STATUS, ®_data); + me->status = reg_data; + + schedule_delayed_work(&me->irq_work, IRQ_WORK_DELAY); + return IRQ_HANDLED; +} + +static void max77840_chgdet_irq_work(struct work_struct *work) +{ + struct max77840_chgdet_data *me = + container_of(work, struct max77840_chgdet_data, irq_work.work); + u8 irq; + + __lock(me); + + irq = me->irq - me->chgtyp_irq; + + max77840_do_irq(me, irq); + + __unlock(me); +} + +static void max77840_chgdet_initialize(struct max77840_chgdet_data *chgdet) +{ + int rc; + u8 val; + + /* interrupt mask - if you want to enable some bits, you should clear them */ + val = 0; + val |= BIT_CHGTYPE_I; + val |= BIT_CHGDETRUN_I; + val |= BIT_DCDTMR_I; + val |= BIT_DXOVP_I; + val |= BIT_VDNMON_I; + + rc = max77840_write(chgdet->regmap, REG_CHGDET_INT_MASK, val); + if (rc < 0) { + pr_err("CHGDET_INT_MASK write error [%d]\n", rc); + goto out; + } + +out: + return; +} + +static int max77840_chgdet_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77840_chgdet_data *chgdet = + power_supply_get_drvdata(psy); + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = max77840_chgdet_get_input_current(chgdet); + break; + + default: + rc = -EINVAL; + goto out; + } + +out: + pr_info("%s: psp %d val %d [%d]\n", + __func__, psp, val->intval, rc); + return rc; +} + +#ifdef CONFIG_OF +static int max77840_chgdet_parse_dt(struct max77840_chgdet_data *chgdet) +{ + struct device *dev = chgdet->dev; + struct device_node *np = of_find_node_by_name(NULL, "charger-detect"); + struct max77840_chgdet_platform_data *pdata; + + int ret = 0; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (unlikely(!pdata)) + return -ENOMEM; + + pdata->input_current_limit = 500; /* 500mA */ + ret |= of_property_read_u32(np, "input_current_limit", &pdata->input_current_limit); + pr_debug("property:INPUT_CURRENT_LIMIT %umA\n", pdata->input_current_limit); + + if (ret < 0) + return ret; + chgdet->pdata = pdata; + return 0; +} +#endif + +#ifdef __TEST_DEVICE_NODE__ +static struct max77840_chgdet_data *test_if; +static ssize_t max77840_test_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret, reg; + unsigned long strval; + u8 rval; + char *args[3]; + char *sep = ",\t"; + char *buff = (char *)buf; + struct regmap *if_regmap = test_if->regmap; + + args[0] = strsep(&buff, sep); + if (!args[0]) + return -2; + + // register read + args[1] = strsep(&buff, sep); + if (strncmp("read", args[0], 4) == 0) { + ret = kstrtoul(args[1], 0, &strval); + reg = (int)strval; + ret = max77840_read(if_regmap, reg, &rval); + if (ret < 0) + dev_err(test_if->dev, "failed to read i2c: %d @ function %s\n", + ret, __func__); + else + dev_err(test_if->dev, "read [0x%x] = 0x%x\n", reg, rval); + return size; + } + // register write + else if (strncmp("write", args[0], 5) == 0) { + ret = kstrtoul(args[1], 0, &strval); + reg = (int)strval; + args[2] = strsep(&buff, sep); + ret = kstrtoul(args[2], 0, &strval); + rval = (int)strval; + ret = max77840_write(if_regmap, reg, rval); + if (ret < 0) + dev_err(test_if->dev, "failed to write i2c: %d @ function %s\n", + ret, __func__); + else + dev_err(test_if->dev, "write [0x%x] = 0x%x\n", reg, rval); + return size; + } + dev_err(test_if->dev, "Command not supported.\n"); + + return 0; +} + +static struct device_attribute max77840_attribute = { + .attr = { + .name = "max77840_test_node", + .mode = 0x0666, + }, + .show = NULL, + .store = max77840_test_store, +}; + +static int max77840_test_node(struct max77840_chgdet_data *chgdet) +{ + int ret; + + ret = sysfs_create_file(&chgdet->psy_chgdet->dev.kobj, &max77840_attribute.attr); + test_if = chgdet; + return ret; +} +#else +static int max77840_test_node(struct max77840_chgdet_data *chgdet) +{ + return 0; +} +#endif + +static int max77840_chgdet_probe(struct platform_device *pdev) +{ + struct max77840_dev *max77840 = dev_get_drvdata(pdev->dev.parent); + struct max77840_chgdet_platform_data *pdata; + struct max77840_chgdet_data *chgdet; + int ret = 0; + u8 val = 0; + struct power_supply_config chgdet_cfg; + + pr_info("%s: Max77840 Charger Detect Driver Loading\n", __func__); + + chgdet = kzalloc(sizeof(*chgdet), GFP_KERNEL); + if (!chgdet) + return -ENOMEM; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (unlikely(!pdata)) { + pr_err("%s: out of memory\n", __func__); + pdata = ERR_PTR(-ENOMEM); + return -ENOMEM; + } + + mutex_init(&chgdet->lock); + + chgdet->dev = &pdev->dev; + chgdet->regmap = max77840->regmap_chg_det; + chgdet->pdata = pdata; + +#if defined(CONFIG_OF) + ret = max77840_chgdet_parse_dt(chgdet); + if (ret < 0) + pr_err("%s not found charger dt! ret[%d]\n", __func__, ret); +#else + pdata = dev_get_platdata(&pdev->dev); +#endif + + platform_set_drvdata(pdev, chgdet); + chgdet->psy_chgdet_d.name = "max77840-charger-detect"; + chgdet->psy_chgdet_d.type = POWER_SUPPLY_TYPE_UNKNOWN; + chgdet->psy_chgdet_d.get_property = max77840_chgdet_get_property; + chgdet->psy_chgdet_d.properties = max77840_chgdet_props; + chgdet->psy_chgdet_d.num_properties = ARRAY_SIZE(max77840_chgdet_props); + chgdet_cfg.drv_data = chgdet; + chgdet_cfg.supplied_to = chgdet_supplied_to; + chgdet_cfg.of_node = max77840->dev->of_node; + chgdet_cfg.num_supplicants = ARRAY_SIZE(chgdet_supplied_to); + + max77840_chgdet_initialize(chgdet); + + chgdet->psy_chgdet = devm_power_supply_register(max77840->dev, + &chgdet->psy_chgdet_d, + &chgdet_cfg); + if (IS_ERR(chgdet->psy_chgdet)) { + pr_err("Couldn't register charger detect rc=%ld\n", PTR_ERR(chgdet->psy_chgdet)); + goto err_power_supply_register; + } + + INIT_DELAYED_WORK(&chgdet->irq_work, max77840_chgdet_irq_work); + + chgdet->chgtyp_irq = regmap_irq_get_virq(max77840->irqc_chgdet, CHGDET_IRQ_CHGTYPE_I); + + ret = request_threaded_irq(chgdet->chgtyp_irq, NULL, + max77840_chgdet_chgin_isr, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, + "charger-detect-chgtyp", + chgdet); + if (ret) { + pr_err("%s: Failed to Request CHGTYP IRQ\n", __func__); + goto err_chgtyp_irq; + } + + max77840_read(chgdet->regmap, REG_CHGDET_INT, &val); + max77840_read(chgdet->regmap, REG_CHGDET_INT_MASK, &val); + + pr_info("%s: Max77840 Charger Detect Driver Loaded\n", __func__); + max77840_test_node(chgdet); + + return 0; + +err_chgtyp_irq: + power_supply_unregister(chgdet->psy_chgdet); +err_power_supply_register: + kfree(chgdet); + + return ret; +} + +static int max77840_chgdet_remove(struct platform_device *pdev) +{ + struct max77840_chgdet_data *chgdet = + platform_get_drvdata(pdev); + + free_irq(chgdet->chgtyp_irq, NULL); + power_supply_unregister(chgdet->psy_chgdet); + + kfree(chgdet); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max77840_chgdet_suspend(struct device *dev) +{ + return 0; +} + +static int max77840_chgdet_resume(struct device *dev) +{ + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(max77840_chgdet_pm_ops, max77840_chgdet_suspend, + max77840_chgdet_resume); + +#ifdef CONFIG_OF +static const struct of_device_id max77840_chgdet_dt_ids[] = { + { .compatible = "maxim,max77840-charger-detect" }, + { } +}; +MODULE_DEVICE_TABLE(of, max77840_chgdet_dt_ids); +#endif + +static struct platform_driver max77840_chgdet_driver = { + .driver = { + .name = MAX77840_CHARGER_DETECT_NAME, + .pm = &max77840_chgdet_pm_ops, +#ifdef CONFIG_OF + .of_match_table = max77840_chgdet_dt_ids, +#endif + }, + .probe = max77840_chgdet_probe, + .remove = max77840_chgdet_remove, +}; + +static int __init max77840_chgdet_init(void) +{ + return platform_driver_register(&max77840_chgdet_driver); +} + +static void __exit max77840_chgdet_exit(void) +{ + platform_driver_unregister(&max77840_chgdet_driver); +} + +module_init(max77840_chgdet_init); +module_exit(max77840_chgdet_exit); + +MODULE_DESCRIPTION("MAX77840 Charger Detect Driver"); +MODULE_AUTHOR("joan.na@analog.com"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); diff --git a/drivers/power/supply/max77840-charger.c b/drivers/power/supply/max77840-charger.c new file mode 100644 index 00000000000000..694d7c22e6113e --- /dev/null +++ b/drivers/power/supply/max77840-charger.c @@ -0,0 +1,1014 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Maxim Integrated Products, Inc. + * Copyright (C) 2024 Analog Devices, Inc. + * + * Author : Analog Devices + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define DEBUG + +#define log_level 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* for Regmap */ +#include + +/* for Device Tree */ +#include +#include +#include + +#include +#include +#include +#ifdef __TEST_DEVICE_NODE__ +#include +#include +#endif + +#define M2SH __ffs + +#define __lock(_me) mutex_lock(&(_me)->lock) +#define __unlock(_me) mutex_unlock(&(_me)->lock) + +/* detail register bit description */ + +#define SIOP_INPUT_LIMIT_CURRENT 1200 +#define SIOP_CHARGING_LIMIT_CURRENT 1000 +#define SLOW_CHARGING_CURRENT_STANDARD 400 + +#define IRQ_WORK_DELAY 0 +static char *chg_supplied_to[] = { + "max77840-charger", +}; + +struct max77840_charger_data { + struct device *dev; + struct max77840_dev *max77840; + struct regmap *regmap; + struct power_supply *psy_chg; + struct power_supply_desc psy_chg_d; + + int byp_irq; + int chgin_irq; + int aicl_irq; + int chg_irq; + + int irq; + int irq_mask; + int details_0; + int details_1; + int details_2; + spinlock_t irq_lock; /* spin lock */ + struct delayed_work irq_work; + + /* mutex */ + struct mutex lock; + + int present; + int health; + int status; + int charge_type; + + struct max77840_charger_platform_data *pdata; +}; + +static inline struct power_supply *get_power_supply_by_name(char *name) +{ + if (!name) + return (struct power_supply *)NULL; + else + return power_supply_get_by_name(name); +} + +static enum power_supply_property max77840_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static inline int GET_TO_ITH(int x) +{ + if (x < 4) + return x * 25 + 100; + else + return x * 50; +} + +static inline int SET_TO_ITH(int x) +{ + if (x < 100) + return 0x00; + else if (x < 200) + return (x - 100) / 25; + else if (x < 350) + return x / 50; + else + return 0x07; +} + +static inline int max77840_ffs(unsigned int x) +{ + return x ? __ffs(x) : 0; +} + +static void max77840_charger_initialize(struct max77840_charger_data *charger); + +/* charger API function */ +static int max77840_charger_unlock(struct max77840_charger_data *charger) +{ + int rc; + + rc = regmap_update_bits(charger->regmap, REG_CHG_CNFG_06, BIT_CHGPROT, BIT_CHGPROT); + if (rc < 0) { + pr_err("%s: failed to unlock [%d]\n", __func__, rc); + goto out; + } + +out: + return rc; +} + +static bool max77840_charger_present_input(struct max77840_charger_data *charger) +{ + u8 chg_int_ok = 0; + int rc; + + rc = max77840_read(charger->regmap, REG_CHG_INT_OK, &chg_int_ok); + if (rc < 0) + return false; + + if ((chg_int_ok & BIT_CHGIN_OK) == BIT_CHGIN_OK) + return true; + /* check whether charging or not in the UVLO condition */ + if (((charger->details_0 & BIT_CHGIN_DTLS) == 0) && + (((charger->details_1 & BIT_CHG_DTLS) == CHG_DTLS_FASTCHARGE_CC) || + ((charger->details_1 & BIT_CHG_DTLS) == CHG_DTLS_FASTCHARGE_CV))) { + return true; + } else { + return false; + } +} + +static int max77840_charger_get_input_current(struct max77840_charger_data *charger) +{ + u8 reg_data = 0; + int steps[3] = { 0, 33, 67 }; /* mA */ + int get_current, quotient, remainder; + + pr_info("max77840 charger get input current"); + + max77840_read(charger->regmap, REG_CHG_CNFG_09, ®_data); + + quotient = reg_data / 3; + remainder = reg_data % 3; + + if ((reg_data & BIT_CHGIN_ILIM) < 3) + get_current = 100; /* 100mA */ + else if ((reg_data & BIT_CHGIN_ILIM) > 0x78) + get_current = 4000; /* 4000mA */ + else + get_current = quotient * 100 + steps[remainder]; + + return get_current; +} + +static int max77840_charger_set_input_current(struct max77840_charger_data + *charger, + int input_current) +{ + int quotient, remainder; + u8 reg_data = 0; + + /* unit mA */ + if (!input_current) { + reg_data = 0; + } else { + quotient = input_current / 100; + remainder = input_current % 100; + + if (remainder >= 67) + reg_data |= (quotient * 3) + 2; + else if (remainder >= 33) + reg_data |= (quotient * 3) + 1; + else if (remainder < 33) + reg_data |= quotient * 3; + } + pr_info("%s: reg_data(0x%02x), charging current(%d)\n", + __func__, reg_data, input_current); + + return regmap_update_bits(charger->regmap, REG_CHG_CNFG_09, + BIT_CHGIN_ILIM, reg_data); +} + +static int max77840_charger_get_charge_current(struct max77840_charger_data *charger) +{ + struct regmap *regmap = charger->regmap; + + u8 reg_data = 0; + int get_current; + + max77840_read(regmap, REG_CHG_CNFG_02, ®_data); + + if ((reg_data & BIT_CHG_CC) < 2) + get_current = 100; /* 100mA */ + else + get_current = (reg_data & BIT_CHG_CC) * 50; + + pr_info("%s: reg_data(0x%02x), get_current(%d)\n", + __func__, reg_data, get_current); + + return get_current; +} + +static int max77840_charger_set_charge_current(struct max77840_charger_data *charger, + int fast_charging_current) +{ + int curr_step = 50; + u8 reg_data = 0; + int rc; + + /* unit mA */ + if (!fast_charging_current) { + rc = regmap_update_bits(charger->regmap, REG_CHG_CNFG_02, BIT_CHG_CC, 0); + + } else { + reg_data = (fast_charging_current / curr_step); + rc = regmap_update_bits(charger->regmap, REG_CHG_CNFG_02, BIT_CHG_CC, reg_data); + } + pr_info("%s: reg_data(0x%02x), charging current(%d)\n", + __func__, reg_data, fast_charging_current); + + return rc; +} + +static int max77840_charger_set_topoff_current(struct max77840_charger_data *charger, + int termination_current, + int termination_time) +{ + u8 reg_data; + + /* termination_current (mA) */ + reg_data = SET_TO_ITH(termination_current); + + /* termination_time (min) */ + reg_data |= ((termination_time / 10) << M2SH(BIT_TO_TIME)); + pr_info("%s: reg_data(0x%02x), topoff(%d), time(%d)\n", + __func__, reg_data, termination_current, termination_time); + return regmap_update_bits(charger->regmap, REG_CHG_CNFG_03, + BIT_TO_ITH | BIT_TO_TIME, reg_data); +} + +static int max77840_charger_set_enable(struct max77840_charger_data *charger, int en) +{ + return regmap_update_bits(charger->regmap, + REG_CHG_CNFG_00, BIT_MODE_CHARGER, !!en); +} + +static int max77840_charger_exit_dev(struct max77840_charger_data *charger) +{ + struct max77840_charger_platform_data *pdata = charger->pdata; + int rc; + + rc = max77840_charger_set_enable(charger, false); + if (rc < 0) { + pr_err("CHG_CNFG_00 write error [%d]\n", rc); + return rc; + } + + rc = max77840_charger_set_charge_current(charger, pdata->fast_charge_current); + + return rc; +} + +static int max77840_charger_init_dev(struct max77840_charger_data *charger) +{ + int rc; + + /* charger enable */ + rc = max77840_charger_set_enable(charger, true); + + return rc; +} + +static void max77840_charger_initialize(struct max77840_charger_data *charger) +{ + struct max77840_charger_platform_data *pdata = charger->pdata; + int rc; + u8 val, temp_val; + + /* interrupt mask - if you want to enable some bits, you should clear them */ + val = 0; + val |= BIT_AICL; + val |= BIT_CHGIN; + //val |= BIT_TOPOFF; + //val |= BIT_CHG; + val |= BIT_BAT; + val |= BIT_BATP; + val |= BIT_BAT2SOC; + val |= BIT_BYP; + + rc = max77840_write(charger->regmap, REG_CHG_INT_MASK, val); + if (rc < 0) { + pr_err("CHG_INT_MASK write error [%d]\n", rc); + goto out; + } + + pr_info("%s: CHG_INT_MASK is set to 0x%x\n", __func__, val); + + /* unlock charger register */ + rc = max77840_charger_unlock(charger); + if (rc < 0) + goto out; + + /* charge current (mA) */ + rc = max77840_charger_set_charge_current(charger, pdata->fast_charge_current); + if (rc < 0) + goto out; + + /* input current limit (mA) */ + rc = max77840_charger_set_input_current(charger, pdata->input_current_limit); + if (rc < 0) + goto out; + + /* topoff current(mA) and topoff timer(min) */ + rc = max77840_charger_set_topoff_current(charger, + pdata->topoff_current, + pdata->topoff_timer); + if (rc < 0) + goto out; + + /* charge restart threshold(mV) and fast-charge timer(hr) */ + val = pdata->restart_threshold < 200 ? + (int)(pdata->restart_threshold - 100) / 50 : 0x03; + + temp_val = pdata->fast_charge_timer == 0 ? 0x00 : + pdata->fast_charge_timer < 4 ? 0x01 : + pdata->fast_charge_timer < 16 ? + (int)DIV_ROUND_UP(pdata->fast_charge_timer - 4, 2) + 1 : 0x00; + + val = val << M2SH(BIT_CHG_RSTRT) | temp_val << M2SH(BIT_FCHGTIME); + + rc = regmap_update_bits(charger->regmap, REG_CHG_CNFG_01, + (BIT_CHG_RSTRT | BIT_FCHGTIME), val); + if (rc < 0) + goto out; + + /* charge termination voltage (mV) */ + val = pdata->termination_voltage < 3650 ? 0x00 : + pdata->termination_voltage <= 4325 ? + (int)DIV_ROUND_UP(pdata->termination_voltage - 3650, 25) : + pdata->termination_voltage <= 4340 ? 0x1C : + pdata->termination_voltage <= 4700 ? + (int)DIV_ROUND_UP(pdata->termination_voltage - 3650, 25) + 1 : 0x2B; + rc = regmap_update_bits(charger->regmap, + REG_CHG_CNFG_04, + BIT_CHG_CV_PRM, + val << M2SH(BIT_CHG_CV_PRM)); + if (rc < 0) + goto out; + +out: + return; +} + +struct max77840_charger_status_map { + int health, status, charge_type; +}; + +static struct max77840_charger_status_map max77840_charger_status_map[] = { +#define STATUS_MAP(_chg_dtls, _health, _status, _charge_type) \ + [CHG_DTLS_##_chg_dtls] = {\ + .health = POWER_SUPPLY_HEALTH_##_health,\ + .status = POWER_SUPPLY_STATUS_##_status,\ + .charge_type = POWER_SUPPLY_CHARGE_TYPE_##_charge_type,\ + } + /* chg_details_xx, health, status, charge_type */ + STATUS_MAP(PREQUAL, GOOD, CHARGING, TRICKLE), + STATUS_MAP(FASTCHARGE_CC, GOOD, CHARGING, FAST), + STATUS_MAP(FASTCHARGE_CV, GOOD, CHARGING, FAST), + STATUS_MAP(TOPOFF, GOOD, CHARGING, FAST), + STATUS_MAP(DONE, GOOD, FULL, NONE), + STATUS_MAP(OFF_TIMER_FAULT, SAFETY_TIMER_EXPIRE, NOT_CHARGING, NONE), + STATUS_MAP(OFF_SUSPEND, UNKNOWN, NOT_CHARGING, NONE), + STATUS_MAP(OFF_INPUT_INVALID, UNKNOWN, NOT_CHARGING, NONE), + STATUS_MAP(OFF_JUCTION_TEMP, UNKNOWN, NOT_CHARGING, UNKNOWN), + STATUS_MAP(OFF_WDT_EXPIRED, WATCHDOG_TIMER_EXPIRE, NOT_CHARGING, UNKNOWN), +}; + +static int max77840_charger_update(struct max77840_charger_data *charger) +{ + int rc; + u8 chg_details[3]; + u8 chg_dtls; + + charger->health = POWER_SUPPLY_HEALTH_UNKNOWN; + charger->status = POWER_SUPPLY_STATUS_UNKNOWN; + charger->charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + + rc = max77840_bulk_read(charger->regmap, REG_CHG_DTLS_00, chg_details, 3); + if (rc < 0) { + pr_err("CHG_DETAILS read error [%d]\n", rc); + goto out; + } + + pr_info("%s: chg_details 00=0x%x, 01=0x%x, 02=0x%x\n", + __func__, chg_details[0], chg_details[1], chg_details[2]); + + charger->present = max77840_charger_present_input(charger); + if (unlikely(!charger->present)) { + /* no charger present */ + charger->health = POWER_SUPPLY_HEALTH_UNKNOWN; + charger->status = POWER_SUPPLY_STATUS_DISCHARGING; + charger->charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + goto out; + } + + chg_dtls = chg_details[1] & BIT_CHG_DTLS; + + charger->health = max77840_charger_status_map[chg_dtls].health; + charger->status = max77840_charger_status_map[chg_dtls].status; + charger->charge_type = max77840_charger_status_map[chg_dtls].charge_type; + + if (likely(charger->health != POWER_SUPPLY_HEALTH_UNKNOWN)) + goto out; + + /* override health by TREG */ + if ((chg_details[1] & BIT_TREG) != 0) + charger->health = POWER_SUPPLY_HEALTH_OVERHEAT; + +out: + pr_info("%s: PRESENT %d HEALTH %d STATUS %d CHARGE_TYPE %d\n", + __func__, + charger->present, charger->health, + charger->status, charger->charge_type); + return rc; +} + +static int max77840_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77840_charger_data *charger = power_supply_get_drvdata(psy); + int rc = 0; + + rc = max77840_charger_update(charger); + if (rc < 0) + 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: + val->intval = max77840_charger_get_charge_current(charger); + break; + + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = max77840_charger_get_input_current(charger); + break; + + default: + rc = -EINVAL; + goto out; + } + +out: + pr_info("%s: psp %d val %d [%d]\n", + __func__, psp, val->intval, rc); + return rc; +} + +static int max77840_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77840_charger_data *charger = power_supply_get_drvdata(psy); + int rc = 0; + + __lock(charger); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + rc = max77840_charger_set_enable(charger, val->intval); + if (rc < 0) + goto out; + /* apply charge current */ + rc = max77840_charger_set_charge_current(charger, + charger->pdata->fast_charge_current); + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + /* val->intval - uA */ + rc = max77840_charger_set_charge_current(charger, val->intval / 1000); /* mA */ + if (rc < 0) + goto out; + charger->pdata->fast_charge_current = val->intval / 1000; /* mA */ + break; + + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = max77840_charger_set_input_current(charger, val->intval / 1000); /* mA */ + if (rc < 0) + goto out; + charger->pdata->input_current_limit = val->intval / 1000; /* mA */ + break; + + default: + rc = -EINVAL; + goto out; + } + +out: + pr_info("%s: psp %d val %d [%d]\n", + __func__, psp, val->intval, rc); + __unlock(charger); + return rc; +} + +/* interrupt handler and workqueue */ +static void max77840_do_irq(struct max77840_charger_data *charger, int irq) +{ + u8 val, chg_details[3]; + bool chg_input, bat2soc_state; + bool chgini_mode, aicl_mode, topoff_state; + + chg_details[0] = charger->details_0; + chg_details[1] = charger->details_1; + chg_details[2] = charger->details_2; + + switch (irq) { + case CHG_INT_AICL_CHGINI_I: + aicl_mode = (chg_details[2] & BIT_AICL_DTLS) ? false : true; + chgini_mode = (chg_details[2] & BIT_CHGINI_DTLS) ? false : true; + pr_debug("CHG_INT_AICL_CHGINI: AICL_DTLS %s\n", + aicl_mode ? "In AICL mode" : "Not in AICL mode"); + pr_debug("CHG_INT_AICL_CHGINI: CHGINI_DTLS %s\n", + chgini_mode ? "In CHGINI mode" : "Not in CHGINI mode"); + break; + + case CHG_INT_CHGIN_I: + chg_input = max77840_charger_present_input(charger); + + if (chg_input) { + /* charger insert */ + max77840_charger_init_dev(charger); + } else { + /* charger remove */ + max77840_charger_exit_dev(charger); + } + pr_debug("CHG_INT_CHGIN: Charger input %s\n", + chg_input ? "inserted" : "removed"); + break; + + case CHG_INT_TOPOFF_I: + max77840_read(charger->regmap, REG_CHG_INT_OK, &val); + topoff_state = (val & BIT_TOPOFF_OK) ? false : true; + pr_debug("CHG_INT_TOPOFF: TOPOFF %s\n", + topoff_state ? "Not in TOPOFF state" : "in TOPOFF state"); + break; + + case CHG_INT_CHG_I: + /* do insert code here */ + val = (chg_details[1] & BIT_CHG_DTLS) >> max77840_ffs(BIT_CHG_DTLS); + pr_debug("CHG_INT_CHG: chg_dtls = %02Xh\n", val); + break; + + case CHG_INT_BAT_I: + /* do insert code here */ + val = (chg_details[1] & BIT_BAT_DTLS) >> max77840_ffs(BIT_BAT_DTLS); + pr_debug("CHG_INT_BAT: bat_dtls = %02Xh\n", val); + break; + + case CHG_INT_BATP_I: + /* do insert code here */ + val = (chg_details[0] & BIT_BATP_DTLS) >> max77840_ffs(BIT_BATP_DTLS); + pr_debug("CHG_INT_BATP: battery %s\n", + val ? "no presence" : "presence"); + break; + + case CHG_INT_BAT2SOC_I: + /* do insert code here */ + max77840_read(charger->regmap, REG_CHG_INT_OK, &val); + bat2soc_state = (val & BIT_BAT2SOC_OK) ? false : true; + pr_debug("CHG_INT_BAT2SOC: battery to SYS %s\n", + val ? "has hit overcurrent limit" : "is okay"); + break; + + case CHG_INT_BYP_I: + /* do insert code here */ + val = (chg_details[2] & BIT_BYP_DTLS) >> max77840_ffs(BIT_BYP_DTLS); + pr_debug("CHG_INT_BYP: byp_dtls = %02Xh\n", val); + break; + + default: + break; + } + /* notify psy changed */ + power_supply_changed(charger->psy_chg); +} + +static void max77840_charger_irq_work(struct work_struct *work) +{ + struct max77840_charger_data *me = + container_of(work, struct max77840_charger_data, irq_work.work); + u8 irq; + + __lock(me); + + irq = me->irq - me->byp_irq; + + max77840_do_irq(me, irq); + + __unlock(me); +} + +static irqreturn_t max77840_charger_chgin_isr(int irq, void *data) +{ + struct max77840_charger_data *me = data; + u8 reg_details[3]; + + me->irq = irq; + + max77840_bulk_read(me->regmap, REG_CHG_DTLS_00, reg_details, 3); + pr_info("%s: chg_dtls[0]=0x%x, [1]=0x%x, [2]=0x%x\n", + __func__, reg_details[0], reg_details[1], reg_details[2]); + + me->details_0 = reg_details[0]; + me->details_1 = reg_details[1]; + me->details_2 = reg_details[2]; + + schedule_delayed_work(&me->irq_work, IRQ_WORK_DELAY); + return IRQ_HANDLED; +} + +static irqreturn_t max77840_charger_aicl_isr(int irq, void *data) +{ + struct max77840_charger_data *me = data; + u8 reg_details[3]; + + me->irq = irq; + + max77840_bulk_read(me->regmap, REG_CHG_DTLS_00, reg_details, 3); + pr_info("%s: chg_dtls[0]=0x%x, [1]=0x%x, [2]=0x%x\n", + __func__, reg_details[0], reg_details[1], reg_details[2]); + + me->details_0 = reg_details[0]; + me->details_1 = reg_details[1]; + me->details_2 = reg_details[2]; + + schedule_delayed_work(&me->irq_work, IRQ_WORK_DELAY); + return IRQ_HANDLED; +} + +static irqreturn_t max77840_charger_chg_isr(int irq, void *data) +{ + struct max77840_charger_data *me = data; + u8 reg_details[3]; + + me->irq = irq; + + max77840_bulk_read(me->regmap, REG_CHG_DTLS_00, reg_details, 3); + pr_info("%s: chg_dtls[0]=0x%x, [1]=0x%x, [2]=0x%x\n", + __func__, reg_details[0], reg_details[1], reg_details[2]); + + me->details_0 = reg_details[0]; + me->details_1 = reg_details[1]; + me->details_2 = reg_details[2]; + + schedule_delayed_work(&me->irq_work, IRQ_WORK_DELAY); + return IRQ_HANDLED; +} + +#ifdef __TEST_DEVICE_NODE__ +static struct max77840_charger_data *test_if; +static ssize_t max77840_test_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret, reg; + unsigned long val; + u8 rval; + char *args[3]; + char *sep = ",\t"; + char *buff = (char *)buf; + struct regmap *if_regmap = test_if->regmap; + + args[0] = strsep(&buff, sep); + if (!args[0]) { + dev_err(test_if->dev, "Command has wrong format.\n"); + return -2; + } + + // register read + args[1] = strsep(&buff, sep); + if (strncmp("read", args[0], 4) == 0) { + ret = kstrtoul(args[1], 0, &val); + reg = (int)val; + ret = max77840_read(if_regmap, reg, &rval); + if (ret < 0) + dev_err(test_if->dev, "failed to read i2c: %d @ function %s\n", + ret, __func__); + else + dev_err(test_if->dev, "read [0x%x] = 0x%x\n", reg, rval); + return size; + } + // register write + else if (strncmp("write", args[0], 5) == 0) { + ret = kstrtoul(args[1], 0, &val); + reg = (int)val; + args[2] = strsep(&buff, sep); + ret = kstrtoul(args[2], 0, &val); + rval = (int)val; + ret = max77840_write(if_regmap, reg, rval); + if (ret < 0) + dev_err(test_if->dev, "failed to write i2c: %d @ function %s\n", + ret, __func__); + else + dev_err(test_if->dev, "write [0x%x] = 0x%x\n", reg, rval); + return size; + } + dev_err(test_if->dev, "Command not supported.\n"); + + return 0; +} + +static struct device_attribute max77840_attribute = { + .attr = { + .name = "max77840_test_node", + .mode = 0x0666, + }, + .show = NULL, + .store = max77840_test_store, +}; + +static int max77840_test_node(struct max77840_charger_data *charger) +{ + int ret; + + ret = sysfs_create_file(&charger->psy_chg->dev.kobj, &max77840_attribute.attr); + test_if = charger; + return ret; +} +#else +static int max77840_test_node(struct max77840_charger_data *charger) +{ + return 0; +} +#endif + +#ifdef CONFIG_OF +static int max77840_charger_parse_dt(struct max77840_charger_data *charger) +{ + struct device *dev = charger->dev; + struct device_node *np = of_find_node_by_name(NULL, "charger"); + struct max77840_charger_platform_data *pdata; + + int ret = 0; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (unlikely(!pdata)) + return -ENOMEM; + + pdata->fast_charge_timer = 4; /* disable */ + ret |= of_property_read_u32(np, "fast_charge_timer", &pdata->fast_charge_timer); + pr_debug("property:FCHGTIME %uhour\n", pdata->fast_charge_timer); + + pdata->fast_charge_current = 500; /* 500mA */ + ret |= of_property_read_u32(np, "fast_charge_current", &pdata->fast_charge_current); + pr_debug("property:CHG_CC %umA\n", pdata->fast_charge_current); + + pdata->termination_voltage = 4350; /* 4350mV */ + ret |= of_property_read_u32(np, "charge_termination_voltage", &pdata->termination_voltage); + pr_debug("property:CHG_CV_PRM %umV\n", pdata->termination_voltage); + + pdata->topoff_timer = 30; /* 0 min */ + ret |= of_property_read_u32(np, "topoff_timer", &pdata->topoff_timer); + pr_debug("property:TOPOFF_TIME %umin\n", pdata->topoff_timer); + + pdata->topoff_current = 150; /* 200mA */ + ret |= of_property_read_u32(np, "topoff_current", &pdata->topoff_current); + pr_debug("property:TOPOFF_ITH %umA\n", pdata->topoff_current); + + pdata->restart_threshold = 150; /* 150mV */ + ret |= of_property_read_u32(np, "restart_threshold", &pdata->restart_threshold); + pr_debug("property:CHG_RSTRT %umV\n", pdata->restart_threshold); + + pdata->input_current_limit = 500; /* 500mA */ + ret |= of_property_read_u32(np, "input_current_limit", &pdata->input_current_limit); + pr_debug("property:INPUT_CURRENT_LIMIT %umA\n", pdata->input_current_limit); + + if (ret < 0) + return ret; + charger->pdata = pdata; + return 0; +} +#endif + +static int max77840_charger_probe(struct platform_device *pdev) +{ + struct max77840_dev *max77840 = dev_get_drvdata(pdev->dev.parent); + struct max77840_charger_platform_data *pdata; + struct max77840_charger_data *charger; + int ret = 0; + u8 val = 0; + struct power_supply_config charger_cfg; + + pr_err("%s: Max77840 Charger Driver Loading\n", __func__); + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (unlikely(!pdata)) { + pr_err("%s: out of memory\n", __func__); + pdata = ERR_PTR(-ENOMEM); + return -ENOMEM; + } + + mutex_init(&charger->lock); + + charger->dev = &pdev->dev; + charger->regmap = max77840->regmap_chg; + charger->pdata = pdata; + +#if defined(CONFIG_OF) + ret = max77840_charger_parse_dt(charger); + if (ret < 0) + pr_err("%s not found charger dt! ret[%d]\n", __func__, ret); +#else + pdata = dev_get_platdata(&pdev->dev); +#endif + + platform_set_drvdata(pdev, charger); + charger->psy_chg_d.name = "max77840-charger"; + charger->psy_chg_d.type = POWER_SUPPLY_TYPE_UNKNOWN; + charger->psy_chg_d.get_property = max77840_charger_get_property; + charger->psy_chg_d.set_property = max77840_charger_set_property; + charger->psy_chg_d.properties = max77840_charger_props; + charger->psy_chg_d.num_properties = ARRAY_SIZE(max77840_charger_props); + charger_cfg.drv_data = charger; + charger_cfg.supplied_to = chg_supplied_to; + charger_cfg.of_node = max77840->dev->of_node; + charger_cfg.num_supplicants = ARRAY_SIZE(chg_supplied_to); + + max77840_charger_initialize(charger); + + charger->psy_chg = devm_power_supply_register(max77840->dev, + &charger->psy_chg_d, + &charger_cfg); + if (IS_ERR(charger->psy_chg)) { + pr_err("Couldn't register psy_chg rc=%ld\n", PTR_ERR(charger->psy_chg)); + goto err_power_supply_register; + } + + INIT_DELAYED_WORK(&charger->irq_work, max77840_charger_irq_work); + + charger->byp_irq = regmap_irq_get_virq(max77840->irqc_chg, CHG_IRQ_BYP_I); + charger->chgin_irq = regmap_irq_get_virq(max77840->irqc_chg, CHG_IRQ_CHGIN_I); + charger->aicl_irq = regmap_irq_get_virq(max77840->irqc_chg, CHG_IRQ_AICL_I); + charger->chg_irq = regmap_irq_get_virq(max77840->irqc_chg, CHG_IRQ_CHG_I); + + ret = request_threaded_irq(charger->chgin_irq, NULL, + max77840_charger_chgin_isr, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, "charger-chgin", charger); + if (ret) { + pr_err("%s: Failed to Request CHGIN IRQ\n", __func__); + goto err_chgin_irq; + } + + ret = request_threaded_irq(charger->aicl_irq, NULL, + max77840_charger_aicl_isr, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, "charger-aicl", charger); + if (ret) { + pr_err("%s: Failed to Request AICL IRQ\n", __func__); + goto err_aicl_irq; + } + + ret = request_threaded_irq(charger->chg_irq, NULL, + max77840_charger_chg_isr, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, "charger-chg", charger); + if (ret) { + pr_err("%s: Failed to Request CHG IRQ\n", __func__); + goto err_chg_irq; + } + + /* clear IRQ */ + max77840_read(charger->regmap, REG_CHG_INT, &val); + pr_err("%s: REG_CHG_INT %Xh\n", __func__, val); + + max77840_read(charger->regmap, REG_CHG_INT_MASK, &val); + + pr_info("%s: Max77840 Charger Driver Loaded\n", __func__); + max77840_test_node(charger); + return 0; + +err_chg_irq: + free_irq(charger->aicl_irq, NULL); +err_aicl_irq: + free_irq(charger->chgin_irq, NULL); +err_chgin_irq: + power_supply_unregister(charger->psy_chg); +err_power_supply_register: + kfree(charger); + + return ret; +} + +static int max77840_charger_remove(struct platform_device *pdev) +{ + struct max77840_charger_data *charger = + platform_get_drvdata(pdev); + + free_irq(charger->chgin_irq, NULL); + free_irq(charger->aicl_irq, NULL); + power_supply_unregister(charger->psy_chg); + + kfree(charger); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max77840_charger_suspend(struct device *dev) +{ + return 0; +} + +static int max77840_charger_resume(struct device *dev) +{ + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(max77840_charger_pm_ops, max77840_charger_suspend, + max77840_charger_resume); + +#ifdef CONFIG_OF +static const struct of_device_id max77840_charger_dt_ids[] = { + { .compatible = "maxim,max77840-charger" }, + { } +}; +MODULE_DEVICE_TABLE(of, max77840_charger_dt_ids); +#endif + +static struct platform_driver max77840_charger_driver = { + .driver = { + .name = MAX77840_CHARGER_NAME, + .pm = &max77840_charger_pm_ops, +#ifdef CONFIG_OF + .of_match_table = max77840_charger_dt_ids, +#endif + }, + .probe = max77840_charger_probe, + .remove = max77840_charger_remove, +}; + +static int __init max77840_charger_init(void) +{ + return platform_driver_register(&max77840_charger_driver); +} + +static void __exit max77840_charger_exit(void) +{ + platform_driver_unregister(&max77840_charger_driver); +} + +module_init(max77840_charger_init); +module_exit(max77840_charger_exit); + +MODULE_DESCRIPTION("MAX77840 Charger Driver"); +MODULE_AUTHOR("joan.na@analog.com"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); diff --git a/include/linux/power/max77840-battery.h b/include/linux/power/max77840-battery.h new file mode 100644 index 00000000000000..3a6a15f717d4e6 --- /dev/null +++ b/include/linux/power/max77840-battery.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Maxim Integrated + * Copyright (C) 2024 Analog Devices, Inc. + * + * Author : Analog Devices + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __MAX77840_BATTERY_H_ +#define __MAX77840_BATTERY_H_ + +#define __TEST_DEVICE_NODE__ + +/* Register map */ +#define REG_STATUS 0x00 +#define BIT_SMX BIT(14) +#define BIT_TMX BIT(13) +#define BIT_VMX BIT(12) +#define BIT_SMN BIT(10) +#define BIT_TMN BIT(9) +#define BIT_VMN BIT(8) +#define BIT_DSOCI BIT(7) + +#define REG_VALRT_TH 0x01 +#define REG_TALRT_TH 0x02 +#define REG_SALRT_TH 0x03 +#define REG_TEMP 0x08 +#define REG_VCELL 0x09 +#define REG_AVGVCELL 0x19 +#define REG_CONFIG 0x1D +#define BIT_Aen BIT(2) + +#define REG_VERSION 0x21 +#define REG_LEARNCFG 0x28 +#define REG_FILTERCFG 0x29 +#define REG_MISCCFG 0x2B +#define REG_CGAIN 0x2E +#define REG_RCOMP0 0x38 +#define REG_CONFIG2 0xBB +#define BIT_DSOCEN BIT(7) + +#define REG_VFOCV 0xFB +#define REG_VFSOC 0xFF + +struct max77840_fg_platform_data { + int soc_alert_threshold; +}; +#endif diff --git a/include/linux/power/max77840-charger-detect.h b/include/linux/power/max77840-charger-detect.h new file mode 100644 index 00000000000000..4f8a75c5f568f2 --- /dev/null +++ b/include/linux/power/max77840-charger-detect.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020 Maxim Integrated Products, Inc. + * Copyright (C) 2024 Analog Devices, Inc. + * + * Author : Analog Devices + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __MAX77840_CHARGER_DETECT_H__ +#define __MAX77840_CHARGER_DETECT_H__ + +#define __TEST_DEVICE_NODE__ + +/* Register map */ +#define REG_CHGDET_INT 0x01 +#define REG_CHGDET_INT_MASK 0x03 +#define BIT_CHGTYPE_I BIT(0) +#define BIT_CHGDETRUN_I BIT(1) +#define BIT_DCDTMR_I BIT(2) +#define BIT_DXOVP_I BIT(3) +#define BIT_VDNMON_I BIT(4) + +#define REG_CHGDET_STATUS 0x02 +#define BIT_CHGTYPE BITS(2, 0) +#define BIT_CHGDETRUN BIT(3) +#define BIT_DCDTMR BIT(4) +#define BIT_DXOVP BIT(5) +#define BIT_VDNMON BIT(6) + +#define REG_CHGDET_CHGIN_ILIM 0x04 +#define BIT_CHGIN_ILIM BITS(6, 0) + +#define REG_CHGDET_CTRL1 0x05 +#define BIT_CHGTYPMAN BIT(1) +#define BIT_RFU BIT(2) +#define BIT_DCD2SCT BIT(3) +#define BIT_CDDELAY BIT(4) +#define BIT_DCDCPL BIT(5) +#define BIT_CDPDET BIT(7) + +#define REG_CHGDET_CTRL2 0x06 +#define BIT_SFOUTASRT BIT(0) +#define BIT_SFOUTORD BIT(1) +#define BIT_DPDNVDEN BIT(2) +#define BIT_DXOVPEN BIT(3) +#define BIT_NOBCCOMP BIT(4) +#define BIT_NOAUTOIBUS BIT(5) +#define BIT_DISENU BIT(6) + +#define REG_CHGDET_CTRL3 0x07 +#define BIT_USBLOWSP BIT(0) +#define BIT_CDP_500MA BIT(1) +#define BIT_DCP_IN_MAX BIT(2) +#define BIT_APPLE_1A BIT(3) + +#define REG_CHGDET_QCNTRL 0x08 +#define BIT_DNVD BITS(1, 0) +#define BIT_DPVD BITS(3, 2) +#define BIT_DPDVDEN BIT(4) +#define BIT_ENU_EN BIT(6) +#define BIT_ENUCTRLEN BIT(7) + +/* detail register bit description */ +enum { + CHGDET_DNVD_HI_Z, + CHGDET_DNVD_GND, + CHGDET_DNVD_VDP_SRC, + CHGDET_DNVD_VD33, +}; + +enum { + CHGDET_CHGTYP_NONE, + CHGDET_CHGTYP_USB, + CHGDET_CHGTYP_DP, + CHGDET_CHGTYP_DEDICATED, + CHGDET_CHGTYP_APPLE_500MA, + CHGDET_CHGTYP_APPLE_1A, + CHGDET_CHGTYP_SPECIAL, + CHGDET_CHGTYP_CHARGER_LAST +}; + +enum { + CHGDET_INT_CHGTYPE_I, + CHGDET_INT_CHGDETRUN_I, + CHGDET_INT_DCDTMR_I, + CHGDET_INT_DXOVP_I, + CHGDET_INT_VDNMON_I, +}; + +struct max77840_chgdet_platform_data { + int input_current_limit; +}; + +#endif /* !__MAX77840_CHARGER_DETECT_H__ */ diff --git a/include/linux/power/max77840-charger.h b/include/linux/power/max77840-charger.h new file mode 100644 index 00000000000000..c3d91de1fe3277 --- /dev/null +++ b/include/linux/power/max77840-charger.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020 Maxim Integrated Products, Inc. + * Copyright (C) 2024 Analog Devices, Inc. + * + * Author : Analog Devices + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __MAX77840_CHARGER_H__ +#define __MAX77840_CHARGER_H__ + +#define __TEST_DEVICE_NODE__ + +/* Register map */ +#define REG_CHG_INT 0xB0 +#define REG_CHG_INT_MASK 0xB1 +#define BIT_BYP BIT(0) +#define BIT_BAT2SOC BIT(1) +#define BIT_BATP BIT(2) +#define BIT_BAT BIT(3) +#define BIT_CHG BIT(4) +#define BIT_TOPOFF BIT(5) +#define BIT_CHGIN BIT(6) +#define BIT_AICL BIT(7) + +#define REG_CHG_INT_OK 0xB2 +#define BIT_BYP_OK BIT(0) +#define BIT_BAT2SOC_OK BIT(1) +#define BIT_BATP_OK BIT(2) +#define BIT_BAT_OK BIT(3) +#define BIT_CHG_OK BIT(4) +#define BIT_TOPOFF_OK BIT(5) +#define BIT_CHGIN_OK BIT(6) +#define BIT_AICL_CHGINI_OK BIT(7) + +#define REG_CHG_DTLS_00 0xB3 +#define BIT_BATP_DTLS BIT(0) +#define BIT_OVPDRV_DTLS BIT(1) +#define BIT_VBUSDET_DTLS BIT(2) +#define BIT_CHGIN_DTLS BITS(6, 5) + +#define REG_CHG_DTLS_01 0xB4 +#define BIT_CHG_DTLS BITS(3, 0) +#define BIT_BAT_DTLS BITS(6, 4) +#define BIT_TREG BIT(7) + +#define REG_CHG_DTLS_02 0xB5 +#define BIT_BYP_DTLS BITS(2, 0) +#define BIT_OTGILIM BIT(0) +#define BIT_BSTILIM BIT(1) +#define BIT_BCKNEGILIM BIT(2) +#define BIT_AICL_DTLS BIT(3) +#define BIT_CHGINI_DTLS BIT(4) + +#define REG_CHG_CNFG_00 0xB7 +#define BIT_MODE BITS(3, 0) +#define BIT_MODE_CHARGER BIT(0) +#define BIT_MODE_OTG BIT(1) +#define BIT_MODE_BUCK BIT(2) +#define BIT_MODE_BOOST BIT(3) +#define BIT_WDTEN BIT(4) +#define BIT_SPREAD BIT(5) +#define BIT_DISIBS BIT(6) +#define BIT_DIS_CD_CTRL BIT(7) + +#define REG_CHG_CNFG_01 0xB8 +#define BIT_FCHGTIME BITS(2, 0) +#define BIT_FSW BIT(3) +#define BIT_CHG_RSTRT BITS(5, 4) +#define BIT_LSEL BIT(6) +#define BIT_PQEN BIT(7) + +#define REG_CHG_CNFG_02 0xB9 +#define BIT_CHG_CC BITS(5, 0) +#define BIT_OTG_ILIM BITS(7, 6) + +#define REG_CHG_CNFG_03 0xBA +#define BIT_TO_ITH BITS(2, 0) +#define BIT_TO_TIME BITS(5, 3) +#define BIT_ILIM BITS(7, 6) + +#define REG_CHG_CNFG_04 0xBB +#define BIT_CHG_CV_PRM BITS(5, 0) +#define BIT_MINVSYS BITS(7, 6) + +#define REG_CHG_CNFG_06 0xBD +#define BIT_WDTCLR BITS(1, 0) +#define BIT_CHGPROT BITS(3, 2) +#define BIT_MAXOTG_EN BIT(4) +#define BIT_OTG_DC BIT(5) +#define BIT_LEDEN BIT(7) + +#define REG_CHG_CNFG_07 0xBE +#define BIT_DIS_QBATOFF BIT(2) +#define BIT_REGTEMP BITS(6, 3) +#define BIT_WD_QBATOFF BIT(7) + +#define REG_CHG_CNFG_09 0xC0 +#define BIT_CHGIN_ILIM BITS(6, 0) +#define BIT_OVPDRV_CTL BIT(7) + +#define REG_CHG_CNFG_10 0xC1 +#define BIT_DISSKIP BIT(0) +#define BIT_TODEB_EN BIT(1) +#define BIT_TODEN BITS(3, 2) + +#define REG_CHG_CNFG_11 0xC2 +#define BIT_VBYPSET BITS(6, 0) + +#define REG_CHG_CNFG_12 0xC3 +#define BIT_B2SOVRC BITS(3, 0) +#define BIT_VCHGIN_REG BITS(5, 4) +#define BIT_CHGINSEL BIT(6) +#define BIT_CHG_LPM BIT(7) + +enum { + CHGIN_DTLS_UVLO, + CHGIN_DTLS_INVALID_01, + CHGIN_DTLS_OVLO, + CHGIN_DTLS_VALID, +}; + +enum { + CHG_DTLS_PREQUAL, + CHG_DTLS_FASTCHARGE_CC, + CHG_DTLS_FASTCHARGE_CV, + CHG_DTLS_TOPOFF, + CHG_DTLS_DONE, + CHG_DTLS_RESEVRED_05, + CHG_DTLS_OFF_TIMER_FAULT, + CHG_DTLS_OFF_SUSPEND, + CHG_DTLS_OFF_INPUT_INVALID, + CHG_DTLS_RESERVED_09, + CHG_DTLS_OFF_JUCTION_TEMP, + CHG_DTLS_OFF_WDT_EXPIRED, +}; + +enum { + BAT_DTLS_NO_BATTERY, + BAT_DTLS_RESERVED_01, + BAT_DTLS_TIMER_FAULT, + BAT_DTLS_OKAY, + BAT_DTLS_OKAY_LOW, + BAT_DTLS_OVERVOLTAGE, + BAT_DTLS_OVERCURRENT, + BAT_DTLS_RESERVED_07, +}; + +enum { + CHG_INT_BYP_I, + CHG_INT_BAT2SOC_I, + CHG_INT_BATP_I, + CHG_INT_BAT_I, + CHG_INT_CHG_I, + CHG_INT_TOPOFF_I, + CHG_INT_CHGIN_I, + CHG_INT_AICL_CHGINI_I, +}; + +struct max77840_charger_platform_data { + int fast_charge_timer; + int fast_charge_current; + int topoff_current; + int topoff_timer; + int restart_threshold; + int termination_voltage; + int input_current_limit; +}; + +#endif /* !__MAX77840_CHARGER_H__ */ From 8cf13d67ced76a7c26b34f80473ced46765f3d72 Mon Sep 17 00:00:00 2001 From: joan-na-good Date: Sun, 8 Jun 2025 18:56:58 +0900 Subject: [PATCH 4/4] regulator: max77840: Add regulator driver for MAX77840 PMIC Add regulator support for the MAX77840 PMIC to control voltage and current regulation. This enables proper power management features required for various submodules dependent on this regulator. Signed-off-by: joan-na-good --- drivers/regulator/Kconfig | 14 + drivers/regulator/Makefile | 1 + drivers/regulator/max77840-regulator.c | 290 +++++++++++++++++++ include/linux/regulator/max77840-regulator.h | 26 ++ 4 files changed, 331 insertions(+) create mode 100644 drivers/regulator/max77840-regulator.c create mode 100644 include/linux/regulator/max77840-regulator.h diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index a6d9cfda23eb03..f30cba2eb9703f 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -615,6 +615,20 @@ config REGULATOR_MAX77650 Semiconductor. This device has a SIMO with three independent power rails and an LDO. +config REGULATOR_MAX77840 + tristate "Maxim Integrated MAX77840 Regulator" + depends on MFD_MAX77840 + help + Say Y here to enable support for the voltage regulators provided by + the Maxim Integrated MAX77840 PMIC. This driver supports the control + and monitoring of the on-chip LDOs and buck converters via the regulator + framework. + + The MAX77840 communicates over the I2C bus and is commonly used in + mobile and battery-powered devices. This driver allows setting regulator + voltage levels and handling regulator enable/disable through standard + kernel interfaces. + config REGULATOR_MAX77857 tristate "ADI MAX77857/MAX77831 regulator support" depends on I2C diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 96f4b2c9a2443e..66bab5d7cb4ba6 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -89,6 +89,7 @@ obj-$(CONFIG_REGULATOR_MAX77686) += max77686-regulator.o obj-$(CONFIG_REGULATOR_MAX77693) += max77693-regulator.o obj-$(CONFIG_REGULATOR_MAX77802) += max77802-regulator.o obj-$(CONFIG_REGULATOR_MAX77826) += max77826-regulator.o +obj-$(CONFIG_REGULATOR_MAX77840) += max77840-regulator.o obj-$(CONFIG_REGULATOR_MAX77857) += max77857-regulator.o obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o diff --git a/drivers/regulator/max77840-regulator.c b/drivers/regulator/max77840-regulator.c new file mode 100644 index 00000000000000..97fb9a4ce38a9d --- /dev/null +++ b/drivers/regulator/max77840-regulator.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Maxim Integrated Products, Inc. + * Copyright (C) 2024 Analog Devices, Inc. + * + * Author : Analog Devices + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __TEST_DEVICE_NODE__ +#include +#include +#endif + +#define M2SH __CONST_FFS + +/* Register */ +/* Safeout */ +#define REG_SAFEOUTCTRL 0xC6 +#define BIT_SAFEOUT1 BITS(1, 0) +#define BIT_ACTDISSAFEO1 BIT(4) +#define BIT_ENSAFEOUT1 BIT(6) + +struct max77840_data { + struct device *dev; + struct max77840_dev *iodev; + struct regulator_dev *rdev; +}; + +static unsigned int max77840_safeout_volt_table[] = { + 4850000, 4900000, 4950000, 3300000, +}; + +static const struct regulator_ops max77840_safeout_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static struct regulator_desc max77840_safeout_desc = { + .name = "SAFEOUT1", + .id = MAX77840_SAFEOUT1, + .ops = &max77840_safeout_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(max77840_safeout_volt_table), + .volt_table = max77840_safeout_volt_table, + .vsel_reg = REG_SAFEOUTCTRL, + .vsel_mask = BIT_SAFEOUT1, + .enable_reg = REG_SAFEOUTCTRL, + .enable_mask = BIT_ENSAFEOUT1, +}; + +#ifdef CONFIG_OF + static struct max77840_regulator_platform_data +*max77840_regulator_parse_dt(struct device *dev) +{ + struct device_node *np = of_find_node_by_name(NULL, "regulator"); + struct max77840_regulator_platform_data *pdata; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (unlikely(!pdata)) + return ERR_PTR(-ENOMEM); + + if (!np) { + pr_err("%s np NULL\n", __func__); + pdata = ERR_PTR(-EINVAL); + goto out; + } + + if (!of_node_cmp(np->name, max77840_safeout_desc.name)) + pdata->initdata = of_get_regulator_init_data(dev, np, &max77840_safeout_desc); + + pdata->of_node = np; + of_node_put(np); + +out: + return pdata; +} +#endif + +#ifdef __TEST_DEVICE_NODE__ +static struct max77840_data *test_if; +static ssize_t max77840_test_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret, reg; + unsigned long strval; + u8 rval; + char *args[3]; + char *sep = ",\t"; + char *buff = (char *)buf; + struct regmap *if_regmap = test_if->iodev->regmap_pmic; + + args[0] = strsep(&buff, sep); + if (!args[0]) + return -2; + + // register read + args[1] = strsep(&buff, sep); + if (strncmp("read", args[0], 4) == 0) { + ret = kstrtoul(args[1], 0, &strval); + reg = (int)strval; + ret = max77840_read(if_regmap, reg, &rval); + if (ret < 0) + dev_err(test_if->dev, "failed to read i2c: %d @ function %s\n", + ret, __func__); + else + dev_err(test_if->dev, "read [0x%x] = 0x%x\n", reg, rval); + return size; + } + // register write + else if (strncmp("write", args[0], 5) == 0) { + ret = kstrtoul(args[1], 0, &strval); + reg = (int)strval; + args[2] = strsep(&buff, sep); + ret = kstrtoul(args[2], 0, &strval); + rval = (int)strval; + ret = max77840_write(if_regmap, reg, rval); + if (ret < 0) + dev_err(test_if->dev, "failed to write i2c: %d @ function %s\n", + ret, __func__); + else + dev_err(test_if->dev, "write [0x%x] = 0x%x\n", reg, rval); + return size; + } + dev_err(test_if->dev, "Command not supported.\n"); + + return 0; +} + +static struct device_attribute max77840_attribute = { + .attr = { + .name = "max77840_test_if", + .mode = 0x0666, + }, + .show = NULL, + .store = max77840_test_store, +}; + +static int max77840_test_node(struct max77840_data *max77840) +{ + int ret; + + ret = sysfs_create_file(&max77840->dev->kobj, &max77840_attribute.attr); + test_if = max77840; + return ret; +} +#else +static int max77840_test_node(struct max77840_data *pmic) +{ + return 0; +} +#endif + +static int max77840_regulator_probe(struct platform_device *pdev) +{ + struct max77840_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max77840_regulator_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct regulator_dev *rdev; + struct max77840_data *max77840; + struct regmap *regmap; + struct regulator_config config; + int ret, size; + + pr_info("%s: Max77840 Regulator Driver Loading\n", __func__); + +#ifdef CONFIG_OF + pdata = max77840_regulator_parse_dt(&pdev->dev); +#endif + if (IS_ERR(pdata)) { + pr_info("[%s:%d] !pdata\n", __FILE__, __LINE__); + dev_err(pdev->dev.parent, "No platform init data supplied.\n"); + return PTR_ERR(pdata); + } + + max77840 = kzalloc(sizeof(*max77840), GFP_KERNEL); + if (!max77840) + return -ENOMEM; + + size = sizeof(struct regulator_dev *); + max77840->rdev = kzalloc(size, GFP_KERNEL); + if (!max77840->rdev) { + pr_info("[%s:%d] if (!max77840->rdev)\n", __FILE__, __LINE__); + kfree(max77840); + return -ENOMEM; + } + + rdev = max77840->rdev; + max77840->dev = &pdev->dev; + max77840->iodev = iodev; + platform_set_drvdata(pdev, max77840); + regmap = max77840->iodev->regmap_pmic; + + pr_info("[%s:%d] for regulator\n", + __FILE__, + __LINE__); + + config.dev = &pdev->dev; + config.driver_data = max77840; + config.init_data = pdata->initdata; + config.of_node = pdata->of_node; + config.regmap = regmap; + rdev = devm_regulator_register(&pdev->dev, &max77840_safeout_desc, &config); + + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(max77840->dev, "regulator init failed!\n"); + rdev = NULL; + goto err; + } + + max77840_test_node(max77840); + return 0; +err: + pr_info("[%s:%d] err:\n", __FILE__, __LINE__); + pr_info("[%s:%d] err_alloc\n", __FILE__, __LINE__); + kfree(max77840->rdev); + kfree(max77840); + + return ret; +} + +static int max77840_regulator_remove(struct platform_device *pdev) +{ + struct max77840_data *max77840 = platform_get_drvdata(pdev); + struct regulator_dev *rdev = max77840->rdev; + + regulator_unregister(rdev); + kfree(max77840->rdev); + kfree(max77840); + + return 0; +} + +static const struct platform_device_id max77840_regulator_id[] = { + {"max77840-regulator", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(platform, max77840_regulator_id); + +static struct platform_driver max77840_regulator_driver = { + .driver = { + .name = MAX77840_REGULATOR_NAME, + }, + .probe = max77840_regulator_probe, + .remove = max77840_regulator_remove, + .id_table = max77840_regulator_id, +}; + +static int __init max77840_regulator_init(void) +{ + return platform_driver_register(&max77840_regulator_driver); +} + +static void __exit max77840_regulator_exit(void) +{ + platform_driver_unregister(&max77840_regulator_driver); +} + +subsys_initcall(max77840_regulator_init); +module_exit(max77840_regulator_exit); + +MODULE_DESCRIPTION("MAXIM 77840 Regulator Driver"); +MODULE_AUTHOR("joan.na@analog.com"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); diff --git a/include/linux/regulator/max77840-regulator.h b/include/linux/regulator/max77840-regulator.h new file mode 100644 index 00000000000000..1ae6aee2b58a65 --- /dev/null +++ b/include/linux/regulator/max77840-regulator.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019 Maxim Integrated Products, Inc. + * Copyright (C) 2024 Analog Devices, Inc. + * + * Author : Analog Devices + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_MAX77840_REGULATOR_H +#define __LINUX_MAX77840_REGULATOR_H + +struct max77840_regulator_platform_data { + struct regulator_init_data *initdata; + struct device_node *of_node; +}; + +enum max77840_regulators { + MAX77840_SAFEOUT1 = 0, + MAX77840_REG_MAX, +}; + +#endif