summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/power/reset/syscon-reboot-mode.txt35
-rw-r--r--Documentation/devicetree/bindings/power/reset/syscon-reboot-mode.yaml55
-rw-r--r--Documentation/devicetree/bindings/power/supply/cw2015_battery.yaml82
-rw-r--r--Documentation/devicetree/bindings/power/supply/power-supply.yaml40
-rw-r--r--Documentation/devicetree/bindings/power/supply/power_supply.txt25
-rw-r--r--Documentation/devicetree/bindings/vendor-prefixes.yaml2
-rw-r--r--MAINTAINERS7
-rw-r--r--drivers/power/reset/Kconfig7
-rw-r--r--drivers/power/reset/Makefile1
-rw-r--r--drivers/power/reset/ltc2952-poweroff.c3
-rw-r--r--drivers/power/reset/oxnas-restart.c233
-rw-r--r--drivers/power/reset/qcom-pon.c3
-rw-r--r--drivers/power/supply/88pm860x_battery.c8
-rw-r--r--drivers/power/supply/Kconfig13
-rw-r--r--drivers/power/supply/Makefile1
-rw-r--r--drivers/power/supply/ab8500_fg.c2
-rw-r--r--drivers/power/supply/axp288_charger.c5
-rw-r--r--drivers/power/supply/axp288_fuel_gauge.c6
-rw-r--r--drivers/power/supply/bq25890_charger.c116
-rw-r--r--drivers/power/supply/charger-manager.c40
-rw-r--r--drivers/power/supply/cw2015_battery.c749
-rw-r--r--drivers/power/supply/generic-adc-battery.c22
-rw-r--r--drivers/power/supply/lp8788-charger.c18
-rw-r--r--drivers/power/supply/max14656_charger_detector.c5
-rw-r--r--drivers/power/supply/max17042_battery.c8
-rw-r--r--drivers/power/supply/power_supply_hwmon.c64
-rw-r--r--drivers/power/supply/power_supply_sysfs.c479
-rw-r--r--drivers/power/supply/sc27xx_fuel_gauge.c77
-rw-r--r--include/linux/power_supply.h4
29 files changed, 1682 insertions, 428 deletions
diff --git a/Documentation/devicetree/bindings/power/reset/syscon-reboot-mode.txt b/Documentation/devicetree/bindings/power/reset/syscon-reboot-mode.txt
deleted file mode 100644
index f7ce1d8af04a..000000000000
--- a/Documentation/devicetree/bindings/power/reset/syscon-reboot-mode.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-SYSCON reboot mode driver
-
-This driver gets reboot mode magic value form reboot-mode driver
-and stores it in a SYSCON mapped register. Then the bootloader
-can read it and take different action according to the magic
-value stored.
-
-This DT node should be represented as a sub-node of a "syscon", "simple-mfd"
-node.
-
-Required properties:
-- compatible: should be "syscon-reboot-mode"
-- offset: offset in the register map for the storage register (in bytes)
-
-Optional property:
-- mask: bits mask of the bits in the register to store the reboot mode magic value,
- default set to 0xffffffff if missing.
-
-The rest of the properties should follow the generic reboot-mode description
-found in reboot-mode.txt
-
-Example:
- pmu: pmu@20004000 {
- compatible = "rockchip,rk3066-pmu", "syscon", "simple-mfd";
- reg = <0x20004000 0x100>;
-
- reboot-mode {
- compatible = "syscon-reboot-mode";
- offset = <0x40>;
- mode-normal = <BOOT_NORMAL>;
- mode-recovery = <BOOT_RECOVERY>;
- mode-bootloader = <BOOT_FASTBOOT>;
- mode-loader = <BOOT_BL_DOWNLOAD>;
- };
- };
diff --git a/Documentation/devicetree/bindings/power/reset/syscon-reboot-mode.yaml b/Documentation/devicetree/bindings/power/reset/syscon-reboot-mode.yaml
new file mode 100644
index 000000000000..9b1ffceefe3d
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/reset/syscon-reboot-mode.yaml
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: GPL-2.0-only
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/power/reset/syscon-reboot-mode.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Generic SYSCON reboot mode driver
+
+maintainers:
+ - Sebastian Reichel <sre@kernel.org>
+
+description: |
+ This driver gets reboot mode magic value from reboot-mode driver
+ and stores it in a SYSCON mapped register. Then the bootloader
+ can read it and take different action according to the magic
+ value stored. The SYSCON mapped register is retrieved from the
+ parental dt-node plus the offset. So the SYSCON reboot-mode node
+ should be represented as a sub-node of a "syscon", "simple-mfd" node.
+
+properties:
+ compatible:
+ const: syscon-reboot-mode
+
+ mask:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Update only the register bits defined by the mask (32 bit)
+
+ offset:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Offset in the register map for the mode register (in bytes)
+
+patternProperties:
+ "^mode-.+":
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Vendor-specific mode value written to the mode register
+
+additionalProperties: false
+
+required:
+ - compatible
+ - offset
+
+examples:
+ - |
+ #include <dt-bindings/soc/rockchip,boot-mode.h>
+
+ reboot-mode {
+ compatible = "syscon-reboot-mode";
+ offset = <0x40>;
+ mode-normal = <BOOT_NORMAL>;
+ mode-recovery = <BOOT_RECOVERY>;
+ mode-bootloader = <BOOT_FASTBOOT>;
+ mode-loader = <BOOT_BL_DOWNLOAD>;
+ };
+...
diff --git a/Documentation/devicetree/bindings/power/supply/cw2015_battery.yaml b/Documentation/devicetree/bindings/power/supply/cw2015_battery.yaml
new file mode 100644
index 000000000000..4a265d4234b9
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/cw2015_battery.yaml
@@ -0,0 +1,82 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/power/supply/cw2015_battery.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Battery driver for CW2015 shuntless fuel gauge by CellWise.
+
+maintainers:
+ - Tobias Schramm <t.schramm@manjaro.org>
+
+description: |
+ The driver can utilize information from a simple-battery linked via a
+ phandle in monitored-battery. If specified the driver uses the
+ charge-full-design-microamp-hours property of the battery.
+
+properties:
+ compatible:
+ const: cellwise,cw2015
+
+ reg:
+ maxItems: 1
+
+ cellwise,battery-profile:
+ description: |
+ This property specifies characteristics of the battery used. The format
+ of this binary blob is kept secret by CellWise. The only way to obtain
+ it is to mail two batteries to a test facility of CellWise and receive
+ back a test report with the binary blob.
+ allOf:
+ - $ref: /schemas/types.yaml#definitions/uint8-array
+ items:
+ - minItems: 64
+ maxItems: 64
+
+ cellwise,monitor-interval-ms:
+ description:
+ Specifies the interval in milliseconds gauge values are polled at
+ minimum: 250
+
+ power-supplies:
+ description:
+ Specifies supplies used for charging the battery connected to this gauge
+ allOf:
+ - $ref: /schemas/types.yaml#/definitions/phandle-array
+ - minItems: 1
+ maxItems: 8 # Should be enough
+
+ monitored-battery:
+ description:
+ Specifies the phandle of a simple-battery connected to this gauge
+ $ref: /schemas/types.yaml#/definitions/phandle
+
+required:
+ - compatible
+ - reg
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cw2015@62 {
+ compatible = "cellwise,cw201x";
+ reg = <0x62>;
+ cellwise,battery-profile = /bits/ 8 <
+ 0x17 0x67 0x80 0x73 0x6E 0x6C 0x6B 0x63
+ 0x77 0x51 0x5C 0x58 0x50 0x4C 0x48 0x36
+ 0x15 0x0C 0x0C 0x19 0x5B 0x7D 0x6F 0x69
+ 0x69 0x5B 0x0C 0x29 0x20 0x40 0x52 0x59
+ 0x57 0x56 0x54 0x4F 0x3B 0x1F 0x7F 0x17
+ 0x06 0x1A 0x30 0x5A 0x85 0x93 0x96 0x2D
+ 0x48 0x77 0x9C 0xB3 0x80 0x52 0x94 0xCB
+ 0x2F 0x00 0x64 0xA5 0xB5 0x11 0xF0 0x11
+ >;
+ cellwise,monitor-interval-ms = <5000>;
+ monitored-battery = <&bat>;
+ power-supplies = <&mains_charger>, <&usb_charger>;
+ };
+ };
+
diff --git a/Documentation/devicetree/bindings/power/supply/power-supply.yaml b/Documentation/devicetree/bindings/power/supply/power-supply.yaml
new file mode 100644
index 000000000000..3bb02bb3a2d8
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/power-supply.yaml
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/power/supply/power-supply.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Power Supply Core Support
+
+maintainers:
+ - Sebastian Reichel <sre@kernel.org>
+
+properties:
+ power-supplies:
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ description:
+ This property is added to a supply in order to list the devices which
+ supply it power, referenced by their phandles.
+
+examples:
+ - |
+ power {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ usb_charger:charger@e {
+ compatible = "some,usb-charger";
+ reg = <0xe>;
+ };
+
+ ac_charger:charger@c {
+ compatible = "some,ac-charger";
+ reg = <0xc>;
+ };
+
+ battery:battery@b {
+ compatible = "some,battery";
+ reg = <0xb>;
+ power-supplies = <&usb_charger>, <&ac_charger>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/power/supply/power_supply.txt b/Documentation/devicetree/bindings/power/supply/power_supply.txt
index 8391bfa0edac..d9693e054509 100644
--- a/Documentation/devicetree/bindings/power/supply/power_supply.txt
+++ b/Documentation/devicetree/bindings/power/supply/power_supply.txt
@@ -1,23 +1,2 @@
-Power Supply Core Support
-
-Optional Properties:
- - power-supplies : This property is added to a supply in order to list the
- devices which supply it power, referenced by their phandles.
-
-Example:
-
- usb-charger: power@e {
- compatible = "some,usb-charger";
- ...
- };
-
- ac-charger: power@c {
- compatible = "some,ac-charger";
- ...
- };
-
- battery@b {
- compatible = "some,battery";
- ...
- power-supplies = <&usb-charger>, <&ac-charger>;
- };
+This binding has been converted to yaml please see power-supply.yaml in this
+directory.
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index d3891386d671..58cf4e8b8d56 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -179,6 +179,8 @@ patternProperties:
description: Cadence Design Systems Inc.
"^cdtech,.*":
description: CDTech(H.K.) Electronics Limited
+ "^cellwise,.*":
+ description: CellWise Microelectronics Co., Ltd
"^ceva,.*":
description: Ceva, Inc.
"^chipidea,.*":
diff --git a/MAINTAINERS b/MAINTAINERS
index 2926327e4976..baa75a324cbd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2165,6 +2165,7 @@ L: linux-oxnas@groups.io (moderated for non-subscribers)
S: Maintained
F: arch/arm/boot/dts/ox8*.dts*
F: arch/arm/mach-oxnas/
+F: drivers/power/reset/oxnas-restart.c
N: oxnas
ARM/PALM TREO SUPPORT
@@ -3933,6 +3934,12 @@ F: arch/powerpc/include/uapi/asm/spu*.h
F: arch/powerpc/oprofile/*cell*
F: arch/powerpc/platforms/cell/
+CELLWISE CW2015 BATTERY DRIVER
+M: Tobias Schrammm <t.schramm@manjaro.org>
+S: Maintained
+F: Documentation/devicetree/bindings/power/supply/cw2015_battery.yaml
+F: drivers/power/supply/cw2015_battery.c
+
CEPH COMMON CODE (LIBCEPH)
M: Ilya Dryomov <idryomov@gmail.com>
M: Jeff Layton <jlayton@kernel.org>
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 890380302080..4dfac618b942 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -123,6 +123,13 @@ config POWER_RESET_OCELOT_RESET
help
This driver supports restart for Microsemi Ocelot SoC.
+config POWER_RESET_OXNAS
+ bool "OXNAS SoC restart driver"
+ depends on ARCH_OXNAS
+ default MACH_OX820
+ help
+ Restart support for OXNAS/PLXTECH OX820 SoC.
+
config POWER_RESET_PIIX4_POWEROFF
tristate "Intel PIIX4 power-off driver"
depends on PCI
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index da37f8b851dc..5710ca469517 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o
+obj-$(CONFIG_POWER_RESET_OXNAS) += oxnas-restart.o
obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
obj-$(CONFIG_POWER_RESET_OCELOT_RESET) += ocelot-reset.o
obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o
diff --git a/drivers/power/reset/ltc2952-poweroff.c b/drivers/power/reset/ltc2952-poweroff.c
index e4a0cc45b3d1..318927938b05 100644
--- a/drivers/power/reset/ltc2952-poweroff.c
+++ b/drivers/power/reset/ltc2952-poweroff.c
@@ -94,7 +94,6 @@ static enum hrtimer_restart ltc2952_poweroff_timer_wde(struct hrtimer *timer)
{
ktime_t now;
int state;
- unsigned long overruns;
struct ltc2952_poweroff *data = to_ltc2952(timer, timer_wde);
if (data->kernel_panic)
@@ -104,7 +103,7 @@ static enum hrtimer_restart ltc2952_poweroff_timer_wde(struct hrtimer *timer)
gpiod_set_value(data->gpio_watchdog, !state);
now = hrtimer_cb_get_time(timer);
- overruns = hrtimer_forward(timer, now, data->wde_interval);
+ hrtimer_forward(timer, now, data->wde_interval);
return HRTIMER_RESTART;
}
diff --git a/drivers/power/reset/oxnas-restart.c b/drivers/power/reset/oxnas-restart.c
new file mode 100644
index 000000000000..13090bec058a
--- /dev/null
+++ b/drivers/power/reset/oxnas-restart.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: (GPL-2.0)
+/*
+ * oxnas SoC reset driver
+ * based on:
+ * Microsemi MIPS SoC reset driver
+ * and ox820_assert_system_reset() written by Ma Hajun <mahaijuns@gmail.com>
+ *
+ * Copyright (c) 2013 Ma Hajun <mahaijuns@gmail.com>
+ * Copyright (c) 2017 Microsemi Corporation
+ * Copyright (c) 2020 Daniel Golle <daniel@makrotopia.org>
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/notifier.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+
+/* bit numbers of reset control register */
+#define OX820_SYS_CTRL_RST_SCU 0
+#define OX820_SYS_CTRL_RST_COPRO 1
+#define OX820_SYS_CTRL_RST_ARM0 2
+#define OX820_SYS_CTRL_RST_ARM1 3
+#define OX820_SYS_CTRL_RST_USBHS 4
+#define OX820_SYS_CTRL_RST_USBHSPHYA 5
+#define OX820_SYS_CTRL_RST_MACA 6
+#define OX820_SYS_CTRL_RST_MAC OX820_SYS_CTRL_RST_MACA
+#define OX820_SYS_CTRL_RST_PCIEA 7
+#define OX820_SYS_CTRL_RST_SGDMA 8
+#define OX820_SYS_CTRL_RST_CIPHER 9
+#define OX820_SYS_CTRL_RST_DDR 10
+#define OX820_SYS_CTRL_RST_SATA 11
+#define OX820_SYS_CTRL_RST_SATA_LINK 12
+#define OX820_SYS_CTRL_RST_SATA_PHY 13
+#define OX820_SYS_CTRL_RST_PCIEPHY 14
+#define OX820_SYS_CTRL_RST_STATIC 15
+#define OX820_SYS_CTRL_RST_GPIO 16
+#define OX820_SYS_CTRL_RST_UART1 17
+#define OX820_SYS_CTRL_RST_UART2 18
+#define OX820_SYS_CTRL_RST_MISC 19
+#define OX820_SYS_CTRL_RST_I2S 20
+#define OX820_SYS_CTRL_RST_SD 21
+#define OX820_SYS_CTRL_RST_MACB 22
+#define OX820_SYS_CTRL_RST_PCIEB 23
+#define OX820_SYS_CTRL_RST_VIDEO 24
+#define OX820_SYS_CTRL_RST_DDR_PHY 25
+#define OX820_SYS_CTRL_RST_USBHSPHYB 26
+#define OX820_SYS_CTRL_RST_USBDEV 27
+#define OX820_SYS_CTRL_RST_ARMDBG 29
+#define OX820_SYS_CTRL_RST_PLLA 30
+#define OX820_SYS_CTRL_RST_PLLB 31
+
+/* bit numbers of clock control register */
+#define OX820_SYS_CTRL_CLK_COPRO 0
+#define OX820_SYS_CTRL_CLK_DMA 1
+#define OX820_SYS_CTRL_CLK_CIPHER 2
+#define OX820_SYS_CTRL_CLK_SD 3
+#define OX820_SYS_CTRL_CLK_SATA 4
+#define OX820_SYS_CTRL_CLK_I2S 5
+#define OX820_SYS_CTRL_CLK_USBHS 6
+#define OX820_SYS_CTRL_CLK_MACA 7
+#define OX820_SYS_CTRL_CLK_MAC OX820_SYS_CTRL_CLK_MACA
+#define OX820_SYS_CTRL_CLK_PCIEA 8
+#define OX820_SYS_CTRL_CLK_STATIC 9
+#define OX820_SYS_CTRL_CLK_MACB 10
+#define OX820_SYS_CTRL_CLK_PCIEB 11
+#define OX820_SYS_CTRL_CLK_REF600 12
+#define OX820_SYS_CTRL_CLK_USBDEV 13
+#define OX820_SYS_CTRL_CLK_DDR 14
+#define OX820_SYS_CTRL_CLK_DDRPHY 15
+#define OX820_SYS_CTRL_CLK_DDRCK 16
+
+/* Regmap offsets */
+#define OX820_CLK_SET_REGOFFSET 0x2c
+#define OX820_CLK_CLR_REGOFFSET 0x30
+#define OX820_RST_SET_REGOFFSET 0x34
+#define OX820_RST_CLR_REGOFFSET 0x38
+#define OX820_SECONDARY_SEL_REGOFFSET 0x14
+#define OX820_TERTIARY_SEL_REGOFFSET 0x8c
+#define OX820_QUATERNARY_SEL_REGOFFSET 0x94
+#define OX820_DEBUG_SEL_REGOFFSET 0x9c
+#define OX820_ALTERNATIVE_SEL_REGOFFSET 0xa4
+#define OX820_PULLUP_SEL_REGOFFSET 0xac
+#define OX820_SEC_SECONDARY_SEL_REGOFFSET 0x100014
+#define OX820_SEC_TERTIARY_SEL_REGOFFSET 0x10008c
+#define OX820_SEC_QUATERNARY_SEL_REGOFFSET 0x100094
+#define OX820_SEC_DEBUG_SEL_REGOFFSET 0x10009c
+#define OX820_SEC_ALTERNATIVE_SEL_REGOFFSET 0x1000a4
+#define OX820_SEC_PULLUP_SEL_REGOFFSET 0x1000ac
+
+struct oxnas_restart_context {
+ struct regmap *sys_ctrl;
+ struct notifier_block restart_handler;
+};
+
+static int ox820_restart_handle(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct oxnas_restart_context *ctx = container_of(this, struct
+ oxnas_restart_context,
+ restart_handler);
+ u32 value;
+
+ /*
+ * Assert reset to cores as per power on defaults
+ * Don't touch the DDR interface as things will come to an impromptu
+ * stop NB Possibly should be asserting reset for PLLB, but there are
+ * timing concerns here according to the docs
+ */
+ value = BIT(OX820_SYS_CTRL_RST_COPRO) |
+ BIT(OX820_SYS_CTRL_RST_USBHS) |
+ BIT(OX820_SYS_CTRL_RST_USBHSPHYA) |
+ BIT(OX820_SYS_CTRL_RST_MACA) |
+ BIT(OX820_SYS_CTRL_RST_PCIEA) |
+ BIT(OX820_SYS_CTRL_RST_SGDMA) |
+ BIT(OX820_SYS_CTRL_RST_CIPHER) |
+ BIT(OX820_SYS_CTRL_RST_SATA) |
+ BIT(OX820_SYS_CTRL_RST_SATA_LINK) |
+ BIT(OX820_SYS_CTRL_RST_SATA_PHY) |
+ BIT(OX820_SYS_CTRL_RST_PCIEPHY) |
+ BIT(OX820_SYS_CTRL_RST_STATIC) |
+ BIT(OX820_SYS_CTRL_RST_UART1) |
+ BIT(OX820_SYS_CTRL_RST_UART2) |
+ BIT(OX820_SYS_CTRL_RST_MISC) |
+ BIT(OX820_SYS_CTRL_RST_I2S) |
+ BIT(OX820_SYS_CTRL_RST_SD) |
+ BIT(OX820_SYS_CTRL_RST_MACB) |
+ BIT(OX820_SYS_CTRL_RST_PCIEB) |
+ BIT(OX820_SYS_CTRL_RST_VIDEO) |
+ BIT(OX820_SYS_CTRL_RST_USBHSPHYB) |
+ BIT(OX820_SYS_CTRL_RST_USBDEV);
+
+ regmap_write(ctx->sys_ctrl, OX820_RST_SET_REGOFFSET, value);
+
+ /* Release reset to cores as per power on defaults */
+ regmap_write(ctx->sys_ctrl, OX820_RST_CLR_REGOFFSET,
+ BIT(OX820_SYS_CTRL_RST_GPIO));
+
+ /*
+ * Disable clocks to cores as per power-on defaults - must leave DDR
+ * related clocks enabled otherwise we'll stop rather abruptly.
+ */
+ value = BIT(OX820_SYS_CTRL_CLK_COPRO) |
+ BIT(OX820_SYS_CTRL_CLK_DMA) |
+ BIT(OX820_SYS_CTRL_CLK_CIPHER) |
+ BIT(OX820_SYS_CTRL_CLK_SD) |
+ BIT(OX820_SYS_CTRL_CLK_SATA) |
+ BIT(OX820_SYS_CTRL_CLK_I2S) |
+ BIT(OX820_SYS_CTRL_CLK_USBHS) |
+ BIT(OX820_SYS_CTRL_CLK_MAC) |
+ BIT(OX820_SYS_CTRL_CLK_PCIEA) |
+ BIT(OX820_SYS_CTRL_CLK_STATIC) |
+ BIT(OX820_SYS_CTRL_CLK_MACB) |
+ BIT(OX820_SYS_CTRL_CLK_PCIEB) |
+ BIT(OX820_SYS_CTRL_CLK_REF600) |
+ BIT(OX820_SYS_CTRL_CLK_USBDEV);
+
+ regmap_write(ctx->sys_ctrl, OX820_CLK_CLR_REGOFFSET, value);
+
+ /* Enable clocks to cores as per power-on defaults */
+
+ /* Set sys-control pin mux'ing as per power-on defaults */
+ regmap_write(ctx->sys_ctrl, OX820_SECONDARY_SEL_REGOFFSET, 0);
+ regmap_write(ctx->sys_ctrl, OX820_TERTIARY_SEL_REGOFFSET, 0);
+ regmap_write(ctx->sys_ctrl, OX820_QUATERNARY_SEL_REGOFFSET, 0);
+ regmap_write(ctx->sys_ctrl, OX820_DEBUG_SEL_REGOFFSET, 0);
+ regmap_write(ctx->sys_ctrl, OX820_ALTERNATIVE_SEL_REGOFFSET, 0);
+ regmap_write(ctx->sys_ctrl, OX820_PULLUP_SEL_REGOFFSET, 0);
+
+ regmap_write(ctx->sys_ctrl, OX820_SEC_SECONDARY_SEL_REGOFFSET, 0);
+ regmap_write(ctx->sys_ctrl, OX820_SEC_TERTIARY_SEL_REGOFFSET, 0);
+ regmap_write(ctx->sys_ctrl, OX820_SEC_QUATERNARY_SEL_REGOFFSET, 0);
+ regmap_write(ctx->sys_ctrl, OX820_SEC_DEBUG_SEL_REGOFFSET, 0);
+ regmap_write(ctx->sys_ctrl, OX820_SEC_ALTERNATIVE_SEL_REGOFFSET, 0);
+ regmap_write(ctx->sys_ctrl, OX820_SEC_PULLUP_SEL_REGOFFSET, 0);
+
+ /*
+ * No need to save any state, as the ROM loader can determine whether
+ * reset is due to power cycling or programatic action, just hit the
+ * (self-clearing) CPU reset bit of the block reset register
+ */
+ value =
+ BIT(OX820_SYS_CTRL_RST_SCU) |
+ BIT(OX820_SYS_CTRL_RST_ARM0) |
+ BIT(OX820_SYS_CTRL_RST_ARM1);
+
+ regmap_write(ctx->sys_ctrl, OX820_RST_SET_REGOFFSET, value);
+
+ pr_emerg("Unable to restart system\n");
+ return NOTIFY_DONE;
+}
+
+static int ox820_restart_probe(struct platform_device *pdev)
+{
+ struct oxnas_restart_context *ctx;
+ struct regmap *sys_ctrl;
+ struct device *dev = &pdev->dev;
+ int err = 0;
+
+ sys_ctrl = syscon_node_to_regmap(pdev->dev.of_node);
+ if (IS_ERR(sys_ctrl))
+ return PTR_ERR(sys_ctrl);
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->sys_ctrl = sys_ctrl;
+ ctx->restart_handler.notifier_call = ox820_restart_handle;
+ ctx->restart_handler.priority = 192;
+ err = register_restart_handler(&ctx->restart_handler);
+ if (err)
+ dev_err(dev, "can't register restart notifier (err=%d)\n", err);
+
+ return err;
+}
+
+static const struct of_device_id ox820_restart_of_match[] = {
+ { .compatible = "oxsemi,ox820-sys-ctrl" },
+ {}
+};
+
+static struct platform_driver ox820_restart_driver = {
+ .probe = ox820_restart_probe,
+ .driver = {
+ .name = "ox820-chip-reset",
+ .of_match_table = ox820_restart_of_match,
+ },
+};
+builtin_platform_driver(ox820_restart_driver);
diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c
index 22a743a0bf28..4a688741a88a 100644
--- a/drivers/power/reset/qcom-pon.c
+++ b/drivers/power/reset/qcom-pon.c
@@ -34,7 +34,8 @@ static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot,
ret = regmap_update_bits(pon->regmap,
pon->baseaddr + PON_SOFT_RB_SPARE,
- 0xfc, magic << pon->reason_shift);
+ GENMASK(7, pon->reason_shift),
+ magic << pon->reason_shift);
if (ret < 0)
dev_err(pon->dev, "update reboot mode bits failed\n");
diff --git a/drivers/power/supply/88pm860x_battery.c b/drivers/power/supply/88pm860x_battery.c
index 5ca047b3f58f..1308f3a185f3 100644
--- a/drivers/power/supply/88pm860x_battery.c
+++ b/drivers/power/supply/88pm860x_battery.c
@@ -919,16 +919,12 @@ static int pm860x_battery_probe(struct platform_device *pdev)
return -ENOMEM;
info->irq_cc = platform_get_irq(pdev, 0);
- if (info->irq_cc <= 0) {
- dev_err(&pdev->dev, "No IRQ resource!\n");
+ if (info->irq_cc <= 0)
return -EINVAL;
- }
info->irq_batt = platform_get_irq(pdev, 1);
- if (info->irq_batt <= 0) {
- dev_err(&pdev->dev, "No IRQ resource!\n");
+ if (info->irq_batt <= 0)
return -EINVAL;
- }
info->chip = chip;
info->i2c =
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index f3424fdce341..efdcf49f27bf 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -116,6 +116,17 @@ config BATTERY_CPCAP
Say Y here to enable support for battery on Motorola
phones and tablets such as droid 4.
+config BATTERY_CW2015
+ tristate "CW2015 Battery driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y here to enable support for the cellwise cw2015
+ battery fuel gauge (used in the Pinebook Pro & others)
+
+ This driver can also be built as a module. If so, the module will be
+ called cw2015_battery.
+
config BATTERY_DS2760
tristate "DS2760 battery driver (HP iPAQ & others)"
depends on W1
@@ -577,7 +588,7 @@ config CHARGER_BQ24257
tristate "TI BQ24250/24251/24257 battery charger driver"
depends on I2C
depends on GPIOLIB || COMPILE_TEST
- depends on REGMAP_I2C
+ select REGMAP_I2C
help
Say Y to enable support for the TI BQ24250, BQ24251, and BQ24257 battery
chargers.
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 6c7da920ea83..69727a10e835 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o
+obj-$(CONFIG_BATTERY_CW2015) += cw2015_battery.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index b96f90a82ecf..751c4f6c7487 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -2399,7 +2399,7 @@ static void ab8500_fg_reinit_work(struct work_struct *work)
struct ab8500_fg *di = container_of(work, struct ab8500_fg,
fg_reinit_work.work);
- if (di->flags.calibrate == false) {
+ if (!di->flags.calibrate) {
dev_dbg(di->dev, "Resetting FG state machine to init.\n");
ab8500_fg_clear_cap_samples(di);
ab8500_fg_calc_cap_discharge_voltage(di, true);
diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c
index cf4c67b2d235..9d981b76c1e7 100644
--- a/drivers/power/supply/axp288_charger.c
+++ b/drivers/power/supply/axp288_charger.c
@@ -880,10 +880,9 @@ static int axp288_charger_probe(struct platform_device *pdev)
/* Register charger interrupts */
for (i = 0; i < CHRG_INTR_END; i++) {
pirq = platform_get_irq(info->pdev, i);
- if (pirq < 0) {
- dev_err(&pdev->dev, "Failed to get IRQ: %d\n", pirq);
+ if (pirq < 0)
return pirq;
- }
+
info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
if (info->irq[i] < 0) {
dev_warn(&info->pdev->dev,
diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c
index f40fa0e63b6e..148eb8105803 100644
--- a/drivers/power/supply/axp288_fuel_gauge.c
+++ b/drivers/power/supply/axp288_fuel_gauge.c
@@ -718,6 +718,12 @@ static const struct dmi_system_id axp288_fuel_gauge_blacklist[] = {
},
},
{
+ /* Meegopad T02 */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "MEEGOPAD T02"),
+ },
+ },
+ {
/* Meegopad T08 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Default string"),
diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c
index aebd1253dbc9..20b9824ef5ac 100644
--- a/drivers/power/supply/bq25890_charger.c
+++ b/drivers/power/supply/bq25890_charger.c
@@ -32,6 +32,13 @@ enum bq25890_chip_version {
BQ25896,
};
+static const char *const bq25890_chip_name[] = {
+ "BQ25890",
+ "BQ25892",
+ "BQ25895",
+ "BQ25896",
+};
+
enum bq25890_fields {
F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */
F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */
@@ -400,17 +407,7 @@ static int bq25890_power_supply_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
- if (bq->chip_version == BQ25890)
- val->strval = "BQ25890";
- else if (bq->chip_version == BQ25892)
- val->strval = "BQ25892";
- else if (bq->chip_version == BQ25895)
- val->strval = "BQ25895";
- else if (bq->chip_version == BQ25896)
- val->strval = "BQ25896";
- else
- val->strval = "UNKNOWN";
-
+ val->strval = bq25890_chip_name[bq->chip_version];
break;
case POWER_SUPPLY_PROP_ONLINE:
@@ -513,74 +510,50 @@ static int bq25890_get_chip_state(struct bq25890_device *bq,
return 0;
}
-static bool bq25890_state_changed(struct bq25890_device *bq,
- struct bq25890_state *new_state)
-{
- struct bq25890_state old_state;
-
- mutex_lock(&bq->lock);
- old_state = bq->state;
- mutex_unlock(&bq->lock);
-
- return (old_state.chrg_status != new_state->chrg_status ||
- old_state.chrg_fault != new_state->chrg_fault ||
- old_state.online != new_state->online ||
- old_state.bat_fault != new_state->bat_fault ||
- old_state.boost_fault != new_state->boost_fault ||
- old_state.vsys_status != new_state->vsys_status);
-}
-
-static void bq25890_handle_state_change(struct bq25890_device *bq,
- struct bq25890_state *new_state)
+static irqreturn_t __bq25890_handle_irq(struct bq25890_device *bq)
{
+ struct bq25890_state new_state;
int ret;
- struct bq25890_state old_state;
- mutex_lock(&bq->lock);
- old_state = bq->state;
- mutex_unlock(&bq->lock);
+ ret = bq25890_get_chip_state(bq, &new_state);
+ if (ret < 0)
+ return IRQ_NONE;
+
+ if (!memcmp(&bq->state, &new_state, sizeof(new_state)))
+ return IRQ_NONE;
- if (!new_state->online) { /* power removed */
+ if (!new_state.online && bq->state.online) { /* power removed */
/* disable ADC */
ret = bq25890_field_write(bq, F_CONV_START, 0);
if (ret < 0)
goto error;
- } else if (!old_state.online) { /* power inserted */
+ } else if (new_state.online && !bq->state.online) { /* power inserted */
/* enable ADC, to have control of charge current/voltage */
ret = bq25890_field_write(bq, F_CONV_START, 1);
if (ret < 0)
goto error;
}
- return;
+ bq->state = new_state;
+ power_supply_changed(bq->charger);
+ return IRQ_HANDLED;
error:
- dev_err(bq->dev, "Error communicating with the chip.\n");
+ dev_err(bq->dev, "Error communicating with the chip: %pe\n",
+ ERR_PTR(ret));
+ return IRQ_HANDLED;
}
static irqreturn_t bq25890_irq_handler_thread(int irq, void *private)
{
struct bq25890_device *bq = private;
- int ret;
- struct bq25890_state state;
-
- ret = bq25890_get_chip_state(bq, &state);
- if (ret < 0)
- goto handled;
-
- if (!bq25890_state_changed(bq, &state))
- goto handled;
-
- bq25890_handle_state_change(bq, &state);
+ irqreturn_t ret;
mutex_lock(&bq->lock);
- bq->state = state;
+ ret = __bq25890_handle_irq(bq);
mutex_unlock(&bq->lock);
- power_supply_changed(bq->charger);
-
-handled:
- return IRQ_HANDLED;
+ return ret;
}
static int bq25890_chip_reset(struct bq25890_device *bq)
@@ -610,7 +583,6 @@ static int bq25890_hw_init(struct bq25890_device *bq)
{
int ret;
int i;
- struct bq25890_state state;
const struct {
enum bq25890_fields id;
@@ -658,20 +630,16 @@ static int bq25890_hw_init(struct bq25890_device *bq)
return ret;
}
- ret = bq25890_get_chip_state(bq, &state);
+ ret = bq25890_get_chip_state(bq, &bq->state);
if (ret < 0) {
dev_dbg(bq->dev, "Get state failed %d\n", ret);
return ret;
}
- mutex_lock(&bq->lock);
- bq->state = state;
- mutex_unlock(&bq->lock);
-
return 0;
}
-static enum power_supply_property bq25890_power_supply_props[] = {
+static const enum power_supply_property bq25890_power_supply_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_STATUS,
@@ -881,17 +849,11 @@ static int bq25890_fw_probe(struct bq25890_device *bq)
static int bq25890_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct i2c_adapter *adapter = client->adapter;
struct device *dev = &client->dev;
struct bq25890_device *bq;
int ret;
int i;
- if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
- dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
- return -ENODEV;
- }
-
bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL);
if (!bq)
return -ENOMEM;
@@ -1010,28 +972,28 @@ static int bq25890_suspend(struct device *dev)
static int bq25890_resume(struct device *dev)
{
int ret;
- struct bq25890_state state;
struct bq25890_device *bq = dev_get_drvdata(dev);
- ret = bq25890_get_chip_state(bq, &state);
- if (ret < 0)
- return ret;
-
mutex_lock(&bq->lock);
- bq->state = state;
- mutex_unlock(&bq->lock);
+
+ ret = bq25890_get_chip_state(bq, &bq->state);
+ if (ret < 0)
+ goto unlock;
/* Re-enable ADC only if charger is plugged in. */
- if (state.online) {
+ if (bq->state.online) {
ret = bq25890_field_write(bq, F_CONV_START, 1);
if (ret < 0)
- return ret;
+ goto unlock;
}
/* signal userspace, maybe state changed while suspended */
power_supply_changed(bq->charger);
- return 0;
+unlock:
+ mutex_unlock(&bq->lock);
+
+ return ret;
}
#endif
diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c
index a21e1a2673f8..2ef53dc1f2fb 100644
--- a/drivers/power/supply/charger-manager.c
+++ b/drivers/power/supply/charger-manager.c
@@ -1422,7 +1422,9 @@ static int charger_manager_prepare_sysfs(struct charger_manager *cm)
}
static int cm_init_thermal_data(struct charger_manager *cm,
- struct power_supply *fuel_gauge)
+ struct power_supply *fuel_gauge,
+ enum power_supply_property *properties,
+ size_t *num_properties)
{
struct charger_desc *desc = cm->desc;
union power_supply_propval val;
@@ -1433,9 +1435,8 @@ static int cm_init_thermal_data(struct charger_manager *cm,
POWER_SUPPLY_PROP_TEMP, &val);
if (!ret) {
- cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] =
- POWER_SUPPLY_PROP_TEMP;
- cm->charger_psy_desc.num_properties++;
+ properties[*num_properties] = POWER_SUPPLY_PROP_TEMP;
+ (*num_properties)++;
cm->desc->measure_battery_temp = true;
}
#ifdef CONFIG_THERMAL
@@ -1446,9 +1447,8 @@ static int cm_init_thermal_data(struct charger_manager *cm,
return PTR_ERR(cm->tzd_batt);
/* Use external thermometer */
- cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] =
- POWER_SUPPLY_PROP_TEMP_AMBIENT;
- cm->charger_psy_desc.num_properties++;
+ properties[*num_properties] = POWER_SUPPLY_PROP_TEMP_AMBIENT;
+ (*num_properties)++;
cm->desc->measure_battery_temp = true;
ret = 0;
}
@@ -1621,6 +1621,8 @@ static int charger_manager_probe(struct platform_device *pdev)
int j = 0;
union power_supply_propval val;
struct power_supply *fuel_gauge;
+ enum power_supply_property *properties;
+ size_t num_properties;
struct power_supply_config psy_cfg = {};
if (IS_ERR(desc)) {
@@ -1717,18 +1719,17 @@ static int charger_manager_probe(struct platform_device *pdev)
cm->charger_psy_desc.name = cm->psy_name_buf;
/* Allocate for psy properties because they may vary */
- cm->charger_psy_desc.properties =
- devm_kcalloc(&pdev->dev,
+ properties = devm_kcalloc(&pdev->dev,
ARRAY_SIZE(default_charger_props) +
NUM_CHARGER_PSY_OPTIONAL,
- sizeof(enum power_supply_property), GFP_KERNEL);
- if (!cm->charger_psy_desc.properties)
+ sizeof(*properties), GFP_KERNEL);
+ if (!properties)
return -ENOMEM;
- memcpy(cm->charger_psy_desc.properties, default_charger_props,
+ memcpy(properties, default_charger_props,
sizeof(enum power_supply_property) *
ARRAY_SIZE(default_charger_props));
- cm->charger_psy_desc.num_properties = psy_default.num_properties;
+ num_properties = ARRAY_SIZE(default_charger_props);
/* Find which optional psy-properties are available */
fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge);
@@ -1739,25 +1740,28 @@ static int charger_manager_probe(struct platform_device *pdev)
}
if (!power_supply_get_property(fuel_gauge,
POWER_SUPPLY_PROP_CHARGE_NOW, &val)) {
- cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] =
+ properties[num_properties] =
POWER_SUPPLY_PROP_CHARGE_NOW;
- cm->charger_psy_desc.num_properties++;
+ num_properties++;
}
if (!power_supply_get_property(fuel_gauge,
POWER_SUPPLY_PROP_CURRENT_NOW,
&val)) {
- cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] =
+ properties[num_properties] =
POWER_SUPPLY_PROP_CURRENT_NOW;
- cm->charger_psy_desc.num_properties++;
+ num_properties++;
}
- ret = cm_init_thermal_data(cm, fuel_gauge);
+ ret = cm_init_thermal_data(cm, fuel_gauge, properties, &num_properties);
if (ret) {
dev_err(&pdev->dev, "Failed to initialize thermal data\n");
cm->desc->measure_battery_temp = false;
}
power_supply_put(fuel_gauge);
+ cm->charger_psy_desc.properties = properties;
+ cm->charger_psy_desc.num_properties = num_properties;
+
INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
/* Register sysfs entry for charger(regulator) */
diff --git a/drivers/power/supply/cw2015_battery.c b/drivers/power/supply/cw2015_battery.c
new file mode 100644
index 000000000000..19f62ea957ee
--- /dev/null
+++ b/drivers/power/supply/cw2015_battery.c
@@ -0,0 +1,749 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Fuel gauge driver for CellWise 2013 / 2015
+ *
+ * Copyright (C) 2012, RockChip
+ * Copyright (C) 2020, Tobias Schramm
+ *
+ * Authors: xuhuicong <xhc@rock-chips.com>
+ * Authors: Tobias Schramm <t.schramm@manjaro.org>
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/gfp.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/time.h>
+#include <linux/workqueue.h>
+
+#define CW2015_SIZE_BATINFO 64
+
+#define CW2015_RESET_TRIES 5
+
+#define CW2015_REG_VERSION 0x00
+#define CW2015_REG_VCELL 0x02
+#define CW2015_REG_SOC 0x04
+#define CW2015_REG_RRT_ALERT 0x06
+#define CW2015_REG_CONFIG 0x08
+#define CW2015_REG_MODE 0x0A
+#define CW2015_REG_BATINFO 0x10
+
+#define CW2015_MODE_SLEEP_MASK GENMASK(7, 6)
+#define CW2015_MODE_SLEEP (0x03 << 6)
+#define CW2015_MODE_NORMAL (0x00 << 6)
+#define CW2015_MODE_QUICK_START (0x03 << 4)
+#define CW2015_MODE_RESTART (0x0f << 0)
+
+#define CW2015_CONFIG_UPDATE_FLG (0x01 << 1)
+#define CW2015_ATHD(x) ((x) << 3)
+#define CW2015_MASK_ATHD GENMASK(7, 3)
+#define CW2015_MASK_SOC GENMASK(12, 0)
+
+/* reset gauge of no valid state of charge could be polled for 40s */
+#define CW2015_BAT_SOC_ERROR_MS (40 * MSEC_PER_SEC)
+/* reset gauge if state of charge stuck for half an hour during charging */
+#define CW2015_BAT_CHARGING_STUCK_MS (1800 * MSEC_PER_SEC)
+
+/* poll interval from CellWise GPL Android driver example */
+#define CW2015_DEFAULT_POLL_INTERVAL_MS 8000
+
+#define CW2015_AVERAGING_SAMPLES 3
+
+struct cw_battery {
+ struct device *dev;
+ struct workqueue_struct *battery_workqueue;
+ struct delayed_work battery_delay_work;
+ struct regmap *regmap;
+ struct power_supply *rk_bat;
+ struct power_supply_battery_info battery;
+ u8 *bat_profile;
+
+ bool charger_attached;
+ bool battery_changed;
+
+ int soc;
+ int voltage_mv;
+ int status;
+ int time_to_empty;
+ int charge_count;
+
+ u32 poll_interval_ms;
+ u8 alert_level;
+
+ unsigned int read_errors;
+ unsigned int charge_stuck_cnt;
+};
+
+static int cw_read_word(struct cw_battery *cw_bat, u8 reg, u16 *val)
+{
+ __be16 value;
+ int ret;
+
+ ret = regmap_bulk_read(cw_bat->regmap, reg, &value, sizeof(value));
+ if (ret)
+ return ret;
+
+ *val = be16_to_cpu(value);
+ return 0;
+}
+
+static int cw_update_profile(struct cw_battery *cw_bat)
+{
+ int ret;
+ unsigned int reg_val;
+ u8 reset_val;
+
+ /* make sure gauge is not in sleep mode */
+ ret = regmap_read(cw_bat->regmap, CW2015_REG_MODE, &reg_val);
+ if (ret)
+ return ret;
+
+ reset_val = reg_val;
+ if ((reg_val & CW2015_MODE_SLEEP_MASK) == CW2015_MODE_SLEEP) {
+ dev_err(cw_bat->dev,
+ "Gauge is in sleep mode, can't update battery info\n");
+ return -EINVAL;
+ }
+
+ /* write new battery info */
+ ret = regmap_raw_write(cw_bat->regmap, CW2015_REG_BATINFO,
+ cw_bat->bat_profile,
+ CW2015_SIZE_BATINFO);
+ if (ret)
+ return ret;
+
+ /* set config update flag */
+ reg_val |= CW2015_CONFIG_UPDATE_FLG;
+ reg_val &= ~CW2015_MASK_ATHD;
+ reg_val |= CW2015_ATHD(cw_bat->alert_level);
+ ret = regmap_write(cw_bat->regmap, CW2015_REG_CONFIG, reg_val);
+ if (ret)
+ return ret;
+
+ /* reset gauge to apply new battery profile */
+ reset_val &= ~CW2015_MODE_RESTART;
+ reg_val = reset_val | CW2015_MODE_RESTART;
+ ret = regmap_write(cw_bat->regmap, CW2015_REG_MODE, reg_val);
+ if (ret)
+ return ret;
+
+ /* wait for gauge to reset */
+ msleep(20);
+
+ /* clear reset flag */
+ ret = regmap_write(cw_bat->regmap, CW2015_REG_MODE, reset_val);
+ if (ret)
+ return ret;
+
+ /* wait for gauge to become ready */
+ ret = regmap_read_poll_timeout(cw_bat->regmap, CW2015_REG_SOC,
+ reg_val, reg_val <= 100,
+ 10 * USEC_PER_MSEC, 10 * USEC_PER_SEC);
+ if (ret)
+ dev_err(cw_bat->dev,
+ "Gauge did not become ready after profile upload\n");
+ else
+ dev_dbg(cw_bat->dev, "Battery profile updated\n");
+
+ return ret;
+}
+
+static int cw_init(struct cw_battery *cw_bat)
+{
+ int ret;
+ unsigned int reg_val = CW2015_MODE_SLEEP;
+
+ if ((reg_val & CW2015_MODE_SLEEP_MASK) == CW2015_MODE_SLEEP) {
+ reg_val = CW2015_MODE_NORMAL;
+ ret = regmap_write(cw_bat->regmap, CW2015_REG_MODE, reg_val);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_read(cw_bat->regmap, CW2015_REG_CONFIG, &reg_val);
+ if (ret)
+ return ret;
+
+ if ((reg_val & CW2015_MASK_ATHD) != CW2015_ATHD(cw_bat->alert_level)) {
+ dev_dbg(cw_bat->dev, "Setting new alert level\n");
+ reg_val &= ~CW2015_MASK_ATHD;
+ reg_val |= ~CW2015_ATHD(cw_bat->alert_level);
+ ret = regmap_write(cw_bat->regmap, CW2015_REG_CONFIG, reg_val);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_read(cw_bat->regmap, CW2015_REG_CONFIG, &reg_val);
+ if (ret)
+ return ret;
+
+ if (!(reg_val & CW2015_CONFIG_UPDATE_FLG)) {
+ dev_dbg(cw_bat->dev,
+ "Battery profile not present, uploading battery profile\n");
+ if (cw_bat->bat_profile) {
+ ret = cw_update_profile(cw_bat);
+ if (ret) {
+ dev_err(cw_bat->dev,
+ "Failed to upload battery profile\n");
+ return ret;
+ }
+ } else {
+ dev_warn(cw_bat->dev,
+ "No profile specified, continuing without profile\n");
+ }
+ } else if (cw_bat->bat_profile) {
+ u8 bat_info[CW2015_SIZE_BATINFO];
+
+ ret = regmap_raw_read(cw_bat->regmap, CW2015_REG_BATINFO,
+ bat_info, CW2015_SIZE_BATINFO);
+ if (ret) {
+ dev_err(cw_bat->dev,
+ "Failed to read stored battery profile\n");
+ return ret;
+ }
+
+ if (memcmp(bat_info, cw_bat->bat_profile, CW2015_SIZE_BATINFO)) {
+ dev_warn(cw_bat->dev, "Replacing stored battery profile\n");
+ ret = cw_update_profile(cw_bat);
+ if (ret)
+ return ret;
+ }
+ } else {
+ dev_warn(cw_bat->dev,
+ "Can't check current battery profile, no profile provided\n");
+ }
+
+ dev_dbg(cw_bat->dev, "Battery profile configured\n");
+ return 0;
+}
+
+static int cw_power_on_reset(struct cw_battery *cw_bat)
+{
+ int ret;
+ unsigned char reset_val;
+
+ reset_val = CW2015_MODE_SLEEP;
+ ret = regmap_write(cw_bat->regmap, CW2015_REG_MODE, reset_val);
+ if (ret)
+ return ret;
+
+ /* wait for gauge to enter sleep */
+ msleep(20);
+
+ reset_val = CW2015_MODE_NORMAL;
+ ret = regmap_write(cw_bat->regmap, CW2015_REG_MODE, reset_val);
+ if (ret)
+ return ret;
+
+ ret = cw_init(cw_bat);
+ if (ret)
+ return ret;
+ return 0;
+}
+
+#define HYSTERESIS(current, previous, up, down) \
+ (((current) < (previous) + (up)) && ((current) > (previous) - (down)))
+
+static int cw_get_soc(struct cw_battery *cw_bat)
+{
+ unsigned int soc;
+ int ret;
+
+ ret = regmap_read(cw_bat->regmap, CW2015_REG_SOC, &soc);
+ if (ret)
+ return ret;
+
+ if (soc > 100) {
+ int max_error_cycles =
+ CW2015_BAT_SOC_ERROR_MS / cw_bat->poll_interval_ms;
+
+ dev_err(cw_bat->dev, "Invalid SoC %d%%\n", soc);
+ cw_bat->read_errors++;
+ if (cw_bat->read_errors > max_error_cycles) {
+ dev_warn(cw_bat->dev,
+ "Too many invalid SoC reports, resetting gauge\n");
+ cw_power_on_reset(cw_bat);
+ cw_bat->read_errors = 0;
+ }
+ return cw_bat->soc;
+ }
+ cw_bat->read_errors = 0;
+
+ /* Reset gauge if stuck while charging */
+ if (cw_bat->status == POWER_SUPPLY_STATUS_CHARGING && soc == cw_bat->soc) {
+ int max_stuck_cycles =
+ CW2015_BAT_CHARGING_STUCK_MS / cw_bat->poll_interval_ms;
+
+ cw_bat->charge_stuck_cnt++;
+ if (cw_bat->charge_stuck_cnt > max_stuck_cycles) {
+ dev_warn(cw_bat->dev,
+ "SoC stuck @%u%%, resetting gauge\n", soc);
+ cw_power_on_reset(cw_bat);
+ cw_bat->charge_stuck_cnt = 0;
+ }
+ } else {
+ cw_bat->charge_stuck_cnt = 0;
+ }
+
+ /* Ignore voltage dips during charge */
+ if (cw_bat->charger_attached && HYSTERESIS(soc, cw_bat->soc, 0, 3))
+ soc = cw_bat->soc;
+
+ /* Ignore voltage spikes during discharge */
+ if (!cw_bat->charger_attached && HYSTERESIS(soc, cw_bat->soc, 3, 0))
+ soc = cw_bat->soc;
+
+ return soc;
+}
+
+static int cw_get_voltage(struct cw_battery *cw_bat)
+{
+ int ret, i, voltage_mv;
+ u16 reg_val;
+ u32 avg = 0;
+
+ for (i = 0; i < CW2015_AVERAGING_SAMPLES; i++) {
+ ret = cw_read_word(cw_bat, CW2015_REG_VCELL, &reg_val);
+ if (ret)
+ return ret;
+
+ avg += reg_val;
+ }
+ avg /= CW2015_AVERAGING_SAMPLES;
+
+ /*
+ * 305 uV per ADC step
+ * Use 312 / 1024 as efficient approximation of 305 / 1000
+ * Negligible error of 0.1%
+ */
+ voltage_mv = avg * 312 / 1024;
+
+ dev_dbg(cw_bat->dev, "Read voltage: %d mV, raw=0x%04x\n",
+ voltage_mv, reg_val);
+ return voltage_mv;
+}
+
+static int cw_get_time_to_empty(struct cw_battery *cw_bat)
+{
+ int ret;
+ u16 value16;
+
+ ret = cw_read_word(cw_bat, CW2015_REG_RRT_ALERT, &value16);
+ if (ret)
+ return ret;
+
+ return value16 & CW2015_MASK_SOC;
+}
+
+static void cw_update_charge_status(struct cw_battery *cw_bat)
+{
+ int ret;
+
+ ret = power_supply_am_i_supplied(cw_bat->rk_bat);
+ if (ret < 0) {
+ dev_warn(cw_bat->dev, "Failed to get supply state: %d\n", ret);
+ } else {
+ bool charger_attached;
+
+ charger_attached = !!ret;
+ if (cw_bat->charger_attached != charger_attached) {
+ cw_bat->battery_changed = true;
+ if (charger_attached)
+ cw_bat->charge_count++;
+ }
+ cw_bat->charger_attached = charger_attached;
+ }
+}
+
+static void cw_update_soc(struct cw_battery *cw_bat)
+{
+ int soc;
+
+ soc = cw_get_soc(cw_bat);
+ if (soc < 0)
+ dev_err(cw_bat->dev, "Failed to get SoC from gauge: %d\n", soc);
+ else if (cw_bat->soc != soc) {
+ cw_bat->soc = soc;
+ cw_bat->battery_changed = true;
+ }
+}
+
+static void cw_update_voltage(struct cw_battery *cw_bat)
+{
+ int voltage_mv;
+
+ voltage_mv = cw_get_voltage(cw_bat);
+ if (voltage_mv < 0)
+ dev_err(cw_bat->dev, "Failed to get voltage from gauge: %d\n",
+ voltage_mv);
+ else
+ cw_bat->voltage_mv = voltage_mv;
+}
+
+static void cw_update_status(struct cw_battery *cw_bat)
+{
+ int status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ if (cw_bat->charger_attached) {
+ if (cw_bat->soc >= 100)
+ status = POWER_SUPPLY_STATUS_FULL;
+ else
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ if (cw_bat->status != status)
+ cw_bat->battery_changed = true;
+ cw_bat->status = status;
+}
+
+static void cw_update_time_to_empty(struct cw_battery *cw_bat)
+{
+ int time_to_empty;
+
+ time_to_empty = cw_get_time_to_empty(cw_bat);
+ if (time_to_empty < 0)
+ dev_err(cw_bat->dev, "Failed to get time to empty from gauge: %d\n",
+ time_to_empty);
+ else if (cw_bat->time_to_empty != time_to_empty) {
+ cw_bat->time_to_empty = time_to_empty;
+ cw_bat->battery_changed = true;
+ }
+}
+
+static void cw_bat_work(struct work_struct *work)
+{
+ struct delayed_work *delay_work;
+ struct cw_battery *cw_bat;
+ int ret;
+ unsigned int reg_val;
+
+ delay_work = to_delayed_work(work);
+ cw_bat = container_of(delay_work, struct cw_battery, battery_delay_work);
+ ret = regmap_read(cw_bat->regmap, CW2015_REG_MODE, &reg_val);
+ if (ret) {
+ dev_err(cw_bat->dev, "Failed to read mode from gauge: %d\n", ret);
+ } else {
+ if ((reg_val & CW2015_MODE_SLEEP_MASK) == CW2015_MODE_SLEEP) {
+ int i;
+
+ for (i = 0; i < CW2015_RESET_TRIES; i++) {
+ if (!cw_power_on_reset(cw_bat))
+ break;
+ }
+ }
+ cw_update_soc(cw_bat);
+ cw_update_voltage(cw_bat);
+ cw_update_charge_status(cw_bat);
+ cw_update_status(cw_bat);
+ cw_update_time_to_empty(cw_bat);
+ }
+ dev_dbg(cw_bat->dev, "charger_attached = %d\n", cw_bat->charger_attached);
+ dev_dbg(cw_bat->dev, "status = %d\n", cw_bat->status);
+ dev_dbg(cw_bat->dev, "soc = %d%%\n", cw_bat->soc);
+ dev_dbg(cw_bat->dev, "voltage = %dmV\n", cw_bat->voltage_mv);
+
+ if (cw_bat->battery_changed)
+ power_supply_changed(cw_bat->rk_bat);
+ cw_bat->battery_changed = false;
+
+ queue_delayed_work(cw_bat->battery_workqueue,
+ &cw_bat->battery_delay_work,
+ msecs_to_jiffies(cw_bat->poll_interval_ms));
+}
+
+static bool cw_battery_valid_time_to_empty(struct cw_battery *cw_bat)
+{
+ return cw_bat->time_to_empty > 0 &&
+ cw_bat->time_to_empty < CW2015_MASK_SOC &&
+ cw_bat->status == POWER_SUPPLY_STATUS_DISCHARGING;
+}
+
+static int cw_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct cw_battery *cw_bat;
+
+ cw_bat = power_supply_get_drvdata(psy);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = cw_bat->soc;
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = cw_bat->status;
+ break;
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!cw_bat->voltage_mv;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = cw_bat->voltage_mv * 1000;
+ break;
+
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ if (cw_battery_valid_time_to_empty(cw_bat))
+ val->intval = cw_bat->time_to_empty;
+ else
+ val->intval = 0;
+ break;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ val->intval = cw_bat->charge_count;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ if (cw_bat->battery.charge_full_design_uah > 0)
+ val->intval = cw_bat->battery.charge_full_design_uah;
+ else
+ val->intval = 0;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (cw_battery_valid_time_to_empty(cw_bat) &&
+ cw_bat->battery.charge_full_design_uah > 0) {
+ /* calculate remaining capacity */
+ val->intval = cw_bat->battery.charge_full_design_uah;
+ val->intval = val->intval * cw_bat->soc / 100;
+
+ /* estimate current based on time to empty */
+ val->intval = 60 * val->intval / cw_bat->time_to_empty;
+ } else {
+ val->intval = 0;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ return 0;
+}
+
+static enum power_supply_property cw_battery_properties[] = {
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static const struct power_supply_desc cw2015_bat_desc = {
+ .name = "cw2015-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = cw_battery_properties,
+ .num_properties = ARRAY_SIZE(cw_battery_properties),
+ .get_property = cw_battery_get_property,
+};
+
+static int cw2015_parse_properties(struct cw_battery *cw_bat)
+{
+ struct device *dev = cw_bat->dev;
+ int length;
+ int ret;
+
+ length = device_property_count_u8(dev, "cellwise,battery-profile");
+ if (length < 0) {
+ dev_warn(cw_bat->dev,
+ "No battery-profile found, using current flash contents\n");
+ } else if (length != CW2015_SIZE_BATINFO) {
+ dev_err(cw_bat->dev, "battery-profile must be %d bytes\n",
+ CW2015_SIZE_BATINFO);
+ return -EINVAL;
+ } else {
+ cw_bat->bat_profile = devm_kzalloc(dev, length, GFP_KERNEL);
+ if (!cw_bat->bat_profile)
+ return -ENOMEM;
+
+ ret = device_property_read_u8_array(dev,
+ "cellwise,battery-profile",
+ cw_bat->bat_profile,
+ length);
+ if (ret)
+ return ret;
+ }
+
+ ret = device_property_read_u32(dev, "cellwise,monitor-interval-ms",
+ &cw_bat->poll_interval_ms);
+ if (ret) {
+ dev_dbg(cw_bat->dev, "Using default poll interval\n");
+ cw_bat->poll_interval_ms = CW2015_DEFAULT_POLL_INTERVAL_MS;
+ }
+
+ return 0;
+}
+
+static const struct regmap_range regmap_ranges_rd_yes[] = {
+ regmap_reg_range(CW2015_REG_VERSION, CW2015_REG_VERSION),
+ regmap_reg_range(CW2015_REG_VCELL, CW2015_REG_CONFIG),
+ regmap_reg_range(CW2015_REG_MODE, CW2015_REG_MODE),
+ regmap_reg_range(CW2015_REG_BATINFO,
+ CW2015_REG_BATINFO + CW2015_SIZE_BATINFO - 1),
+};
+
+static const struct regmap_access_table regmap_rd_table = {
+ .yes_ranges = regmap_ranges_rd_yes,
+ .n_yes_ranges = 4,
+};
+
+static const struct regmap_range regmap_ranges_wr_yes[] = {
+ regmap_reg_range(CW2015_REG_RRT_ALERT, CW2015_REG_CONFIG),
+ regmap_reg_range(CW2015_REG_MODE, CW2015_REG_MODE),
+ regmap_reg_range(CW2015_REG_BATINFO,
+ CW2015_REG_BATINFO + CW2015_SIZE_BATINFO - 1),
+};
+
+static const struct regmap_access_table regmap_wr_table = {
+ .yes_ranges = regmap_ranges_wr_yes,
+ .n_yes_ranges = 3,
+};
+
+static const struct regmap_range regmap_ranges_vol_yes[] = {
+ regmap_reg_range(CW2015_REG_VCELL, CW2015_REG_SOC + 1),
+};
+
+static const struct regmap_access_table regmap_vol_table = {
+ .yes_ranges = regmap_ranges_vol_yes,
+ .n_yes_ranges = 1,
+};
+
+static const struct regmap_config cw2015_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &regmap_rd_table,
+ .wr_table = &regmap_wr_table,
+ .volatile_table = &regmap_vol_table,
+ .max_register = CW2015_REG_BATINFO + CW2015_SIZE_BATINFO - 1,
+};
+
+static int cw_bat_probe(struct i2c_client *client)
+{
+ int ret;
+ struct cw_battery *cw_bat;
+ struct power_supply_config psy_cfg = { 0 };
+
+ cw_bat = devm_kzalloc(&client->dev, sizeof(*cw_bat), GFP_KERNEL);
+ if (!cw_bat)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, cw_bat);
+ cw_bat->dev = &client->dev;
+ cw_bat->soc = 1;
+
+ ret = cw2015_parse_properties(cw_bat);
+ if (ret) {
+ dev_err(cw_bat->dev, "Failed to parse cw2015 properties\n");
+ return ret;
+ }
+
+ cw_bat->regmap = devm_regmap_init_i2c(client, &cw2015_regmap_config);
+ if (IS_ERR(cw_bat->regmap)) {
+ dev_err(cw_bat->dev, "Failed to allocate regmap: %ld\n",
+ PTR_ERR(cw_bat->regmap));
+ return PTR_ERR(cw_bat->regmap);
+ }
+
+ ret = cw_init(cw_bat);
+ if (ret) {
+ dev_err(cw_bat->dev, "Init failed: %d\n", ret);
+ return ret;
+ }
+
+ psy_cfg.drv_data = cw_bat;
+ psy_cfg.fwnode = dev_fwnode(cw_bat->dev);
+
+ cw_bat->rk_bat = devm_power_supply_register(&client->dev,
+ &cw2015_bat_desc,
+ &psy_cfg);
+ if (IS_ERR(cw_bat->rk_bat)) {
+ dev_err(cw_bat->dev, "Failed to register power supply\n");
+ return PTR_ERR(cw_bat->rk_bat);
+ }
+
+ ret = power_supply_get_battery_info(cw_bat->rk_bat, &cw_bat->battery);
+ if (ret) {
+ dev_warn(cw_bat->dev,
+ "No monitored battery, some properties will be missing\n");
+ }
+
+ cw_bat->battery_workqueue = create_singlethread_workqueue("rk_battery");
+ INIT_DELAYED_WORK(&cw_bat->battery_delay_work, cw_bat_work);
+ queue_delayed_work(cw_bat->battery_workqueue,
+ &cw_bat->battery_delay_work, msecs_to_jiffies(10));
+ return 0;
+}
+
+static int __maybe_unused cw_bat_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cw_battery *cw_bat = i2c_get_clientdata(client);
+
+ cancel_delayed_work_sync(&cw_bat->battery_delay_work);
+ return 0;
+}
+
+static int __maybe_unused cw_bat_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cw_battery *cw_bat = i2c_get_clientdata(client);
+
+ queue_delayed_work(cw_bat->battery_workqueue,
+ &cw_bat->battery_delay_work, 0);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(cw_bat_pm_ops, cw_bat_suspend, cw_bat_resume);
+
+static int cw_bat_remove(struct i2c_client *client)
+{
+ struct cw_battery *cw_bat = i2c_get_clientdata(client);
+
+ cancel_delayed_work_sync(&cw_bat->battery_delay_work);
+ power_supply_put_battery_info(cw_bat->rk_bat, &cw_bat->battery);
+ return 0;
+}
+
+static const struct i2c_device_id cw_bat_id_table[] = {
+ { "cw2015", 0 },
+ { }
+};
+
+static const struct of_device_id cw2015_of_match[] = {
+ { .compatible = "cellwise,cw2015" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, cw2015_of_match);
+
+static struct i2c_driver cw_bat_driver = {
+ .driver = {
+ .name = "cw2015",
+ .pm = &cw_bat_pm_ops,
+ },
+ .probe_new = cw_bat_probe,
+ .remove = cw_bat_remove,
+ .id_table = cw_bat_id_table,
+};
+
+module_i2c_driver(cw_bat_driver);
+
+MODULE_AUTHOR("xhc<xhc@rock-chips.com>");
+MODULE_AUTHOR("Tobias Schramm <t.schramm@manjaro.org>");
+MODULE_DESCRIPTION("cw2015/cw2013 battery driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c
index bc462d1ec963..caa829738ef7 100644
--- a/drivers/power/supply/generic-adc-battery.c
+++ b/drivers/power/supply/generic-adc-battery.c
@@ -241,6 +241,7 @@ static int gab_probe(struct platform_device *pdev)
struct power_supply_desc *psy_desc;
struct power_supply_config psy_cfg = {};
struct gab_platform_data *pdata = pdev->dev.platform_data;
+ enum power_supply_property *properties;
int ret = 0;
int chan;
int index = ARRAY_SIZE(gab_props);
@@ -268,16 +269,16 @@ static int gab_probe(struct platform_device *pdev)
* copying the static properties and allocating extra memory for holding
* the extra configurable properties received from platform data.
*/
- psy_desc->properties = kcalloc(ARRAY_SIZE(gab_props) +
- ARRAY_SIZE(gab_chan_name),
- sizeof(*psy_desc->properties),
- GFP_KERNEL);
- if (!psy_desc->properties) {
+ properties = kcalloc(ARRAY_SIZE(gab_props) +
+ ARRAY_SIZE(gab_chan_name),
+ sizeof(*properties),
+ GFP_KERNEL);
+ if (!properties) {
ret = -ENOMEM;
goto first_mem_fail;
}
- memcpy(psy_desc->properties, gab_props, sizeof(gab_props));
+ memcpy(properties, gab_props, sizeof(gab_props));
/*
* getting channel from iio and copying the battery properties
@@ -294,13 +295,11 @@ static int gab_probe(struct platform_device *pdev)
int index2;
for (index2 = 0; index2 < index; index2++) {
- if (psy_desc->properties[index2] ==
- gab_dyn_props[chan])
+ if (properties[index2] == gab_dyn_props[chan])
break; /* already known */
}
if (index2 == index) /* really new */
- psy_desc->properties[index++] =
- gab_dyn_props[chan];
+ properties[index++] = gab_dyn_props[chan];
any = true;
}
}
@@ -317,6 +316,7 @@ static int gab_probe(struct platform_device *pdev)
* as come channels may be not be supported by the device.So
* we need to take care of that.
*/
+ psy_desc->properties = properties;
psy_desc->num_properties = index;
adc_bat->psy = power_supply_register(&pdev->dev, psy_desc, &psy_cfg);
@@ -358,7 +358,7 @@ err_reg_fail:
iio_channel_release(adc_bat->channel[chan]);
}
second_mem_fail:
- kfree(psy_desc->properties);
+ kfree(properties);
first_mem_fail:
return ret;
}
diff --git a/drivers/power/supply/lp8788-charger.c b/drivers/power/supply/lp8788-charger.c
index 84a206f42a8e..e7931ffb7151 100644
--- a/drivers/power/supply/lp8788-charger.c
+++ b/drivers/power/supply/lp8788-charger.c
@@ -572,27 +572,14 @@ static void lp8788_setup_adc_channel(struct device *dev,
return;
/* ADC channel for battery voltage */
- chan = iio_channel_get(dev, pdata->adc_vbatt);
+ chan = devm_iio_channel_get(dev, pdata->adc_vbatt);
pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan;
/* ADC channel for battery temperature */
- chan = iio_channel_get(dev, pdata->adc_batt_temp);
+ chan = devm_iio_channel_get(dev, pdata->adc_batt_temp);
pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan;
}
-static void lp8788_release_adc_channel(struct lp8788_charger *pchg)
-{
- int i;
-
- for (i = 0; i < LP8788_NUM_CHG_ADC; i++) {
- if (!pchg->chan[i])
- continue;
-
- iio_channel_release(pchg->chan[i]);
- pchg->chan[i] = NULL;
- }
-}
-
static ssize_t lp8788_show_charger_status(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -735,7 +722,6 @@ static int lp8788_charger_remove(struct platform_device *pdev)
flush_work(&pchg->charger_work);
lp8788_irq_unregister(pdev, pchg);
lp8788_psy_unregister(pchg);
- lp8788_release_adc_channel(pchg);
return 0;
}
diff --git a/drivers/power/supply/max14656_charger_detector.c b/drivers/power/supply/max14656_charger_detector.c
index 3bbb8b4c8ae7..137f9fafce8c 100644
--- a/drivers/power/supply/max14656_charger_detector.c
+++ b/drivers/power/supply/max14656_charger_detector.c
@@ -139,10 +139,9 @@ static void max14656_irq_worker(struct work_struct *work)
u8 buf[REG_TOTAL_NUM];
u8 chg_type;
- int ret = 0;
- ret = max14656_read_block_reg(chip->client, MAX14656_DEVICE_ID,
- REG_TOTAL_NUM, buf);
+ max14656_read_block_reg(chip->client, MAX14656_DEVICE_ID,
+ REG_TOTAL_NUM, buf);
if ((buf[MAX14656_STATUS_1] & STATUS1_VB_VALID_MASK) &&
(buf[MAX14656_STATUS_1] & STATUS1_CHG_TYPE_MASK)) {
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 69ec4295d55d..f284547913d6 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -87,6 +87,7 @@ static enum power_supply_property max17042_battery_props[] = {
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
};
static int max17042_get_temperature(struct max17042_chip *chip, int *temp)
@@ -411,6 +412,13 @@ static int max17042_get_property(struct power_supply *psy,
return -EINVAL;
}
break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ ret = regmap_read(map, MAX17042_TTE, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data * 5625 / 1000;
+ break;
default:
return -EINVAL;
}
diff --git a/drivers/power/supply/power_supply_hwmon.c b/drivers/power/supply/power_supply_hwmon.c
index 75cf861ba492..7fe4b6b6ddc8 100644
--- a/drivers/power/supply/power_supply_hwmon.c
+++ b/drivers/power/supply/power_supply_hwmon.c
@@ -13,6 +13,11 @@ struct power_supply_hwmon {
unsigned long *props;
};
+static const char *const ps_temp_label[] = {
+ "temp",
+ "ambient temp",
+};
+
static int power_supply_hwmon_in_to_property(u32 attr)
{
switch (attr) {
@@ -98,6 +103,39 @@ static bool power_supply_hwmon_is_a_label(enum hwmon_sensor_types type,
return type == hwmon_temp && attr == hwmon_temp_label;
}
+struct hwmon_type_attr_list {
+ const u32 *attrs;
+ size_t n_attrs;
+};
+
+static const u32 ps_temp_attrs[] = {
+ hwmon_temp_input,
+ hwmon_temp_min, hwmon_temp_max,
+ hwmon_temp_min_alarm, hwmon_temp_max_alarm,
+};
+
+static const struct hwmon_type_attr_list ps_type_attrs[hwmon_max] = {
+ [hwmon_temp] = { ps_temp_attrs, ARRAY_SIZE(ps_temp_attrs) },
+};
+
+static bool power_supply_hwmon_has_input(
+ const struct power_supply_hwmon *psyhw,
+ enum hwmon_sensor_types type, int channel)
+{
+ const struct hwmon_type_attr_list *attr_list = &ps_type_attrs[type];
+ size_t i;
+
+ for (i = 0; i < attr_list->n_attrs; ++i) {
+ int prop = power_supply_hwmon_to_property(type,
+ attr_list->attrs[i], channel);
+
+ if (prop >= 0 && test_bit(prop, psyhw->props))
+ return true;
+ }
+
+ return false;
+}
+
static bool power_supply_hwmon_is_writable(enum hwmon_sensor_types type,
u32 attr)
{
@@ -124,9 +162,12 @@ static umode_t power_supply_hwmon_is_visible(const void *data,
const struct power_supply_hwmon *psyhw = data;
int prop;
-
- if (power_supply_hwmon_is_a_label(type, attr))
- return 0444;
+ if (power_supply_hwmon_is_a_label(type, attr)) {
+ if (power_supply_hwmon_has_input(psyhw, type, channel))
+ return 0444;
+ else
+ return 0;
+ }
prop = power_supply_hwmon_to_property(type, attr, channel);
if (prop < 0 || !test_bit(prop, psyhw->props))
@@ -144,7 +185,20 @@ static int power_supply_hwmon_read_string(struct device *dev,
u32 attr, int channel,
const char **str)
{
- *str = channel ? "temp" : "temp ambient";
+ switch (type) {
+ case hwmon_temp:
+ *str = ps_temp_label[channel];
+ break;
+ default:
+ /* unreachable, but see:
+ * gcc bug #51513 [1] and clang bug #978 [2]
+ *
+ * [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51513
+ * [2] https://github.com/ClangBuiltLinux/linux/issues/978
+ */
+ break;
+ }
+
return 0;
}
@@ -304,7 +358,7 @@ int power_supply_add_hwmon_sysfs(struct power_supply *psy)
goto error;
}
- ret = devm_add_action(dev, power_supply_hwmon_bitmap_free,
+ ret = devm_add_action_or_reset(dev, power_supply_hwmon_bitmap_free,
psyhw->props);
if (ret)
goto error;
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index f37ad4eae60b..d21b4e0edf38 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -18,68 +18,206 @@
#include "power_supply.h"
-/*
- * This is because the name "current" breaks the device attr macro.
- * The "current" word resolves to "(get_current())" so instead of
- * "current" "(get_current())" appears in the sysfs.
- *
- * The source of this definition is the device.h which calls __ATTR
- * macro in sysfs.h which calls the __stringify macro.
- *
- * Only modification that the name is not tried to be resolved
- * (as a macro let's say).
- */
+#define MAX_PROP_NAME_LEN 30
+
+struct power_supply_attr {
+ const char *prop_name;
+ char attr_name[MAX_PROP_NAME_LEN + 1];
+ struct device_attribute dev_attr;
+ const char * const *text_values;
+ int text_values_len;
+};
-#define POWER_SUPPLY_ATTR(_name) \
-{ \
- .attr = { .name = #_name }, \
- .show = power_supply_show_property, \
- .store = power_supply_store_property, \
+#define _POWER_SUPPLY_ATTR(_name, _text, _len) \
+[POWER_SUPPLY_PROP_ ## _name] = \
+{ \
+ .prop_name = #_name, \
+ .attr_name = #_name "\0", \
+ .text_values = _text, \
+ .text_values_len = _len, \
}
-static struct device_attribute power_supply_attrs[];
+#define POWER_SUPPLY_ATTR(_name) _POWER_SUPPLY_ATTR(_name, NULL, 0)
+#define _POWER_SUPPLY_ENUM_ATTR(_name, _text) \
+ _POWER_SUPPLY_ATTR(_name, _text, ARRAY_SIZE(_text))
+#define POWER_SUPPLY_ENUM_ATTR(_name) \
+ _POWER_SUPPLY_ENUM_ATTR(_name, POWER_SUPPLY_ ## _name ## _TEXT)
+
+static const char * const POWER_SUPPLY_TYPE_TEXT[] = {
+ [POWER_SUPPLY_TYPE_UNKNOWN] = "Unknown",
+ [POWER_SUPPLY_TYPE_BATTERY] = "Battery",
+ [POWER_SUPPLY_TYPE_UPS] = "UPS",
+ [POWER_SUPPLY_TYPE_MAINS] = "Mains",
+ [POWER_SUPPLY_TYPE_USB] = "USB",
+ [POWER_SUPPLY_TYPE_USB_DCP] = "USB_DCP",
+ [POWER_SUPPLY_TYPE_USB_CDP] = "USB_CDP",
+ [POWER_SUPPLY_TYPE_USB_ACA] = "USB_ACA",
+ [POWER_SUPPLY_TYPE_USB_TYPE_C] = "USB_C",
+ [POWER_SUPPLY_TYPE_USB_PD] = "USB_PD",
+ [POWER_SUPPLY_TYPE_USB_PD_DRP] = "USB_PD_DRP",
+ [POWER_SUPPLY_TYPE_APPLE_BRICK_ID] = "BrickID",
+};
-static const char * const power_supply_type_text[] = {
- "Unknown", "Battery", "UPS", "Mains", "USB",
- "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
- "USB_PD", "USB_PD_DRP", "BrickID"
+static const char * const POWER_SUPPLY_USB_TYPE_TEXT[] = {
+ [POWER_SUPPLY_USB_TYPE_UNKNOWN] = "Unknown",
+ [POWER_SUPPLY_USB_TYPE_SDP] = "SDP",
+ [POWER_SUPPLY_USB_TYPE_DCP] = "DCP",
+ [POWER_SUPPLY_USB_TYPE_CDP] = "CDP",
+ [POWER_SUPPLY_USB_TYPE_ACA] = "ACA",
+ [POWER_SUPPLY_USB_TYPE_C] = "C",
+ [POWER_SUPPLY_USB_TYPE_PD] = "PD",
+ [POWER_SUPPLY_USB_TYPE_PD_DRP] = "PD_DRP",
+ [POWER_SUPPLY_USB_TYPE_PD_PPS] = "PD_PPS",
+ [POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID] = "BrickID",
};
-static const char * const power_supply_usb_type_text[] = {
- "Unknown", "SDP", "DCP", "CDP", "ACA", "C",
- "PD", "PD_DRP", "PD_PPS", "BrickID"
+static const char * const POWER_SUPPLY_STATUS_TEXT[] = {
+ [POWER_SUPPLY_STATUS_UNKNOWN] = "Unknown",
+ [POWER_SUPPLY_STATUS_CHARGING] = "Charging",
+ [POWER_SUPPLY_STATUS_DISCHARGING] = "Discharging",
+ [POWER_SUPPLY_STATUS_NOT_CHARGING] = "Not charging",
+ [POWER_SUPPLY_STATUS_FULL] = "Full",
};
-static const char * const power_supply_status_text[] = {
- "Unknown", "Charging", "Discharging", "Not charging", "Full"
+static const char * const POWER_SUPPLY_CHARGE_TYPE_TEXT[] = {
+ [POWER_SUPPLY_CHARGE_TYPE_UNKNOWN] = "Unknown",
+ [POWER_SUPPLY_CHARGE_TYPE_NONE] = "N/A",
+ [POWER_SUPPLY_CHARGE_TYPE_TRICKLE] = "Trickle",
+ [POWER_SUPPLY_CHARGE_TYPE_FAST] = "Fast",
+ [POWER_SUPPLY_CHARGE_TYPE_STANDARD] = "Standard",
+ [POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE] = "Adaptive",
+ [POWER_SUPPLY_CHARGE_TYPE_CUSTOM] = "Custom",
};
-static const char * const power_supply_charge_type_text[] = {
- "Unknown", "N/A", "Trickle", "Fast", "Standard", "Adaptive", "Custom"
+static const char * const POWER_SUPPLY_HEALTH_TEXT[] = {
+ [POWER_SUPPLY_HEALTH_UNKNOWN] = "Unknown",
+ [POWER_SUPPLY_HEALTH_GOOD] = "Good",
+ [POWER_SUPPLY_HEALTH_OVERHEAT] = "Overheat",
+ [POWER_SUPPLY_HEALTH_DEAD] = "Dead",
+ [POWER_SUPPLY_HEALTH_OVERVOLTAGE] = "Over voltage",
+ [POWER_SUPPLY_HEALTH_UNSPEC_FAILURE] = "Unspecified failure",
+ [POWER_SUPPLY_HEALTH_COLD] = "Cold",
+ [POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE] = "Watchdog timer expire",
+ [POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE] = "Safety timer expire",
+ [POWER_SUPPLY_HEALTH_OVERCURRENT] = "Over current",
};
-static const char * const power_supply_health_text[] = {
- "Unknown", "Good", "Overheat", "Dead", "Over voltage",
- "Unspecified failure", "Cold", "Watchdog timer expire",
- "Safety timer expire", "Over current"
+static const char * const POWER_SUPPLY_TECHNOLOGY_TEXT[] = {
+ [POWER_SUPPLY_TECHNOLOGY_UNKNOWN] = "Unknown",
+ [POWER_SUPPLY_TECHNOLOGY_NiMH] = "NiMH",
+ [POWER_SUPPLY_TECHNOLOGY_LION] = "Li-ion",
+ [POWER_SUPPLY_TECHNOLOGY_LIPO] = "Li-poly",
+ [POWER_SUPPLY_TECHNOLOGY_LiFe] = "LiFe",
+ [POWER_SUPPLY_TECHNOLOGY_NiCd] = "NiCd",
+ [POWER_SUPPLY_TECHNOLOGY_LiMn] = "LiMn",
};
-static const char * const power_supply_technology_text[] = {
- "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",
- "LiMn"
+static const char * const POWER_SUPPLY_CAPACITY_LEVEL_TEXT[] = {
+ [POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN] = "Unknown",
+ [POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL] = "Critical",
+ [POWER_SUPPLY_CAPACITY_LEVEL_LOW] = "Low",
+ [POWER_SUPPLY_CAPACITY_LEVEL_NORMAL] = "Normal",
+ [POWER_SUPPLY_CAPACITY_LEVEL_HIGH] = "High",
+ [POWER_SUPPLY_CAPACITY_LEVEL_FULL] = "Full",
};
-static const char * const power_supply_capacity_level_text[] = {
- "Unknown", "Critical", "Low", "Normal", "High", "Full"
+static const char * const POWER_SUPPLY_SCOPE_TEXT[] = {
+ [POWER_SUPPLY_SCOPE_UNKNOWN] = "Unknown",
+ [POWER_SUPPLY_SCOPE_SYSTEM] = "System",
+ [POWER_SUPPLY_SCOPE_DEVICE] = "Device",
};
-static const char * const power_supply_scope_text[] = {
- "Unknown", "System", "Device"
+static struct power_supply_attr power_supply_attrs[] = {
+ /* Properties of type `int' */
+ POWER_SUPPLY_ENUM_ATTR(STATUS),
+ POWER_SUPPLY_ENUM_ATTR(CHARGE_TYPE),
+ POWER_SUPPLY_ENUM_ATTR(HEALTH),
+ POWER_SUPPLY_ATTR(PRESENT),
+ POWER_SUPPLY_ATTR(ONLINE),
+ POWER_SUPPLY_ATTR(AUTHENTIC),
+ POWER_SUPPLY_ENUM_ATTR(TECHNOLOGY),
+ POWER_SUPPLY_ATTR(CYCLE_COUNT),
+ POWER_SUPPLY_ATTR(VOLTAGE_MAX),
+ POWER_SUPPLY_ATTR(VOLTAGE_MIN),
+ POWER_SUPPLY_ATTR(VOLTAGE_MAX_DESIGN),
+ POWER_SUPPLY_ATTR(VOLTAGE_MIN_DESIGN),
+ POWER_SUPPLY_ATTR(VOLTAGE_NOW),
+ POWER_SUPPLY_ATTR(VOLTAGE_AVG),
+ POWER_SUPPLY_ATTR(VOLTAGE_OCV),
+ POWER_SUPPLY_ATTR(VOLTAGE_BOOT),
+ POWER_SUPPLY_ATTR(CURRENT_MAX),
+ POWER_SUPPLY_ATTR(CURRENT_NOW),
+ POWER_SUPPLY_ATTR(CURRENT_AVG),
+ POWER_SUPPLY_ATTR(CURRENT_BOOT),
+ POWER_SUPPLY_ATTR(POWER_NOW),
+ POWER_SUPPLY_ATTR(POWER_AVG),
+ POWER_SUPPLY_ATTR(CHARGE_FULL_DESIGN),
+ POWER_SUPPLY_ATTR(CHARGE_EMPTY_DESIGN),
+ POWER_SUPPLY_ATTR(CHARGE_FULL),
+ POWER_SUPPLY_ATTR(CHARGE_EMPTY),
+ POWER_SUPPLY_ATTR(CHARGE_NOW),
+ POWER_SUPPLY_ATTR(CHARGE_AVG),
+ POWER_SUPPLY_ATTR(CHARGE_COUNTER),
+ POWER_SUPPLY_ATTR(CONSTANT_CHARGE_CURRENT),
+ POWER_SUPPLY_ATTR(CONSTANT_CHARGE_CURRENT_MAX),
+ POWER_SUPPLY_ATTR(CONSTANT_CHARGE_VOLTAGE),
+ POWER_SUPPLY_ATTR(CONSTANT_CHARGE_VOLTAGE_MAX),
+ POWER_SUPPLY_ATTR(CHARGE_CONTROL_LIMIT),
+ POWER_SUPPLY_ATTR(CHARGE_CONTROL_LIMIT_MAX),
+ POWER_SUPPLY_ATTR(CHARGE_CONTROL_START_THRESHOLD),
+ POWER_SUPPLY_ATTR(CHARGE_CONTROL_END_THRESHOLD),
+ POWER_SUPPLY_ATTR(INPUT_CURRENT_LIMIT),
+ POWER_SUPPLY_ATTR(INPUT_VOLTAGE_LIMIT),
+ POWER_SUPPLY_ATTR(INPUT_POWER_LIMIT),
+ POWER_SUPPLY_ATTR(ENERGY_FULL_DESIGN),
+ POWER_SUPPLY_ATTR(ENERGY_EMPTY_DESIGN),
+ POWER_SUPPLY_ATTR(ENERGY_FULL),
+ POWER_SUPPLY_ATTR(ENERGY_EMPTY),
+ POWER_SUPPLY_ATTR(ENERGY_NOW),
+ POWER_SUPPLY_ATTR(ENERGY_AVG),
+ POWER_SUPPLY_ATTR(CAPACITY),
+ POWER_SUPPLY_ATTR(CAPACITY_ALERT_MIN),
+ POWER_SUPPLY_ATTR(CAPACITY_ALERT_MAX),
+ POWER_SUPPLY_ENUM_ATTR(CAPACITY_LEVEL),
+ POWER_SUPPLY_ATTR(TEMP),
+ POWER_SUPPLY_ATTR(TEMP_MAX),
+ POWER_SUPPLY_ATTR(TEMP_MIN),
+ POWER_SUPPLY_ATTR(TEMP_ALERT_MIN),
+ POWER_SUPPLY_ATTR(TEMP_ALERT_MAX),
+ POWER_SUPPLY_ATTR(TEMP_AMBIENT),
+ POWER_SUPPLY_ATTR(TEMP_AMBIENT_ALERT_MIN),
+ POWER_SUPPLY_ATTR(TEMP_AMBIENT_ALERT_MAX),
+ POWER_SUPPLY_ATTR(TIME_TO_EMPTY_NOW),
+ POWER_SUPPLY_ATTR(TIME_TO_EMPTY_AVG),
+ POWER_SUPPLY_ATTR(TIME_TO_FULL_NOW),
+ POWER_SUPPLY_ATTR(TIME_TO_FULL_AVG),
+ POWER_SUPPLY_ENUM_ATTR(TYPE),
+ POWER_SUPPLY_ATTR(USB_TYPE),
+ POWER_SUPPLY_ENUM_ATTR(SCOPE),
+ POWER_SUPPLY_ATTR(PRECHARGE_CURRENT),
+ POWER_SUPPLY_ATTR(CHARGE_TERM_CURRENT),
+ POWER_SUPPLY_ATTR(CALIBRATE),
+ /* Properties of type `const char *' */
+ POWER_SUPPLY_ATTR(MODEL_NAME),
+ POWER_SUPPLY_ATTR(MANUFACTURER),
+ POWER_SUPPLY_ATTR(SERIAL_NUMBER),
};
+static struct attribute *
+__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1];
+
+static struct power_supply_attr *to_ps_attr(struct device_attribute *attr)
+{
+ return container_of(attr, struct power_supply_attr, dev_attr);
+}
+
+static enum power_supply_property dev_attr_psp(struct device_attribute *attr)
+{
+ return to_ps_attr(attr) - power_supply_attrs;
+}
+
static ssize_t power_supply_show_usb_type(struct device *dev,
- enum power_supply_usb_type *usb_types,
- ssize_t num_usb_types,
+ const struct power_supply_desc *desc,
union power_supply_propval *value,
char *buf)
{
@@ -88,16 +226,16 @@ static ssize_t power_supply_show_usb_type(struct device *dev,
bool match = false;
int i;
- for (i = 0; i < num_usb_types; ++i) {
- usb_type = usb_types[i];
+ for (i = 0; i < desc->num_usb_types; ++i) {
+ usb_type = desc->usb_types[i];
if (value->intval == usb_type) {
count += sprintf(buf + count, "[%s] ",
- power_supply_usb_type_text[usb_type]);
+ POWER_SUPPLY_USB_TYPE_TEXT[usb_type]);
match = true;
} else {
count += sprintf(buf + count, "%s ",
- power_supply_usb_type_text[usb_type]);
+ POWER_SUPPLY_USB_TYPE_TEXT[usb_type]);
}
}
@@ -117,7 +255,8 @@ static ssize_t power_supply_show_property(struct device *dev,
char *buf) {
ssize_t ret;
struct power_supply *psy = dev_get_drvdata(dev);
- enum power_supply_property psp = attr - power_supply_attrs;
+ struct power_supply_attr *ps_attr = to_ps_attr(attr);
+ enum power_supply_property psp = dev_attr_psp(attr);
union power_supply_propval value;
if (psp == POWER_SUPPLY_PROP_TYPE) {
@@ -137,39 +276,15 @@ static ssize_t power_supply_show_property(struct device *dev,
}
}
+ if (ps_attr->text_values_len > 0 &&
+ value.intval < ps_attr->text_values_len && value.intval >= 0) {
+ return sprintf(buf, "%s\n", ps_attr->text_values[value.intval]);
+ }
+
switch (psp) {
- case POWER_SUPPLY_PROP_STATUS:
- ret = sprintf(buf, "%s\n",
- power_supply_status_text[value.intval]);
- break;
- case POWER_SUPPLY_PROP_CHARGE_TYPE:
- ret = sprintf(buf, "%s\n",
- power_supply_charge_type_text[value.intval]);
- break;
- case POWER_SUPPLY_PROP_HEALTH:
- ret = sprintf(buf, "%s\n",
- power_supply_health_text[value.intval]);
- break;
- case POWER_SUPPLY_PROP_TECHNOLOGY:
- ret = sprintf(buf, "%s\n",
- power_supply_technology_text[value.intval]);
- break;
- case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
- ret = sprintf(buf, "%s\n",
- power_supply_capacity_level_text[value.intval]);
- break;
- case POWER_SUPPLY_PROP_TYPE:
- ret = sprintf(buf, "%s\n",
- power_supply_type_text[value.intval]);
- break;
case POWER_SUPPLY_PROP_USB_TYPE:
- ret = power_supply_show_usb_type(dev, psy->desc->usb_types,
- psy->desc->num_usb_types,
- &value, buf);
- break;
- case POWER_SUPPLY_PROP_SCOPE:
- ret = sprintf(buf, "%s\n",
- power_supply_scope_text[value.intval]);
+ ret = power_supply_show_usb_type(dev, psy->desc,
+ &value, buf);
break;
case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER:
ret = sprintf(buf, "%s\n", value.strval);
@@ -186,30 +301,14 @@ static ssize_t power_supply_store_property(struct device *dev,
const char *buf, size_t count) {
ssize_t ret;
struct power_supply *psy = dev_get_drvdata(dev);
- enum power_supply_property psp = attr - power_supply_attrs;
+ struct power_supply_attr *ps_attr = to_ps_attr(attr);
+ enum power_supply_property psp = dev_attr_psp(attr);
union power_supply_propval value;
- switch (psp) {
- case POWER_SUPPLY_PROP_STATUS:
- ret = sysfs_match_string(power_supply_status_text, buf);
- break;
- case POWER_SUPPLY_PROP_CHARGE_TYPE:
- ret = sysfs_match_string(power_supply_charge_type_text, buf);
- break;
- case POWER_SUPPLY_PROP_HEALTH:
- ret = sysfs_match_string(power_supply_health_text, buf);
- break;
- case POWER_SUPPLY_PROP_TECHNOLOGY:
- ret = sysfs_match_string(power_supply_technology_text, buf);
- break;
- case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
- ret = sysfs_match_string(power_supply_capacity_level_text, buf);
- break;
- case POWER_SUPPLY_PROP_SCOPE:
- ret = sysfs_match_string(power_supply_scope_text, buf);
- break;
- default:
- ret = -EINVAL;
+ ret = -EINVAL;
+ if (ps_attr->text_values_len > 0) {
+ ret = __sysfs_match_string(ps_attr->text_values,
+ ps_attr->text_values_len, buf);
}
/*
@@ -235,86 +334,6 @@ static ssize_t power_supply_store_property(struct device *dev,
return count;
}
-/* Must be in the same order as POWER_SUPPLY_PROP_* */
-static struct device_attribute power_supply_attrs[] = {
- /* Properties of type `int' */
- POWER_SUPPLY_ATTR(status),
- POWER_SUPPLY_ATTR(charge_type),
- POWER_SUPPLY_ATTR(health),
- POWER_SUPPLY_ATTR(present),
- POWER_SUPPLY_ATTR(online),
- POWER_SUPPLY_ATTR(authentic),
- POWER_SUPPLY_ATTR(technology),
- POWER_SUPPLY_ATTR(cycle_count),
- POWER_SUPPLY_ATTR(voltage_max),
- POWER_SUPPLY_ATTR(voltage_min),
- POWER_SUPPLY_ATTR(voltage_max_design),
- POWER_SUPPLY_ATTR(voltage_min_design),
- POWER_SUPPLY_ATTR(voltage_now),
- POWER_SUPPLY_ATTR(voltage_avg),
- POWER_SUPPLY_ATTR(voltage_ocv),
- POWER_SUPPLY_ATTR(voltage_boot),
- POWER_SUPPLY_ATTR(current_max),
- POWER_SUPPLY_ATTR(current_now),
- POWER_SUPPLY_ATTR(current_avg),
- POWER_SUPPLY_ATTR(current_boot),
- POWER_SUPPLY_ATTR(power_now),
- POWER_SUPPLY_ATTR(power_avg),
- POWER_SUPPLY_ATTR(charge_full_design),
- POWER_SUPPLY_ATTR(charge_empty_design),
- POWER_SUPPLY_ATTR(charge_full),
- POWER_SUPPLY_ATTR(charge_empty),
- POWER_SUPPLY_ATTR(charge_now),
- POWER_SUPPLY_ATTR(charge_avg),
- POWER_SUPPLY_ATTR(charge_counter),
- POWER_SUPPLY_ATTR(constant_charge_current),
- POWER_SUPPLY_ATTR(constant_charge_current_max),
- POWER_SUPPLY_ATTR(constant_charge_voltage),
- POWER_SUPPLY_ATTR(constant_charge_voltage_max),
- POWER_SUPPLY_ATTR(charge_control_limit),
- POWER_SUPPLY_ATTR(charge_control_limit_max),
- POWER_SUPPLY_ATTR(charge_control_start_threshold),
- POWER_SUPPLY_ATTR(charge_control_end_threshold),
- POWER_SUPPLY_ATTR(input_current_limit),
- POWER_SUPPLY_ATTR(input_voltage_limit),
- POWER_SUPPLY_ATTR(input_power_limit),
- POWER_SUPPLY_ATTR(energy_full_design),
- POWER_SUPPLY_ATTR(energy_empty_design),
- POWER_SUPPLY_ATTR(energy_full),
- POWER_SUPPLY_ATTR(energy_empty),
- POWER_SUPPLY_ATTR(energy_now),
- POWER_SUPPLY_ATTR(energy_avg),
- POWER_SUPPLY_ATTR(capacity),
- POWER_SUPPLY_ATTR(capacity_alert_min),
- POWER_SUPPLY_ATTR(capacity_alert_max),
- POWER_SUPPLY_ATTR(capacity_level),
- POWER_SUPPLY_ATTR(temp),
- POWER_SUPPLY_ATTR(temp_max),
- POWER_SUPPLY_ATTR(temp_min),
- POWER_SUPPLY_ATTR(temp_alert_min),
- POWER_SUPPLY_ATTR(temp_alert_max),
- POWER_SUPPLY_ATTR(temp_ambient),
- POWER_SUPPLY_ATTR(temp_ambient_alert_min),
- POWER_SUPPLY_ATTR(temp_ambient_alert_max),
- POWER_SUPPLY_ATTR(time_to_empty_now),
- POWER_SUPPLY_ATTR(time_to_empty_avg),
- POWER_SUPPLY_ATTR(time_to_full_now),
- POWER_SUPPLY_ATTR(time_to_full_avg),
- POWER_SUPPLY_ATTR(type),
- POWER_SUPPLY_ATTR(usb_type),
- POWER_SUPPLY_ATTR(scope),
- POWER_SUPPLY_ATTR(precharge_current),
- POWER_SUPPLY_ATTR(charge_term_current),
- POWER_SUPPLY_ATTR(calibrate),
- /* Properties of type `const char *' */
- POWER_SUPPLY_ATTR(model_name),
- POWER_SUPPLY_ATTR(manufacturer),
- POWER_SUPPLY_ATTR(serial_number),
-};
-
-static struct attribute *
-__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1];
-
static umode_t power_supply_attr_is_visible(struct kobject *kobj,
struct attribute *attr,
int attrno)
@@ -324,6 +343,9 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj,
umode_t mode = S_IRUSR | S_IRGRP | S_IROTH;
int i;
+ if (!power_supply_attrs[attrno].prop_name)
+ return 0;
+
if (attrno == POWER_SUPPLY_PROP_TYPE)
return mode;
@@ -352,31 +374,69 @@ static const struct attribute_group *power_supply_attr_groups[] = {
NULL,
};
+static void str_to_lower(char *str)
+{
+ while (*str) {
+ *str = tolower(*str);
+ str++;
+ }
+}
+
void power_supply_init_attrs(struct device_type *dev_type)
{
int i;
dev_type->groups = power_supply_attr_groups;
- for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++)
- __power_supply_attrs[i] = &power_supply_attrs[i].attr;
-}
+ for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++) {
+ struct device_attribute *attr;
-static char *kstruprdup(const char *str, gfp_t gfp)
-{
- char *ret, *ustr;
+ if (!power_supply_attrs[i].prop_name) {
+ pr_warn("%s: Property %d skipped because is is missing from power_supply_attrs\n",
+ __func__, i);
+ sprintf(power_supply_attrs[i].attr_name, "_err_%d", i);
+ } else {
+ str_to_lower(power_supply_attrs[i].attr_name);
+ }
- ustr = ret = kmalloc(strlen(str) + 1, gfp);
+ attr = &power_supply_attrs[i].dev_attr;
- if (!ret)
- return NULL;
+ attr->attr.name = power_supply_attrs[i].attr_name;
+ attr->show = power_supply_show_property;
+ attr->store = power_supply_store_property;
+ __power_supply_attrs[i] = &attr->attr;
+ }
+}
+
+static int add_prop_uevent(struct device *dev, struct kobj_uevent_env *env,
+ enum power_supply_property prop, char *prop_buf)
+{
+ int ret = 0;
+ struct power_supply_attr *pwr_attr;
+ struct device_attribute *dev_attr;
+ char *line;
+
+ pwr_attr = &power_supply_attrs[prop];
+ dev_attr = &pwr_attr->dev_attr;
+
+ ret = power_supply_show_property(dev, dev_attr, prop_buf);
+ if (ret == -ENODEV || ret == -ENODATA) {
+ /*
+ * When a battery is absent, we expect -ENODEV. Don't abort;
+ * send the uevent with at least the the PRESENT=0 property
+ */
+ return 0;
+ }
- while (*str)
- *ustr++ = toupper(*str++);
+ if (ret < 0)
+ return ret;
- *ustr = 0;
+ line = strchr(prop_buf, '\n');
+ if (line)
+ *line = 0;
- return ret;
+ return add_uevent_var(env, "POWER_SUPPLY_%s=%s",
+ pwr_attr->prop_name, prop_buf);
}
int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
@@ -384,7 +444,6 @@ int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
struct power_supply *psy = dev_get_drvdata(dev);
int ret = 0, j;
char *prop_buf;
- char *attrname;
if (!psy || !psy->desc) {
dev_dbg(dev, "No power supply yet\n");
@@ -399,35 +458,13 @@ int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
if (!prop_buf)
return -ENOMEM;
- for (j = 0; j < psy->desc->num_properties; j++) {
- struct device_attribute *attr;
- char *line;
-
- attr = &power_supply_attrs[psy->desc->properties[j]];
-
- ret = power_supply_show_property(dev, attr, prop_buf);
- if (ret == -ENODEV || ret == -ENODATA) {
- /* When a battery is absent, we expect -ENODEV. Don't abort;
- send the uevent with at least the the PRESENT=0 property */
- ret = 0;
- continue;
- }
-
- if (ret < 0)
- goto out;
-
- line = strchr(prop_buf, '\n');
- if (line)
- *line = 0;
-
- attrname = kstruprdup(attr->attr.name, GFP_KERNEL);
- if (!attrname) {
- ret = -ENOMEM;
- goto out;
- }
+ ret = add_prop_uevent(dev, env, POWER_SUPPLY_PROP_TYPE, prop_buf);
+ if (ret)
+ goto out;
- ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf);
- kfree(attrname);
+ for (j = 0; j < psy->desc->num_properties; j++) {
+ ret = add_prop_uevent(dev, env, psy->desc->properties[j],
+ prop_buf);
if (ret)
goto out;
}
diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c
index a7c8a8453db1..be42e814ea34 100644
--- a/drivers/power/supply/sc27xx_fuel_gauge.c
+++ b/drivers/power/supply/sc27xx_fuel_gauge.c
@@ -42,6 +42,8 @@
#define SC27XX_FGU_USER_AREA_SET 0xa0
#define SC27XX_FGU_USER_AREA_CLEAR 0xa4
#define SC27XX_FGU_USER_AREA_STATUS 0xa8
+#define SC27XX_FGU_VOLTAGE_BUF 0xd0
+#define SC27XX_FGU_CURRENT_BUF 0xf0
#define SC27XX_WRITE_SELCLB_EN BIT(0)
#define SC27XX_FGU_CLBCNT_MASK GENMASK(15, 0)
@@ -82,6 +84,7 @@
* @init_clbcnt: the initial coulomb counter
* @max_volt: the maximum constant input voltage in millivolt
* @min_volt: the minimum drained battery voltage in microvolt
+ * @boot_volt: the voltage measured during boot in microvolt
* @table_len: the capacity table length
* @resist_table_len: the resistance table length
* @cur_1000ma_adc: ADC value corresponding to 1000 mA
@@ -107,6 +110,7 @@ struct sc27xx_fgu_data {
int init_clbcnt;
int max_volt;
int min_volt;
+ int boot_volt;
int table_len;
int resist_table_len;
int cur_1000ma_adc;
@@ -319,6 +323,7 @@ static int sc27xx_fgu_get_boot_capacity(struct sc27xx_fgu_data *data, int *cap)
volt = sc27xx_fgu_adc_to_voltage(data, volt);
ocv = volt * 1000 - oci * data->internal_resist;
+ data->boot_volt = ocv;
/*
* Parse the capacity table to look up the correct capacity percent
@@ -376,6 +381,44 @@ static int sc27xx_fgu_get_clbcnt(struct sc27xx_fgu_data *data, int *clb_cnt)
return 0;
}
+static int sc27xx_fgu_get_vol_now(struct sc27xx_fgu_data *data, int *val)
+{
+ int ret;
+ u32 vol;
+
+ ret = regmap_read(data->regmap, data->base + SC27XX_FGU_VOLTAGE_BUF,
+ &vol);
+ if (ret)
+ return ret;
+
+ /*
+ * It is ADC values reading from registers which need to convert to
+ * corresponding voltage values.
+ */
+ *val = sc27xx_fgu_adc_to_voltage(data, vol);
+
+ return 0;
+}
+
+static int sc27xx_fgu_get_cur_now(struct sc27xx_fgu_data *data, int *val)
+{
+ int ret;
+ u32 cur;
+
+ ret = regmap_read(data->regmap, data->base + SC27XX_FGU_CURRENT_BUF,
+ &cur);
+ if (ret)
+ return ret;
+
+ /*
+ * It is ADC values reading from registers which need to convert to
+ * corresponding current values.
+ */
+ *val = sc27xx_fgu_adc_to_current(data, cur - SC27XX_FGU_CUR_BASIC_ADC);
+
+ return 0;
+}
+
static int sc27xx_fgu_get_capacity(struct sc27xx_fgu_data *data, int *cap)
{
int ret, cur_clbcnt, delta_clbcnt, delta_cap, temp;
@@ -577,7 +620,7 @@ static int sc27xx_fgu_get_property(struct power_supply *psy,
val->intval = value;
break;
- case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
ret = sc27xx_fgu_get_vbat_vol(data, &value);
if (ret)
goto error;
@@ -601,7 +644,6 @@ static int sc27xx_fgu_get_property(struct power_supply *psy,
val->intval = value;
break;
- case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_CURRENT_AVG:
ret = sc27xx_fgu_get_current(data, &value);
if (ret)
@@ -625,6 +667,26 @@ static int sc27xx_fgu_get_property(struct power_supply *psy,
break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = sc27xx_fgu_get_vol_now(data, &value);
+ if (ret)
+ goto error;
+
+ val->intval = value * 1000;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = sc27xx_fgu_get_cur_now(data, &value);
+ if (ret)
+ goto error;
+
+ val->intval = value * 1000;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_BOOT:
+ val->intval = data->boot_volt;
+ break;
+
default:
ret = -EINVAL;
break;
@@ -656,6 +718,11 @@ static int sc27xx_fgu_set_property(struct power_supply *psy,
ret = 0;
break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+ data->total_cap = val->intval / 1000;
+ ret = 0;
+ break;
+
default:
ret = -EINVAL;
}
@@ -676,7 +743,8 @@ static int sc27xx_fgu_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
return psp == POWER_SUPPLY_PROP_CAPACITY ||
- psp == POWER_SUPPLY_PROP_CALIBRATE;
+ psp == POWER_SUPPLY_PROP_CALIBRATE ||
+ psp == POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
}
static enum power_supply_property sc27xx_fgu_props[] = {
@@ -688,6 +756,8 @@ static enum power_supply_property sc27xx_fgu_props[] = {
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_BOOT,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
@@ -705,6 +775,7 @@ static const struct power_supply_desc sc27xx_fgu_desc = {
.set_property = sc27xx_fgu_set_property,
.external_power_changed = sc27xx_fgu_external_power_changed,
.property_is_writeable = sc27xx_fgu_property_is_writeable,
+ .no_thermal = true,
};
static void sc27xx_fgu_adjust_cap(struct sc27xx_fgu_data *data, int cap)
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index dcd5a71e6c67..6a34df65d4d1 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -223,9 +223,9 @@ struct power_supply_config {
struct power_supply_desc {
const char *name;
enum power_supply_type type;
- enum power_supply_usb_type *usb_types;
+ const enum power_supply_usb_type *usb_types;
size_t num_usb_types;
- enum power_supply_property *properties;
+ const enum power_supply_property *properties;
size_t num_properties;
/*