All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 1/3] dt-bindings: power: supply: add Maxim MAX77976 battery charger
@ 2021-11-08 16:27 Luca Ceresoli
  2021-11-08 16:27 ` [PATCH v2 2/3] power: supply: core: add POWER_SUPPLY_HEALTH_NO_BATTERY Luca Ceresoli
  2021-11-08 16:27 ` [PATCH v2 3/3] power: supply: max77976: add Maxim MAX77976 charger driver Luca Ceresoli
  0 siblings, 2 replies; 5+ messages in thread
From: Luca Ceresoli @ 2021-11-08 16:27 UTC (permalink / raw)
  To: linux-pm
  Cc: Luca Ceresoli, devicetree, linux-kernel, Sebastian Reichel,
	Rob Herring, Randy Dunlap

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

Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net>

---

Changes in v2:
- use power-supply.yaml and unevaluatedProperties (Sebastian Reichel)
---
 .../bindings/power/supply/maxim,max77976.yaml | 44 +++++++++++++++++++
 MAINTAINERS                                   |  5 +++
 2 files changed, 49 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..675b9b26d233
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
@@ -0,0 +1,44 @@
+# 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 <luca@lucaceresoli.net>
+
+description: |
+  The Maxim MAX77976 is a 19Vin / 5.5A, 1-Cell Li+ battery charger
+  configured via I2C.
+
+allOf:
+  - $ref: power-supply.yaml#
+
+properties:
+  compatible:
+    const: maxim,max77976
+
+  reg:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      charger@6b {
+        compatible = "maxim,max77976";
+        reg = <0x6b>;
+      };
+    };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index a6d670c58f3e..21b38ada178c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11409,6 +11409,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 <luca@lucaceresoli.net>
+S:	Supported
+F:	Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
+
 MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
 M:	Krzysztof Kozlowski <krzysztof.kozlowski@canonical.com>
 M:	Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v2 2/3] power: supply: core: add POWER_SUPPLY_HEALTH_NO_BATTERY
  2021-11-08 16:27 [PATCH v2 1/3] dt-bindings: power: supply: add Maxim MAX77976 battery charger Luca Ceresoli
@ 2021-11-08 16:27 ` Luca Ceresoli
  2021-11-08 16:27 ` [PATCH v2 3/3] power: supply: max77976: add Maxim MAX77976 charger driver Luca Ceresoli
  1 sibling, 0 replies; 5+ messages in thread
From: Luca Ceresoli @ 2021-11-08 16:27 UTC (permalink / raw)
  To: linux-pm
  Cc: Luca Ceresoli, devicetree, linux-kernel, Sebastian Reichel,
	Rob Herring, Randy Dunlap, Sebastian Reichel

Some chargers can keep the system powered from the mains even when no
battery is present. It this case none of the currently defined health
statuses applies. Add a new status to report that no battery is present.

Suggested-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net>

---

This patch is new in v2.
---
 Documentation/ABI/testing/sysfs-class-power | 2 +-
 drivers/power/supply/power_supply_sysfs.c   | 1 +
 include/linux/power_supply.h                | 1 +
 3 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power
index f7904efc4cfa..a0b2a4280e38 100644
--- a/Documentation/ABI/testing/sysfs-class-power
+++ b/Documentation/ABI/testing/sysfs-class-power
@@ -413,7 +413,7 @@ Description:
 			      "Over voltage", "Unspecified failure", "Cold",
 			      "Watchdog timer expire", "Safety timer expire",
 			      "Over current", "Calibration required", "Warm",
-			      "Cool", "Hot"
+			      "Cool", "Hot", "No battery"
 
 What:		/sys/class/power_supply/<supply_name>/precharge_current
 Date:		June 2017
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index c3d7cbcd4fad..6ac88fbee3cb 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -106,6 +106,7 @@ static const char * const POWER_SUPPLY_HEALTH_TEXT[] = {
 	[POWER_SUPPLY_HEALTH_WARM]		    = "Warm",
 	[POWER_SUPPLY_HEALTH_COOL]		    = "Cool",
 	[POWER_SUPPLY_HEALTH_HOT]		    = "Hot",
+	[POWER_SUPPLY_HEALTH_NO_BATTERY]	    = "No battery",
 };
 
 static const char * const POWER_SUPPLY_TECHNOLOGY_TEXT[] = {
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 9ca1f120a211..2d1318fe2455 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -66,6 +66,7 @@ enum {
 	POWER_SUPPLY_HEALTH_WARM,
 	POWER_SUPPLY_HEALTH_COOL,
 	POWER_SUPPLY_HEALTH_HOT,
+	POWER_SUPPLY_HEALTH_NO_BATTERY,
 };
 
 enum {
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v2 3/3] power: supply: max77976: add Maxim MAX77976 charger driver
  2021-11-08 16:27 [PATCH v2 1/3] dt-bindings: power: supply: add Maxim MAX77976 battery charger Luca Ceresoli
  2021-11-08 16:27 ` [PATCH v2 2/3] power: supply: core: add POWER_SUPPLY_HEALTH_NO_BATTERY Luca Ceresoli
@ 2021-11-08 16:27 ` Luca Ceresoli
  2021-11-16 15:21   ` Sebastian Reichel
  1 sibling, 1 reply; 5+ messages in thread
From: Luca Ceresoli @ 2021-11-08 16:27 UTC (permalink / raw)
  To: linux-pm
  Cc: Luca Ceresoli, devicetree, linux-kernel, Sebastian Reichel,
	Rob Herring, Randy Dunlap

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

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

Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net>

---

Changes in v2:
 - don't depend on REGMAP_I2C, select it and depend on I2C (Randy Dunlap)
 - use the newly-introduced POWER_SUPPLY_HEALTH_NO_BATTERY (Sebastian Reichel)
 - report "unknown" battery health when BAT_DTLS reports prequalification
   state (as discussed with Sebastian Reichel)
 - change type to POWER_SUPPLY_TYPE_USB (Sebastian Reichel)
 - add missing empty line (Sebastian Reichel)
---
 MAINTAINERS                             |   1 +
 drivers/power/supply/Kconfig            |  12 +
 drivers/power/supply/Makefile           |   1 +
 drivers/power/supply/max77976_charger.c | 509 ++++++++++++++++++++++++
 4 files changed, 523 insertions(+)
 create mode 100644 drivers/power/supply/max77976_charger.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 21b38ada178c..94a1f2d03a43 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11413,6 +11413,7 @@ MAXIM MAX77976 BATTERY CHARGER
 M:	Luca Ceresoli <luca@lucaceresoli.net>
 S:	Supported
 F:	Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
+F:	drivers/power/supply/max77976_charger.c
 
 MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
 M:	Krzysztof Kozlowski <krzysztof.kozlowski@canonical.com>
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 5cf5bb56d2e3..b366e2fd8e97 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -557,6 +557,18 @@ 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 I2C
+	select 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..1ff2042071c3
--- /dev/null
+++ b/drivers/power/supply/max77976_charger.c
@@ -0,0 +1,509 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * max77976_charger.c - Driver for the Maxim MAX77976 battery charger
+ *
+ * Copyright (C) 2021 Luca Ceresoli
+ * Author: Luca Ceresoli <luca@lucaceresoli.net>
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+
+#define MAX77976_DRIVER_NAME	"max77976-charger"
+#define MAX77976_CHIP_ID	0x76
+
+static const char *max77976_manufacturer	= "Maxim Integrated";
+static const char *max77976_model		= "MAX77976";
+
+/* --------------------------------------------------------------------------
+ * Register map
+ */
+
+#define MAX77976_REG_CHIP_ID		0x00
+#define MAX77976_REG_CHIP_REVISION	0x01
+#define MAX77976_REG_CHG_INT_OK		0x12
+#define MAX77976_REG_CHG_DETAILS_01	0x14
+#define MAX77976_REG_CHG_CNFG_00	0x16
+#define MAX77976_REG_CHG_CNFG_02	0x18
+#define MAX77976_REG_CHG_CNFG_06	0x1c
+#define MAX77976_REG_CHG_CNFG_09	0x1f
+
+/* CHG_DETAILS_01.CHG_DTLS values */
+enum max77976_charging_state {
+	MAX77976_CHARGING_PREQUALIFICATION = 0x0,
+	MAX77976_CHARGING_FAST_CONST_CURRENT,
+	MAX77976_CHARGING_FAST_CONST_VOLTAGE,
+	MAX77976_CHARGING_TOP_OFF,
+	MAX77976_CHARGING_DONE,
+	MAX77976_CHARGING_RESERVED_05,
+	MAX77976_CHARGING_TIMER_FAULT,
+	MAX77976_CHARGING_SUSPENDED_QBATT_OFF,
+	MAX77976_CHARGING_OFF,
+	MAX77976_CHARGING_RESERVED_09,
+	MAX77976_CHARGING_THERMAL_SHUTDOWN,
+	MAX77976_CHARGING_WATCHDOG_EXPIRED,
+	MAX77976_CHARGING_SUSPENDED_JEITA,
+	MAX77976_CHARGING_SUSPENDED_THM_REMOVAL,
+	MAX77976_CHARGING_SUSPENDED_PIN,
+	MAX77976_CHARGING_RESERVED_0F,
+};
+
+/* CHG_DETAILS_01.BAT_DTLS values */
+enum max77976_battery_state {
+	MAX77976_BATTERY_BATTERY_REMOVAL = 0x0,
+	MAX77976_BATTERY_PREQUALIFICATION,
+	MAX77976_BATTERY_TIMER_FAULT,
+	MAX77976_BATTERY_REGULAR_VOLTAGE,
+	MAX77976_BATTERY_LOW_VOLTAGE,
+	MAX77976_BATTERY_OVERVOLTAGE,
+	MAX77976_BATTERY_RESERVED,
+	MAX77976_BATTERY_BATTERY_ONLY, // No valid adapter is present
+};
+
+/* CHG_CNFG_00.MODE values */
+enum max77976_mode {
+	MAX77976_MODE_CHARGER_BUCK		= 0x5,
+	MAX77976_MODE_BOOST			= 0x9,
+};
+
+/* CHG_CNFG_02.CHG_CC: charge current limit, 100..5500 mA, 50 mA steps */
+#define MAX77976_CHG_CC_STEP			  50000U
+#define MAX77976_CHG_CC_MIN			 100000U
+#define MAX77976_CHG_CC_MAX			5500000U
+
+/* CHG_CNFG_09.CHGIN_ILIM: input current limit, 100..3200 mA, 100 mA steps */
+#define MAX77976_CHGIN_ILIM_STEP		 100000U
+#define MAX77976_CHGIN_ILIM_MIN			 100000U
+#define MAX77976_CHGIN_ILIM_MAX			3200000U
+
+enum max77976_field_idx {
+	VERSION, REVISION,                      /* CHIP_REVISION */
+	CHGIN_OK,                               /* CHG_INT_OK */
+	BAT_DTLS, CHG_DTLS,                     /* CHG_DETAILS_01 */
+	MODE,                                   /* CHG_CNFG_00 */
+	CHG_CC,                                 /* CHG_CNFG_02 */
+	CHGPROT,                                /* CHG_CNFG_06 */
+	CHGIN_ILIM,                             /* CHG_CNFG_09 */
+	MAX77976_N_REGMAP_FIELDS
+};
+
+static const struct reg_field max77976_reg_field[MAX77976_N_REGMAP_FIELDS] = {
+	[VERSION]        = REG_FIELD(MAX77976_REG_CHIP_REVISION,   4, 7),
+	[REVISION]       = REG_FIELD(MAX77976_REG_CHIP_REVISION,   0, 3),
+	[CHGIN_OK]       = REG_FIELD(MAX77976_REG_CHG_INT_OK,      6, 6),
+	[CHG_DTLS]       = REG_FIELD(MAX77976_REG_CHG_DETAILS_01,  0, 3),
+	[BAT_DTLS]       = REG_FIELD(MAX77976_REG_CHG_DETAILS_01,  4, 6),
+	[MODE]           = REG_FIELD(MAX77976_REG_CHG_CNFG_00,     0, 3),
+	[CHG_CC]         = REG_FIELD(MAX77976_REG_CHG_CNFG_02,     0, 6),
+	[CHGPROT]        = REG_FIELD(MAX77976_REG_CHG_CNFG_06,     2, 3),
+	[CHGIN_ILIM]     = REG_FIELD(MAX77976_REG_CHG_CNFG_09,     0, 5),
+};
+
+static const struct regmap_config max77976_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0x24,
+};
+
+/* --------------------------------------------------------------------------
+ * Data structures
+ */
+
+struct max77976 {
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct regmap_field	*rfield[MAX77976_N_REGMAP_FIELDS];
+};
+
+/* --------------------------------------------------------------------------
+ * power_supply properties
+ */
+
+static int max77976_get_status(struct max77976 *chg, int *val)
+{
+	unsigned int regval;
+	int err;
+
+	err = regmap_field_read(chg->rfield[CHG_DTLS], &regval);
+	if (err < 0)
+		return err;
+
+	switch (regval) {
+	case MAX77976_CHARGING_PREQUALIFICATION:
+	case MAX77976_CHARGING_FAST_CONST_CURRENT:
+	case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
+	case MAX77976_CHARGING_TOP_OFF:
+		*val = POWER_SUPPLY_STATUS_CHARGING;
+		break;
+	case MAX77976_CHARGING_DONE:
+		*val = POWER_SUPPLY_STATUS_FULL;
+		break;
+	case MAX77976_CHARGING_TIMER_FAULT:
+	case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
+	case MAX77976_CHARGING_SUSPENDED_JEITA:
+	case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
+	case MAX77976_CHARGING_SUSPENDED_PIN:
+		*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		break;
+	case MAX77976_CHARGING_OFF:
+	case MAX77976_CHARGING_THERMAL_SHUTDOWN:
+	case MAX77976_CHARGING_WATCHDOG_EXPIRED:
+		*val = POWER_SUPPLY_STATUS_DISCHARGING;
+		break;
+	default:
+		*val = POWER_SUPPLY_STATUS_UNKNOWN;
+	}
+
+	return 0;
+}
+
+static int max77976_get_charge_type(struct max77976 *chg, int *val)
+{
+	unsigned int regval;
+	int err;
+
+	err = regmap_field_read(chg->rfield[CHG_DTLS], &regval);
+	if (err < 0)
+		return err;
+
+	switch (regval) {
+	case MAX77976_CHARGING_PREQUALIFICATION:
+		*val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+		break;
+	case MAX77976_CHARGING_FAST_CONST_CURRENT:
+	case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
+		*val = POWER_SUPPLY_CHARGE_TYPE_FAST;
+		break;
+	case MAX77976_CHARGING_TOP_OFF:
+		*val = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+		break;
+	case MAX77976_CHARGING_DONE:
+	case MAX77976_CHARGING_TIMER_FAULT:
+	case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
+	case MAX77976_CHARGING_OFF:
+	case MAX77976_CHARGING_THERMAL_SHUTDOWN:
+	case MAX77976_CHARGING_WATCHDOG_EXPIRED:
+	case MAX77976_CHARGING_SUSPENDED_JEITA:
+	case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
+	case MAX77976_CHARGING_SUSPENDED_PIN:
+		*val = POWER_SUPPLY_CHARGE_TYPE_NONE;
+		break;
+	default:
+		*val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+	}
+
+	return 0;
+}
+
+static int max77976_get_health(struct max77976 *chg, int *val)
+{
+	unsigned int regval;
+	int err;
+
+	err = regmap_field_read(chg->rfield[BAT_DTLS], &regval);
+	if (err < 0)
+		return err;
+
+	switch (regval) {
+	case MAX77976_BATTERY_BATTERY_REMOVAL:
+		*val = POWER_SUPPLY_HEALTH_NO_BATTERY;
+		break;
+	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_PREQUALIFICATION:
+	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_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;
+}
+
+static int max77976_configure(struct max77976 *chg)
+{
+	struct device *dev = &chg->client->dev;
+	int err;
+
+	/* Magic value to unlock writing to some registers */
+	err = regmap_field_write(chg->rfield[CHGPROT], 0x3);
+	if (err)
+		goto err;
+
+	/*
+	 * Mode 5 = Charger ON, OTG OFF, buck ON, boost OFF.
+	 * Other modes are not implemented by this driver.
+	 */
+	err = regmap_field_write(chg->rfield[MODE], MAX77976_MODE_CHARGER_BUCK);
+	if (err)
+		goto err;
+
+	return 0;
+
+err:
+	return dev_err_probe(dev, err, "error while configuring");
+}
+
+static int max77976_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct power_supply_config psy_cfg = {};
+	struct power_supply *psy;
+	struct max77976 *chg;
+	int err;
+	int i;
+
+	chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL);
+	if (!chg)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, chg);
+	psy_cfg.drv_data = chg;
+	chg->client = client;
+
+	chg->regmap = devm_regmap_init_i2c(client, &max77976_regmap_config);
+	if (IS_ERR(chg->regmap))
+		return dev_err_probe(dev, PTR_ERR(chg->regmap),
+				     "cannot allocate regmap\n");
+
+	for (i = 0; i < MAX77976_N_REGMAP_FIELDS; i++) {
+		chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap,
+							 max77976_reg_field[i]);
+		if (IS_ERR(chg->rfield[i]))
+			return dev_err_probe(dev, PTR_ERR(chg->rfield[i]),
+					     "cannot allocate regmap field\n");
+	}
+
+	err = max77976_detect(chg);
+	if (err)
+		return err;
+
+	err = max77976_configure(chg);
+	if (err)
+		return err;
+
+	psy = devm_power_supply_register_no_ws(dev, &max77976_psy_desc, &psy_cfg);
+	if (IS_ERR(psy))
+		return dev_err_probe(dev, PTR_ERR(psy), "cannot register\n");
+
+	return 0;
+}
+
+static const struct i2c_device_id max77976_i2c_id[] = {
+	{ MAX77976_DRIVER_NAME, 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, max77976_i2c_id);
+
+static const struct of_device_id max77976_of_id[] = {
+	{ .compatible = "maxim,max77976" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, max77976_of_id);
+
+static struct i2c_driver max77976_driver = {
+	.driver = {
+		.name		= MAX77976_DRIVER_NAME,
+		.of_match_table	= of_match_ptr(max77976_of_id),
+	},
+	.probe_new	= max77976_probe,
+	.id_table	= max77976_i2c_id,
+};
+module_i2c_driver(max77976_driver);
+
+MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>");
+MODULE_DESCRIPTION("Maxim MAX77976 charger driver");
+MODULE_LICENSE("GPL v2");
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v2 3/3] power: supply: max77976: add Maxim MAX77976 charger driver
  2021-11-08 16:27 ` [PATCH v2 3/3] power: supply: max77976: add Maxim MAX77976 charger driver Luca Ceresoli
@ 2021-11-16 15:21   ` Sebastian Reichel
  2021-11-16 17:35     ` Luca Ceresoli
  0 siblings, 1 reply; 5+ messages in thread
From: Sebastian Reichel @ 2021-11-16 15:21 UTC (permalink / raw)
  To: Luca Ceresoli
  Cc: linux-pm, devicetree, linux-kernel, Rob Herring, Randy Dunlap

[-- Attachment #1: Type: text/plain, Size: 19507 bytes --]

Hi,

On Mon, Nov 08, 2021 at 05:27:06PM +0100, Luca Ceresoli wrote:
> Add support for the MAX77976 3.5/5.5A 1-Cell Li+ Battery Charger.
> 
> This is a simple implementation enough to be used as a simple battery
> charger without OTG and boost.
> 
> Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net>
> 
> ---

Thanks, I queued this to power-supply's for-next branch together
with the other 2 patches with one small change: The of_match_ptr()
is not needed and actually should result in a warning when building
the driver with CONFIG_OF disabled.

> 
> Changes in v2:
>  - don't depend on REGMAP_I2C, select it and depend on I2C (Randy Dunlap)
>  - use the newly-introduced POWER_SUPPLY_HEALTH_NO_BATTERY (Sebastian Reichel)
>  - report "unknown" battery health when BAT_DTLS reports prequalification
>    state (as discussed with Sebastian Reichel)
>  - change type to POWER_SUPPLY_TYPE_USB (Sebastian Reichel)
>  - add missing empty line (Sebastian Reichel)
> ---
>  MAINTAINERS                             |   1 +
>  drivers/power/supply/Kconfig            |  12 +
>  drivers/power/supply/Makefile           |   1 +
>  drivers/power/supply/max77976_charger.c | 509 ++++++++++++++++++++++++
>  4 files changed, 523 insertions(+)
>  create mode 100644 drivers/power/supply/max77976_charger.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 21b38ada178c..94a1f2d03a43 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11413,6 +11413,7 @@ MAXIM MAX77976 BATTERY CHARGER
>  M:	Luca Ceresoli <luca@lucaceresoli.net>
>  S:	Supported
>  F:	Documentation/devicetree/bindings/power/supply/maxim,max77976.yaml
> +F:	drivers/power/supply/max77976_charger.c
>  
>  MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
>  M:	Krzysztof Kozlowski <krzysztof.kozlowski@canonical.com>
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index 5cf5bb56d2e3..b366e2fd8e97 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -557,6 +557,18 @@ 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 I2C
> +	select 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..1ff2042071c3
> --- /dev/null
> +++ b/drivers/power/supply/max77976_charger.c
> @@ -0,0 +1,509 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * max77976_charger.c - Driver for the Maxim MAX77976 battery charger
> + *
> + * Copyright (C) 2021 Luca Ceresoli
> + * Author: Luca Ceresoli <luca@lucaceresoli.net>
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/power_supply.h>
> +#include <linux/regmap.h>
> +
> +#define MAX77976_DRIVER_NAME	"max77976-charger"
> +#define MAX77976_CHIP_ID	0x76
> +
> +static const char *max77976_manufacturer	= "Maxim Integrated";
> +static const char *max77976_model		= "MAX77976";
> +
> +/* --------------------------------------------------------------------------
> + * Register map
> + */
> +
> +#define MAX77976_REG_CHIP_ID		0x00
> +#define MAX77976_REG_CHIP_REVISION	0x01
> +#define MAX77976_REG_CHG_INT_OK		0x12
> +#define MAX77976_REG_CHG_DETAILS_01	0x14
> +#define MAX77976_REG_CHG_CNFG_00	0x16
> +#define MAX77976_REG_CHG_CNFG_02	0x18
> +#define MAX77976_REG_CHG_CNFG_06	0x1c
> +#define MAX77976_REG_CHG_CNFG_09	0x1f
> +
> +/* CHG_DETAILS_01.CHG_DTLS values */
> +enum max77976_charging_state {
> +	MAX77976_CHARGING_PREQUALIFICATION = 0x0,
> +	MAX77976_CHARGING_FAST_CONST_CURRENT,
> +	MAX77976_CHARGING_FAST_CONST_VOLTAGE,
> +	MAX77976_CHARGING_TOP_OFF,
> +	MAX77976_CHARGING_DONE,
> +	MAX77976_CHARGING_RESERVED_05,
> +	MAX77976_CHARGING_TIMER_FAULT,
> +	MAX77976_CHARGING_SUSPENDED_QBATT_OFF,
> +	MAX77976_CHARGING_OFF,
> +	MAX77976_CHARGING_RESERVED_09,
> +	MAX77976_CHARGING_THERMAL_SHUTDOWN,
> +	MAX77976_CHARGING_WATCHDOG_EXPIRED,
> +	MAX77976_CHARGING_SUSPENDED_JEITA,
> +	MAX77976_CHARGING_SUSPENDED_THM_REMOVAL,
> +	MAX77976_CHARGING_SUSPENDED_PIN,
> +	MAX77976_CHARGING_RESERVED_0F,
> +};
> +
> +/* CHG_DETAILS_01.BAT_DTLS values */
> +enum max77976_battery_state {
> +	MAX77976_BATTERY_BATTERY_REMOVAL = 0x0,
> +	MAX77976_BATTERY_PREQUALIFICATION,
> +	MAX77976_BATTERY_TIMER_FAULT,
> +	MAX77976_BATTERY_REGULAR_VOLTAGE,
> +	MAX77976_BATTERY_LOW_VOLTAGE,
> +	MAX77976_BATTERY_OVERVOLTAGE,
> +	MAX77976_BATTERY_RESERVED,
> +	MAX77976_BATTERY_BATTERY_ONLY, // No valid adapter is present
> +};
> +
> +/* CHG_CNFG_00.MODE values */
> +enum max77976_mode {
> +	MAX77976_MODE_CHARGER_BUCK		= 0x5,
> +	MAX77976_MODE_BOOST			= 0x9,
> +};
> +
> +/* CHG_CNFG_02.CHG_CC: charge current limit, 100..5500 mA, 50 mA steps */
> +#define MAX77976_CHG_CC_STEP			  50000U
> +#define MAX77976_CHG_CC_MIN			 100000U
> +#define MAX77976_CHG_CC_MAX			5500000U
> +
> +/* CHG_CNFG_09.CHGIN_ILIM: input current limit, 100..3200 mA, 100 mA steps */
> +#define MAX77976_CHGIN_ILIM_STEP		 100000U
> +#define MAX77976_CHGIN_ILIM_MIN			 100000U
> +#define MAX77976_CHGIN_ILIM_MAX			3200000U
> +
> +enum max77976_field_idx {
> +	VERSION, REVISION,                      /* CHIP_REVISION */
> +	CHGIN_OK,                               /* CHG_INT_OK */
> +	BAT_DTLS, CHG_DTLS,                     /* CHG_DETAILS_01 */
> +	MODE,                                   /* CHG_CNFG_00 */
> +	CHG_CC,                                 /* CHG_CNFG_02 */
> +	CHGPROT,                                /* CHG_CNFG_06 */
> +	CHGIN_ILIM,                             /* CHG_CNFG_09 */
> +	MAX77976_N_REGMAP_FIELDS
> +};
> +
> +static const struct reg_field max77976_reg_field[MAX77976_N_REGMAP_FIELDS] = {
> +	[VERSION]        = REG_FIELD(MAX77976_REG_CHIP_REVISION,   4, 7),
> +	[REVISION]       = REG_FIELD(MAX77976_REG_CHIP_REVISION,   0, 3),
> +	[CHGIN_OK]       = REG_FIELD(MAX77976_REG_CHG_INT_OK,      6, 6),
> +	[CHG_DTLS]       = REG_FIELD(MAX77976_REG_CHG_DETAILS_01,  0, 3),
> +	[BAT_DTLS]       = REG_FIELD(MAX77976_REG_CHG_DETAILS_01,  4, 6),
> +	[MODE]           = REG_FIELD(MAX77976_REG_CHG_CNFG_00,     0, 3),
> +	[CHG_CC]         = REG_FIELD(MAX77976_REG_CHG_CNFG_02,     0, 6),
> +	[CHGPROT]        = REG_FIELD(MAX77976_REG_CHG_CNFG_06,     2, 3),
> +	[CHGIN_ILIM]     = REG_FIELD(MAX77976_REG_CHG_CNFG_09,     0, 5),
> +};
> +
> +static const struct regmap_config max77976_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.max_register = 0x24,
> +};
> +
> +/* --------------------------------------------------------------------------
> + * Data structures
> + */
> +
> +struct max77976 {
> +	struct i2c_client	*client;
> +	struct regmap		*regmap;
> +	struct regmap_field	*rfield[MAX77976_N_REGMAP_FIELDS];
> +};
> +
> +/* --------------------------------------------------------------------------
> + * power_supply properties
> + */
> +
> +static int max77976_get_status(struct max77976 *chg, int *val)
> +{
> +	unsigned int regval;
> +	int err;
> +
> +	err = regmap_field_read(chg->rfield[CHG_DTLS], &regval);
> +	if (err < 0)
> +		return err;
> +
> +	switch (regval) {
> +	case MAX77976_CHARGING_PREQUALIFICATION:
> +	case MAX77976_CHARGING_FAST_CONST_CURRENT:
> +	case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
> +	case MAX77976_CHARGING_TOP_OFF:
> +		*val = POWER_SUPPLY_STATUS_CHARGING;
> +		break;
> +	case MAX77976_CHARGING_DONE:
> +		*val = POWER_SUPPLY_STATUS_FULL;
> +		break;
> +	case MAX77976_CHARGING_TIMER_FAULT:
> +	case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
> +	case MAX77976_CHARGING_SUSPENDED_JEITA:
> +	case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
> +	case MAX77976_CHARGING_SUSPENDED_PIN:
> +		*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
> +		break;
> +	case MAX77976_CHARGING_OFF:
> +	case MAX77976_CHARGING_THERMAL_SHUTDOWN:
> +	case MAX77976_CHARGING_WATCHDOG_EXPIRED:
> +		*val = POWER_SUPPLY_STATUS_DISCHARGING;
> +		break;
> +	default:
> +		*val = POWER_SUPPLY_STATUS_UNKNOWN;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max77976_get_charge_type(struct max77976 *chg, int *val)
> +{
> +	unsigned int regval;
> +	int err;
> +
> +	err = regmap_field_read(chg->rfield[CHG_DTLS], &regval);
> +	if (err < 0)
> +		return err;
> +
> +	switch (regval) {
> +	case MAX77976_CHARGING_PREQUALIFICATION:
> +		*val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
> +		break;
> +	case MAX77976_CHARGING_FAST_CONST_CURRENT:
> +	case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
> +		*val = POWER_SUPPLY_CHARGE_TYPE_FAST;
> +		break;
> +	case MAX77976_CHARGING_TOP_OFF:
> +		*val = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
> +		break;
> +	case MAX77976_CHARGING_DONE:
> +	case MAX77976_CHARGING_TIMER_FAULT:
> +	case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
> +	case MAX77976_CHARGING_OFF:
> +	case MAX77976_CHARGING_THERMAL_SHUTDOWN:
> +	case MAX77976_CHARGING_WATCHDOG_EXPIRED:
> +	case MAX77976_CHARGING_SUSPENDED_JEITA:
> +	case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
> +	case MAX77976_CHARGING_SUSPENDED_PIN:
> +		*val = POWER_SUPPLY_CHARGE_TYPE_NONE;
> +		break;
> +	default:
> +		*val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max77976_get_health(struct max77976 *chg, int *val)
> +{
> +	unsigned int regval;
> +	int err;
> +
> +	err = regmap_field_read(chg->rfield[BAT_DTLS], &regval);
> +	if (err < 0)
> +		return err;
> +
> +	switch (regval) {
> +	case MAX77976_BATTERY_BATTERY_REMOVAL:
> +		*val = POWER_SUPPLY_HEALTH_NO_BATTERY;
> +		break;
> +	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_PREQUALIFICATION:
> +	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_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;
> +}
> +
> +static int max77976_configure(struct max77976 *chg)
> +{
> +	struct device *dev = &chg->client->dev;
> +	int err;
> +
> +	/* Magic value to unlock writing to some registers */
> +	err = regmap_field_write(chg->rfield[CHGPROT], 0x3);
> +	if (err)
> +		goto err;
> +
> +	/*
> +	 * Mode 5 = Charger ON, OTG OFF, buck ON, boost OFF.
> +	 * Other modes are not implemented by this driver.
> +	 */
> +	err = regmap_field_write(chg->rfield[MODE], MAX77976_MODE_CHARGER_BUCK);
> +	if (err)
> +		goto err;
> +
> +	return 0;
> +
> +err:
> +	return dev_err_probe(dev, err, "error while configuring");
> +}
> +
> +static int max77976_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct power_supply_config psy_cfg = {};
> +	struct power_supply *psy;
> +	struct max77976 *chg;
> +	int err;
> +	int i;
> +
> +	chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL);
> +	if (!chg)
> +		return -ENOMEM;
> +
> +	i2c_set_clientdata(client, chg);
> +	psy_cfg.drv_data = chg;
> +	chg->client = client;
> +
> +	chg->regmap = devm_regmap_init_i2c(client, &max77976_regmap_config);
> +	if (IS_ERR(chg->regmap))
> +		return dev_err_probe(dev, PTR_ERR(chg->regmap),
> +				     "cannot allocate regmap\n");
> +
> +	for (i = 0; i < MAX77976_N_REGMAP_FIELDS; i++) {
> +		chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap,
> +							 max77976_reg_field[i]);
> +		if (IS_ERR(chg->rfield[i]))
> +			return dev_err_probe(dev, PTR_ERR(chg->rfield[i]),
> +					     "cannot allocate regmap field\n");
> +	}
> +
> +	err = max77976_detect(chg);
> +	if (err)
> +		return err;
> +
> +	err = max77976_configure(chg);
> +	if (err)
> +		return err;
> +
> +	psy = devm_power_supply_register_no_ws(dev, &max77976_psy_desc, &psy_cfg);
> +	if (IS_ERR(psy))
> +		return dev_err_probe(dev, PTR_ERR(psy), "cannot register\n");
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id max77976_i2c_id[] = {
> +	{ MAX77976_DRIVER_NAME, 0 },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(i2c, max77976_i2c_id);
> +
> +static const struct of_device_id max77976_of_id[] = {
> +	{ .compatible = "maxim,max77976" },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, max77976_of_id);
> +
> +static struct i2c_driver max77976_driver = {
> +	.driver = {
> +		.name		= MAX77976_DRIVER_NAME,
> +		.of_match_table	= of_match_ptr(max77976_of_id),
> +	},
> +	.probe_new	= max77976_probe,
> +	.id_table	= max77976_i2c_id,
> +};
> +module_i2c_driver(max77976_driver);
> +
> +MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>");
> +MODULE_DESCRIPTION("Maxim MAX77976 charger driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.25.1
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v2 3/3] power: supply: max77976: add Maxim MAX77976 charger driver
  2021-11-16 15:21   ` Sebastian Reichel
@ 2021-11-16 17:35     ` Luca Ceresoli
  0 siblings, 0 replies; 5+ messages in thread
From: Luca Ceresoli @ 2021-11-16 17:35 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: linux-pm, devicetree, linux-kernel, Rob Herring, Randy Dunlap

Hi Sebastian,

On 16/11/21 16:21, Sebastian Reichel wrote:
> Hi,
> 
> On Mon, Nov 08, 2021 at 05:27:06PM +0100, Luca Ceresoli wrote:
>> Add support for the MAX77976 3.5/5.5A 1-Cell Li+ Battery Charger.
>>
>> This is a simple implementation enough to be used as a simple battery
>> charger without OTG and boost.
>>
>> Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net>
>>
>> ---
> 
> Thanks, I queued this to power-supply's for-next branch together
> with the other 2 patches

Thanks!

> with one small change: The of_match_ptr()
> is not needed and actually should result in a warning when building
> the driver with CONFIG_OF disabled.

Ouch. I made the same mistake in the past and the kernel test robot
noticed, I wonder why it didn't this time but hopefully I will remember
in the future!

Regards,
-- 
Luca

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2021-11-16 17:36 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-08 16:27 [PATCH v2 1/3] dt-bindings: power: supply: add Maxim MAX77976 battery charger Luca Ceresoli
2021-11-08 16:27 ` [PATCH v2 2/3] power: supply: core: add POWER_SUPPLY_HEALTH_NO_BATTERY Luca Ceresoli
2021-11-08 16:27 ` [PATCH v2 3/3] power: supply: max77976: add Maxim MAX77976 charger driver Luca Ceresoli
2021-11-16 15:21   ` Sebastian Reichel
2021-11-16 17:35     ` Luca Ceresoli

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.