2024-04-15 08:14:23

by Mike Looijmans

[permalink] [raw]
Subject: [PATCH v4 1/5] dt-bindings: power: Extend battery chemistry with capacitor

Another technology to store energy is a (super)capacitor.

Signed-off-by: Mike Looijmans <[email protected]>
---

(no changes since v1)

Documentation/devicetree/bindings/power/supply/battery.yaml | 1 +
1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/power/supply/battery.yaml b/Documentation/devicetree/bindings/power/supply/battery.yaml
index 491488e7b970..a22c97dfad88 100644
--- a/Documentation/devicetree/bindings/power/supply/battery.yaml
+++ b/Documentation/devicetree/bindings/power/supply/battery.yaml
@@ -44,6 +44,7 @@ properties:
- const: lithium-ion-polymer
- const: lithium-ion-iron-phosphate
- const: lithium-ion-manganese-oxide
+ - const: capacitor

over-voltage-threshold-microvolt:
description: battery over-voltage limit
--
2.34.1


Met vriendelijke groet / kind regards,

Mike Looijmans
System Expert


TOPIC Embedded Products B.V.
Materiaalweg 4, 5681 RJ Best
The Netherlands

T: +31 (0) 499 33 69 69
E: [email protected]
W: http://www.topic.nl

Please consider the environment before printing this e-mail


2024-04-15 08:14:49

by Mike Looijmans

[permalink] [raw]
Subject: [PATCH v4 4/5] dt-bindings: power: supply: ltc3350-charger: Add bindings

The LTC3350 is a backup power controller that can charge and monitor
a series stack of one to four supercapacitors.

Signed-off-by: Mike Looijmans <[email protected]>
Reviewed-by: Krzysztof Kozlowski <[email protected]>

---

(no changes since v3)

Changes in v3:
Fix $id after rename to lltc,ltc3350.yaml

Changes in v2:
Rename to lltc,ltc3350.yaml
Fix spaces and indentation

.../bindings/power/supply/lltc,ltc3350.yaml | 54 +++++++++++++++++++
1 file changed, 54 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/supply/lltc,ltc3350.yaml

diff --git a/Documentation/devicetree/bindings/power/supply/lltc,ltc3350.yaml b/Documentation/devicetree/bindings/power/supply/lltc,ltc3350.yaml
new file mode 100644
index 000000000000..dca7fe0f0d8f
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/lltc,ltc3350.yaml
@@ -0,0 +1,54 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+# Copyright (C) 2024 Topic Embedded Products
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/power/supply/lltc,ltc3350.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Linear Technology (Analog Devices) LTC3350 Supercap Charger
+
+maintainers:
+ - Mike Looijmans <[email protected]>
+
+description: |
+ The LTC3350 is a High Current Supercapacitor Backup Controller and System
+ Monitor.
+ Specifications about the charger can be found at:
+ https://www.analog.com/en/products/ltc3350.html
+
+properties:
+ compatible:
+ enum:
+ - lltc,ltc3350
+
+ reg:
+ maxItems: 1
+
+ lltc,rsnsc-micro-ohms:
+ description: Capacitor charger sense resistor in microohm.
+ minimum: 1000
+
+ lltc,rsnsi-micro-ohms:
+ description: Input current sense resistor in microohm.
+ minimum: 1000
+
+required:
+ - compatible
+ - reg
+ - lltc,rsnsc-micro-ohms
+ - lltc,rsnsi-micro-ohms
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ charger: battery-charger@9 {
+ compatible = "lltc,ltc3350";
+ reg = <0x9>;
+ lltc,rsnsc-micro-ohms = <10000>;
+ lltc,rsnsi-micro-ohms = <10000>;
+ };
+ };
--
2.34.1


Met vriendelijke groet / kind regards,

Mike Looijmans
System Expert


TOPIC Embedded Products B.V.
Materiaalweg 4, 5681 RJ Best
The Netherlands

T: +31 (0) 499 33 69 69
E: [email protected]
W: http://www.topic.nl

Please consider the environment before printing this e-mail

2024-04-15 08:14:50

by Mike Looijmans

[permalink] [raw]
Subject: [PATCH v4 3/5] power: supply: core: Add POWER_SUPPLY_PROP_*CELL* entries

For multi-cell chargers (or stacks of capacitors), allow to report the
number of cells in series and the voltage of each cell.

Signed-off-by: Mike Looijmans <[email protected]>
---

(no changes since v1)

Documentation/ABI/testing/sysfs-class-power | 51 +++++++++++++++++++++
Documentation/power/power_supply_class.rst | 7 +++
drivers/power/supply/power_supply_sysfs.c | 7 +++
include/linux/power_supply.h | 7 +++
4 files changed, 72 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power
index 1f6a04a17c81..f5b194ad61b3 100644
--- a/Documentation/ABI/testing/sysfs-class-power
+++ b/Documentation/ABI/testing/sysfs-class-power
@@ -516,6 +516,57 @@ Description:
Integer > 0: representing full cycles
Integer = 0: cycle_count info is not available

+What: /sys/class/power_supply/<supply_name>/number_of_serial_cells
+Date: April 2024
+Contact: [email protected]
+Description:
+ If the energy storage is composed of multiple cells in series,
+ this provides the number of stacked cells. Parallel cells aren't
+ considered here.
+
+ Access: Read
+
+ Valid values:
+ Integer > 0: number of cells
+ Integer = 0: info is not available
+
+What: /sys/class/power_supply/<supply_name>/cell1_voltage_now
+Date: April 2024
+Contact: [email protected]
+Description:
+ Reports an instant, single cell voltage reading. The voltage is
+ measured across the cell. The battery reports voltages for up to
+ 'number_of_serial_cells' cells, in cell2_voltage_now and so on.
+
+ Access: Read
+
+ Valid values: Represented in microvolts
+
+What: /sys/class/power_supply/<supply_name>/cell_voltage_max
+Date: April 2024
+Contact: [email protected]
+Description:
+ Maximum allowed voltage for a single cell. This value is shared
+ across all cells in the range 1 to 'number_of_serial_cells'.
+ Typically used to trigger an alert for userspace.
+
+ Access: Read, Write
+
+ Valid values: Represented in microvolts
+
+What: /sys/class/power_supply/<supply_name>/cell_voltage_min
+Date: April 2024
+Contact: [email protected]
+Description:
+ Minimum allowed voltage for a single cell. This value is shared
+ across all cells in the range 1 to 'number_of_serial_cells'.
+ Typically used to trigger an alert for userspace.
+
+ Access: Read, Write
+
+ Valid values: Represented in microvolts
+
+
**USB Properties**

What: /sys/class/power_supply/<supply_name>/input_current_limit
diff --git a/Documentation/power/power_supply_class.rst b/Documentation/power/power_supply_class.rst
index da8e275a14ff..42110cbbea4a 100644
--- a/Documentation/power/power_supply_class.rst
+++ b/Documentation/power/power_supply_class.rst
@@ -213,6 +213,13 @@ TIME_TO_FULL
seconds left for battery to be considered full
(i.e. while battery is charging)

+NUMBER_OF_SERIAL_CELLS
+ If the energy storage is composed of multiple cells in series, this provides
+ the number of stacked cells. Parallel cells aren't considered here.
+CELLn_VOLTAGE
+ voltage measured of the n-th cell in the stack
+CELL_VOLTAGE
+ single cell voltage when the cells share the same value (usually MIN or MAX)

Battery <-> external power supply interaction
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 9d8540ce1f7e..4ea6b556f3fd 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -218,6 +218,13 @@ static struct power_supply_attr power_supply_attrs[] = {
POWER_SUPPLY_ATTR(MANUFACTURE_YEAR),
POWER_SUPPLY_ATTR(MANUFACTURE_MONTH),
POWER_SUPPLY_ATTR(MANUFACTURE_DAY),
+ POWER_SUPPLY_ATTR(NUMBER_OF_SERIAL_CELLS),
+ POWER_SUPPLY_ATTR(CELL1_VOLTAGE_NOW),
+ POWER_SUPPLY_ATTR(CELL2_VOLTAGE_NOW),
+ POWER_SUPPLY_ATTR(CELL3_VOLTAGE_NOW),
+ POWER_SUPPLY_ATTR(CELL4_VOLTAGE_NOW),
+ POWER_SUPPLY_ATTR(CELL_VOLTAGE_MIN),
+ POWER_SUPPLY_ATTR(CELL_VOLTAGE_MAX),
/* Properties of type `const char *' */
POWER_SUPPLY_ATTR(MODEL_NAME),
POWER_SUPPLY_ATTR(MANUFACTURER),
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index af5a4e700881..3c208dff8af8 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -171,6 +171,13 @@ enum power_supply_property {
POWER_SUPPLY_PROP_MANUFACTURE_YEAR,
POWER_SUPPLY_PROP_MANUFACTURE_MONTH,
POWER_SUPPLY_PROP_MANUFACTURE_DAY,
+ POWER_SUPPLY_PROP_NUMBER_OF_SERIAL_CELLS,
+ POWER_SUPPLY_PROP_CELL1_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CELL2_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CELL3_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CELL4_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CELL_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_CELL_VOLTAGE_MAX,
/* Properties of type `const char *' */
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
--
2.34.1


Met vriendelijke groet / kind regards,

Mike Looijmans
System Expert


TOPIC Embedded Products B.V.
Materiaalweg 4, 5681 RJ Best
The Netherlands

T: +31 (0) 499 33 69 69
E: [email protected]
W: http://www.topic.nl

Please consider the environment before printing this e-mail

2024-04-15 08:30:51

by Mike Looijmans

[permalink] [raw]
Subject: [PATCH v4 2/5] power: supply: core: Add POWER_SUPPLY_TECHNOLOGY_CAPACITOR

Another technology to store energy is a (super)capacitor.

Signed-off-by: Mike Looijmans <[email protected]>
---

(no changes since v1)

Documentation/ABI/testing/sysfs-class-power | 2 +-
drivers/power/supply/power_supply_core.c | 2 ++
drivers/power/supply/power_supply_sysfs.c | 1 +
include/linux/power_supply.h | 1 +
4 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power
index 7c81f0a25a48..1f6a04a17c81 100644
--- a/Documentation/ABI/testing/sysfs-class-power
+++ b/Documentation/ABI/testing/sysfs-class-power
@@ -488,7 +488,7 @@ Description:

Valid values:
"Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe",
- "NiCd", "LiMn"
+ "NiCd", "LiMn", "Capacitor"


What: /sys/class/power_supply/<supply_name>/voltage_avg,
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index fefe938c9342..6d1327e16ff6 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -686,6 +686,8 @@ int power_supply_get_battery_info(struct power_supply *psy,
info->technology = POWER_SUPPLY_TECHNOLOGY_LiFe;
else if (!strcmp("lithium-ion-manganese-oxide", value))
info->technology = POWER_SUPPLY_TECHNOLOGY_LiMn;
+ else if (!strcmp("capacitor", value))
+ info->technology = POWER_SUPPLY_TECHNOLOGY_CAPACITOR;
else
dev_warn(&psy->dev, "%s unknown battery type\n", value);
}
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 0d2c3724d0bc..9d8540ce1f7e 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -119,6 +119,7 @@ static const char * const POWER_SUPPLY_TECHNOLOGY_TEXT[] = {
[POWER_SUPPLY_TECHNOLOGY_LiFe] = "LiFe",
[POWER_SUPPLY_TECHNOLOGY_NiCd] = "NiCd",
[POWER_SUPPLY_TECHNOLOGY_LiMn] = "LiMn",
+ [POWER_SUPPLY_TECHNOLOGY_CAPACITOR] = "Capacitor",
};

static const char * const POWER_SUPPLY_CAPACITY_LEVEL_TEXT[] = {
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 8e5705a56b85..af5a4e700881 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -78,6 +78,7 @@ enum {
POWER_SUPPLY_TECHNOLOGY_LiFe,
POWER_SUPPLY_TECHNOLOGY_NiCd,
POWER_SUPPLY_TECHNOLOGY_LiMn,
+ POWER_SUPPLY_TECHNOLOGY_CAPACITOR,
};

enum {
--
2.34.1


Met vriendelijke groet / kind regards,

Mike Looijmans
System Expert


TOPIC Embedded Products B.V.
Materiaalweg 4, 5681 RJ Best
The Netherlands

T: +31 (0) 499 33 69 69
E: [email protected]
W: http://www.topic.nl

Please consider the environment before printing this e-mail

2024-04-15 08:31:46

by Mike Looijmans

[permalink] [raw]
Subject: [PATCH v4 5/5] power: supply: ltc3350-charger: Add driver

The LTC3350 is a backup power controller that can charge and monitor
a series stack of one to four supercapacitors.

Signed-off-by: Mike Looijmans <[email protected]>

---

Changes in v4:
Split into "charger" and "capacitor" parts
Use (new) standard properties
Header include fixups
Explain local "scale" units
Drop i2c_check_functionality
Use dev_err_probe
Use dev_fwnode
Drop of_match_ptr

Changes in v2:
Duplicate "vin_ov" and "vin_uv" attributes

# bash /tmp/tools/testing/selftests/power_supply/test_power_supply_properties.sh
TAP version 13
1..66
# Testing device ltc3350
ok 1 ltc3350.exists
ok 2 ltc3350.uevent.NAME
ok 3 ltc3350.sysfs.type
ok 4 ltc3350.uevent.TYPE
ok 5 ltc3350.sysfs.usb_type # SKIP
# Reported: '1' ()
ok 6 ltc3350.sysfs.online
ok 7 ltc3350.sysfs.present # SKIP
# Reported: 'Full'
ok 8 ltc3350.sysfs.status
ok 9 ltc3350.sysfs.capacity # SKIP
ok 10 ltc3350.sysfs.capacity_level # SKIP
ok 11 ltc3350.sysfs.model_name # SKIP
ok 12 ltc3350.sysfs.manufacturer # SKIP
ok 13 ltc3350.sysfs.serial_number # SKIP
ok 14 ltc3350.sysfs.technology # SKIP
ok 15 ltc3350.sysfs.cycle_count # SKIP
ok 16 ltc3350.sysfs.scope # SKIP
# Reported: '3200000' uA (3200 mA)
ok 17 ltc3350.sysfs.input_current_limit
ok 18 ltc3350.sysfs.input_voltage_limit # SKIP
# Reported: '23742030' uV (23.742 V)
ok 19 ltc3350.sysfs.voltage_now
ok 20 ltc3350.sysfs.voltage_min # SKIP
ok 21 ltc3350.sysfs.voltage_max # SKIP
ok 22 ltc3350.sysfs.voltage_min_design # SKIP
ok 23 ltc3350.sysfs.voltage_max_design # SKIP
# Reported: '467900' uA (467.9 mA)
ok 24 ltc3350.sysfs.current_now
ok 25 ltc3350.sysfs.current_max # SKIP
ok 26 ltc3350.sysfs.charge_now # SKIP
ok 27 ltc3350.sysfs.charge_full # SKIP
ok 28 ltc3350.sysfs.charge_full_design # SKIP
ok 29 ltc3350.sysfs.power_now # SKIP
ok 30 ltc3350.sysfs.energy_now # SKIP
ok 31 ltc3350.sysfs.energy_full # SKIP
ok 32 ltc3350.sysfs.energy_full_design # SKIP
ok 33 ltc3350.sysfs.energy_full_design # SKIP
# Testing device ltc3350_capacitor
ok 34 ltc3350_capacitor.exists
ok 35 ltc3350_capacitor.uevent.NAME
ok 36 ltc3350_capacitor.sysfs.type
ok 37 ltc3350_capacitor.uevent.TYPE
ok 38 ltc3350_capacitor.sysfs.usb_type # SKIP
ok 39 ltc3350_capacitor.sysfs.online # SKIP
ok 40 ltc3350_capacitor.sysfs.present # SKIP
# Reported: 'Full'
ok 41 ltc3350_capacitor.sysfs.status
ok 42 ltc3350_capacitor.sysfs.capacity # SKIP
ok 43 ltc3350_capacitor.sysfs.capacity_level # SKIP
ok 44 ltc3350_capacitor.sysfs.model_name # SKIP
ok 45 ltc3350_capacitor.sysfs.manufacturer # SKIP
ok 46 ltc3350_capacitor.sysfs.serial_number # SKIP
ok 47 ltc3350_capacitor.sysfs.technology # SKIP
ok 48 ltc3350_capacitor.sysfs.cycle_count # SKIP
ok 49 ltc3350_capacitor.sysfs.scope # SKIP
ok 50 ltc3350_capacitor.sysfs.input_current_limit # SKIP
ok 51 ltc3350_capacitor.sysfs.input_voltage_limit # SKIP
# Reported: '5053824' uV (5.05382 V)
ok 52 ltc3350_capacitor.sysfs.voltage_now
# Reported: '4299588' uV (4.29959 V)
ok 53 ltc3350_capacitor.sysfs.voltage_min
# Reported: '4680396' uV (4.6804 V)
ok 54 ltc3350_capacitor.sysfs.voltage_max
ok 55 ltc3350_capacitor.sysfs.voltage_min_design # SKIP
ok 56 ltc3350_capacitor.sysfs.voltage_max_design # SKIP
ok 57 ltc3350_capacitor.sysfs.current_now # SKIP
ok 58 ltc3350_capacitor.sysfs.current_max # SKIP
ok 59 ltc3350_capacitor.sysfs.charge_now # SKIP
ok 60 ltc3350_capacitor.sysfs.charge_full # SKIP
ok 61 ltc3350_capacitor.sysfs.charge_full_design # SKIP
ok 62 ltc3350_capacitor.sysfs.power_now # SKIP
ok 63 ltc3350_capacitor.sysfs.energy_now # SKIP
ok 64 ltc3350_capacitor.sysfs.energy_full # SKIP
ok 65 ltc3350_capacitor.sysfs.energy_full_design # SKIP
ok 66 ltc3350_capacitor.sysfs.energy_full_design # SKIP
# Totals: pass:17 fail:0 xfail:0 xpass:0 skip:49 error:0


.../ABI/testing/sysfs-class-power-ltc3350 | 88 ++
drivers/power/supply/Kconfig | 10 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/ltc3350-charger.c | 786 ++++++++++++++++++
4 files changed, 885 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-class-power-ltc3350
create mode 100644 drivers/power/supply/ltc3350-charger.c

diff --git a/Documentation/ABI/testing/sysfs-class-power-ltc3350 b/Documentation/ABI/testing/sysfs-class-power-ltc3350
new file mode 100644
index 000000000000..d4a2bb0fb62b
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-power-ltc3350
@@ -0,0 +1,88 @@
+What: /sys/class/power_supply/ltc3350/charge_status
+Date: April 2024
+KernelVersion: 6.9
+Description:
+ Detailed charge status information as reported by the chip. This
+ returns the raw register value in hex.
+
+ Access: Read
+
+What: /sys/class/power_supply/ltc3350/vshunt
+Date: April 2024
+KernelVersion: 6.9
+Description:
+ Voltage for "shunting" the capacitors in the stack. When the
+ capacitor voltage is above this value, the chip will discharge
+ the excess voltage using a shunt resistor.
+ This is typically used to limit the voltage on a single cell,
+ to compensate for imbalance and prevent damaging the capacitor
+ while charging. It can also be used to forcibly discharge the
+ capacitors.
+
+ Access: Read, Write
+
+ Valid values: In microvolts, defaults to 2.7V
+
+What: /sys/class/power_supply/ltc3350/gpi
+Date: April 2024
+KernelVersion: 6.9
+Description:
+ General purpose input voltage. Returns a single measurement.
+
+ Access: Read
+
+ Valid values: In microvolts
+
+What: /sys/class/power_supply/ltc3350/gpi_ov
+Date: April 2024
+KernelVersion: 6.9
+Description:
+ General purpose input voltage overvoltage level. Triggers an
+ alert for userspace when the voltage goes above this value.
+
+ Access: Read, Write
+
+ Valid values: In microvolts
+
+What: /sys/class/power_supply/ltc3350/gpi_uv
+Date: April 2024
+KernelVersion: 6.9
+Description:
+ General purpose input voltage undervoltage level. Triggers an
+ alert for userspace when the voltage drops below this value.
+
+ Access: Read, Write
+
+ Valid values: In microvolts
+
+What: /sys/class/power_supply/ltc3350/vin
+Date: April 2024
+KernelVersion: 6.9
+Description:
+ Charger input voltage. Returns a single measurement.
+
+ Access: Read
+
+ Valid values: In microvolts
+
+What: /sys/class/power_supply/ltc3350/vin_ov
+Date: April 2024
+KernelVersion: 6.9
+Description:
+ Input voltage overvoltage level. Triggers an alert for userspace
+ when the voltage goes above this value.
+
+ Access: Read, Write
+
+ Valid values: In microvolts
+
+What: /sys/class/power_supply/ltc3350/vin_uv
+Date: April 2024
+KernelVersion: 6.9
+Description:
+ Input voltage undervoltage level. Triggers an alert for
+ userspace when the voltage drops below this value.
+
+ Access: Read, Write
+
+ Valid values: In microvolts
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 3e31375491d5..7cb1a66e522d 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -514,6 +514,16 @@ config CHARGER_LT3651
Say Y to include support for the Analog Devices (Linear Technology)
LT3651 battery charger which reports its status via GPIO lines.

+config CHARGER_LTC3350
+ tristate "LTC3350 Supercapacitor Backup Controller and System Monitor"
+ depends on I2C
+ select REGMAP_I2C
+ select I2C_SMBUS
+ help
+ Say Y to include support for the Analog Devices (Linear Technology)
+ LTC3350 Supercapacitor Backup Controller and System Monitor connected
+ to I2C.
+
config CHARGER_LTC4162L
tristate "LTC4162-L charger"
depends on I2C
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 58b567278034..a8d618e4ac91 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
obj-$(CONFIG_CHARGER_LT3651) += lt3651-charger.o
+obj-$(CONFIG_CHARGER_LTC3350) += ltc3350-charger.o
obj-$(CONFIG_CHARGER_LTC4162L) += ltc4162-l-charger.o
obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
diff --git a/drivers/power/supply/ltc3350-charger.c b/drivers/power/supply/ltc3350-charger.c
new file mode 100644
index 000000000000..6e0e238766ca
--- /dev/null
+++ b/drivers/power/supply/ltc3350-charger.c
@@ -0,0 +1,786 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Analog Devices (Linear Technology) LTC3350
+ * High Current Supercapacitor Backup Controller and System Monitor
+ * Copyright (C) 2024, Topic Embedded Products
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+/* Registers (names based on what datasheet uses) */
+#define LTC3350_REG_CLR_ALARMS 0x00
+#define LTC3350_REG_MSK_ALARMS 0x01
+#define LTC3350_REG_MSK_MON_STATUS 0x02
+#define LTC3350_REG_CAP_ESR_PER 0x04
+#define LTC3350_REG_VCAPFB_DAC 0x05
+#define LTC3350_REG_VSHUNT 0x06
+#define LTC3350_REG_CAP_UV_LVL 0x07
+#define LTC3350_REG_CAP_OV_LVL 0x08
+#define LTC3350_REG_GPI_UV_LVL 0x09
+#define LTC3350_REG_GPI_OV_LVL 0x0A
+#define LTC3350_REG_VIN_UV_LVL 0x0B
+#define LTC3350_REG_VIN_OV_LVL 0x0C
+#define LTC3350_REG_VCAP_UV_LVL 0x0D
+#define LTC3350_REG_VCAP_OV_LVL 0x0E
+#define LTC3350_REG_VOUT_UV_LVL 0x0F
+#define LTC3350_REG_VOUT_OV_LVL 0x10
+#define LTC3350_REG_IIN_OC_LVL 0x11
+#define LTC3350_REG_ICHG_UC_LVL 0x12
+#define LTC3350_REG_DTEMP_COLD_LVL 0x13
+#define LTC3350_REG_DTEMP_HOT_LVL 0x14
+#define LTC3350_REG_ESR_HI_LVL 0x15
+#define LTC3350_REG_CAP_LO_LVL 0x16
+#define LTC3350_REG_CTL_REG 0x17
+#define LTC3350_REG_NUM_CAPS 0x1A
+#define LTC3350_REG_CHRG_STATUS 0x1B
+#define LTC3350_REG_MON_STATUS 0x1C
+#define LTC3350_REG_ALARM_REG 0x1D
+#define LTC3350_REG_MEAS_CAP 0x1E
+#define LTC3350_REG_MEAS_ESR 0x1F
+#define LTC3350_REG_MEAS_VCAP1 0x20
+#define LTC3350_REG_MEAS_VCAP2 0x21
+#define LTC3350_REG_MEAS_VCAP3 0x22
+#define LTC3350_REG_MEAS_VCAP4 0x23
+#define LTC3350_REG_MEAS_GPI 0x24
+#define LTC3350_REG_MEAS_VIN 0x25
+#define LTC3350_REG_MEAS_VCAP 0x26
+#define LTC3350_REG_MEAS_VOUT 0x27
+#define LTC3350_REG_MEAS_IIN 0x28
+#define LTC3350_REG_MEAS_ICHG 0x29
+#define LTC3350_REG_MEAS_DTEMP 0x2A
+
+/* LTC3350_REG_CLR_ALARMS, LTC3350_REG_MASK_ALARMS, LTC3350_REG_ALARM_REG */
+#define LTC3350_MSK_CAP_UV BIT(0) /* capacitor undervoltage alarm */
+#define LTC3350_MSK_CAP_OV BIT(1) /* capacitor overvoltage alarm */
+#define LTC3350_MSK_GPI_UV BIT(2) /* GPI undervoltage alarm */
+#define LTC3350_MSK_GPI_OV BIT(3) /* GPI overvoltage alarm */
+#define LTC3350_MSK_VIN_UV BIT(4) /* VIN undervoltage alarm */
+#define LTC3350_MSK_VIN_OV BIT(5) /* VIN overvoltage alarm */
+#define LTC3350_MSK_VCAP_UV BIT(6) /* VCAP undervoltage alarm */
+#define LTC3350_MSK_VCAP_OV BIT(7) /* VCAP overvoltage alarm */
+#define LTC3350_MSK_VOUT_UV BIT(8) /* VOUT undervoltage alarm */
+#define LTC3350_MSK_VOUT_OV BIT(9) /* VOUT overvoltage alarm */
+#define LTC3350_MSK_IIN_OC BIT(10) /* input overcurrent alarm */
+#define LTC3350_MSK_ICHG_UC BIT(11) /* charge undercurrent alarm */
+#define LTC3350_MSK_DTEMP_COLD BIT(12) /* die temperature cold alarm */
+#define LTC3350_MSK_DTEMP_HOT BIT(13) /* die temperature hot alarm */
+#define LTC3350_MSK_ESR_HI BIT(14) /* ESR high alarm */
+#define LTC3350_MSK_CAP_LO BIT(15) /* capacitance low alarm */
+
+/* LTC3350_REG_MSK_MON_STATUS masks */
+#define LTC3350_MSK_MON_CAPESR_ACTIVE BIT(0)
+#define LTC3350_MSK_MON_CAPESR_SCHEDULED BIT(1)
+#define LTC3350_MSK_MON_CAPESR_PENDING BIT(2)
+#define LTC3350_MSK_MON_CAP_DONE BIT(3)
+#define LTC3350_MSK_MON_ESR_DONE BIT(4)
+#define LTC3350_MSK_MON_CAP_FAILED BIT(5)
+#define LTC3350_MSK_MON_ESR_FAILED BIT(6)
+#define LTC3350_MSK_MON_POWER_FAILED BIT(8)
+#define LTC3350_MSK_MON_POWER_RETURNED BIT(9)
+
+/* LTC3350_REG_CTL_REG */
+/* Begin a capacitance and ESR measurement when possible */
+#define LTC3350_CTL_STRT_CAPESR BIT(0)
+/* A one in this bit location enables the input buffer on the GPI pin */
+#define LTC3350_CTL_GPI_BUFFER_EN BIT(1)
+/* Stops an active capacitance/ESR measurement */
+#define LTC3350_CTL_STOP_CAPESR BIT(2)
+/* Increases capacitor measurement resolution by 100x for smaller capacitors */
+#define LTC3350_CTL_CAP_SCALE BIT(3)
+
+/* LTC3350_REG_CHRG_STATUS */
+#define LTC3350_CHRG_STEPDOWN BIT(0) /* Synchronous controller in step-down mode (charging) */
+#define LTC3350_CHRG_STEPUP BIT(1) /* Synchronous controller in step-up mode (backup) */
+#define LTC3350_CHRG_CV BIT(2) /* The charger is in constant voltage mode */
+#define LTC3350_CHRG_UVLO BIT(3) /* The charger is in undervoltage lockout */
+#define LTC3350_CHRG_INPUT_ILIM BIT(4) /* The charger is in input current limit */
+#define LTC3350_CHRG_CAPPG BIT(5) /* The capacitor voltage is above power good threshold */
+#define LTC3350_CHRG_SHNT BIT(6) /* The capacitor manager is shunting */
+#define LTC3350_CHRG_BAL BIT(7) /* The capacitor manager is balancing */
+#define LTC3350_CHRG_DIS BIT(8) /* Charger disabled for capacitance measurement */
+#define LTC3350_CHRG_CI BIT(9) /* The charger is in constant current mode */
+#define LTC3350_CHRG_PFO BIT(11) /* Input voltage is below PFI threshold */
+
+/* LTC3350_REG_MON_STATUS */
+#define LTC3350_MON_CAPESR_ACTIVE BIT(0) /* Capacitance/ESR measurement in progress */
+#define LTC3350_MON_CAPESR_SCHEDULED BIT(1) /* Waiting programmed time */
+#define LTC3350_MON_CAPESR_PENDING BIT(2) /* Waiting for satisfactory conditions */
+#define LTC3350_MON_CAP_DONE BIT(3) /* Capacitance measurement has completed */
+#define LTC3350_MON_ESR_DONE BIT(4) /* ESR Measurement has completed */
+#define LTC3350_MON_CAP_FAILED BIT(5) /* Last capacitance measurement failed */
+#define LTC3350_MON_ESR_FAILED BIT(6) /* Last ESR measurement failed */
+#define LTC3350_MON_POWER_FAILED BIT(8) /* Unable to charge */
+#define LTC3350_MON_POWER_RETURNED BIT(9) /* Able to charge */
+
+
+struct ltc3350_info {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct power_supply *charger;
+ struct power_supply *capacitor;
+ u32 rsnsc; /* Series resistor that sets charge current, microOhm */
+ u32 rsnsi; /* Series resistor to measure input current, microOhm */
+};
+
+/*
+ * About LTC3350 "alarm" functions: Setting a bit in the LTC3350_REG_MSK_ALARMS
+ * register enables the alarm. The alarm will trigger an SMBALERT only once.
+ * To reset the alarm, write a "1" bit to LTC3350_REG_CLR_ALARMS. Then the alarm
+ * will trigger another SMBALERT when conditions are met (may be immediately).
+ * After writing to one of the corresponding level registers, enable the alarm,
+ * so that a UEVENT triggers when the alarm goes off.
+ */
+static void ltc3350_enable_alarm(struct ltc3350_info *info, unsigned int reg)
+{
+ unsigned int mask;
+
+ /* Register locations correspond to alarm mask bits */
+ mask = BIT(reg - LTC3350_REG_CAP_UV_LVL);
+ /* Clear the alarm bit so it may trigger again */
+ regmap_write(info->regmap, LTC3350_REG_CLR_ALARMS, mask);
+ /* Enable the alarm */
+ regmap_update_bits(info->regmap, LTC3350_REG_MSK_ALARMS, mask, mask);
+}
+
+/* Convert enum value to POWER_SUPPLY_STATUS value */
+static int ltc3350_state_decode(unsigned int value)
+{
+ if (value & LTC3350_CHRG_STEPUP)
+ return POWER_SUPPLY_STATUS_DISCHARGING; /* running on backup */
+
+ if (value & LTC3350_CHRG_PFO)
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ if (value & LTC3350_CHRG_STEPDOWN) {
+ /* The chip remains in CV mode indefinitely, hence "full" */
+ if (value & LTC3350_CHRG_CV)
+ return POWER_SUPPLY_STATUS_FULL;
+
+ return POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ /* Not in step down? Must be full then (never seen this) */
+ return POWER_SUPPLY_STATUS_FULL;
+};
+
+static int ltc3350_get_status(struct ltc3350_info *info, union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(info->regmap, LTC3350_REG_CHRG_STATUS, &regval);
+ if (ret)
+ return ret;
+
+ val->intval = ltc3350_state_decode(regval);
+
+ return 0;
+}
+
+static int ltc3350_charge_status_decode(unsigned int value)
+{
+ if (!(value & LTC3350_CHRG_STEPDOWN))
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+
+ /* constant voltage is "topping off" */
+ if (value & LTC3350_CHRG_CV)
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+
+ /* input limiter */
+ if (value & LTC3350_CHRG_INPUT_ILIM)
+ return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE;
+
+ /* constant current is "fast" */
+ if (value & LTC3350_CHRG_CI)
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+
+ return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+}
+
+static int ltc3350_get_charge_type(struct ltc3350_info *info, union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(info->regmap, LTC3350_REG_CHRG_STATUS, &regval);
+ if (ret)
+ return ret;
+
+ val->intval = ltc3350_charge_status_decode(regval);
+
+ return 0;
+}
+
+static int ltc3350_get_online(struct ltc3350_info *info, union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(info->regmap, LTC3350_REG_MON_STATUS, &regval);
+ if (ret)
+ return ret;
+
+ /* indicates if input voltage is sufficient to charge */
+ val->intval = !!(regval & LTC3350_MON_POWER_RETURNED);
+
+ return 0;
+}
+
+static int ltc3350_get_scaled(struct ltc3350_info *info, unsigned int reg,
+ int scale, int *value)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(info->regmap, reg, &regval);
+ if (ret)
+ return ret;
+
+ /*
+ * The "scale" here is 10 μV per LSB, this allows all calculations to
+ * be done in 32-bit integer without overflow. This converts the
+ * register value to μV.
+ */
+ *value = regval * scale / 10;
+
+ return 0;
+}
+
+static int ltc3350_set_scaled(struct ltc3350_info *info, unsigned int reg, int scale, int value)
+{
+ int ret;
+
+ value *= 10;
+ value = DIV_ROUND_CLOSEST(value, scale);
+
+ ret = regmap_write(info->regmap, reg, value);
+ if (ret)
+ return ret;
+
+ /* When writing to one of the LVL registers, update the alarm mask */
+ if (reg >= LTC3350_REG_CAP_UV_LVL && reg <= LTC3350_REG_CAP_LO_LVL)
+ ltc3350_enable_alarm(info, reg);
+
+ return 0;
+}
+
+static int ltc3350_get_input_current(struct ltc3350_info *info, union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(info->regmap, LTC3350_REG_MEAS_IIN, &regval);
+ if (ret)
+ return ret;
+
+ /* 1.983µV/RSNSI amperes per LSB */
+ ret = regval * 19830;
+ ret /= info->rsnsi;
+ ret *= 100;
+
+ val->intval = ret;
+
+ return 0;
+}
+
+static int ltc3350_get_icharge(struct ltc3350_info *info, union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(info->regmap, LTC3350_REG_MEAS_ICHG, &regval);
+ if (ret)
+ return ret;
+
+ /* 1.983µV/RSNSC amperes per LSB */
+ ret = regval * 19830;
+ ret /= info->rsnsc;
+ ret *= 100;
+
+ val->intval = ret;
+
+ return 0;
+}
+
+static int ltc3350_get_icharge_max(struct ltc3350_info *info, union power_supply_propval *val)
+{
+ /* I_CHG(MAX) = 32mV / RSNSC (Ampere) */
+ val->intval = 3200000000U / (info->rsnsc / 10);
+
+ return 0;
+}
+
+static int ltc3350_get_iin_max(struct ltc3350_info *info, union power_supply_propval *val)
+{
+ /* I_IN(MAX) = 32mV / RSNSI (Ampere) */
+ val->intval = 3200000000U / (info->rsnsi / 10);
+
+ return 0;
+}
+
+
+static int ltc3350_get_die_temp(struct ltc3350_info *info, unsigned int reg,
+ union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(info->regmap, reg, &regval);
+ if (ret)
+ return ret;
+
+ /* 0.028°C per LSB – 251.4°C */
+ ret = 280 * regval;
+ ret /= 100; /* Centidegrees scale */
+ ret -= 25140;
+ val->intval = ret;
+
+ return 0;
+}
+
+static int ltc3350_set_die_temp(struct ltc3350_info *info, unsigned int reg, int val)
+{
+ unsigned int regval;
+ int ret;
+
+ /* 0.028°C per LSB – 251.4°C */
+ regval = val + 25140;
+ regval *= 100;
+ regval /= 280;
+
+ ret = regmap_write(info->regmap, reg, regval);
+ if (ret)
+ return ret;
+
+ ltc3350_enable_alarm(info, reg);
+ return 0;
+}
+
+static int ltc3350_get_num_caps(struct ltc3350_info *info, union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(info->regmap, LTC3350_REG_NUM_CAPS, &regval);
+ if (ret)
+ return ret;
+
+ val->intval = regval + 1;
+
+ return 0;
+}
+
+
+/* Custom properties */
+static ssize_t ltc3350_attr_show(struct device *dev,
+ struct device_attribute *attr, char *buf,
+ unsigned int reg, int scale)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct ltc3350_info *info = power_supply_get_drvdata(psy);
+ int value;
+ int ret;
+
+ ret = ltc3350_get_scaled(info, reg, scale, &value);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t ltc3350_attr_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count,
+ unsigned int reg, unsigned int scale)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct ltc3350_info *info = power_supply_get_drvdata(psy);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ ret = ltc3350_set_scaled(info, reg, val, scale);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+#define LTC3350_DEVICE_ATTR_RO(_name, _reg, _scale) \
+static ssize_t _name##_show(struct device *dev, struct device_attribute *attr, \
+ char *buf) \
+{ \
+ return ltc3350_attr_show(dev, attr, buf, _reg, _scale); \
+} \
+static DEVICE_ATTR_RO(_name)
+
+#define LTC3350_DEVICE_ATTR_RW(_name, _reg, _scale) \
+static ssize_t _name##_show(struct device *dev, struct device_attribute *attr, \
+ char *buf) \
+{ \
+ return ltc3350_attr_show(dev, attr, buf, _reg, _scale); \
+} \
+static ssize_t _name##_store(struct device *dev, struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ return ltc3350_attr_store(dev, attr, buf, count, _reg, _scale); \
+} \
+static DEVICE_ATTR_RW(_name)
+
+/* Shunt voltage, 183.5μV per LSB */
+LTC3350_DEVICE_ATTR_RW(vshunt, LTC3350_REG_VSHUNT, 1835);
+
+/* General purpose input, 183.5μV per LSB */
+LTC3350_DEVICE_ATTR_RO(gpi, LTC3350_REG_MEAS_GPI, 1835);
+LTC3350_DEVICE_ATTR_RW(gpi_uv, LTC3350_REG_GPI_UV_LVL, 1835);
+LTC3350_DEVICE_ATTR_RW(gpi_ov, LTC3350_REG_GPI_OV_LVL, 1835);
+
+/* Input voltage, 2.21mV per LSB */
+LTC3350_DEVICE_ATTR_RO(vin, LTC3350_REG_MEAS_VIN, 22100);
+LTC3350_DEVICE_ATTR_RW(vin_uv, LTC3350_REG_VIN_UV_LVL, 22100);
+LTC3350_DEVICE_ATTR_RW(vin_ov, LTC3350_REG_VIN_OV_LVL, 22100);
+
+static ssize_t charge_status_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct ltc3350_info *info = power_supply_get_drvdata(psy);
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(info->regmap, LTC3350_REG_CHRG_STATUS, &regval);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "0x%x\n", regval);
+}
+static DEVICE_ATTR_RO(charge_status);
+
+static struct attribute *ltc3350_sysfs_entries[] = {
+ &dev_attr_vshunt.attr,
+ &dev_attr_gpi.attr,
+ &dev_attr_gpi_uv.attr,
+ &dev_attr_gpi_ov.attr,
+ &dev_attr_vin.attr,
+ &dev_attr_vin_uv.attr,
+ &dev_attr_vin_ov.attr,
+ &dev_attr_charge_status.attr,
+ NULL,
+};
+
+static const struct attribute_group ltc3350_attr_group = {
+ .name = NULL, /* put in device directory */
+ .attrs = ltc3350_sysfs_entries,
+};
+
+static const struct attribute_group *ltc3350_attr_groups[] = {
+ &ltc3350_attr_group,
+ NULL,
+};
+
+static int ltc3350_get_property(struct power_supply *psy, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ltc3350_info *info = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ return ltc3350_get_status(info, val);
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ return ltc3350_get_charge_type(info, val);
+ case POWER_SUPPLY_PROP_ONLINE:
+ return ltc3350_get_online(info, val);
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return ltc3350_get_scaled(info, LTC3350_REG_MEAS_VOUT, 22100, &val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ return ltc3350_get_scaled(info, LTC3350_REG_VOUT_UV_LVL, 22100, &val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ return ltc3350_get_scaled(info, LTC3350_REG_VOUT_OV_LVL, 22100, &val->intval);
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return ltc3350_get_input_current(info, val);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ return ltc3350_get_icharge(info, val);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ return ltc3350_get_icharge_max(info, val);
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return ltc3350_get_iin_max(info, val);
+ case POWER_SUPPLY_PROP_TEMP:
+ return ltc3350_get_die_temp(info, LTC3350_REG_MEAS_DTEMP, val);
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ return ltc3350_get_die_temp(info, LTC3350_REG_DTEMP_COLD_LVL, val);
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ return ltc3350_get_die_temp(info, LTC3350_REG_DTEMP_HOT_LVL, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ltc3350_set_property(struct power_supply *psy, enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct ltc3350_info *info = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ return ltc3350_set_die_temp(info, LTC3350_REG_DTEMP_COLD_LVL, val->intval);
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ return ltc3350_set_die_temp(info, LTC3350_REG_DTEMP_HOT_LVL, val->intval);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ltc3350_property_is_writeable(struct power_supply *psy, enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ case POWER_SUPPLY_PROP_CELL_VOLTAGE_MIN:
+ case POWER_SUPPLY_PROP_CELL_VOLTAGE_MAX:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/* Charger power supply property routines */
+static enum power_supply_property ltc3350_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
+ POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
+};
+
+static const struct power_supply_desc ltc3350_desc = {
+ .name = "ltc3350",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = ltc3350_properties,
+ .num_properties = ARRAY_SIZE(ltc3350_properties),
+ .get_property = ltc3350_get_property,
+ .set_property = ltc3350_set_property,
+ .property_is_writeable = ltc3350_property_is_writeable,
+};
+
+static int ltc3350_capacitor_get_property(struct power_supply *psy, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ltc3350_info *info = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ return ltc3350_get_status(info, val);
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ /* Capacitor stack voltage, 1.476 mV per LSB */
+ return ltc3350_get_scaled(info, LTC3350_REG_MEAS_VCAP, 14760, &val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ return ltc3350_get_scaled(info, LTC3350_REG_VCAP_UV_LVL, 14760, &val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ return ltc3350_get_scaled(info, LTC3350_REG_VCAP_OV_LVL, 14760, &val->intval);
+ case POWER_SUPPLY_PROP_NUMBER_OF_SERIAL_CELLS:
+ return ltc3350_get_num_caps(info, val);
+ case POWER_SUPPLY_PROP_CELL1_VOLTAGE_NOW:
+ /* Single capacitor voltages, 183.5μV per LSB */
+ return ltc3350_get_scaled(info, LTC3350_REG_MEAS_VCAP1, 1835, &val->intval);
+ case POWER_SUPPLY_PROP_CELL2_VOLTAGE_NOW:
+ return ltc3350_get_scaled(info, LTC3350_REG_MEAS_VCAP2, 1835, &val->intval);
+ case POWER_SUPPLY_PROP_CELL3_VOLTAGE_NOW:
+ return ltc3350_get_scaled(info, LTC3350_REG_MEAS_VCAP3, 1835, &val->intval);
+ case POWER_SUPPLY_PROP_CELL4_VOLTAGE_NOW:
+ return ltc3350_get_scaled(info, LTC3350_REG_MEAS_VCAP4, 1835, &val->intval);
+ case POWER_SUPPLY_PROP_CELL_VOLTAGE_MIN:
+ return ltc3350_get_scaled(info, LTC3350_REG_CAP_UV_LVL, 1835, &val->intval);
+ case POWER_SUPPLY_PROP_CELL_VOLTAGE_MAX:
+ return ltc3350_get_scaled(info, LTC3350_REG_CAP_OV_LVL, 1835, &val->intval);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ltc3350_capacitor_set_property(struct power_supply *psy, enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct ltc3350_info *info = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ return ltc3350_set_scaled(info, LTC3350_REG_VCAP_UV_LVL, 14760, val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ return ltc3350_set_scaled(info, LTC3350_REG_VCAP_OV_LVL, 14760, val->intval);
+ case POWER_SUPPLY_PROP_CELL_VOLTAGE_MIN:
+ return ltc3350_set_scaled(info, LTC3350_REG_CAP_UV_LVL, 1835, val->intval);
+ case POWER_SUPPLY_PROP_CELL_VOLTAGE_MAX:
+ return ltc3350_set_scaled(info, LTC3350_REG_CAP_OV_LVL, 1835, val->intval);
+ default:
+ return -EINVAL;
+ }
+}
+
+static enum power_supply_property ltc3350_capacitor_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_NUMBER_OF_SERIAL_CELLS,
+ POWER_SUPPLY_PROP_CELL1_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CELL2_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CELL3_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CELL4_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CELL_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_CELL_VOLTAGE_MAX,
+};
+
+static const struct power_supply_desc ltc3350_capacitor_desc = {
+ .name = "ltc3350_capacitor",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = ltc3350_capacitor_properties,
+ .num_properties = ARRAY_SIZE(ltc3350_capacitor_properties),
+ .get_property = ltc3350_capacitor_get_property,
+ .set_property = ltc3350_capacitor_set_property,
+ .property_is_writeable = ltc3350_property_is_writeable,
+};
+
+static char *ltc3350_supply_interface_capacitor[] = { "ltc3350_capacitor" };
+
+static bool ltc3350_is_writeable_reg(struct device *dev, unsigned int reg)
+{
+ /* all registers up to this one are writeable */
+ return reg < LTC3350_REG_NUM_CAPS;
+}
+
+static bool ltc3350_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ /* read-only status registers and self-clearing register */
+ return reg > LTC3350_REG_NUM_CAPS || reg == LTC3350_REG_CLR_ALARMS;
+}
+
+static const struct regmap_config ltc3350_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+ .writeable_reg = ltc3350_is_writeable_reg,
+ .volatile_reg = ltc3350_is_volatile_reg,
+ .max_register = LTC3350_REG_MEAS_DTEMP,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int ltc3350_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct ltc3350_info *info;
+ struct power_supply_config ltc3350_config = {};
+ int ret;
+
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->client = client;
+ i2c_set_clientdata(client, info);
+
+ info->regmap = devm_regmap_init_i2c(client, &ltc3350_regmap_config);
+ if (IS_ERR(info->regmap))
+ return dev_err_probe(dev, PTR_ERR(info->regmap),
+ "Failed to initialize register map\n");
+
+ ret = device_property_read_u32(dev, "lltc,rsnsc-micro-ohms",
+ &info->rsnsc);
+ if (ret)
+ return dev_err_probe(dev, ret, "Missing lltc,rsnsc-micro-ohms property\n");
+
+ if (!info->rsnsc)
+ return -EINVAL;
+
+ ret = device_property_read_u32(dev, "lltc,rsnsi-micro-ohms",
+ &info->rsnsi);
+ if (ret)
+ return dev_err_probe(dev, ret, "Missing lltc,rsnsi-micro-ohms property\n");
+
+ if (!info->rsnsi)
+ return -EINVAL;
+
+ /* Clear and disable all interrupt sources*/
+ ret = regmap_write(info->regmap, LTC3350_REG_CLR_ALARMS, 0xFFFF);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write configuration\n");
+
+ regmap_write(info->regmap, LTC3350_REG_MSK_ALARMS, 0);
+ regmap_write(info->regmap, LTC3350_REG_MSK_MON_STATUS, 0);
+
+ ltc3350_config.fwnode = dev_fwnode(dev);
+ ltc3350_config.drv_data = info;
+ info->capacitor = devm_power_supply_register(dev, &ltc3350_capacitor_desc, &ltc3350_config);
+ if (IS_ERR(info->capacitor)) {
+ dev_err(dev, "Failed to register capacitor\n");
+ return PTR_ERR(info->capacitor);
+ }
+
+ ltc3350_config.supplied_to = ltc3350_supply_interface_capacitor;
+ ltc3350_config.num_supplicants = ARRAY_SIZE(ltc3350_supply_interface_capacitor);
+ ltc3350_config.attr_grp = ltc3350_attr_groups;
+ info->charger = devm_power_supply_register(dev, &ltc3350_desc, &ltc3350_config);
+ if (IS_ERR(info->charger)) {
+ dev_err(dev, "Failed to register charger\n");
+ return PTR_ERR(info->charger);
+ }
+
+ /* Enable interrupts on status changes */
+ regmap_write(info->regmap, LTC3350_REG_MSK_MON_STATUS,
+ LTC3350_MON_POWER_FAILED | LTC3350_MON_POWER_RETURNED);
+
+ return 0;
+}
+
+static void ltc3350_alert(struct i2c_client *client, enum i2c_alert_protocol type,
+ unsigned int flag)
+{
+ struct ltc3350_info *info = i2c_get_clientdata(client);
+
+ if (type != I2C_PROTOCOL_SMBUS_ALERT)
+ return;
+
+ power_supply_changed(info->charger);
+}
+
+static const struct i2c_device_id ltc3350_i2c_id_table[] = {
+ { "ltc3350", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, ltc3350_i2c_id_table);
+
+static const struct of_device_id ltc3350_of_match[] = {
+ { .compatible = "lltc,ltc3350", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ltc3350_of_match);
+
+static struct i2c_driver ltc3350_driver = {
+ .probe = ltc3350_probe,
+ .alert = ltc3350_alert,
+ .id_table = ltc3350_i2c_id_table,
+ .driver = {
+ .name = "ltc3350-charger",
+ .of_match_table = ltc3350_of_match,
+ },
+};
+module_i2c_driver(ltc3350_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mike Looijmans <[email protected]>");
+MODULE_DESCRIPTION("LTC3350 charger driver");
--
2.34.1


Met vriendelijke groet / kind regards,

Mike Looijmans
System Expert


TOPIC Embedded Products B.V.
Materiaalweg 4, 5681 RJ Best
The Netherlands

T: +31 (0) 499 33 69 69
E: [email protected]
W: http://www.topic.nl

Please consider the environment before printing this e-mail

2024-04-16 16:00:24

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v4 1/5] dt-bindings: power: Extend battery chemistry with capacitor

On 15/04/2024 10:13, Mike Looijmans wrote:
> Another technology to store energy is a (super)capacitor.
>
> Signed-off-by: Mike Looijmans <[email protected]>
> ---
>
> (no changes since v1)
>
> Documentation/devicetree/bindings/power/supply/battery.yaml | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/Documentation/devicetree/bindings/power/supply/battery.yaml b/Documentation/devicetree/bindings/power/supply/battery.yaml
> index 491488e7b970..a22c97dfad88 100644
> --- a/Documentation/devicetree/bindings/power/supply/battery.yaml
> +++ b/Documentation/devicetree/bindings/power/supply/battery.yaml
> @@ -44,6 +44,7 @@ properties:
> - const: lithium-ion-polymer
> - const: lithium-ion-iron-phosphate
> - const: lithium-ion-manganese-oxide
> + - const: capacitor

Please keep some sort of order... everyone insists on adding to the end
of the lists...

Acked-by: Krzysztof Kozlowski <[email protected]>

Best regards,
Krzysztof


2024-04-22 07:51:45

by Mike Looijmans

[permalink] [raw]
Subject: Re: [PATCH v4 1/5] dt-bindings: power: Extend battery chemistry with capacitor

On 15-04-2024 17:24, Krzysztof Kozlowski wrote:
> On 15/04/2024 10:13, Mike Looijmans wrote:
>> Another technology to store energy is a (super)capacitor.
>>
>> Signed-off-by: Mike Looijmans <[email protected]>
>> ---
>>
>> (no changes since v1)
>>
>> Documentation/devicetree/bindings/power/supply/battery.yaml | 1 +
>> 1 file changed, 1 insertion(+)
>>
>> diff --git a/Documentation/devicetree/bindings/power/supply/battery.yaml b/Documentation/devicetree/bindings/power/supply/battery.yaml
>> index 491488e7b970..a22c97dfad88 100644
>> --- a/Documentation/devicetree/bindings/power/supply/battery.yaml
>> +++ b/Documentation/devicetree/bindings/power/supply/battery.yaml
>> @@ -44,6 +44,7 @@ properties:
>> - const: lithium-ion-polymer
>> - const: lithium-ion-iron-phosphate
>> - const: lithium-ion-manganese-oxide
>> + - const: capacitor
> Please keep some sort of order... everyone insists on adding to the end
> of the lists...

Could make it alphabetical, but that requires re-ordering the existing
ones so "lithium" sorts before "nickel".

I think the intention here was that the order is the same as the
matching defines in power-supply.h


> Acked-by: Krzysztof Kozlowski <[email protected]>
>
> Best regards,
> Krzysztof
>

--
Mike Looijmans
System Expert

TOPIC Embedded Products B.V.
Materiaalweg 4, 5681 RJ Best
The Netherlands

T: +31 (0) 499 33 69 69
E: [email protected]
W: http://www.topic.nl




2024-04-22 11:56:39

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v4 1/5] dt-bindings: power: Extend battery chemistry with capacitor

On 22/04/2024 09:50, Mike Looijmans wrote:
> On 15-04-2024 17:24, Krzysztof Kozlowski wrote:
>> On 15/04/2024 10:13, Mike Looijmans wrote:
>>> Another technology to store energy is a (super)capacitor.
>>>
>>> Signed-off-by: Mike Looijmans <[email protected]>
>>> ---
>>>
>>> (no changes since v1)
>>>
>>> Documentation/devicetree/bindings/power/supply/battery.yaml | 1 +
>>> 1 file changed, 1 insertion(+)
>>>
>>> diff --git a/Documentation/devicetree/bindings/power/supply/battery.yaml b/Documentation/devicetree/bindings/power/supply/battery.yaml
>>> index 491488e7b970..a22c97dfad88 100644
>>> --- a/Documentation/devicetree/bindings/power/supply/battery.yaml
>>> +++ b/Documentation/devicetree/bindings/power/supply/battery.yaml
>>> @@ -44,6 +44,7 @@ properties:
>>> - const: lithium-ion-polymer
>>> - const: lithium-ion-iron-phosphate
>>> - const: lithium-ion-manganese-oxide
>>> + - const: capacitor
>> Please keep some sort of order... everyone insists on adding to the end
>> of the lists...
>
> Could make it alphabetical, but that requires re-ordering the existing
> ones so "lithium" sorts before "nickel".

So just put it at beginning, less sorting later.

>
> I think the intention here was that the order is the same as the
> matching defines in power-supply.h

There is no such binding as power-supply.h. And other headers do not
matter, they are not bindings.

Best regards,
Krzysztof


2024-04-22 12:13:25

by Mike Looijmans

[permalink] [raw]
Subject: Re: [PATCH v4 1/5] dt-bindings: power: Extend battery chemistry with capacitor

On 22-04-2024 13:56, Krzysztof Kozlowski wrote:
> On 22/04/2024 09:50, Mike Looijmans wrote:
>> On 15-04-2024 17:24, Krzysztof Kozlowski wrote:
>>> On 15/04/2024 10:13, Mike Looijmans wrote:
>>>> Another technology to store energy is a (super)capacitor.
>>>>
>>>> Signed-off-by: Mike Looijmans <[email protected]>
>>>> ---
>>>>
>>>> (no changes since v1)
>>>>
>>>> Documentation/devicetree/bindings/power/supply/battery.yaml | 1 +
>>>> 1 file changed, 1 insertion(+)
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/power/supply/battery.yaml b/Documentation/devicetree/bindings/power/supply/battery.yaml
>>>> index 491488e7b970..a22c97dfad88 100644
>>>> --- a/Documentation/devicetree/bindings/power/supply/battery.yaml
>>>> +++ b/Documentation/devicetree/bindings/power/supply/battery.yaml
>>>> @@ -44,6 +44,7 @@ properties:
>>>> - const: lithium-ion-polymer
>>>> - const: lithium-ion-iron-phosphate
>>>> - const: lithium-ion-manganese-oxide
>>>> + - const: capacitor
>>> Please keep some sort of order... everyone insists on adding to the end
>>> of the lists...
>> Could make it alphabetical, but that requires re-ordering the existing
>> ones so "lithium" sorts before "nickel".
> So just put it at beginning, less sorting later.

Ok. I'll wait a bit for comments on the other parts, before sending a v6
of the series.


>
>> I think the intention here was that the order is the same as the
>> matching defines in power-supply.h
> There is no such binding as power-supply.h. And other headers do not
> matter, they are not bindings.

Clear, thanks.


--
Mike Looijmans
System Expert

TOPIC Embedded Products B.V.
Materiaalweg 4, 5681 RJ Best
The Netherlands

T: +31 (0) 499 33 69 69
E: [email protected]
W: http://www.topic.nl