* [PATCH v2 0/2] Add support for Samsung S2MU005 battery fuel gauge device
@ 2026-02-08 17:52 Kaustabh Chakraborty
2026-02-08 17:52 ` [PATCH v2 1/2] dt-bindings: power: supply: document Samsung S2MU005 battery fuel gauge Kaustabh Chakraborty
2026-02-08 17:52 ` [PATCH v2 2/2] power: supply: add support for S2MU005 battery fuel gauge device Kaustabh Chakraborty
0 siblings, 2 replies; 5+ messages in thread
From: Kaustabh Chakraborty @ 2026-02-08 17:52 UTC (permalink / raw)
To: Yassine Oudjana, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-pm, devicetree, linux-kernel, Kaustabh Chakraborty
This patch series adds supports for the battery fuel gauge device for
Samsung S2MU005 PMIC battery chargers. It reports various metrics,
including incoming voltage, current, battery capacity, etc.
Although this device is independent of the actual PMIC which it
accompanies in functionality, it is used in conjunction with the PMIC's
charger sub-device to provide complete battery status.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
Changes in v2:
- s/fuelgauge/fuel-gauge in dt-bindings (Conor Dooley)
- do not print error messages for -ENOMEM (Sebastian Reichel)
- remove redundant irq_get_trigger_type() (Sebastian Reichel)
- set regmap val_bits to 16 (Sebastian Reichel)
- switch to regmap_{read,write}()
- add current_avg and voltage_avg properties
- implement workaround for false positives due to hysteresis
- Link to v1: https://lore.kernel.org/r/20260126-s2mu005-fuelgauge-v1-0-68a146ed0819@disroot.org
---
Kaustabh Chakraborty (1):
dt-bindings: power: supply: document Samsung S2MU005 battery fuel gauge
Yassine Oudjana (1):
power: supply: add support for S2MU005 battery fuel gauge device
.../power/supply/samsung,s2mu005-fuel-gauge.yaml | 49 ++++
drivers/power/supply/Kconfig | 9 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/s2mu005-battery.c | 306 +++++++++++++++++++++
4 files changed, 365 insertions(+)
---
base-commit: 9845cf73f7db6094c0d8419d6adb848028f4a921
change-id: 20260126-s2mu005-fuelgauge-25e9d95e30b6
Best regards,
--
Kaustabh Chakraborty <kauschluss@disroot.org>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 1/2] dt-bindings: power: supply: document Samsung S2MU005 battery fuel gauge
2026-02-08 17:52 [PATCH v2 0/2] Add support for Samsung S2MU005 battery fuel gauge device Kaustabh Chakraborty
@ 2026-02-08 17:52 ` Kaustabh Chakraborty
2026-02-10 3:01 ` Rob Herring (Arm)
2026-02-08 17:52 ` [PATCH v2 2/2] power: supply: add support for S2MU005 battery fuel gauge device Kaustabh Chakraborty
1 sibling, 1 reply; 5+ messages in thread
From: Kaustabh Chakraborty @ 2026-02-08 17:52 UTC (permalink / raw)
To: Yassine Oudjana, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-pm, devicetree, linux-kernel, Kaustabh Chakraborty
Samsung S2MU005 is a PMIC device which has LED controllers, an MUIC and
a battery charger. The battery charger is paired with an independent
device connected via I2C which can be used to access various metrics of
the battery. Document the device as a schema.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
.../power/supply/samsung,s2mu005-fuel-gauge.yaml | 49 ++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/Documentation/devicetree/bindings/power/supply/samsung,s2mu005-fuel-gauge.yaml b/Documentation/devicetree/bindings/power/supply/samsung,s2mu005-fuel-gauge.yaml
new file mode 100644
index 0000000000000..05e420316a26b
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/samsung,s2mu005-fuel-gauge.yaml
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/power/supply/samsung,s2mu005-fuel-gauge.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Battery Fuel Gauge for Samsung S2M series PMICs
+
+maintainers:
+ - Kaustabh Chakraborty <kauschluss@disroot.org>
+
+allOf:
+ - $ref: power-supply.yaml#
+
+properties:
+ compatible:
+ enum:
+ - samsung,s2mu005-fuel-gauge
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ fuel-gauge@3b {
+ compatible = "samsung,s2mu005-fuel-gauge";
+ reg = <0x3b>;
+
+ interrupt-parent = <&gpa0>;
+ interrupts = <3 IRQ_TYPE_EDGE_BOTH>;
+
+ monitored-battery = <&battery>;
+ };
+ };
--
2.52.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v2 2/2] power: supply: add support for S2MU005 battery fuel gauge device
2026-02-08 17:52 [PATCH v2 0/2] Add support for Samsung S2MU005 battery fuel gauge device Kaustabh Chakraborty
2026-02-08 17:52 ` [PATCH v2 1/2] dt-bindings: power: supply: document Samsung S2MU005 battery fuel gauge Kaustabh Chakraborty
@ 2026-02-08 17:52 ` Kaustabh Chakraborty
2026-03-02 23:40 ` Sebastian Reichel
1 sibling, 1 reply; 5+ messages in thread
From: Kaustabh Chakraborty @ 2026-02-08 17:52 UTC (permalink / raw)
To: Yassine Oudjana, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-pm, devicetree, linux-kernel, Kaustabh Chakraborty
From: Yassine Oudjana <y.oudjana@protonmail.com>
Samsung's S2MU005 PMIC, which contains battery charger functionality
also includes a battery fuel gauge device, which is separate from the
PMIC itself, and typically connected to an I2C bus. Add a generic driver
to support said device.
Signed-off-by: Yassine Oudjana <y.oudjana@protonmail.com>
Co-developed-by: Kaustabh Chakraborty <kauschluss@disroot.org>
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
drivers/power/supply/Kconfig | 9 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/s2mu005-battery.c | 306 +++++++++++++++++++++++++++++++++
3 files changed, 316 insertions(+)
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 92f9f7aae92f2..a5777309b1f62 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -229,6 +229,15 @@ config BATTERY_SAMSUNG_SDI
Say Y to enable support for Samsung SDI battery data.
These batteries are used in Samsung mobile phones.
+config BATTERY_S2MU005
+ tristate "Samsung S2MU005 PMIC fuel gauge driver"
+ help
+ Say Y to enable support for the Samsung S2MU005 PMIC integrated
+ fuel gauge, which works indepenently of the PMIC battery charger
+ counterpart, and reports battery metrics.
+
+ This driver, if built as a module, will be called s2mu005-fuel-gauge.
+
config BATTERY_COLLIE
tristate "Sharp SL-5500 (collie) battery"
depends on SA1100_COLLIE && MCP_UCB1200
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 4b79d5abc49a7..cd061887c1727 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
obj-$(CONFIG_BATTERY_QCOM_BATTMGR) += qcom_battmgr.o
obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
obj-$(CONFIG_BATTERY_SAMSUNG_SDI) += samsung-sdi-battery.o
+obj-$(CONFIG_BATTERY_S2MU005) += s2mu005-battery.o
obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
obj-$(CONFIG_BATTERY_INGENIC) += ingenic-battery.o
obj-$(CONFIG_BATTERY_INTEL_DC_TI) += intel_dc_ti_battery.o
diff --git a/drivers/power/supply/s2mu005-battery.c b/drivers/power/supply/s2mu005-battery.c
new file mode 100644
index 0000000000000..f0e414407d3b5
--- /dev/null
+++ b/drivers/power/supply/s2mu005-battery.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Battery Fuel Gauge Driver for Samsung S2MU005 PMIC.
+ *
+ * Copyright (C) 2015 Samsung Electronics
+ * Copyright (C) 2023 Yassine Oudjana <y.oudjana@protonmail.com>
+ * Copyright (C) 2025 Kaustabh Chakraborty <kauschluss@disroot.org>
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#define S2MU005_FG_REG_STATUS 0x00
+#define S2MU005_FG_REG_IRQ 0x02
+#define S2MU005_FG_REG_RVBAT 0x04
+#define S2MU005_FG_REG_RCURCC 0x06
+#define S2MU005_FG_REG_RSOC 0x08
+#define S2MU005_FG_REG_MONOUT 0x0a
+#define S2MU005_FG_REG_MONOUTSEL 0x0c
+#define S2MU005_FG_REG_RBATCAP 0x0e
+#define S2MU005_FG_REG_RZADJ 0x12
+#define S2MU005_FG_REG_RBATZ0 0x16
+#define S2MU005_FG_REG_RBATZ1 0x18
+#define S2MU005_FG_REG_IRQLVL 0x1a
+#define S2MU005_FG_REG_START 0x1e
+
+#define S2MU005_FG_MONOUTSEL_AVGCURRENT 0x26
+#define S2MU005_FG_MONOUTSEL_AVGVOLTAGE 0x27
+
+struct s2mu005_fg {
+ struct device *dev;
+ struct regmap *regmap;
+ struct power_supply *psy;
+ struct mutex monout_mutex;
+};
+
+static const struct regmap_config s2mu005_fg_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static irqreturn_t s2mu005_handle_irq(int irq, void *data)
+{
+ struct s2mu005_fg *priv = data;
+
+ msleep(100);
+ power_supply_changed(priv->psy);
+
+ return IRQ_HANDLED;
+}
+
+static int s2mu005_fg_get_voltage_now(struct s2mu005_fg *priv, int *value)
+{
+ struct regmap *regmap = priv->regmap;
+ u32 val;
+ int ret;
+
+ ret = regmap_read(regmap, S2MU005_FG_REG_RVBAT, &val);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to read voltage register (%d)\n", ret);
+ return ret;
+ }
+
+ *value = (val * MICRO) >> 13;
+
+ return 0;
+}
+
+static int s2mu005_fg_get_voltage_avg(struct s2mu005_fg *priv, int *value)
+{
+ struct regmap *regmap = priv->regmap;
+ u32 val;
+ int ret;
+
+ mutex_lock(&priv->monout_mutex);
+
+ ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL,
+ S2MU005_FG_MONOUTSEL_AVGVOLTAGE);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to enable average voltage monitoring (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to read current register (%d)\n", ret);
+ goto unlock;
+ }
+
+ *value = (val * MICRO) >> 12;
+
+unlock:
+ mutex_unlock(&priv->monout_mutex);
+
+ return ret;
+}
+static int s2mu005_fg_get_current_now(struct s2mu005_fg *priv, int *value)
+{
+ struct regmap *regmap = priv->regmap;
+ u32 val;
+ int ret;
+
+ ret = regmap_read(regmap, S2MU005_FG_REG_RCURCC, &val);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to read current register (%d)\n", ret);
+ return ret;
+ }
+
+ *value = -((s16)val * MICRO) >> 12;
+
+ return 0;
+}
+
+static int s2mu005_fg_get_current_avg(struct s2mu005_fg *priv, int *value)
+{
+ struct regmap *regmap = priv->regmap;
+ u32 val;
+ int ret;
+
+ mutex_lock(&priv->monout_mutex);
+
+ ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL,
+ S2MU005_FG_MONOUTSEL_AVGCURRENT);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to enable average current monitoring (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to read current register (%d)\n", ret);
+ goto unlock;
+ }
+
+ *value = -((s16)val * MICRO) >> 12;
+
+unlock:
+ mutex_unlock(&priv->monout_mutex);
+
+ return ret;
+}
+
+static int s2mu005_fg_get_capacity(struct s2mu005_fg *priv, int *value)
+{
+ struct regmap *regmap = priv->regmap;
+ u32 val;
+ int ret;
+
+ ret = regmap_read(regmap, S2MU005_FG_REG_RSOC, &val);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to read capacity register (%d)\n", ret);
+ return ret;
+ }
+
+ *value = (val * CENTI) >> 14;
+
+ return 0;
+}
+
+static int s2mu005_fg_get_status(struct s2mu005_fg *priv, int *value)
+{
+ int current_now, current_avg, capacity;
+ int ret;
+
+ ret = s2mu005_fg_get_current_now(priv, ¤t_now);
+ if (ret < 0)
+ return ret;
+
+ ret = s2mu005_fg_get_current_avg(priv, ¤t_avg);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Verify both current values reported to reduce inaccuracies due to
+ * internal hysteresis.
+ */
+ if (current_now < 0 && current_avg < 0) {
+ *value = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else if (current_now == 0) {
+ *value = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ *value = POWER_SUPPLY_STATUS_CHARGING;
+
+ ret = s2mu005_fg_get_capacity(priv, &capacity);
+ if (!ret && capacity > 98)
+ *value = POWER_SUPPLY_STATUS_FULL;
+ return ret;
+ }
+
+ return 0;
+}
+
+static const enum power_supply_property s2mu005_fg_properties[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_STATUS,
+};
+
+static int s2mu005_fg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct s2mu005_fg *priv = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return s2mu005_fg_get_voltage_now(priv, &val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ return s2mu005_fg_get_voltage_avg(priv, &val->intval);
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return s2mu005_fg_get_current_now(priv, &val->intval);
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ return s2mu005_fg_get_current_avg(priv, &val->intval);
+ case POWER_SUPPLY_PROP_CAPACITY:
+ return s2mu005_fg_get_capacity(priv, &val->intval);
+ case POWER_SUPPLY_PROP_STATUS:
+ return s2mu005_fg_get_status(priv, &val->intval);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct power_supply_desc s2mu005_fg_desc = {
+ .name = "s2mu005-fuel-gauge",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = s2mu005_fg_properties,
+ .num_properties = ARRAY_SIZE(s2mu005_fg_properties),
+ .get_property = s2mu005_fg_get_property,
+};
+
+static int s2mu005_fg_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct s2mu005_fg *priv;
+ struct power_supply_config psy_cfg = {};
+ const struct power_supply_desc *psy_desc;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, priv);
+ priv->dev = dev;
+
+ priv->regmap = devm_regmap_init_i2c(client, &s2mu005_fg_regmap_config);
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap),
+ "failed to initialize regmap\n");
+
+ psy_desc = device_get_match_data(dev);
+
+ psy_cfg.drv_data = priv;
+ priv->psy = devm_power_supply_register(priv->dev, psy_desc, &psy_cfg);
+ if (IS_ERR(priv->psy))
+ return dev_err_probe(dev, PTR_ERR(priv->psy),
+ "failed to register power supply subsystem\n");
+
+ ret = devm_mutex_init(dev, &priv->monout_mutex);
+ if (ret)
+ dev_err_probe(dev, ret, "failed to initialize MONOUT mutex\n");
+
+ ret = devm_request_threaded_irq(priv->dev, client->irq, NULL,
+ s2mu005_handle_irq, IRQF_ONESHOT,
+ psy_desc->name, priv);
+ if (ret)
+ dev_err_probe(dev, ret, "failed to request IRQ\n");
+
+ return 0;
+}
+
+static const struct of_device_id s2mu005_fg_of_match_table[] = {
+ {
+ .compatible = "samsung,s2mu005-fuel-gauge",
+ .data = &s2mu005_fg_desc,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, s2mu005_fg_of_match_table);
+
+static struct i2c_driver s2mu005_fg_i2c_driver = {
+ .probe = s2mu005_fg_i2c_probe,
+ .driver = {
+ .name = "s2mu005-fuel-gauge",
+ .of_match_table = s2mu005_fg_of_match_table,
+ },
+};
+module_i2c_driver(s2mu005_fg_i2c_driver);
+
+MODULE_DESCRIPTION("Samsung S2MU005 PMIC Battery Fuel Gauge Driver");
+MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>");
+MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
+MODULE_LICENSE("GPL");
--
2.52.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] dt-bindings: power: supply: document Samsung S2MU005 battery fuel gauge
2026-02-08 17:52 ` [PATCH v2 1/2] dt-bindings: power: supply: document Samsung S2MU005 battery fuel gauge Kaustabh Chakraborty
@ 2026-02-10 3:01 ` Rob Herring (Arm)
0 siblings, 0 replies; 5+ messages in thread
From: Rob Herring (Arm) @ 2026-02-10 3:01 UTC (permalink / raw)
To: Kaustabh Chakraborty
Cc: Sebastian Reichel, linux-pm, Conor Dooley, devicetree,
Krzysztof Kozlowski, Yassine Oudjana, linux-kernel
On Sun, 08 Feb 2026 23:22:30 +0530, Kaustabh Chakraborty wrote:
> Samsung S2MU005 is a PMIC device which has LED controllers, an MUIC and
> a battery charger. The battery charger is paired with an independent
> device connected via I2C which can be used to access various metrics of
> the battery. Document the device as a schema.
>
> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
> ---
> .../power/supply/samsung,s2mu005-fuel-gauge.yaml | 49 ++++++++++++++++++++++
> 1 file changed, 49 insertions(+)
>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2 2/2] power: supply: add support for S2MU005 battery fuel gauge device
2026-02-08 17:52 ` [PATCH v2 2/2] power: supply: add support for S2MU005 battery fuel gauge device Kaustabh Chakraborty
@ 2026-03-02 23:40 ` Sebastian Reichel
0 siblings, 0 replies; 5+ messages in thread
From: Sebastian Reichel @ 2026-03-02 23:40 UTC (permalink / raw)
To: Kaustabh Chakraborty
Cc: Yassine Oudjana, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-pm, devicetree, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 11785 bytes --]
Hi,
On Sun, Feb 08, 2026 at 11:22:31PM +0530, Kaustabh Chakraborty wrote:
> From: Yassine Oudjana <y.oudjana@protonmail.com>
>
> Samsung's S2MU005 PMIC, which contains battery charger functionality
> also includes a battery fuel gauge device, which is separate from the
> PMIC itself, and typically connected to an I2C bus. Add a generic driver
> to support said device.
>
> Signed-off-by: Yassine Oudjana <y.oudjana@protonmail.com>
> Co-developed-by: Kaustabh Chakraborty <kauschluss@disroot.org>
> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
> ---
> drivers/power/supply/Kconfig | 9 +
> drivers/power/supply/Makefile | 1 +
> drivers/power/supply/s2mu005-battery.c | 306 +++++++++++++++++++++++++++++++++
> 3 files changed, 316 insertions(+)
>
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index 92f9f7aae92f2..a5777309b1f62 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -229,6 +229,15 @@ config BATTERY_SAMSUNG_SDI
> Say Y to enable support for Samsung SDI battery data.
> These batteries are used in Samsung mobile phones.
>
> +config BATTERY_S2MU005
> + tristate "Samsung S2MU005 PMIC fuel gauge driver"
This is missing some dependencies, which will trigger build issues
with random config:
depends on I2C
select REGMAP_I2C
> + help
> + Say Y to enable support for the Samsung S2MU005 PMIC integrated
> + fuel gauge, which works indepenently of the PMIC battery charger
> + counterpart, and reports battery metrics.
> +
> + This driver, if built as a module, will be called s2mu005-fuel-gauge.
> +
> config BATTERY_COLLIE
> tristate "Sharp SL-5500 (collie) battery"
> depends on SA1100_COLLIE && MCP_UCB1200
> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
> index 4b79d5abc49a7..cd061887c1727 100644
> --- a/drivers/power/supply/Makefile
> +++ b/drivers/power/supply/Makefile
> @@ -40,6 +40,7 @@ obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
> obj-$(CONFIG_BATTERY_QCOM_BATTMGR) += qcom_battmgr.o
> obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
> obj-$(CONFIG_BATTERY_SAMSUNG_SDI) += samsung-sdi-battery.o
> +obj-$(CONFIG_BATTERY_S2MU005) += s2mu005-battery.o
> obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
> obj-$(CONFIG_BATTERY_INGENIC) += ingenic-battery.o
> obj-$(CONFIG_BATTERY_INTEL_DC_TI) += intel_dc_ti_battery.o
> diff --git a/drivers/power/supply/s2mu005-battery.c b/drivers/power/supply/s2mu005-battery.c
> new file mode 100644
> index 0000000000000..f0e414407d3b5
> --- /dev/null
> +++ b/drivers/power/supply/s2mu005-battery.c
> @@ -0,0 +1,306 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Battery Fuel Gauge Driver for Samsung S2MU005 PMIC.
> + *
> + * Copyright (C) 2015 Samsung Electronics
> + * Copyright (C) 2023 Yassine Oudjana <y.oudjana@protonmail.com>
> + * Copyright (C) 2025 Kaustabh Chakraborty <kauschluss@disroot.org>
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
why?
> +#include <linux/mutex.h>
> +#include <linux/of.h>
unused. You only use of_device_id from <include/linux/mod_devicetable.h>
> +#include <linux/power_supply.h>
> +#include <linux/regmap.h>
> +#include <linux/units.h>
> +
> +#define S2MU005_FG_REG_STATUS 0x00
> +#define S2MU005_FG_REG_IRQ 0x02
> +#define S2MU005_FG_REG_RVBAT 0x04
> +#define S2MU005_FG_REG_RCURCC 0x06
> +#define S2MU005_FG_REG_RSOC 0x08
> +#define S2MU005_FG_REG_MONOUT 0x0a
> +#define S2MU005_FG_REG_MONOUTSEL 0x0c
> +#define S2MU005_FG_REG_RBATCAP 0x0e
> +#define S2MU005_FG_REG_RZADJ 0x12
> +#define S2MU005_FG_REG_RBATZ0 0x16
> +#define S2MU005_FG_REG_RBATZ1 0x18
> +#define S2MU005_FG_REG_IRQLVL 0x1a
> +#define S2MU005_FG_REG_START 0x1e
> +
> +#define S2MU005_FG_MONOUTSEL_AVGCURRENT 0x26
> +#define S2MU005_FG_MONOUTSEL_AVGVOLTAGE 0x27
> +
> +struct s2mu005_fg {
> + struct device *dev;
> + struct regmap *regmap;
> + struct power_supply *psy;
> + struct mutex monout_mutex;
> +};
> +
> +static const struct regmap_config s2mu005_fg_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 16,
> + .val_format_endian = REGMAP_ENDIAN_LITTLE,
> +};
> +
> +static irqreturn_t s2mu005_handle_irq(int irq, void *data)
> +{
> + struct s2mu005_fg *priv = data;
> +
> + msleep(100);
> + power_supply_changed(priv->psy);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int s2mu005_fg_get_voltage_now(struct s2mu005_fg *priv, int *value)
> +{
> + struct regmap *regmap = priv->regmap;
> + u32 val;
> + int ret;
> +
> + ret = regmap_read(regmap, S2MU005_FG_REG_RVBAT, &val);
> + if (ret < 0) {
> + dev_err(priv->dev, "failed to read voltage register (%d)\n", ret);
> + return ret;
> + }
> +
> + *value = (val * MICRO) >> 13;
> +
> + return 0;
> +}
> +
> +static int s2mu005_fg_get_voltage_avg(struct s2mu005_fg *priv, int *value)
> +{
> + struct regmap *regmap = priv->regmap;
> + u32 val;
> + int ret;
> +
> + mutex_lock(&priv->monout_mutex);
> +
> + ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL,
> + S2MU005_FG_MONOUTSEL_AVGVOLTAGE);
> + if (ret < 0) {
> + dev_err(priv->dev, "failed to enable average voltage monitoring (%d)\n",
> + ret);
> + goto unlock;
> + }
> +
> + ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val);
> + if (ret < 0) {
> + dev_err(priv->dev, "failed to read current register (%d)\n", ret);
> + goto unlock;
> + }
> +
> + *value = (val * MICRO) >> 12;
> +
> +unlock:
> + mutex_unlock(&priv->monout_mutex);
> +
> + return ret;
> +}
> +static int s2mu005_fg_get_current_now(struct s2mu005_fg *priv, int *value)
> +{
> + struct regmap *regmap = priv->regmap;
> + u32 val;
> + int ret;
> +
> + ret = regmap_read(regmap, S2MU005_FG_REG_RCURCC, &val);
> + if (ret < 0) {
> + dev_err(priv->dev, "failed to read current register (%d)\n", ret);
> + return ret;
> + }
> +
> + *value = -((s16)val * MICRO) >> 12;
> +
> + return 0;
> +}
> +
> +static int s2mu005_fg_get_current_avg(struct s2mu005_fg *priv, int *value)
> +{
> + struct regmap *regmap = priv->regmap;
> + u32 val;
> + int ret;
> +
> + mutex_lock(&priv->monout_mutex);
> +
> + ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL,
> + S2MU005_FG_MONOUTSEL_AVGCURRENT);
> + if (ret < 0) {
> + dev_err(priv->dev, "failed to enable average current monitoring (%d)\n",
> + ret);
> + goto unlock;
> + }
> +
> + ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val);
> + if (ret < 0) {
> + dev_err(priv->dev, "failed to read current register (%d)\n", ret);
> + goto unlock;
> + }
> +
> + *value = -((s16)val * MICRO) >> 12;
> +
> +unlock:
> + mutex_unlock(&priv->monout_mutex);
> +
> + return ret;
> +}
> +
> +static int s2mu005_fg_get_capacity(struct s2mu005_fg *priv, int *value)
> +{
> + struct regmap *regmap = priv->regmap;
> + u32 val;
> + int ret;
> +
> + ret = regmap_read(regmap, S2MU005_FG_REG_RSOC, &val);
> + if (ret < 0) {
> + dev_err(priv->dev, "failed to read capacity register (%d)\n", ret);
> + return ret;
> + }
> +
> + *value = (val * CENTI) >> 14;
> +
> + return 0;
> +}
> +
> +static int s2mu005_fg_get_status(struct s2mu005_fg *priv, int *value)
> +{
> + int current_now, current_avg, capacity;
> + int ret;
> +
> + ret = s2mu005_fg_get_current_now(priv, ¤t_now);
> + if (ret < 0)
> + return ret;
> +
> + ret = s2mu005_fg_get_current_avg(priv, ¤t_avg);
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * Verify both current values reported to reduce inaccuracies due to
> + * internal hysteresis.
> + */
> + if (current_now < 0 && current_avg < 0) {
> + *value = POWER_SUPPLY_STATUS_DISCHARGING;
> + } else if (current_now == 0) {
> + *value = POWER_SUPPLY_STATUS_NOT_CHARGING;
> + } else {
> + *value = POWER_SUPPLY_STATUS_CHARGING;
> +
> + ret = s2mu005_fg_get_capacity(priv, &capacity);
> + if (!ret && capacity > 98)
> + *value = POWER_SUPPLY_STATUS_FULL;
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const enum power_supply_property s2mu005_fg_properties[] = {
> + POWER_SUPPLY_PROP_VOLTAGE_NOW,
> + POWER_SUPPLY_PROP_VOLTAGE_AVG,
> + POWER_SUPPLY_PROP_CURRENT_NOW,
> + POWER_SUPPLY_PROP_CURRENT_AVG,
> + POWER_SUPPLY_PROP_CAPACITY,
> + POWER_SUPPLY_PROP_STATUS,
> +};
> +
> +static int s2mu005_fg_get_property(struct power_supply *psy,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + struct s2mu005_fg *priv = power_supply_get_drvdata(psy);
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_VOLTAGE_NOW:
> + return s2mu005_fg_get_voltage_now(priv, &val->intval);
> + case POWER_SUPPLY_PROP_VOLTAGE_AVG:
> + return s2mu005_fg_get_voltage_avg(priv, &val->intval);
> + case POWER_SUPPLY_PROP_CURRENT_NOW:
> + return s2mu005_fg_get_current_now(priv, &val->intval);
> + case POWER_SUPPLY_PROP_CURRENT_AVG:
> + return s2mu005_fg_get_current_avg(priv, &val->intval);
> + case POWER_SUPPLY_PROP_CAPACITY:
> + return s2mu005_fg_get_capacity(priv, &val->intval);
> + case POWER_SUPPLY_PROP_STATUS:
> + return s2mu005_fg_get_status(priv, &val->intval);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static const struct power_supply_desc s2mu005_fg_desc = {
> + .name = "s2mu005-fuel-gauge",
> + .type = POWER_SUPPLY_TYPE_BATTERY,
> + .properties = s2mu005_fg_properties,
> + .num_properties = ARRAY_SIZE(s2mu005_fg_properties),
> + .get_property = s2mu005_fg_get_property,
> +};
> +
> +static int s2mu005_fg_i2c_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct s2mu005_fg *priv;
> + struct power_supply_config psy_cfg = {};
> + const struct power_supply_desc *psy_desc;
> + int ret;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + dev_set_drvdata(dev, priv);
> + priv->dev = dev;
> +
> + priv->regmap = devm_regmap_init_i2c(client, &s2mu005_fg_regmap_config);
> + if (IS_ERR(priv->regmap))
> + return dev_err_probe(dev, PTR_ERR(priv->regmap),
> + "failed to initialize regmap\n");
> +
> + psy_desc = device_get_match_data(dev);
> +
> + psy_cfg.drv_data = priv;
psy_cfg.fwnode = dev_fwnode(dev);
> + priv->psy = devm_power_supply_register(priv->dev, psy_desc, &psy_cfg);
> + if (IS_ERR(priv->psy))
> + return dev_err_probe(dev, PTR_ERR(priv->psy),
> + "failed to register power supply subsystem\n");
> +
> + ret = devm_mutex_init(dev, &priv->monout_mutex);
> + if (ret)
> + dev_err_probe(dev, ret, "failed to initialize MONOUT mutex\n");
> +
> + ret = devm_request_threaded_irq(priv->dev, client->irq, NULL,
> + s2mu005_handle_irq, IRQF_ONESHOT,
> + psy_desc->name, priv);
> + if (ret)
> + dev_err_probe(dev, ret, "failed to request IRQ\n");
> +
> + return 0;
> +}
> +
> +static const struct of_device_id s2mu005_fg_of_match_table[] = {
> + {
> + .compatible = "samsung,s2mu005-fuel-gauge",
> + .data = &s2mu005_fg_desc,
> + },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, s2mu005_fg_of_match_table);
> +
> +static struct i2c_driver s2mu005_fg_i2c_driver = {
> + .probe = s2mu005_fg_i2c_probe,
> + .driver = {
> + .name = "s2mu005-fuel-gauge",
> + .of_match_table = s2mu005_fg_of_match_table,
> + },
> +};
> +module_i2c_driver(s2mu005_fg_i2c_driver);
> +
> +MODULE_DESCRIPTION("Samsung S2MU005 PMIC Battery Fuel Gauge Driver");
> +MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>");
> +MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
> +MODULE_LICENSE("GPL");
Greetings,
-- Sebastian
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-03-02 23:41 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-08 17:52 [PATCH v2 0/2] Add support for Samsung S2MU005 battery fuel gauge device Kaustabh Chakraborty
2026-02-08 17:52 ` [PATCH v2 1/2] dt-bindings: power: supply: document Samsung S2MU005 battery fuel gauge Kaustabh Chakraborty
2026-02-10 3:01 ` Rob Herring (Arm)
2026-02-08 17:52 ` [PATCH v2 2/2] power: supply: add support for S2MU005 battery fuel gauge device Kaustabh Chakraborty
2026-03-02 23:40 ` Sebastian Reichel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox