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