diff options
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, ®_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, ®_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, ®_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, ®_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, ®_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 = ®map_rd_table, + .wr_table = ®map_wr_table, + .volatile_table = ®map_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; /* |