2021-10-04 13:13:52

by Luca Ceresoli

[permalink] [raw]
Subject: [PATCH 1/2] dt-bindings: power: supply: add Maxim MAX77976 battery charger

Add bindings for the Maxim MAX77976 I2C-controlled battery charger.

Signed-off-by: Luca Ceresoli <[email protected]>
---
.../bindings/power/supply/maxim,max77976.yaml | 41 +++++++++++++++++++
MAINTAINERS | 5 +++
2 files changed, 46 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml

diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
new file mode 100644
index 000000000000..b508d9cc04a0
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/power/supply/maxim,max77976.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim Integrated MAX77976 Battery charger
+
+maintainers:
+ - Luca Ceresoli <[email protected]>
+
+description: |
+ The Maxim MAX77976 is a 19Vin / 5.5A, 1-Cell Li+ battery charger
+ configured via I2C.
+
+properties:
+ compatible:
+ const: maxim,max77976
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ charger@6b {
+ compatible = "maxim,max77976";
+ reg = <0x6b>;
+ };
+ };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index eeb4c70b3d5b..b3a3667cef46 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11388,6 +11388,11 @@ F: Documentation/devicetree/bindings/*/*max77802.txt
F: drivers/regulator/max77802-regulator.c
F: include/dt-bindings/*/*max77802.h

+MAXIM MAX77976 BATTERY CHARGER
+M: Luca Ceresoli <[email protected]>
+S: Supported
+F: Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
+
MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
M: Krzysztof Kozlowski <[email protected]>
M: Bartlomiej Zolnierkiewicz <[email protected]>
--
2.25.1


2021-10-04 13:13:52

by Luca Ceresoli

[permalink] [raw]
Subject: [PATCH 2/2] power: supply: max77976: add Maxim MAX77976 charger driver

Add support for the MAX77976 3.5/5.5A 1-Cell Li+ Battery Charger.

This is a simple implementation enough to be used as a simple battery
charger without OTG and boost.

Signed-off-by: Luca Ceresoli <[email protected]>
---
MAINTAINERS | 1 +
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/max77976_charger.c | 508 ++++++++++++++++++++++++
4 files changed, 521 insertions(+)
create mode 100644 drivers/power/supply/max77976_charger.c

diff --git a/MAINTAINERS b/MAINTAINERS
index b3a3667cef46..064c0560b810 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11392,6 +11392,7 @@ MAXIM MAX77976 BATTERY CHARGER
M: Luca Ceresoli <[email protected]>
S: Supported
F: Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
+F: drivers/power/supply/max77976_charger.c

MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
M: Krzysztof Kozlowski <[email protected]>
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index ad93b3550d6d..622d690c883a 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -557,6 +557,17 @@ config CHARGER_MAX77693
help
Say Y to enable support for the Maxim MAX77693 battery charger.

+config CHARGER_MAX77976
+ tristate "Maxim MAX77976 battery charger driver"
+ depends on REGMAP_I2C
+ help
+ The Maxim MAX77976 is a 19 Vin, 5.5A 1-Cell Li+ Battery Charger
+ USB OTG support. It has an I2C interface for configuration.
+
+ Say Y to enable support for the Maxim MAX77976 battery charger.
+ This driver can also be built as a module. If so, the module will be
+ called max77976_charger.
+
config CHARGER_MAX8997
tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
depends on MFD_MAX8997 && REGULATOR_MAX8997
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 4e55a11aab79..2c1b264b2046 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -75,6 +75,7 @@ obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o
obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
+obj-$(CONFIG_CHARGER_MAX77976) += max77976_charger.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
obj-$(CONFIG_CHARGER_MP2629) += mp2629_charger.o
diff --git a/drivers/power/supply/max77976_charger.c b/drivers/power/supply/max77976_charger.c
new file mode 100644
index 000000000000..2ff900b1843a
--- /dev/null
+++ b/drivers/power/supply/max77976_charger.c
@@ -0,0 +1,508 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * max77976_charger.c - Driver for the Maxim MAX77976 battery charger
+ *
+ * Copyright (C) 2021 Luca Ceresoli
+ * Author: Luca Ceresoli <[email protected]>
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+
+#define MAX77976_DRIVER_NAME "max77976-charger"
+#define MAX77976_CHIP_ID 0x76
+
+static const char *max77976_manufacturer = "Maxim Integrated";
+static const char *max77976_model = "MAX77976";
+
+/* --------------------------------------------------------------------------
+ * Register map
+ */
+
+#define MAX77976_REG_CHIP_ID 0x00
+#define MAX77976_REG_CHIP_REVISION 0x01
+#define MAX77976_REG_CHG_INT_OK 0x12
+#define MAX77976_REG_CHG_DETAILS_01 0x14
+#define MAX77976_REG_CHG_CNFG_00 0x16
+#define MAX77976_REG_CHG_CNFG_02 0x18
+#define MAX77976_REG_CHG_CNFG_06 0x1c
+#define MAX77976_REG_CHG_CNFG_09 0x1f
+
+/* CHG_DETAILS_01.CHG_DTLS values */
+enum max77976_charging_state {
+ MAX77976_CHARGING_PREQUALIFICATION = 0x0,
+ MAX77976_CHARGING_FAST_CONST_CURRENT,
+ MAX77976_CHARGING_FAST_CONST_VOLTAGE,
+ MAX77976_CHARGING_TOP_OFF,
+ MAX77976_CHARGING_DONE,
+ MAX77976_CHARGING_RESERVED_05,
+ MAX77976_CHARGING_TIMER_FAULT,
+ MAX77976_CHARGING_SUSPENDED_QBATT_OFF,
+ MAX77976_CHARGING_OFF,
+ MAX77976_CHARGING_RESERVED_09,
+ MAX77976_CHARGING_THERMAL_SHUTDOWN,
+ MAX77976_CHARGING_WATCHDOG_EXPIRED,
+ MAX77976_CHARGING_SUSPENDED_JEITA,
+ MAX77976_CHARGING_SUSPENDED_THM_REMOVAL,
+ MAX77976_CHARGING_SUSPENDED_PIN,
+ MAX77976_CHARGING_RESERVED_0F,
+};
+
+/* CHG_DETAILS_01.BAT_DTLS values */
+enum max77976_battery_state {
+ MAX77976_BATTERY_BATTERY_REMOVAL = 0x0,
+ MAX77976_BATTERY_PREQUALIFICATION,
+ MAX77976_BATTERY_TIMER_FAULT,
+ MAX77976_BATTERY_REGULAR_VOLTAGE,
+ MAX77976_BATTERY_LOW_VOLTAGE,
+ MAX77976_BATTERY_OVERVOLTAGE,
+ MAX77976_BATTERY_RESERVED,
+ MAX77976_BATTERY_BATTERY_ONLY, // No valid adapter is present
+};
+
+/* CHG_CNFG_00.MODE values */
+enum max77976_mode {
+ MAX77976_MODE_CHARGER_BUCK = 0x5,
+ MAX77976_MODE_BOOST = 0x9,
+};
+
+/* CHG_CNFG_02.CHG_CC: charge current limit, 100..5500 mA, 50 mA steps */
+#define MAX77976_CHG_CC_STEP 50000U
+#define MAX77976_CHG_CC_MIN 100000U
+#define MAX77976_CHG_CC_MAX 5500000U
+
+/* CHG_CNFG_09.CHGIN_ILIM: input current limit, 100..3200 mA, 100 mA steps */
+#define MAX77976_CHGIN_ILIM_STEP 100000U
+#define MAX77976_CHGIN_ILIM_MIN 100000U
+#define MAX77976_CHGIN_ILIM_MAX 3200000U
+
+enum max77976_field_idx {
+ VERSION, REVISION, /* CHIP_REVISION */
+ CHGIN_OK, /* CHG_INT_OK */
+ BAT_DTLS, CHG_DTLS, /* CHG_DETAILS_01 */
+ MODE, /* CHG_CNFG_00 */
+ CHG_CC, /* CHG_CNFG_02 */
+ CHGPROT, /* CHG_CNFG_06 */
+ CHGIN_ILIM, /* CHG_CNFG_09 */
+ MAX77976_N_REGMAP_FIELDS
+};
+
+static const struct reg_field max77976_reg_field[MAX77976_N_REGMAP_FIELDS] = {
+ [VERSION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 4, 7),
+ [REVISION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 0, 3),
+ [CHGIN_OK] = REG_FIELD(MAX77976_REG_CHG_INT_OK, 6, 6),
+ [CHG_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 0, 3),
+ [BAT_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 4, 6),
+ [MODE] = REG_FIELD(MAX77976_REG_CHG_CNFG_00, 0, 3),
+ [CHG_CC] = REG_FIELD(MAX77976_REG_CHG_CNFG_02, 0, 6),
+ [CHGPROT] = REG_FIELD(MAX77976_REG_CHG_CNFG_06, 2, 3),
+ [CHGIN_ILIM] = REG_FIELD(MAX77976_REG_CHG_CNFG_09, 0, 5),
+};
+
+static const struct regmap_config max77976_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x24,
+};
+
+/* --------------------------------------------------------------------------
+ * Data structures
+ */
+
+struct max77976 {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct regmap_field *rfield[MAX77976_N_REGMAP_FIELDS];
+};
+
+/* --------------------------------------------------------------------------
+ * power_supply properties
+ */
+
+static int max77976_get_status(struct max77976 *chg, int *val)
+{
+ unsigned int regval;
+ int err;
+
+ err = regmap_field_read(chg->rfield[CHG_DTLS], &regval);
+ if (err < 0)
+ return err;
+
+ switch (regval) {
+ case MAX77976_CHARGING_PREQUALIFICATION:
+ case MAX77976_CHARGING_FAST_CONST_CURRENT:
+ case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
+ case MAX77976_CHARGING_TOP_OFF:
+ *val = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case MAX77976_CHARGING_DONE:
+ *val = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case MAX77976_CHARGING_TIMER_FAULT:
+ case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
+ case MAX77976_CHARGING_SUSPENDED_JEITA:
+ case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
+ case MAX77976_CHARGING_SUSPENDED_PIN:
+ *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case MAX77976_CHARGING_OFF:
+ case MAX77976_CHARGING_THERMAL_SHUTDOWN:
+ case MAX77976_CHARGING_WATCHDOG_EXPIRED:
+ *val = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ default:
+ *val = POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static int max77976_get_charge_type(struct max77976 *chg, int *val)
+{
+ unsigned int regval;
+ int err;
+
+ err = regmap_field_read(chg->rfield[CHG_DTLS], &regval);
+ if (err < 0)
+ return err;
+
+ switch (regval) {
+ case MAX77976_CHARGING_PREQUALIFICATION:
+ *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case MAX77976_CHARGING_FAST_CONST_CURRENT:
+ case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
+ *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case MAX77976_CHARGING_TOP_OFF:
+ *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+ break;
+ case MAX77976_CHARGING_DONE:
+ case MAX77976_CHARGING_TIMER_FAULT:
+ case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
+ case MAX77976_CHARGING_OFF:
+ case MAX77976_CHARGING_THERMAL_SHUTDOWN:
+ case MAX77976_CHARGING_WATCHDOG_EXPIRED:
+ case MAX77976_CHARGING_SUSPENDED_JEITA:
+ case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
+ case MAX77976_CHARGING_SUSPENDED_PIN:
+ *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ default:
+ *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static int max77976_get_health(struct max77976 *chg, int *val)
+{
+ unsigned int regval;
+ int err;
+
+ err = regmap_field_read(chg->rfield[BAT_DTLS], &regval);
+ if (err < 0)
+ return err;
+
+ switch (regval) {
+ case MAX77976_BATTERY_BATTERY_REMOVAL:
+ *val = POWER_SUPPLY_HEALTH_DEAD;
+ break;
+ case MAX77976_BATTERY_PREQUALIFICATION:
+ case MAX77976_BATTERY_LOW_VOLTAGE:
+ case MAX77976_BATTERY_REGULAR_VOLTAGE:
+ *val = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case MAX77976_BATTERY_TIMER_FAULT:
+ *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+ break;
+ case MAX77976_BATTERY_OVERVOLTAGE:
+ *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ break;
+ case MAX77976_BATTERY_BATTERY_ONLY:
+ *val = POWER_SUPPLY_HEALTH_UNKNOWN;
+ break;
+ default:
+ *val = POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static int max77976_get_online(struct max77976 *chg, int *val)
+{
+ unsigned int regval;
+ int err;
+
+ err = regmap_field_read(chg->rfield[CHGIN_OK], &regval);
+ if (err < 0)
+ return err;
+
+ *val = (regval ? 1 : 0);
+
+ return 0;
+}
+
+static int max77976_get_integer(struct max77976 *chg, enum max77976_field_idx fidx,
+ unsigned int clamp_min, unsigned int clamp_max,
+ unsigned int mult, int *val)
+{
+ unsigned int regval;
+ int err;
+
+ err = regmap_field_read(chg->rfield[fidx], &regval);
+ if (err < 0)
+ return err;
+
+ *val = clamp_val(regval * mult, clamp_min, clamp_max);
+
+ return 0;
+}
+
+static int max77976_set_integer(struct max77976 *chg, enum max77976_field_idx fidx,
+ unsigned int clamp_min, unsigned int clamp_max,
+ unsigned int div, int val)
+{
+ unsigned int regval;
+
+ regval = clamp_val(val, clamp_min, clamp_max) / div;
+
+ return regmap_field_write(chg->rfield[fidx], regval);
+}
+
+static int max77976_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max77976 *chg = power_supply_get_drvdata(psy);
+ int err = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ err = max77976_get_status(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ err = max77976_get_charge_type(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ err = max77976_get_health(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ err = max77976_get_online(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
+ val->intval = MAX77976_CHG_CC_MAX;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ err = max77976_get_integer(chg, CHG_CC,
+ MAX77976_CHG_CC_MIN,
+ MAX77976_CHG_CC_MAX,
+ MAX77976_CHG_CC_STEP,
+ &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ err = max77976_get_integer(chg, CHGIN_ILIM,
+ MAX77976_CHGIN_ILIM_MIN,
+ MAX77976_CHGIN_ILIM_MAX,
+ MAX77976_CHGIN_ILIM_STEP,
+ &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = max77976_model;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = max77976_manufacturer;
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static int max77976_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max77976 *chg = power_supply_get_drvdata(psy);
+ int err = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ err = max77976_set_integer(chg, CHG_CC,
+ MAX77976_CHG_CC_MIN,
+ MAX77976_CHG_CC_MAX,
+ MAX77976_CHG_CC_STEP,
+ val->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ err = max77976_set_integer(chg, CHGIN_ILIM,
+ MAX77976_CHGIN_ILIM_MIN,
+ MAX77976_CHGIN_ILIM_MAX,
+ MAX77976_CHGIN_ILIM_STEP,
+ val->intval);
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+};
+
+static int max77976_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static enum power_supply_property max77976_psy_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static const struct power_supply_desc max77976_psy_desc = {
+ .name = MAX77976_DRIVER_NAME,
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = max77976_psy_props,
+ .num_properties = ARRAY_SIZE(max77976_psy_props),
+ .get_property = max77976_get_property,
+ .set_property = max77976_set_property,
+ .property_is_writeable = max77976_property_is_writeable,
+};
+
+/* --------------------------------------------------------------------------
+ * Entry point
+ */
+
+static int max77976_detect(struct max77976 *chg)
+{
+ struct device *dev = &chg->client->dev;
+ unsigned int id, ver, rev;
+ int err;
+
+ err = regmap_read(chg->regmap, MAX77976_REG_CHIP_ID, &id);
+ if (err)
+ return dev_err_probe(dev, err, "cannot read chip ID\n");
+
+ if (id != MAX77976_CHIP_ID)
+ return dev_err_probe(dev, -ENXIO, "unknown model ID 0x%02x\n", id);
+
+ err = regmap_field_read(chg->rfield[VERSION], &ver);
+ if (!err)
+ err = regmap_field_read(chg->rfield[REVISION], &rev);
+ if (err)
+ return dev_err_probe(dev, -ENXIO, "cannot read version/revision\n");
+
+ dev_info(dev, "detected model MAX779%02x ver %u rev %u", id, ver, rev);
+
+ return 0;
+}
+static int max77976_configure(struct max77976 *chg)
+{
+ struct device *dev = &chg->client->dev;
+ int err;
+
+ /* Magic value to unlock writing to some registers */
+ err = regmap_field_write(chg->rfield[CHGPROT], 0x3);
+ if (err)
+ goto err;
+
+ /*
+ * Mode 5 = Charger ON, OTG OFF, buck ON, boost OFF.
+ * Other modes are not implemented by this driver.
+ */
+ err = regmap_field_write(chg->rfield[MODE], MAX77976_MODE_CHARGER_BUCK);
+ if (err)
+ goto err;
+
+ return 0;
+
+err:
+ return dev_err_probe(dev, err, "error while configuring");
+}
+
+static int max77976_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct power_supply_config psy_cfg = {};
+ struct power_supply *psy;
+ struct max77976 *chg;
+ int err;
+ int i;
+
+ chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, chg);
+ psy_cfg.drv_data = chg;
+ chg->client = client;
+
+ chg->regmap = devm_regmap_init_i2c(client, &max77976_regmap_config);
+ if (IS_ERR(chg->regmap))
+ return dev_err_probe(dev, PTR_ERR(chg->regmap),
+ "cannot allocate regmap\n");
+
+ for (i = 0; i < MAX77976_N_REGMAP_FIELDS; i++) {
+ chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap,
+ max77976_reg_field[i]);
+ if (IS_ERR(chg->rfield[i]))
+ return dev_err_probe(dev, PTR_ERR(chg->rfield[i]),
+ "cannot allocate regmap field\n");
+ }
+
+ err = max77976_detect(chg);
+ if (err)
+ return err;
+
+ err = max77976_configure(chg);
+ if (err)
+ return err;
+
+ psy = devm_power_supply_register_no_ws(dev, &max77976_psy_desc, &psy_cfg);
+ if (IS_ERR(psy))
+ return dev_err_probe(dev, PTR_ERR(psy), "cannot register\n");
+
+ return 0;
+}
+
+static const struct i2c_device_id max77976_i2c_id[] = {
+ { MAX77976_DRIVER_NAME, 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, max77976_i2c_id);
+
+static const struct of_device_id max77976_of_id[] = {
+ { .compatible = "maxim,max77976" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, max77976_of_id);
+
+static struct i2c_driver max77976_driver = {
+ .driver = {
+ .name = MAX77976_DRIVER_NAME,
+ .of_match_table = of_match_ptr(max77976_of_id),
+ },
+ .probe_new = max77976_probe,
+ .id_table = max77976_i2c_id,
+};
+module_i2c_driver(max77976_driver);
+
+MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
+MODULE_DESCRIPTION("Maxim MAX77976 charger driver");
+MODULE_LICENSE("GPL v2");
--
2.25.1

2021-10-04 21:14:41

by Randy Dunlap

[permalink] [raw]
Subject: Re: [PATCH 2/2] power: supply: max77976: add Maxim MAX77976 charger driver

On 10/4/21 6:07 AM, Luca Ceresoli wrote:
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index ad93b3550d6d..622d690c883a 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -557,6 +557,17 @@ config CHARGER_MAX77693
> help
> Say Y to enable support for the Maxim MAX77693 battery charger.
>
> +config CHARGER_MAX77976
> + tristate "Maxim MAX77976 battery charger driver"
> + depends on REGMAP_I2C
> + help
> + The Maxim MAX77976 is a 19 Vin, 5.5A 1-Cell Li+ Battery Charger
> + USB OTG support. It has an I2C interface for configuration.
> +
> + Say Y to enable support for the Maxim MAX77976 battery charger.
> + This driver can also be built as a module. If so, the module will be
> + called max77976_charger.

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

depends on I2C
select REGMAP_I2C

--
~Randy

2021-10-05 13:17:58

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH 2/2] power: supply: max77976: add Maxim MAX77976 charger driver

Hi Randy,

On 04/10/21 17:28, Randy Dunlap wrote:
> On 10/4/21 6:07 AM, Luca Ceresoli wrote:
>> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
>> index ad93b3550d6d..622d690c883a 100644
>> --- a/drivers/power/supply/Kconfig
>> +++ b/drivers/power/supply/Kconfig
>> @@ -557,6 +557,17 @@ config CHARGER_MAX77693
>>       help
>>         Say Y to enable support for the Maxim MAX77693 battery charger.
>>   +config CHARGER_MAX77976
>> +    tristate "Maxim MAX77976 battery charger driver"
>> +    depends on REGMAP_I2C
>> +    help
>> +      The Maxim MAX77976 is a 19 Vin, 5.5A 1-Cell Li+ Battery Charger
>> +      USB OTG support. It has an I2C interface for configuration.
>> +
>> +      Say Y to enable support for the Maxim MAX77976 battery charger.
>> +      This driver can also be built as a module. If so, the module
>> will be
>> +      called max77976_charger.
>
> REGMAP_I2C is not a user-settable config option, so drivers should not
> "depend on" it. This should be more like:
>
>     depends on I2C
>     select REGMAP_I2C

Ouch, thanks for spotting, will fix in v2.

--
Luca

2021-10-06 16:17:59

by Sebastian Reichel

[permalink] [raw]
Subject: Re: [PATCH 1/2] dt-bindings: power: supply: add Maxim MAX77976 battery charger

Hi,

On Mon, Oct 04, 2021 at 03:07:31PM +0200, Luca Ceresoli wrote:
> Add bindings for the Maxim MAX77976 I2C-controlled battery charger.
>
> Signed-off-by: Luca Ceresoli <[email protected]>
> ---
> .../bindings/power/supply/maxim,max77976.yaml | 41 +++++++++++++++++++
> MAINTAINERS | 5 +++
> 2 files changed, 46 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
>
> diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
> new file mode 100644
> index 000000000000..b508d9cc04a0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
> @@ -0,0 +1,41 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/power/supply/maxim,max77976.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Maxim Integrated MAX77976 Battery charger
> +
> +maintainers:
> + - Luca Ceresoli <[email protected]>
> +
> +description: |
> + The Maxim MAX77976 is a 19Vin / 5.5A, 1-Cell Li+ battery charger
> + configured via I2C.
> +
> +properties:
> + compatible:
> + const: maxim,max77976
> +
> + reg:
> + maxItems: 1
> +
> +required:
> + - compatible
> + - reg
> +
> +additionalProperties: false

Add

allOf:
- $ref: power-supply.yaml#

and replace additionalProperties with unevaluatedProperties, so that
the power-supplies property is also valid.

-- Sebastian

> +
> +examples:
> + - |
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + charger@6b {
> + compatible = "maxim,max77976";
> + reg = <0x6b>;
> + };
> + };
> +
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index eeb4c70b3d5b..b3a3667cef46 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11388,6 +11388,11 @@ F: Documentation/devicetree/bindings/*/*max77802.txt
> F: drivers/regulator/max77802-regulator.c
> F: include/dt-bindings/*/*max77802.h
>
> +MAXIM MAX77976 BATTERY CHARGER
> +M: Luca Ceresoli <[email protected]>
> +S: Supported
> +F: Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
> +
> MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
> M: Krzysztof Kozlowski <[email protected]>
> M: Bartlomiej Zolnierkiewicz <[email protected]>
> --
> 2.25.1
>


Attachments:
(No filename) (2.48 kB)
signature.asc (849.00 B)
Download all attachments

2021-10-26 04:40:31

by Sebastian Reichel

[permalink] [raw]
Subject: Re: [PATCH 2/2] power: supply: max77976: add Maxim MAX77976 charger driver

Hi,

On Mon, Oct 04, 2021 at 03:07:32PM +0200, Luca Ceresoli wrote:
> Add support for the MAX77976 3.5/5.5A 1-Cell Li+ Battery Charger.
>
> This is a simple implementation enough to be used as a simple battery
> charger without OTG and boost.
>
> Signed-off-by: Luca Ceresoli <[email protected]>
> ---
> MAINTAINERS | 1 +
> drivers/power/supply/Kconfig | 11 +
> drivers/power/supply/Makefile | 1 +
> drivers/power/supply/max77976_charger.c | 508 ++++++++++++++++++++++++
> 4 files changed, 521 insertions(+)
> create mode 100644 drivers/power/supply/max77976_charger.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b3a3667cef46..064c0560b810 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11392,6 +11392,7 @@ MAXIM MAX77976 BATTERY CHARGER
> M: Luca Ceresoli <[email protected]>
> S: Supported
> F: Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
> +F: drivers/power/supply/max77976_charger.c
>
> MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
> M: Krzysztof Kozlowski <[email protected]>
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index ad93b3550d6d..622d690c883a 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -557,6 +557,17 @@ config CHARGER_MAX77693
> help
> Say Y to enable support for the Maxim MAX77693 battery charger.
>
> +config CHARGER_MAX77976
> + tristate "Maxim MAX77976 battery charger driver"
> + depends on REGMAP_I2C

needs to be selected as pointed out by Randy.

> + help
> + The Maxim MAX77976 is a 19 Vin, 5.5A 1-Cell Li+ Battery Charger
> + USB OTG support. It has an I2C interface for configuration.
> +
> + Say Y to enable support for the Maxim MAX77976 battery charger.
> + This driver can also be built as a module. If so, the module will be
> + called max77976_charger.
> +
> config CHARGER_MAX8997
> tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
> depends on MFD_MAX8997 && REGULATOR_MAX8997
> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
> index 4e55a11aab79..2c1b264b2046 100644
> --- a/drivers/power/supply/Makefile
> +++ b/drivers/power/supply/Makefile
> @@ -75,6 +75,7 @@ obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
> obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
> obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o
> obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
> +obj-$(CONFIG_CHARGER_MAX77976) += max77976_charger.o
> obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
> obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
> obj-$(CONFIG_CHARGER_MP2629) += mp2629_charger.o
> diff --git a/drivers/power/supply/max77976_charger.c b/drivers/power/supply/max77976_charger.c
> new file mode 100644
> index 000000000000..2ff900b1843a
> --- /dev/null
> +++ b/drivers/power/supply/max77976_charger.c
> @@ -0,0 +1,508 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * max77976_charger.c - Driver for the Maxim MAX77976 battery charger
> + *
> + * Copyright (C) 2021 Luca Ceresoli
> + * Author: Luca Ceresoli <[email protected]>
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/power_supply.h>
> +#include <linux/regmap.h>
> +
> +#define MAX77976_DRIVER_NAME "max77976-charger"
> +#define MAX77976_CHIP_ID 0x76
> +
> +static const char *max77976_manufacturer = "Maxim Integrated";
> +static const char *max77976_model = "MAX77976";
> +
> +/* --------------------------------------------------------------------------
> + * Register map
> + */
> +
> +#define MAX77976_REG_CHIP_ID 0x00
> +#define MAX77976_REG_CHIP_REVISION 0x01
> +#define MAX77976_REG_CHG_INT_OK 0x12
> +#define MAX77976_REG_CHG_DETAILS_01 0x14
> +#define MAX77976_REG_CHG_CNFG_00 0x16
> +#define MAX77976_REG_CHG_CNFG_02 0x18
> +#define MAX77976_REG_CHG_CNFG_06 0x1c
> +#define MAX77976_REG_CHG_CNFG_09 0x1f
> +
> +/* CHG_DETAILS_01.CHG_DTLS values */
> +enum max77976_charging_state {
> + MAX77976_CHARGING_PREQUALIFICATION = 0x0,
> + MAX77976_CHARGING_FAST_CONST_CURRENT,
> + MAX77976_CHARGING_FAST_CONST_VOLTAGE,
> + MAX77976_CHARGING_TOP_OFF,
> + MAX77976_CHARGING_DONE,
> + MAX77976_CHARGING_RESERVED_05,
> + MAX77976_CHARGING_TIMER_FAULT,
> + MAX77976_CHARGING_SUSPENDED_QBATT_OFF,
> + MAX77976_CHARGING_OFF,
> + MAX77976_CHARGING_RESERVED_09,
> + MAX77976_CHARGING_THERMAL_SHUTDOWN,
> + MAX77976_CHARGING_WATCHDOG_EXPIRED,
> + MAX77976_CHARGING_SUSPENDED_JEITA,
> + MAX77976_CHARGING_SUSPENDED_THM_REMOVAL,
> + MAX77976_CHARGING_SUSPENDED_PIN,
> + MAX77976_CHARGING_RESERVED_0F,
> +};
> +
> +/* CHG_DETAILS_01.BAT_DTLS values */
> +enum max77976_battery_state {
> + MAX77976_BATTERY_BATTERY_REMOVAL = 0x0,
> + MAX77976_BATTERY_PREQUALIFICATION,
> + MAX77976_BATTERY_TIMER_FAULT,
> + MAX77976_BATTERY_REGULAR_VOLTAGE,
> + MAX77976_BATTERY_LOW_VOLTAGE,
> + MAX77976_BATTERY_OVERVOLTAGE,
> + MAX77976_BATTERY_RESERVED,
> + MAX77976_BATTERY_BATTERY_ONLY, // No valid adapter is present
> +};
> +
> +/* CHG_CNFG_00.MODE values */
> +enum max77976_mode {
> + MAX77976_MODE_CHARGER_BUCK = 0x5,
> + MAX77976_MODE_BOOST = 0x9,
> +};
> +
> +/* CHG_CNFG_02.CHG_CC: charge current limit, 100..5500 mA, 50 mA steps */
> +#define MAX77976_CHG_CC_STEP 50000U
> +#define MAX77976_CHG_CC_MIN 100000U
> +#define MAX77976_CHG_CC_MAX 5500000U
> +
> +/* CHG_CNFG_09.CHGIN_ILIM: input current limit, 100..3200 mA, 100 mA steps */
> +#define MAX77976_CHGIN_ILIM_STEP 100000U
> +#define MAX77976_CHGIN_ILIM_MIN 100000U
> +#define MAX77976_CHGIN_ILIM_MAX 3200000U
> +
> +enum max77976_field_idx {
> + VERSION, REVISION, /* CHIP_REVISION */
> + CHGIN_OK, /* CHG_INT_OK */
> + BAT_DTLS, CHG_DTLS, /* CHG_DETAILS_01 */
> + MODE, /* CHG_CNFG_00 */
> + CHG_CC, /* CHG_CNFG_02 */
> + CHGPROT, /* CHG_CNFG_06 */
> + CHGIN_ILIM, /* CHG_CNFG_09 */
> + MAX77976_N_REGMAP_FIELDS
> +};
> +
> +static const struct reg_field max77976_reg_field[MAX77976_N_REGMAP_FIELDS] = {
> + [VERSION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 4, 7),
> + [REVISION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 0, 3),
> + [CHGIN_OK] = REG_FIELD(MAX77976_REG_CHG_INT_OK, 6, 6),
> + [CHG_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 0, 3),
> + [BAT_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 4, 6),
> + [MODE] = REG_FIELD(MAX77976_REG_CHG_CNFG_00, 0, 3),
> + [CHG_CC] = REG_FIELD(MAX77976_REG_CHG_CNFG_02, 0, 6),
> + [CHGPROT] = REG_FIELD(MAX77976_REG_CHG_CNFG_06, 2, 3),
> + [CHGIN_ILIM] = REG_FIELD(MAX77976_REG_CHG_CNFG_09, 0, 5),
> +};
> +
> +static const struct regmap_config max77976_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .max_register = 0x24,
> +};
> +
> +/* --------------------------------------------------------------------------
> + * Data structures
> + */
> +
> +struct max77976 {
> + struct i2c_client *client;
> + struct regmap *regmap;
> + struct regmap_field *rfield[MAX77976_N_REGMAP_FIELDS];
> +};
> +
> +/* --------------------------------------------------------------------------
> + * power_supply properties
> + */
> +
> +static int max77976_get_status(struct max77976 *chg, int *val)
> +{
> + unsigned int regval;
> + int err;
> +
> + err = regmap_field_read(chg->rfield[CHG_DTLS], &regval);
> + if (err < 0)
> + return err;
> +
> + switch (regval) {
> + case MAX77976_CHARGING_PREQUALIFICATION:
> + case MAX77976_CHARGING_FAST_CONST_CURRENT:
> + case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
> + case MAX77976_CHARGING_TOP_OFF:
> + *val = POWER_SUPPLY_STATUS_CHARGING;
> + break;
> + case MAX77976_CHARGING_DONE:
> + *val = POWER_SUPPLY_STATUS_FULL;
> + break;
> + case MAX77976_CHARGING_TIMER_FAULT:
> + case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
> + case MAX77976_CHARGING_SUSPENDED_JEITA:
> + case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
> + case MAX77976_CHARGING_SUSPENDED_PIN:
> + *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
> + break;
> + case MAX77976_CHARGING_OFF:
> + case MAX77976_CHARGING_THERMAL_SHUTDOWN:
> + case MAX77976_CHARGING_WATCHDOG_EXPIRED:
> + *val = POWER_SUPPLY_STATUS_DISCHARGING;
> + break;
> + default:
> + *val = POWER_SUPPLY_STATUS_UNKNOWN;
> + }
> +
> + return 0;
> +}
> +
> +static int max77976_get_charge_type(struct max77976 *chg, int *val)
> +{
> + unsigned int regval;
> + int err;
> +
> + err = regmap_field_read(chg->rfield[CHG_DTLS], &regval);
> + if (err < 0)
> + return err;
> +
> + switch (regval) {
> + case MAX77976_CHARGING_PREQUALIFICATION:
> + *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
> + break;
> + case MAX77976_CHARGING_FAST_CONST_CURRENT:
> + case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
> + *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
> + break;
> + case MAX77976_CHARGING_TOP_OFF:
> + *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
> + break;
> + case MAX77976_CHARGING_DONE:
> + case MAX77976_CHARGING_TIMER_FAULT:
> + case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
> + case MAX77976_CHARGING_OFF:
> + case MAX77976_CHARGING_THERMAL_SHUTDOWN:
> + case MAX77976_CHARGING_WATCHDOG_EXPIRED:
> + case MAX77976_CHARGING_SUSPENDED_JEITA:
> + case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
> + case MAX77976_CHARGING_SUSPENDED_PIN:
> + *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
> + break;
> + default:
> + *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
> + }
> +
> + return 0;
> +}
> +
> +static int max77976_get_health(struct max77976 *chg, int *val)
> +{
> + unsigned int regval;
> + int err;
> +
> + err = regmap_field_read(chg->rfield[BAT_DTLS], &regval);
> + if (err < 0)
> + return err;
> +
> + switch (regval) {
> + case MAX77976_BATTERY_BATTERY_REMOVAL:
> + *val = POWER_SUPPLY_HEALTH_DEAD;
> + break;

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

> + case MAX77976_BATTERY_PREQUALIFICATION:
> + case MAX77976_BATTERY_LOW_VOLTAGE:

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

> + case MAX77976_BATTERY_REGULAR_VOLTAGE:
> + *val = POWER_SUPPLY_HEALTH_GOOD;
> + break;
> + case MAX77976_BATTERY_TIMER_FAULT:
> + *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
> + break;
> + case MAX77976_BATTERY_OVERVOLTAGE:
> + *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
> + break;
> + case MAX77976_BATTERY_BATTERY_ONLY:
> + *val = POWER_SUPPLY_HEALTH_UNKNOWN;
> + break;
> + default:
> + *val = POWER_SUPPLY_HEALTH_UNKNOWN;
> + }
> +
> + return 0;
> +}
> +
> +static int max77976_get_online(struct max77976 *chg, int *val)
> +{
> + unsigned int regval;
> + int err;
> +
> + err = regmap_field_read(chg->rfield[CHGIN_OK], &regval);
> + if (err < 0)
> + return err;
> +
> + *val = (regval ? 1 : 0);
> +
> + return 0;
> +}
> +
> +static int max77976_get_integer(struct max77976 *chg, enum max77976_field_idx fidx,
> + unsigned int clamp_min, unsigned int clamp_max,
> + unsigned int mult, int *val)
> +{
> + unsigned int regval;
> + int err;
> +
> + err = regmap_field_read(chg->rfield[fidx], &regval);
> + if (err < 0)
> + return err;
> +
> + *val = clamp_val(regval * mult, clamp_min, clamp_max);
> +
> + return 0;
> +}
> +
> +static int max77976_set_integer(struct max77976 *chg, enum max77976_field_idx fidx,
> + unsigned int clamp_min, unsigned int clamp_max,
> + unsigned int div, int val)
> +{
> + unsigned int regval;
> +
> + regval = clamp_val(val, clamp_min, clamp_max) / div;
> +
> + return regmap_field_write(chg->rfield[fidx], regval);
> +}
> +
> +static int max77976_get_property(struct power_supply *psy,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + struct max77976 *chg = power_supply_get_drvdata(psy);
> + int err = 0;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_STATUS:
> + err = max77976_get_status(chg, &val->intval);
> + break;
> + case POWER_SUPPLY_PROP_CHARGE_TYPE:
> + err = max77976_get_charge_type(chg, &val->intval);
> + break;
> + case POWER_SUPPLY_PROP_HEALTH:
> + err = max77976_get_health(chg, &val->intval);
> + break;
> + case POWER_SUPPLY_PROP_ONLINE:
> + err = max77976_get_online(chg, &val->intval);
> + break;
> + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
> + val->intval = MAX77976_CHG_CC_MAX;
> + break;
> + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
> + err = max77976_get_integer(chg, CHG_CC,
> + MAX77976_CHG_CC_MIN,
> + MAX77976_CHG_CC_MAX,
> + MAX77976_CHG_CC_STEP,
> + &val->intval);
> + break;
> + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
> + err = max77976_get_integer(chg, CHGIN_ILIM,
> + MAX77976_CHGIN_ILIM_MIN,
> + MAX77976_CHGIN_ILIM_MAX,
> + MAX77976_CHGIN_ILIM_STEP,
> + &val->intval);
> + break;
> + case POWER_SUPPLY_PROP_MODEL_NAME:
> + val->strval = max77976_model;
> + break;
> + case POWER_SUPPLY_PROP_MANUFACTURER:
> + val->strval = max77976_manufacturer;
> + break;
> + default:
> + err = -EINVAL;
> + }
> +
> + return err;
> +}
> +
> +static int max77976_set_property(struct power_supply *psy,
> + enum power_supply_property psp,
> + const union power_supply_propval *val)
> +{
> + struct max77976 *chg = power_supply_get_drvdata(psy);
> + int err = 0;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
> + err = max77976_set_integer(chg, CHG_CC,
> + MAX77976_CHG_CC_MIN,
> + MAX77976_CHG_CC_MAX,
> + MAX77976_CHG_CC_STEP,
> + val->intval);
> + break;
> + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
> + err = max77976_set_integer(chg, CHGIN_ILIM,
> + MAX77976_CHGIN_ILIM_MIN,
> + MAX77976_CHGIN_ILIM_MAX,
> + MAX77976_CHGIN_ILIM_STEP,
> + val->intval);
> + break;
> + default:
> + err = -EINVAL;
> + }
> +
> + return err;
> +};
> +
> +static int max77976_property_is_writeable(struct power_supply *psy,
> + enum power_supply_property psp)
> +{
> + switch (psp) {
> + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
> + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static enum power_supply_property max77976_psy_props[] = {
> + POWER_SUPPLY_PROP_STATUS,
> + POWER_SUPPLY_PROP_CHARGE_TYPE,
> + POWER_SUPPLY_PROP_HEALTH,
> + POWER_SUPPLY_PROP_ONLINE,
> + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
> + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
> + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
> + POWER_SUPPLY_PROP_MODEL_NAME,
> + POWER_SUPPLY_PROP_MANUFACTURER,
> +};
> +
> +static const struct power_supply_desc max77976_psy_desc = {
> + .name = MAX77976_DRIVER_NAME,
> + .type = POWER_SUPPLY_TYPE_BATTERY,

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

.type = POWER_SUPPLY_TYPE_USB,

> + .properties = max77976_psy_props,
> + .num_properties = ARRAY_SIZE(max77976_psy_props),
> + .get_property = max77976_get_property,
> + .set_property = max77976_set_property,
> + .property_is_writeable = max77976_property_is_writeable,
> +};
> +
> +/* --------------------------------------------------------------------------
> + * Entry point
> + */
> +
> +static int max77976_detect(struct max77976 *chg)
> +{
> + struct device *dev = &chg->client->dev;
> + unsigned int id, ver, rev;
> + int err;
> +
> + err = regmap_read(chg->regmap, MAX77976_REG_CHIP_ID, &id);
> + if (err)
> + return dev_err_probe(dev, err, "cannot read chip ID\n");
> +
> + if (id != MAX77976_CHIP_ID)
> + return dev_err_probe(dev, -ENXIO, "unknown model ID 0x%02x\n", id);
> +
> + err = regmap_field_read(chg->rfield[VERSION], &ver);
> + if (!err)
> + err = regmap_field_read(chg->rfield[REVISION], &rev);
> + if (err)
> + return dev_err_probe(dev, -ENXIO, "cannot read version/revision\n");
> +
> + dev_info(dev, "detected model MAX779%02x ver %u rev %u", id, ver, rev);
> +
> + return 0;
> +}

missing newline

> +static int max77976_configure(struct max77976 *chg)
> +{
> + struct device *dev = &chg->client->dev;
> + int err;
> +
> + /* Magic value to unlock writing to some registers */
> + err = regmap_field_write(chg->rfield[CHGPROT], 0x3);
> + if (err)
> + goto err;
> +
> + /*
> + * Mode 5 = Charger ON, OTG OFF, buck ON, boost OFF.
> + * Other modes are not implemented by this driver.
> + */
> + err = regmap_field_write(chg->rfield[MODE], MAX77976_MODE_CHARGER_BUCK);
> + if (err)
> + goto err;
> +
> + return 0;
> +
> +err:
> + return dev_err_probe(dev, err, "error while configuring");
> +}
> +
> +static int max77976_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct power_supply_config psy_cfg = {};
> + struct power_supply *psy;
> + struct max77976 *chg;
> + int err;
> + int i;
> +
> + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL);
> + if (!chg)
> + return -ENOMEM;
> +
> + i2c_set_clientdata(client, chg);
> + psy_cfg.drv_data = chg;
> + chg->client = client;
> +
> + chg->regmap = devm_regmap_init_i2c(client, &max77976_regmap_config);
> + if (IS_ERR(chg->regmap))
> + return dev_err_probe(dev, PTR_ERR(chg->regmap),
> + "cannot allocate regmap\n");
> +
> + for (i = 0; i < MAX77976_N_REGMAP_FIELDS; i++) {
> + chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap,
> + max77976_reg_field[i]);
> + if (IS_ERR(chg->rfield[i]))
> + return dev_err_probe(dev, PTR_ERR(chg->rfield[i]),
> + "cannot allocate regmap field\n");
> + }
> +
> + err = max77976_detect(chg);
> + if (err)
> + return err;
> +
> + err = max77976_configure(chg);
> + if (err)
> + return err;
> +
> + psy = devm_power_supply_register_no_ws(dev, &max77976_psy_desc, &psy_cfg);
> + if (IS_ERR(psy))
> + return dev_err_probe(dev, PTR_ERR(psy), "cannot register\n");
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id max77976_i2c_id[] = {
> + { MAX77976_DRIVER_NAME, 0 },
> + { },
> +};
> +MODULE_DEVICE_TABLE(i2c, max77976_i2c_id);
> +
> +static const struct of_device_id max77976_of_id[] = {
> + { .compatible = "maxim,max77976" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, max77976_of_id);
> +
> +static struct i2c_driver max77976_driver = {
> + .driver = {
> + .name = MAX77976_DRIVER_NAME,
> + .of_match_table = of_match_ptr(max77976_of_id),
> + },
> + .probe_new = max77976_probe,
> + .id_table = max77976_i2c_id,
> +};
> +module_i2c_driver(max77976_driver);
> +
> +MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
> +MODULE_DESCRIPTION("Maxim MAX77976 charger driver");
> +MODULE_LICENSE("GPL v2");

Otherwise looks good!

-- Sebastian


Attachments:
(No filename) (19.00 kB)
signature.asc (849.00 B)
Download all attachments

2021-10-26 15:49:31

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH 1/2] dt-bindings: power: supply: add Maxim MAX77976 battery charger

Hi Sebastian,

On 06/10/21 18:15, Sebastian Reichel wrote:
> Hi,
>
> On Mon, Oct 04, 2021 at 03:07:31PM +0200, Luca Ceresoli wrote:
>> Add bindings for the Maxim MAX77976 I2C-controlled battery charger.
>>
>> Signed-off-by: Luca Ceresoli <[email protected]>
>> ---
>> .../bindings/power/supply/maxim,max77976.yaml | 41 +++++++++++++++++++
>> MAINTAINERS | 5 +++
>> 2 files changed, 46 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
>> new file mode 100644
>> index 000000000000..b508d9cc04a0
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
>> @@ -0,0 +1,41 @@
>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/power/supply/maxim,max77976.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Maxim Integrated MAX77976 Battery charger
>> +
>> +maintainers:
>> + - Luca Ceresoli <[email protected]>
>> +
>> +description: |
>> + The Maxim MAX77976 is a 19Vin / 5.5A, 1-Cell Li+ battery charger
>> + configured via I2C.
>> +
>> +properties:
>> + compatible:
>> + const: maxim,max77976
>> +
>> + reg:
>> + maxItems: 1
>> +
>> +required:
>> + - compatible
>> + - reg
>> +
>> +additionalProperties: false
>
> Add
>
> allOf:
> - $ref: power-supply.yaml#
>
> and replace additionalProperties with unevaluatedProperties, so that
> the power-supplies property is also valid.

OK, will fix.

--
Luca

2021-10-26 15:49:31

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH 2/2] power: supply: max77976: add Maxim MAX77976 charger driver

Hi Sebastian,

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

Yes, fix ready for v2.

>> +static int max77976_get_health(struct max77976 *chg, int *val)
>> +{
>> + unsigned int regval;
>> + int err;
>> +
>> + err = regmap_field_read(chg->rfield[BAT_DTLS], &regval);
>> + if (err < 0)
>> + return err;
>> +
>> + switch (regval) {
>> + case MAX77976_BATTERY_BATTERY_REMOVAL:
>> + *val = POWER_SUPPLY_HEALTH_DEAD;
>> + break;
>
> I suppose the charger is still able to power the system when
> there is no battery, so it's not dead.

Correct.

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

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

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

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

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

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

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

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

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

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

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

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

>> +/* --------------------------------------------------------------------------
>> + * Entry point
>> + */
>> +
>> +static int max77976_detect(struct max77976 *chg)
>> +{
>> + struct device *dev = &chg->client->dev;
>> + unsigned int id, ver, rev;
>> + int err;
>> +
>> + err = regmap_read(chg->regmap, MAX77976_REG_CHIP_ID, &id);
>> + if (err)
>> + return dev_err_probe(dev, err, "cannot read chip ID\n");
>> +
>> + if (id != MAX77976_CHIP_ID)
>> + return dev_err_probe(dev, -ENXIO, "unknown model ID 0x%02x\n", id);
>> +
>> + err = regmap_field_read(chg->rfield[VERSION], &ver);
>> + if (!err)
>> + err = regmap_field_read(chg->rfield[REVISION], &rev);
>> + if (err)
>> + return dev_err_probe(dev, -ENXIO, "cannot read version/revision\n");
>> +
>> + dev_info(dev, "detected model MAX779%02x ver %u rev %u", id, ver, rev);
>> +
>> + return 0;
>> +}
>
> missing newline

Ouch.

--
Luca