diff options
Diffstat (limited to 'drivers/power')
123 files changed, 12208 insertions, 1672 deletions
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 60bf0ca64cf3..124afb99febe 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -97,7 +97,7 @@ config POWER_RESET_GEMINI_POWEROFF config POWER_RESET_GPIO bool "GPIO power-off driver" - depends on OF_GPIO + depends on OF help This driver supports turning off your board via a GPIO line. If your board needs a GPIO high/low to power down, say Y and @@ -105,7 +105,7 @@ config POWER_RESET_GPIO config POWER_RESET_GPIO_RESTART bool "GPIO restart driver" - depends on OF_GPIO + depends on OF help This driver supports restarting your board via a GPIO line. If your board needs a GPIO high/low to restart, say Y and @@ -128,6 +128,15 @@ config POWER_RESET_LINKSTATION Say Y here if you have a Buffalo LinkStation LS421D/E. +config POWER_RESET_MACSMC + tristate "Apple SMC reset/power-off driver" + depends on MFD_MACSMC + help + This driver supports reset and power-off on Apple Mac machines + that implement this functionality via the SMC. + + Say Y here if you have an Apple Silicon Mac. + config POWER_RESET_MSM bool "Qualcomm MSM power-off driver" depends on ARCH_QCOM @@ -172,7 +181,7 @@ config POWER_RESET_PIIX4_POWEROFF config POWER_RESET_LTC2952 bool "LTC2952 PowerPath power-off driver" - depends on OF_GPIO + depends on OF help This driver supports an external powerdown trigger and board power down via the LTC2952. Bindings are made in the device tree. @@ -189,7 +198,7 @@ config POWER_RESET_MT6323 config POWER_RESET_QNAP bool "QNAP power-off driver" - depends on OF_GPIO && PLAT_ORION + depends on PLAT_ORION help This driver supports turning off QNAP NAS devices by sending commands to the microcontroller which controls the main power. @@ -216,6 +225,27 @@ config POWER_RESET_ST help Reset support for STMicroelectronics boards. +config POWER_RESET_TH1520_AON + tristate "T-Head TH1520 AON firmware poweroff and reset driver" + depends on TH1520_PM_DOMAINS + help + This driver supports power-off and reset operations for T-Head + TH1520 SoCs running the AON firmware. + +config POWER_RESET_TORADEX_EC + tristate "Toradex Embedded Controller power-off and reset driver" + depends on ARCH_MXC || COMPILE_TEST + depends on I2C + select REGMAP_I2C + help + This driver supports power-off and reset for SMARC Toradex SoMs, + for example the SMARC iMX8MP and SMARC iMX95, using Toradex + Embedded Controller (EC). + + Say Y here if you have a Toradex SMARC SoM. + + If unsure, say N. + config POWER_RESET_TPS65086 bool "TPS65086 restart driver" depends on MFD_TPS65086 @@ -253,6 +283,15 @@ config POWER_RESET_KEYSTONE help Reboot support for the KEYSTONE SoCs. +config POWER_RESET_SPACEMIT_P1 + tristate "SpacemiT P1 poweroff and reset driver" + depends on ARCH_SPACEMIT || COMPILE_TEST + depends on MFD_SPACEMIT_P1 + default MFD_SPACEMIT_P1 + help + This driver supports power-off and reset operations for the SpacemiT + P1 PMIC. + config POWER_RESET_SYSCON bool "Generic SYSCON regmap reset driver" depends on OF @@ -315,4 +354,14 @@ config POWER_MLXBF help This driver supports reset or low power mode handling for Mellanox BlueField. +config POWER_RESET_QEMU_VIRT_CTRL + tristate "QEMU Virt Machine System Controller" + depends on HAS_IOMEM + help + This driver supports the system reset and power off functionality + provided by the QEMU 'virt-ctrl' device. + + Say Y here if you are running Linux on a QEMU virtual machine that + provides this controller, such as the m68k virt machine. + endif diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 10782d32e1da..d7ae97241a83 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o +obj-$(CONFIG_POWER_RESET_MACSMC) += macsmc-reboot.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o @@ -23,7 +24,10 @@ obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o obj-$(CONFIG_POWER_RESET_REGULATOR) += regulator-poweroff.o obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o +obj-$(CONFIG_POWER_RESET_SPACEMIT_P1) += spacemit-p1-reboot.o obj-$(CONFIG_POWER_RESET_ST) += st-poweroff.o +obj-$(CONFIG_POWER_RESET_TH1520_AON) += th1520-aon-reboot.o +obj-$(CONFIG_POWER_RESET_TORADEX_EC) += tdx-ec-poweroff.o obj-$(CONFIG_POWER_RESET_TPS65086) += tps65086-restart.o obj-$(CONFIG_POWER_RESET_VERSATILE) += arm-versatile-reboot.o obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o @@ -37,3 +41,4 @@ obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o obj-$(CONFIG_POWER_RESET_SC27XX) += sc27xx-poweroff.o obj-$(CONFIG_NVMEM_REBOOT_MODE) += nvmem-reboot-mode.o obj-$(CONFIG_POWER_MLXBF) += pwr-mlxbf.o +obj-$(CONFIG_POWER_RESET_QEMU_VIRT_CTRL) += qemu-virt-ctrl.o diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c index 036b18a1f90f..511f5a8f8961 100644 --- a/drivers/power/reset/at91-reset.c +++ b/drivers/power/reset/at91-reset.c @@ -129,12 +129,11 @@ static int at91_reset(struct notifier_block *this, unsigned long mode, " str %4, [%0, %6]\n\t" /* Disable SDRAM1 accesses */ "1: tst %1, #0\n\t" - " beq 2f\n\t" " strne %3, [%1, #" __stringify(AT91_DDRSDRC_RTR) "]\n\t" /* Power down SDRAM1 */ " strne %4, [%1, %6]\n\t" /* Reset CPU */ - "2: str %5, [%2, #" __stringify(AT91_RSTC_CR) "]\n\t" + " str %5, [%2, #" __stringify(AT91_RSTC_CR) "]\n\t" " b .\n\t" : @@ -145,7 +144,7 @@ static int at91_reset(struct notifier_block *this, unsigned long mode, "r" cpu_to_le32(AT91_DDRSDRC_LPCB_POWER_DOWN), "r" (reset->data->reset_args), "r" (reset->ramc_lpr) - : "r4"); + ); return NOTIFY_DONE; } diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index c2801bd6384d..ecf15694f925 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -129,7 +129,7 @@ static void at91_wakeup_status(struct platform_device *pdev) else if (SHDW_RTTWK(reg, &rcfg->shdwc)) reason = "RTT"; - pr_info("AT91: Wake-Up source: %s\n", reason); + dev_info(&pdev->dev, "Wake-Up source: %s\n", reason); } static void at91_poweroff(void) @@ -327,6 +327,7 @@ static const struct of_device_id at91_pmc_ids[] = { { .compatible = "microchip,sam9x60-pmc" }, { .compatible = "microchip,sama7g5-pmc" }, { .compatible = "microchip,sam9x7-pmc" }, + { .compatible = "microchip,sama7d65-pmc" }, { /* Sentinel. */ } }; diff --git a/drivers/power/reset/keystone-reset.c b/drivers/power/reset/keystone-reset.c index d9268d150e1f..3c44cd6cee0a 100644 --- a/drivers/power/reset/keystone-reset.c +++ b/drivers/power/reset/keystone-reset.c @@ -48,8 +48,7 @@ static inline int rsctrl_enable_rspll_write(void) RSCTRL_KEY_MASK, RSCTRL_KEY); } -static int rsctrl_restart_handler(struct notifier_block *this, - unsigned long mode, void *cmd) +static int rsctrl_restart_handler(struct sys_off_data *data) { /* enable write access to RSTCTRL */ rsctrl_enable_rspll_write(); @@ -61,11 +60,6 @@ static int rsctrl_restart_handler(struct notifier_block *this, return NOTIFY_DONE; } -static struct notifier_block rsctrl_restart_nb = { - .notifier_call = rsctrl_restart_handler, - .priority = 128, -}; - static const struct of_device_id rsctrl_of_match[] = { {.compatible = "ti,keystone-reset", }, {}, @@ -140,7 +134,8 @@ static int rsctrl_probe(struct platform_device *pdev) return ret; } - ret = register_restart_handler(&rsctrl_restart_nb); + ret = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART, 128, + rsctrl_restart_handler, NULL); if (ret) dev_err(dev, "cannot register restart handler (err=%d)\n", ret); diff --git a/drivers/power/reset/macsmc-reboot.c b/drivers/power/reset/macsmc-reboot.c new file mode 100644 index 000000000000..e9702acdd366 --- /dev/null +++ b/drivers/power/reset/macsmc-reboot.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC Reboot/Poweroff Handler + * Copyright The Asahi Linux Contributors + */ + +#include <linux/delay.h> +#include <linux/mfd/core.h> +#include <linux/mfd/macsmc.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/nvmem-consumer.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/slab.h> + +struct macsmc_reboot_nvmem { + struct nvmem_cell *shutdown_flag; + struct nvmem_cell *boot_stage; + struct nvmem_cell *boot_error_count; + struct nvmem_cell *panic_count; +}; + +static const char * const nvmem_names[] = { + "shutdown_flag", + "boot_stage", + "boot_error_count", + "panic_count", +}; + +enum boot_stage { + BOOT_STAGE_SHUTDOWN = 0x00, /* Clean shutdown */ + BOOT_STAGE_IBOOT_DONE = 0x2f, /* Last stage of bootloader */ + BOOT_STAGE_KERNEL_STARTED = 0x30, /* Normal OS booting */ +}; + +struct macsmc_reboot { + struct device *dev; + struct apple_smc *smc; + struct notifier_block reboot_notify; + + union { + struct macsmc_reboot_nvmem nvm; + struct nvmem_cell *nvm_cells[ARRAY_SIZE(nvmem_names)]; + }; +}; + +/* Helpers to read/write a u8 given a struct nvmem_cell */ +static int nvmem_cell_get_u8(struct nvmem_cell *cell) +{ + size_t len; + void *bfr; + u8 val; + + bfr = nvmem_cell_read(cell, &len); + if (IS_ERR(bfr)) + return PTR_ERR(bfr); + + if (len < 1) { + kfree(bfr); + return -EINVAL; + } + + val = *(u8 *)bfr; + kfree(bfr); + return val; +} + +static int nvmem_cell_set_u8(struct nvmem_cell *cell, u8 val) +{ + return nvmem_cell_write(cell, &val, sizeof(val)); +} + +/* + * SMC 'MBSE' key actions: + * + * 'offw' - shutdown warning + * 'slpw' - sleep warning + * 'rest' - restart warning + * 'off1' - shutdown (needs PMU bit set to stay on) + * 'susp' - suspend + * 'phra' - restart ("PE Halt Restart Action"?) + * 'panb' - panic beginning + * 'pane' - panic end + */ + +static int macsmc_prepare_atomic(struct sys_off_data *data) +{ + struct macsmc_reboot *reboot = data->cb_data; + + dev_info(reboot->dev, "Preparing SMC for atomic mode\n"); + + apple_smc_enter_atomic(reboot->smc); + return NOTIFY_OK; +} + +static int macsmc_power_off(struct sys_off_data *data) +{ + struct macsmc_reboot *reboot = data->cb_data; + + dev_info(reboot->dev, "Issuing power off (off1)\n"); + + if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(off1)) < 0) { + dev_err(reboot->dev, "Failed to issue MBSE = off1 (power_off)\n"); + } else { + mdelay(100); + WARN_ONCE(1, "Unable to power off system\n"); + } + + return NOTIFY_OK; +} + +static int macsmc_restart(struct sys_off_data *data) +{ + struct macsmc_reboot *reboot = data->cb_data; + + dev_info(reboot->dev, "Issuing restart (phra)\n"); + + if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(phra)) < 0) { + dev_err(reboot->dev, "Failed to issue MBSE = phra (restart)\n"); + } else { + mdelay(100); + WARN_ONCE(1, "Unable to restart system\n"); + } + + return NOTIFY_OK; +} + +static int macsmc_reboot_notify(struct notifier_block *this, unsigned long action, void *data) +{ + struct macsmc_reboot *reboot = container_of(this, struct macsmc_reboot, reboot_notify); + u8 shutdown_flag; + u32 val; + + switch (action) { + case SYS_RESTART: + val = SMC_KEY(rest); + shutdown_flag = 0; + break; + case SYS_POWER_OFF: + val = SMC_KEY(offw); + shutdown_flag = 1; + break; + default: + return NOTIFY_DONE; + } + + dev_info(reboot->dev, "Preparing for reboot (%p4ch)\n", &val); + + /* On the Mac Mini, this will turn off the LED for power off */ + if (apple_smc_write_u32(reboot->smc, SMC_KEY(MBSE), val) < 0) + dev_err(reboot->dev, "Failed to issue MBSE = %p4ch (reboot_prepare)\n", &val); + + /* Set the boot_stage to 0, which means we're doing a clean shutdown/reboot. */ + if (reboot->nvm.boot_stage && + nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_SHUTDOWN) < 0) + dev_err(reboot->dev, "Failed to write boot_stage\n"); + + /* + * Set the PMU flag to actually reboot into the off state. + * Without this, the device will just reboot. We make it optional in case it is no longer + * necessary on newer hardware. + */ + if (reboot->nvm.shutdown_flag && + nvmem_cell_set_u8(reboot->nvm.shutdown_flag, shutdown_flag) < 0) + dev_err(reboot->dev, "Failed to write shutdown_flag\n"); + + return NOTIFY_OK; +} + +static void macsmc_power_init_error_counts(struct macsmc_reboot *reboot) +{ + int boot_error_count, panic_count; + + if (!reboot->nvm.boot_error_count || !reboot->nvm.panic_count) + return; + + boot_error_count = nvmem_cell_get_u8(reboot->nvm.boot_error_count); + if (boot_error_count < 0) { + dev_err(reboot->dev, "Failed to read boot_error_count (%d)\n", boot_error_count); + return; + } + + panic_count = nvmem_cell_get_u8(reboot->nvm.panic_count); + if (panic_count < 0) { + dev_err(reboot->dev, "Failed to read panic_count (%d)\n", panic_count); + return; + } + + if (!boot_error_count && !panic_count) + return; + + dev_warn(reboot->dev, "PMU logged %d boot error(s) and %d panic(s)\n", + boot_error_count, panic_count); + + if (nvmem_cell_set_u8(reboot->nvm.panic_count, 0) < 0) + dev_err(reboot->dev, "Failed to reset panic_count\n"); + if (nvmem_cell_set_u8(reboot->nvm.boot_error_count, 0) < 0) + dev_err(reboot->dev, "Failed to reset boot_error_count\n"); +} + +static int macsmc_reboot_probe(struct platform_device *pdev) +{ + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct macsmc_reboot *reboot; + int ret, i; + + reboot = devm_kzalloc(&pdev->dev, sizeof(*reboot), GFP_KERNEL); + if (!reboot) + return -ENOMEM; + + reboot->dev = &pdev->dev; + reboot->smc = smc; + + platform_set_drvdata(pdev, reboot); + + for (i = 0; i < ARRAY_SIZE(nvmem_names); i++) { + struct nvmem_cell *cell; + + cell = devm_nvmem_cell_get(&pdev->dev, + nvmem_names[i]); + if (IS_ERR(cell)) { + if (PTR_ERR(cell) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n", + nvmem_names[i], PTR_ERR(cell)); + /* Non fatal, we'll deal with it */ + cell = NULL; + } + reboot->nvm_cells[i] = cell; + } + + /* Set the boot_stage to indicate we're running the OS kernel */ + if (reboot->nvm.boot_stage && + nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_KERNEL_STARTED) < 0) + dev_err(reboot->dev, "Failed to write boot_stage\n"); + + /* Display and clear the error counts */ + macsmc_power_init_error_counts(reboot); + + reboot->reboot_notify.notifier_call = macsmc_reboot_notify; + + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF_PREPARE, + SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register power-off prepare handler\n"); + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH, + macsmc_power_off, reboot); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register power-off handler\n"); + + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART_PREPARE, + SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register restart prepare handler\n"); + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_HIGH, + macsmc_restart, reboot); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to register restart handler\n"); + + ret = devm_register_reboot_notifier(&pdev->dev, &reboot->reboot_notify); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to register reboot notifier\n"); + + dev_info(&pdev->dev, "Handling reboot and poweroff requests via SMC\n"); + + return 0; +} + +static const struct of_device_id macsmc_reboot_of_table[] = { + { .compatible = "apple,smc-reboot", }, + {} +}; +MODULE_DEVICE_TABLE(of, macsmc_reboot_of_table); + +static struct platform_driver macsmc_reboot_driver = { + .driver = { + .name = "macsmc-reboot", + .of_match_table = macsmc_reboot_of_table, + }, + .probe = macsmc_reboot_probe, +}; +module_platform_driver(macsmc_reboot_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC reboot/poweroff driver"); +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); diff --git a/drivers/power/reset/nvmem-reboot-mode.c b/drivers/power/reset/nvmem-reboot-mode.c index 41530b70cfc4..d260715fccf6 100644 --- a/drivers/power/reset/nvmem-reboot-mode.c +++ b/drivers/power/reset/nvmem-reboot-mode.c @@ -10,6 +10,7 @@ #include <linux/nvmem-consumer.h> #include <linux/platform_device.h> #include <linux/reboot-mode.h> +#include <linux/slab.h> struct nvmem_reboot_mode { struct reboot_mode_driver reboot; @@ -19,12 +20,22 @@ struct nvmem_reboot_mode { static int nvmem_reboot_mode_write(struct reboot_mode_driver *reboot, unsigned int magic) { - int ret; struct nvmem_reboot_mode *nvmem_rbm; + size_t buf_len; + void *buf; + int ret; nvmem_rbm = container_of(reboot, struct nvmem_reboot_mode, reboot); - ret = nvmem_cell_write(nvmem_rbm->cell, &magic, sizeof(magic)); + buf = nvmem_cell_read(nvmem_rbm->cell, &buf_len); + if (IS_ERR(buf)) + return PTR_ERR(buf); + kfree(buf); + + if (buf_len > sizeof(magic)) + return -EINVAL; + + ret = nvmem_cell_write(nvmem_rbm->cell, &magic, buf_len); if (ret < 0) dev_err(reboot->dev, "update reboot mode bits failed\n"); diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c index 1344b361a475..7e108982a582 100644 --- a/drivers/power/reset/qcom-pon.c +++ b/drivers/power/reset/qcom-pon.c @@ -19,7 +19,7 @@ #define NO_REASON_SHIFT 0 -struct pm8916_pon { +struct qcom_pon { struct device *dev; struct regmap *regmap; u32 baseaddr; @@ -27,11 +27,11 @@ struct pm8916_pon { long reason_shift; }; -static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot, +static int qcom_pon_reboot_mode_write(struct reboot_mode_driver *reboot, unsigned int magic) { - struct pm8916_pon *pon = container_of - (reboot, struct pm8916_pon, reboot_mode); + struct qcom_pon *pon = container_of + (reboot, struct qcom_pon, reboot_mode); int ret; ret = regmap_update_bits(pon->regmap, @@ -44,9 +44,9 @@ static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot, return ret; } -static int pm8916_pon_probe(struct platform_device *pdev) +static int qcom_pon_probe(struct platform_device *pdev) { - struct pm8916_pon *pon; + struct qcom_pon *pon; long reason_shift; int error; @@ -72,7 +72,7 @@ static int pm8916_pon_probe(struct platform_device *pdev) if (reason_shift != NO_REASON_SHIFT) { pon->reboot_mode.dev = &pdev->dev; pon->reason_shift = reason_shift; - pon->reboot_mode.write = pm8916_reboot_mode_write; + pon->reboot_mode.write = qcom_pon_reboot_mode_write; error = devm_reboot_mode_register(&pdev->dev, &pon->reboot_mode); if (error) { dev_err(&pdev->dev, "can't register reboot mode\n"); @@ -85,7 +85,7 @@ static int pm8916_pon_probe(struct platform_device *pdev) return devm_of_platform_populate(&pdev->dev); } -static const struct of_device_id pm8916_pon_id_table[] = { +static const struct of_device_id qcom_pon_id_table[] = { { .compatible = "qcom,pm8916-pon", .data = (void *)GEN1_REASON_SHIFT }, { .compatible = "qcom,pm8941-pon", .data = (void *)NO_REASON_SHIFT }, { .compatible = "qcom,pms405-pon", .data = (void *)GEN1_REASON_SHIFT }, @@ -93,16 +93,16 @@ static const struct of_device_id pm8916_pon_id_table[] = { { .compatible = "qcom,pmk8350-pon", .data = (void *)GEN2_REASON_SHIFT }, { } }; -MODULE_DEVICE_TABLE(of, pm8916_pon_id_table); +MODULE_DEVICE_TABLE(of, qcom_pon_id_table); -static struct platform_driver pm8916_pon_driver = { - .probe = pm8916_pon_probe, +static struct platform_driver qcom_pon_driver = { + .probe = qcom_pon_probe, .driver = { - .name = "pm8916-pon", - .of_match_table = pm8916_pon_id_table, + .name = "qcom-pon", + .of_match_table = qcom_pon_id_table, }, }; -module_platform_driver(pm8916_pon_driver); +module_platform_driver(qcom_pon_driver); -MODULE_DESCRIPTION("pm8916 Power On driver"); +MODULE_DESCRIPTION("Qualcomm Power On driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/qemu-virt-ctrl.c b/drivers/power/reset/qemu-virt-ctrl.c new file mode 100644 index 000000000000..01409dfe2265 --- /dev/null +++ b/drivers/power/reset/qemu-virt-ctrl.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * QEMU Virt Machine System Controller Driver + * + * Copyright (C) 2026 Kuan-Wei Chiu <visitorckw@gmail.com> + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> + +/* Registers */ +#define VIRT_CTRL_REG_FEATURES 0x00 +#define VIRT_CTRL_REG_CMD 0x04 + +/* Commands */ +#define CMD_NOOP 0 +#define CMD_RESET 1 +#define CMD_HALT 2 +#define CMD_PANIC 3 + +struct qemu_virt_ctrl { + void __iomem *base; + struct notifier_block reboot_nb; +}; + +static inline void virt_ctrl_write32(u32 val, void __iomem *addr) +{ + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + iowrite32be(val, addr); + else + iowrite32(val, addr); +} + +static int qemu_virt_ctrl_power_off(struct sys_off_data *data) +{ + struct qemu_virt_ctrl *ctrl = data->cb_data; + + virt_ctrl_write32(CMD_HALT, ctrl->base + VIRT_CTRL_REG_CMD); + + return NOTIFY_DONE; +} + +static int qemu_virt_ctrl_restart(struct sys_off_data *data) +{ + struct qemu_virt_ctrl *ctrl = data->cb_data; + + virt_ctrl_write32(CMD_RESET, ctrl->base + VIRT_CTRL_REG_CMD); + + return NOTIFY_DONE; +} + +static int qemu_virt_ctrl_reboot_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct qemu_virt_ctrl *ctrl = container_of(nb, struct qemu_virt_ctrl, reboot_nb); + + if (action == SYS_HALT) + virt_ctrl_write32(CMD_HALT, ctrl->base + VIRT_CTRL_REG_CMD); + + return NOTIFY_DONE; +} + +static int qemu_virt_ctrl_probe(struct platform_device *pdev) +{ + struct qemu_virt_ctrl *ctrl; + int ret; + + ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + ctrl->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctrl->base)) + return PTR_ERR(ctrl->base); + + ret = devm_register_sys_off_handler(&pdev->dev, + SYS_OFF_MODE_RESTART, + SYS_OFF_PRIO_DEFAULT, + qemu_virt_ctrl_restart, + ctrl); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "cannot register restart handler\n"); + + ret = devm_register_sys_off_handler(&pdev->dev, + SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, + qemu_virt_ctrl_power_off, + ctrl); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "cannot register power-off handler\n"); + + ctrl->reboot_nb.notifier_call = qemu_virt_ctrl_reboot_notify; + ret = devm_register_reboot_notifier(&pdev->dev, &ctrl->reboot_nb); + if (ret) + return dev_err_probe(&pdev->dev, ret, "cannot register reboot notifier\n"); + + return 0; +} + +static const struct platform_device_id qemu_virt_ctrl_id[] = { + { "qemu-virt-ctrl", 0 }, + { } +}; +MODULE_DEVICE_TABLE(platform, qemu_virt_ctrl_id); + +static struct platform_driver qemu_virt_ctrl_driver = { + .probe = qemu_virt_ctrl_probe, + .driver = { + .name = "qemu-virt-ctrl", + }, + .id_table = qemu_virt_ctrl_id, +}; +module_platform_driver(qemu_virt_ctrl_driver); + +MODULE_AUTHOR("Kuan-Wei Chiu <visitorckw@gmail.com>"); +MODULE_DESCRIPTION("QEMU Virt Machine System Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index b4076b10b893..d20e44db0532 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -4,12 +4,16 @@ */ #include <linux/device.h> +#include <linux/err.h> #include <linux/init.h> #include <linux/kernel.h> +#include <linux/list.h> #include <linux/module.h> #include <linux/of.h> #include <linux/reboot.h> #include <linux/reboot-mode.h> +#include <linux/slab.h> +#include <linux/string.h> #define PREFIX "mode-" @@ -19,24 +23,81 @@ struct mode_info { struct list_head list; }; +struct reboot_mode_sysfs_data { + struct device *reboot_mode_device; + struct list_head head; +}; + +static inline void reboot_mode_release_list(struct reboot_mode_sysfs_data *priv) +{ + struct mode_info *info; + struct mode_info *next; + + list_for_each_entry_safe(info, next, &priv->head, list) { + list_del(&info->list); + kfree_const(info->mode); + kfree(info); + } +} + +static ssize_t reboot_modes_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct reboot_mode_sysfs_data *priv; + struct mode_info *sysfs_info; + ssize_t size = 0; + + priv = dev_get_drvdata(dev); + if (!priv) + return -ENODATA; + + list_for_each_entry(sysfs_info, &priv->head, list) + size += sysfs_emit_at(buf, size, "%s ", sysfs_info->mode); + + if (!size) + return -ENODATA; + + return size + sysfs_emit_at(buf, size - 1, "\n"); +} +static DEVICE_ATTR_RO(reboot_modes); + +static struct attribute *reboot_mode_attrs[] = { + &dev_attr_reboot_modes.attr, + NULL, +}; +ATTRIBUTE_GROUPS(reboot_mode); + +static const struct class reboot_mode_class = { + .name = "reboot-mode", + .dev_groups = reboot_mode_groups, +}; + static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot, const char *cmd) { const char *normal = "normal"; - int magic = 0; struct mode_info *info; + char cmd_[110]; if (!cmd) cmd = normal; - list_for_each_entry(info, &reboot->head, list) { - if (!strcmp(info->mode, cmd)) { - magic = info->magic; - break; - } - } + list_for_each_entry(info, &reboot->head, list) + if (!strcmp(info->mode, cmd)) + return info->magic; + + /* try to match again, replacing characters impossible in DT */ + if (strscpy(cmd_, cmd, sizeof(cmd_)) == -E2BIG) + return 0; - return magic; + strreplace(cmd_, ' ', '-'); + strreplace(cmd_, ',', '-'); + strreplace(cmd_, '/', '-'); + + list_for_each_entry(info, &reboot->head, list) + if (!strcmp(info->mode, cmd_)) + return info->magic; + + return 0; } static int reboot_mode_notify(struct notifier_block *this, @@ -53,6 +114,52 @@ static int reboot_mode_notify(struct notifier_block *this, return NOTIFY_DONE; } +static int reboot_mode_create_device(struct reboot_mode_driver *reboot) +{ + struct reboot_mode_sysfs_data *priv; + struct mode_info *sysfs_info; + struct mode_info *info; + int ret; + + priv = kzalloc_obj(*priv, GFP_KERNEL); + if (!priv) + return -ENOMEM; + + INIT_LIST_HEAD(&priv->head); + + list_for_each_entry(info, &reboot->head, list) { + sysfs_info = kzalloc_obj(*sysfs_info, GFP_KERNEL); + if (!sysfs_info) { + ret = -ENOMEM; + goto error; + } + + sysfs_info->mode = kstrdup_const(info->mode, GFP_KERNEL); + if (!sysfs_info->mode) { + kfree(sysfs_info); + ret = -ENOMEM; + goto error; + } + + list_add_tail(&sysfs_info->list, &priv->head); + } + + priv->reboot_mode_device = device_create(&reboot_mode_class, NULL, 0, + (void *)priv, "%s", + reboot->dev->driver->name); + if (IS_ERR(priv->reboot_mode_device)) { + ret = PTR_ERR(priv->reboot_mode_device); + goto error; + } + + return 0; + +error: + reboot_mode_release_list(priv); + kfree(priv); + return ret; +} + /** * reboot_mode_register - register a reboot mode driver * @reboot: reboot mode driver @@ -104,16 +211,49 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) reboot->reboot_notifier.notifier_call = reboot_mode_notify; register_reboot_notifier(&reboot->reboot_notifier); + ret = reboot_mode_create_device(reboot); + if (ret) + goto error; + return 0; error: - list_for_each_entry(info, &reboot->head, list) - kfree_const(info->mode); - + reboot_mode_unregister(reboot); return ret; } EXPORT_SYMBOL_GPL(reboot_mode_register); +static int reboot_mode_match_by_name(struct device *dev, const void *data) +{ + const char *name = data; + + if (!dev || !data) + return 0; + + return dev_name(dev) && strcmp(dev_name(dev), name) == 0; +} + +static inline void reboot_mode_unregister_device(struct reboot_mode_driver *reboot) +{ + struct reboot_mode_sysfs_data *priv; + struct device *reboot_mode_device; + + reboot_mode_device = class_find_device(&reboot_mode_class, NULL, reboot->dev->driver->name, + reboot_mode_match_by_name); + + if (!reboot_mode_device) + return; + + priv = dev_get_drvdata(reboot_mode_device); + device_unregister(reboot_mode_device); + + if (!priv) + return; + + reboot_mode_release_list(priv); + kfree(priv); +} + /** * reboot_mode_unregister - unregister a reboot mode driver * @reboot: reboot mode driver @@ -123,6 +263,7 @@ int reboot_mode_unregister(struct reboot_mode_driver *reboot) struct mode_info *info; unregister_reboot_notifier(&reboot->reboot_notifier); + reboot_mode_unregister_device(reboot); list_for_each_entry(info, &reboot->head, list) kfree_const(info->mode); @@ -190,6 +331,19 @@ void devm_reboot_mode_unregister(struct device *dev, } EXPORT_SYMBOL_GPL(devm_reboot_mode_unregister); +static int __init reboot_mode_init(void) +{ + return class_register(&reboot_mode_class); +} + +static void __exit reboot_mode_exit(void) +{ + class_unregister(&reboot_mode_class); +} + +subsys_initcall(reboot_mode_init); +module_exit(reboot_mode_exit); + MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>"); MODULE_DESCRIPTION("System reboot mode core library"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/sc27xx-poweroff.c b/drivers/power/reset/sc27xx-poweroff.c index 90287c31992c..393bd1c33b73 100644 --- a/drivers/power/reset/sc27xx-poweroff.c +++ b/drivers/power/reset/sc27xx-poweroff.c @@ -28,7 +28,7 @@ static struct regmap *regmap; * taking cpus down to avoid racing regmap or spi mutex lock when poweroff * system through PMIC. */ -static void sc27xx_poweroff_shutdown(void) +static void sc27xx_poweroff_shutdown(void *data) { #ifdef CONFIG_HOTPLUG_CPU int cpu; @@ -40,10 +40,14 @@ static void sc27xx_poweroff_shutdown(void) #endif } -static struct syscore_ops poweroff_syscore_ops = { +static const struct syscore_ops poweroff_syscore_ops = { .shutdown = sc27xx_poweroff_shutdown, }; +static struct syscore poweroff_syscore = { + .ops = &poweroff_syscore_ops, +}; + static void sc27xx_poweroff_do_poweroff(void) { /* Disable the external subsys connection's power firstly */ @@ -62,7 +66,7 @@ static int sc27xx_poweroff_probe(struct platform_device *pdev) return -ENODEV; pm_power_off = sc27xx_poweroff_do_poweroff; - register_syscore_ops(&poweroff_syscore_ops); + register_syscore(&poweroff_syscore); return 0; } diff --git a/drivers/power/reset/spacemit-p1-reboot.c b/drivers/power/reset/spacemit-p1-reboot.c new file mode 100644 index 000000000000..9ec3d1fff8f3 --- /dev/null +++ b/drivers/power/reset/spacemit-p1-reboot.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 by Aurelien Jarno + */ + +#include <linux/bits.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reboot.h> + +/* Power Control Register 2 */ +#define PWR_CTRL2 0x7e +#define PWR_CTRL2_SHUTDOWN BIT(2) /* Shutdown request */ +#define PWR_CTRL2_RST BIT(1) /* Reset request */ + +static int spacemit_p1_pwroff_handler(struct sys_off_data *data) +{ + struct regmap *regmap = data->cb_data; + int ret; + + /* Put the PMIC into shutdown state */ + ret = regmap_set_bits(regmap, PWR_CTRL2, PWR_CTRL2_SHUTDOWN); + if (ret) { + dev_err(data->dev, "shutdown failed: %d\n", ret); + return notifier_from_errno(ret); + } + + return NOTIFY_DONE; +} + +static int spacemit_p1_restart_handler(struct sys_off_data *data) +{ + struct regmap *regmap = data->cb_data; + int ret; + + /* Put the PMIC into reset state */ + ret = regmap_set_bits(regmap, PWR_CTRL2, PWR_CTRL2_RST); + if (ret) { + dev_err(data->dev, "restart failed: %d\n", ret); + return notifier_from_errno(ret); + } + + return NOTIFY_DONE; +} + +static int spacemit_p1_reboot_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct regmap *regmap; + int ret; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + ret = devm_register_power_off_handler(dev, &spacemit_p1_pwroff_handler, + regmap); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register power off handler\n"); + + ret = devm_register_restart_handler(dev, spacemit_p1_restart_handler, + regmap); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register restart handler\n"); + + return 0; +} + +static const struct platform_device_id spacemit_p1_reboot_id_table[] = { + { "spacemit-p1-reboot", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, spacemit_p1_reboot_id_table); + +static struct platform_driver spacemit_p1_reboot_driver = { + .driver = { + .name = "spacemit-p1-reboot", + }, + .probe = spacemit_p1_reboot_probe, + .id_table = spacemit_p1_reboot_id_table, +}; +module_platform_driver(spacemit_p1_reboot_driver); + +MODULE_DESCRIPTION("SpacemiT P1 reboot/poweroff driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/reset/syscon-reboot.c b/drivers/power/reset/syscon-reboot.c index d623d77e657e..2e2cf5f62d73 100644 --- a/drivers/power/reset/syscon-reboot.c +++ b/drivers/power/reset/syscon-reboot.c @@ -14,11 +14,24 @@ #include <linux/reboot.h> #include <linux/regmap.h> -struct syscon_reboot_context { - struct regmap *map; +struct reboot_mode_bits { u32 offset; - u32 value; u32 mask; + u32 value; + bool valid; +}; + +struct reboot_data { + struct reboot_mode_bits mode_bits[REBOOT_SOFT + 1]; + struct reboot_mode_bits catchall; +}; + +struct syscon_reboot_context { + struct regmap *map; + + const struct reboot_data *rd; /* from of match data, if any */ + struct reboot_mode_bits catchall; /* from DT */ + struct notifier_block restart_handler; }; @@ -28,9 +41,21 @@ static int syscon_restart_handle(struct notifier_block *this, struct syscon_reboot_context *ctx = container_of(this, struct syscon_reboot_context, restart_handler); + const struct reboot_mode_bits *mode_bits; + + if (ctx->rd) { + if (mode < ARRAY_SIZE(ctx->rd->mode_bits) && + ctx->rd->mode_bits[mode].valid) + mode_bits = &ctx->rd->mode_bits[mode]; + else + mode_bits = &ctx->rd->catchall; + } else { + mode_bits = &ctx->catchall; + } /* Issue the reboot */ - regmap_update_bits(ctx->map, ctx->offset, ctx->mask, ctx->value); + regmap_update_bits(ctx->map, mode_bits->offset, mode_bits->mask, + mode_bits->value); mdelay(1000); @@ -42,7 +67,6 @@ static int syscon_reboot_probe(struct platform_device *pdev) { struct syscon_reboot_context *ctx; struct device *dev = &pdev->dev; - int mask_err, value_err; int priority; int err; @@ -60,24 +84,33 @@ static int syscon_reboot_probe(struct platform_device *pdev) if (of_property_read_s32(pdev->dev.of_node, "priority", &priority)) priority = 192; - if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset)) - if (of_property_read_u32(pdev->dev.of_node, "reg", &ctx->offset)) - return -EINVAL; + ctx->rd = of_device_get_match_data(dev); + if (!ctx->rd) { + int mask_err, value_err; - value_err = of_property_read_u32(pdev->dev.of_node, "value", &ctx->value); - mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask); - if (value_err && mask_err) { - dev_err(dev, "unable to read 'value' and 'mask'"); - return -EINVAL; - } + if (of_property_read_u32(pdev->dev.of_node, "offset", + &ctx->catchall.offset) && + of_property_read_u32(pdev->dev.of_node, "reg", + &ctx->catchall.offset)) + return -EINVAL; - if (value_err) { - /* support old binding */ - ctx->value = ctx->mask; - ctx->mask = 0xFFFFFFFF; - } else if (mask_err) { - /* support value without mask*/ - ctx->mask = 0xFFFFFFFF; + value_err = of_property_read_u32(pdev->dev.of_node, "value", + &ctx->catchall.value); + mask_err = of_property_read_u32(pdev->dev.of_node, "mask", + &ctx->catchall.mask); + if (value_err && mask_err) { + dev_err(dev, "unable to read 'value' and 'mask'"); + return -EINVAL; + } + + if (value_err) { + /* support old binding */ + ctx->catchall.value = ctx->catchall.mask; + ctx->catchall.mask = 0xFFFFFFFF; + } else if (mask_err) { + /* support value without mask */ + ctx->catchall.mask = 0xFFFFFFFF; + } } ctx->restart_handler.notifier_call = syscon_restart_handle; @@ -89,7 +122,30 @@ static int syscon_reboot_probe(struct platform_device *pdev) return err; } +static const struct reboot_data gs101_reboot_data = { + .mode_bits = { + [REBOOT_WARM] = { + .offset = 0x3a00, /* SYSTEM_CONFIGURATION */ + .mask = 0x00000002, /* SWRESET_SYSTEM */ + .value = 0x00000002, + .valid = true, + }, + [REBOOT_SOFT] = { + .offset = 0x3a00, /* SYSTEM_CONFIGURATION */ + .mask = 0x00000002, /* SWRESET_SYSTEM */ + .value = 0x00000002, + .valid = true, + }, + }, + .catchall = { + .offset = 0x3e9c, /* PAD_CTRL_PWR_HOLD */ + .mask = 0x00000100, + .value = 0x00000000, + }, +}; + static const struct of_device_id syscon_reboot_of_match[] = { + { .compatible = "google,gs101-reboot", .data = &gs101_reboot_data }, { .compatible = "syscon-reboot" }, {} }; diff --git a/drivers/power/reset/tdx-ec-poweroff.c b/drivers/power/reset/tdx-ec-poweroff.c new file mode 100644 index 000000000000..8040aa03d74d --- /dev/null +++ b/drivers/power/reset/tdx-ec-poweroff.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Toradex Embedded Controller driver + * + * Copyright (C) 2025 Toradex + * + * Author: Emanuele Ghidoli <emanuele.ghidoli@toradex.com> + */ + +#include <linux/array_size.h> +#include <linux/bug.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/regmap.h> +#include <linux/types.h> + +#define EC_CHIP_ID_REG 0x00 +#define EC_CHIP_ID_SMARC_IMX95 0x11 +#define EC_CHIP_ID_SMARC_IMX8MP 0x12 + +#define EC_VERSION_REG_MAJOR 0x01 +#define EC_VERSION_REG_MINOR 0x02 +#define EC_ID_VERSION_LEN 3 + +#define EC_CMD_REG 0xD0 +#define EC_CMD_POWEROFF 0x01 +#define EC_CMD_RESET 0x02 + +#define EC_REG_MAX 0xD0 + +#define EC_CMD_TIMEOUT_MS 1000 + +static const struct regmap_range volatile_ranges[] = { + regmap_reg_range(EC_CMD_REG, EC_CMD_REG), +}; + +static const struct regmap_access_table volatile_table = { + .yes_ranges = volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(volatile_ranges), +}; + +static const struct regmap_range read_ranges[] = { + regmap_reg_range(EC_CHIP_ID_REG, EC_VERSION_REG_MINOR), +}; + +static const struct regmap_access_table read_table = { + .yes_ranges = read_ranges, + .n_yes_ranges = ARRAY_SIZE(read_ranges), +}; + +static const struct regmap_config regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = EC_REG_MAX, + .cache_type = REGCACHE_RBTREE, + .rd_table = &read_table, + .volatile_table = &volatile_table, +}; + +static int tdx_ec_cmd(struct regmap *regmap, u8 cmd) +{ + int err = regmap_write(regmap, EC_CMD_REG, cmd); + + if (err) + dev_err(regmap_get_device(regmap), "Failed to send command 0x%02X: %d\n", cmd, err); + + return err; +} + +static int tdx_ec_power_off(struct sys_off_data *data) +{ + struct regmap *regmap = data->cb_data; + int err; + + err = tdx_ec_cmd(regmap, EC_CMD_POWEROFF); + + if (err) { + dev_err(data->dev, "Failed to send power off command\n"); + } else { + mdelay(EC_CMD_TIMEOUT_MS); + WARN_ONCE(1, "Unable to power off system\n"); + } + + return err ? NOTIFY_BAD : NOTIFY_DONE; +} + +static int tdx_ec_restart(struct sys_off_data *data) +{ + struct regmap *regmap = data->cb_data; + int err; + + err = tdx_ec_cmd(regmap, EC_CMD_RESET); + + if (err) { + dev_err(data->dev, "Failed to send restart command\n"); + } else { + mdelay(EC_CMD_TIMEOUT_MS); + WARN_ONCE(1, "Unable to restart system\n"); + } + + return err ? NOTIFY_BAD : NOTIFY_DONE; +} + +static int tdx_ec_register_power_off_restart(struct device *dev, struct regmap *regmap) +{ + int err; + + err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART, + SYS_OFF_PRIO_FIRMWARE, + tdx_ec_restart, regmap); + if (err) + return err; + + return devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_FIRMWARE, + tdx_ec_power_off, regmap); +} + +static int tdx_ec_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + u8 reg_val[EC_ID_VERSION_LEN]; + struct regmap *regmap; + int err; + + regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + err = regmap_bulk_read(regmap, EC_CHIP_ID_REG, ®_val, EC_ID_VERSION_LEN); + if (err) + return dev_err_probe(dev, err, + "Cannot read id and version registers\n"); + + dev_info(dev, "Toradex Embedded Controller id %x - Firmware %u.%u\n", + reg_val[0], reg_val[1], reg_val[2]); + + err = tdx_ec_register_power_off_restart(dev, regmap); + if (err) + return dev_err_probe(dev, err, + "Cannot register system restart handler\n"); + + return 0; +} + +static const struct of_device_id __maybe_unused of_tdx_ec_match[] = { + { .compatible = "toradex,smarc-ec" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_tdx_ec_match); + +static struct i2c_driver tdx_ec_driver = { + .probe = tdx_ec_probe, + .driver = { + .name = "toradex-smarc-ec", + .of_match_table = of_tdx_ec_match, + }, +}; +module_i2c_driver(tdx_ec_driver); + +MODULE_AUTHOR("Emanuele Ghidoli <emanuele.ghidoli@toradex.com>"); +MODULE_DESCRIPTION("Toradex SMARC Embedded Controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/reset/th1520-aon-reboot.c b/drivers/power/reset/th1520-aon-reboot.c new file mode 100644 index 000000000000..ec249667a0ff --- /dev/null +++ b/drivers/power/reset/th1520-aon-reboot.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * T-HEAD TH1520 AON Firmware Reboot Driver + * + * Copyright (c) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#include <linux/auxiliary_bus.h> +#include <linux/firmware/thead/thead,th1520-aon.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/reboot.h> +#include <linux/slab.h> + +#define TH1520_AON_REBOOT_PRIORITY 200 + +struct th1520_aon_msg_empty_body { + struct th1520_aon_rpc_msg_hdr hdr; + u16 reserved[12]; +} __packed __aligned(1); + +static int th1520_aon_pwroff_handler(struct sys_off_data *data) +{ + struct th1520_aon_chan *aon_chan = data->cb_data; + struct th1520_aon_msg_empty_body msg = {}; + + msg.hdr.svc = TH1520_AON_RPC_SVC_WDG; + msg.hdr.func = TH1520_AON_WDG_FUNC_POWER_OFF; + msg.hdr.size = TH1520_AON_RPC_MSG_NUM; + + th1520_aon_call_rpc(aon_chan, &msg); + + return NOTIFY_DONE; +} + +static int th1520_aon_restart_handler(struct sys_off_data *data) +{ + struct th1520_aon_chan *aon_chan = data->cb_data; + struct th1520_aon_msg_empty_body msg = {}; + + msg.hdr.svc = TH1520_AON_RPC_SVC_WDG; + msg.hdr.func = TH1520_AON_WDG_FUNC_RESTART; + msg.hdr.size = TH1520_AON_RPC_MSG_NUM; + + th1520_aon_call_rpc(aon_chan, &msg); + + return NOTIFY_DONE; +} + +static int th1520_aon_reboot_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct device *dev = &adev->dev; + int ret; + + /* Expect struct th1520_aon_chan to be passed via platform_data */ + ret = devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF, + TH1520_AON_REBOOT_PRIORITY, + th1520_aon_pwroff_handler, + adev->dev.platform_data); + + if (ret) { + dev_err(dev, "Failed to register power off handler\n"); + return ret; + } + + ret = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART, + TH1520_AON_REBOOT_PRIORITY, + th1520_aon_restart_handler, + adev->dev.platform_data); + + if (ret) { + dev_err(dev, "Failed to register restart handler\n"); + return ret; + } + + return 0; +} + +static const struct auxiliary_device_id th1520_aon_reboot_id_table[] = { + { .name = "th1520_pm_domains.reboot" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, th1520_aon_reboot_id_table); + +static struct auxiliary_driver th1520_aon_reboot_driver = { + .driver = { + .name = "th1520-aon-reboot", + }, + .probe = th1520_aon_reboot_probe, + .id_table = th1520_aon_reboot_id_table, +}; +module_auxiliary_driver(th1520_aon_reboot_driver); + +MODULE_AUTHOR("Icenowy Zheng <uwu@icenowy.me>"); +MODULE_DESCRIPTION("T-HEAD TH1520 AON-firmware-based reboot driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index ddcc42a98492..1c5f5820f5b7 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -16,7 +16,7 @@ if POWER_SEQUENCING config POWER_SEQUENCING_QCOM_WCN tristate "Qualcomm WCN family PMU driver" default m if ARCH_QCOM - depends on OF + depends on OF || COMPILE_TEST help Say Y here to enable the power sequencing driver for Qualcomm WCN Bluetooth/WLAN chipsets. @@ -27,4 +27,23 @@ config POWER_SEQUENCING_QCOM_WCN this driver is needed for correct power control or else we'd risk not respecting the required delays between enabling Bluetooth and WLAN. +config POWER_SEQUENCING_TH1520_GPU + tristate "T-HEAD TH1520 GPU power sequencing driver" + depends on (ARCH_THEAD && AUXILIARY_BUS) || COMPILE_TEST + help + Say Y here to enable the power sequencing driver for the TH1520 SoC + GPU. This driver handles the complex clock and reset sequence + required to power on the Imagination BXM GPU on this platform. + +config POWER_SEQUENCING_PCIE_M2 + tristate "PCIe M.2 connector power sequencing driver" + depends on OF + depends on PCI + depends on SERIAL_DEV_BUS + select OF_DYNAMIC + help + Say Y here to enable the power sequencing driver for PCIe M.2 + connectors. This driver handles the power sequencing for the M.2 + connectors exposing multiple interfaces like PCIe, SATA, UART, etc... + endif diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/Makefile index 2eec2df7912d..0911d4618298 100644 --- a/drivers/power/sequencing/Makefile +++ b/drivers/power/sequencing/Makefile @@ -4,3 +4,5 @@ obj-$(CONFIG_POWER_SEQUENCING) += pwrseq-core.o pwrseq-core-y := core.o obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) += pwrseq-qcom-wcn.o +obj-$(CONFIG_POWER_SEQUENCING_TH1520_GPU) += pwrseq-thead-gpu.o +obj-$(CONFIG_POWER_SEQUENCING_PCIE_M2) += pwrseq-pcie-m2.o diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 0ffc259c6bb6..4dff71be11b6 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -90,7 +90,7 @@ static struct pwrseq_unit *pwrseq_unit_new(const struct pwrseq_unit_data *data) { struct pwrseq_unit *unit; - unit = kzalloc(sizeof(*unit), GFP_KERNEL); + unit = kzalloc_obj(*unit); if (!unit) return NULL; @@ -138,7 +138,7 @@ static struct pwrseq_unit_dep *pwrseq_unit_dep_new(struct pwrseq_unit *unit) { struct pwrseq_unit_dep *dep; - dep = kzalloc(sizeof(*dep), GFP_KERNEL); + dep = kzalloc_obj(*dep); if (!dep) return NULL; @@ -195,7 +195,7 @@ pwrseq_target_new(const struct pwrseq_target_data *data) { struct pwrseq_target *target; - target = kzalloc(sizeof(*target), GFP_KERNEL); + target = kzalloc_obj(*target); if (!target) return NULL; @@ -628,7 +628,7 @@ static int pwrseq_match_device(struct device *pwrseq_dev, void *data) return 0; ret = pwrseq->match(pwrseq, match_data->dev); - if (ret <= 0) + if (ret == PWRSEQ_NO_MATCH || ret < 0) return ret; /* We got the matching device, let's find the right target. */ @@ -651,7 +651,7 @@ static int pwrseq_match_device(struct device *pwrseq_dev, void *data) match_data->desc->pwrseq = pwrseq_device_get(pwrseq); - return 1; + return PWRSEQ_MATCH_OK; } /** @@ -669,8 +669,7 @@ struct pwrseq_desc *pwrseq_get(struct device *dev, const char *target) struct pwrseq_match_data match_data; int ret; - struct pwrseq_desc *desc __free(kfree) = kzalloc(sizeof(*desc), - GFP_KERNEL); + struct pwrseq_desc *desc __free(kfree) = kzalloc_obj(*desc); if (!desc) return ERR_PTR(-ENOMEM); @@ -684,7 +683,7 @@ struct pwrseq_desc *pwrseq_get(struct device *dev, const char *target) pwrseq_match_device); if (ret < 0) return ERR_PTR(ret); - if (ret == 0) + if (ret == PWRSEQ_NO_MATCH) /* No device matched. */ return ERR_PTR(-EPROBE_DEFER); @@ -914,8 +913,10 @@ int pwrseq_power_on(struct pwrseq_desc *desc) if (target->post_enable) { ret = target->post_enable(pwrseq); if (ret) { - pwrseq_unit_disable(pwrseq, unit); - desc->powered_on = false; + scoped_guard(mutex, &pwrseq->state_lock) { + pwrseq_unit_disable(pwrseq, unit); + desc->powered_on = false; + } } } diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c new file mode 100644 index 000000000000..ef69ae268059 --- /dev/null +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * Author: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com> + */ + +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/of_platform.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/pwrseq/provider.h> +#include <linux/regulator/consumer.h> +#include <linux/serdev.h> +#include <linux/slab.h> + +struct pwrseq_pcie_m2_pdata { + const struct pwrseq_target_data **targets; +}; + +struct pwrseq_pcie_m2_ctx { + struct pwrseq_device *pwrseq; + struct device_node *of_node; + const struct pwrseq_pcie_m2_pdata *pdata; + struct regulator_bulk_data *regs; + size_t num_vregs; + struct notifier_block nb; + struct gpio_desc *w_disable1_gpio; + struct gpio_desc *w_disable2_gpio; + struct serdev_device *serdev; + struct of_changeset *ocs; + struct device *dev; +}; + +static int pwrseq_pcie_m2_vregs_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_bulk_enable(ctx->num_vregs, ctx->regs); +} + +static int pwrseq_pcie_m2_vregs_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_bulk_disable(ctx->num_vregs, ctx->regs); +} + +static const struct pwrseq_unit_data pwrseq_pcie_m2_vregs_unit_data = { + .name = "regulators-enable", + .enable = pwrseq_pcie_m2_vregs_enable, + .disable = pwrseq_pcie_m2_vregs_disable, +}; + +static const struct pwrseq_unit_data *pwrseq_pcie_m2_unit_deps[] = { + &pwrseq_pcie_m2_vregs_unit_data, + NULL +}; + +static int pwrseq_pci_m2_e_uart_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 0); +} + +static int pwrseq_pci_m2_e_uart_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 1); +} + +static const struct pwrseq_unit_data pwrseq_pcie_m2_e_uart_unit_data = { + .name = "uart-enable", + .deps = pwrseq_pcie_m2_unit_deps, + .enable = pwrseq_pci_m2_e_uart_enable, + .disable = pwrseq_pci_m2_e_uart_disable, +}; + +static int pwrseq_pci_m2_e_pcie_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 0); +} + +static int pwrseq_pci_m2_e_pcie_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 1); +} + +static const struct pwrseq_unit_data pwrseq_pcie_m2_e_pcie_unit_data = { + .name = "pcie-enable", + .deps = pwrseq_pcie_m2_unit_deps, + .enable = pwrseq_pci_m2_e_pcie_enable, + .disable = pwrseq_pci_m2_e_pcie_disable, +}; + +static const struct pwrseq_unit_data pwrseq_pcie_m2_m_pcie_unit_data = { + .name = "pcie-enable", + .deps = pwrseq_pcie_m2_unit_deps, +}; + +static int pwrseq_pcie_m2_e_pwup_delay(struct pwrseq_device *pwrseq) +{ + /* + * FIXME: This delay is only required for some Qcom WLAN/BT cards like + * WCN7850 and not for all devices. But currently, there is no way to + * identify the device model before enumeration. + */ + msleep(50); + + return 0; +} + +static const struct pwrseq_target_data pwrseq_pcie_m2_e_uart_target_data = { + .name = "uart", + .unit = &pwrseq_pcie_m2_e_uart_unit_data, + .post_enable = pwrseq_pcie_m2_e_pwup_delay, +}; + +static const struct pwrseq_target_data pwrseq_pcie_m2_e_pcie_target_data = { + .name = "pcie", + .unit = &pwrseq_pcie_m2_e_pcie_unit_data, + .post_enable = pwrseq_pcie_m2_e_pwup_delay, +}; + +static const struct pwrseq_target_data pwrseq_pcie_m2_m_pcie_target_data = { + .name = "pcie", + .unit = &pwrseq_pcie_m2_m_pcie_unit_data, +}; + +static const struct pwrseq_target_data *pwrseq_pcie_m2_e_targets[] = { + &pwrseq_pcie_m2_e_pcie_target_data, + &pwrseq_pcie_m2_e_uart_target_data, + NULL +}; + +static const struct pwrseq_target_data *pwrseq_pcie_m2_m_targets[] = { + &pwrseq_pcie_m2_m_pcie_target_data, + NULL +}; + +static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_e_of_data = { + .targets = pwrseq_pcie_m2_e_targets, +}; + +static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_m_of_data = { + .targets = pwrseq_pcie_m2_m_targets, +}; + +static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq, + struct device *dev) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + struct device_node *endpoint __free(device_node) = NULL; + + /* + * Traverse the 'remote-endpoint' nodes and check if the remote node's + * parent matches the OF node of 'dev'. + */ + for_each_endpoint_of_node(ctx->of_node, endpoint) { + struct device_node *remote __free(device_node) = + of_graph_get_remote_port_parent(endpoint); + if (remote && (remote == dev_of_node(dev))) + return PWRSEQ_MATCH_OK; + } + + return PWRSEQ_NO_MATCH; +} + +static int pwrseq_m2_pcie_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, + struct device_node *parent) +{ + struct device *dev = ctx->dev; + struct device_node *np; + int ret; + + ctx->ocs = kzalloc_obj(*ctx->ocs); + if (!ctx->ocs) + return -ENOMEM; + + of_changeset_init(ctx->ocs); + + np = of_changeset_create_node(ctx->ocs, parent, "bluetooth"); + if (!np) { + dev_err(dev, "Failed to create bluetooth node\n"); + ret = -ENODEV; + goto err_destroy_changeset; + } + + ret = of_changeset_add_prop_string(ctx->ocs, np, "compatible", "qcom,wcn7850-bt"); + if (ret) { + dev_err(dev, "Failed to add bluetooth compatible: %d\n", ret); + goto err_destroy_changeset; + } + + ret = of_changeset_apply(ctx->ocs); + if (ret) { + dev_err(dev, "Failed to apply changeset: %d\n", ret); + goto err_destroy_changeset; + } + + ret = device_add_of_node(&ctx->serdev->dev, np); + if (ret) { + dev_err(dev, "Failed to add OF node: %d\n", ret); + goto err_revert_changeset; + } + + return 0; + +err_revert_changeset: + of_changeset_revert(ctx->ocs); +err_destroy_changeset: + of_changeset_destroy(ctx->ocs); + kfree(ctx->ocs); + ctx->ocs = NULL; + + return ret; +} + +static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) +{ + struct serdev_controller *serdev_ctrl; + struct device *dev = ctx->dev; + int ret; + + struct device_node *serdev_parent __free(device_node) = + of_graph_get_remote_node(dev_of_node(ctx->dev), 3, 0); + if (!serdev_parent) + return 0; + + serdev_ctrl = of_find_serdev_controller_by_node(serdev_parent); + if (!serdev_ctrl) + return 0; + + /* Bail out if the device was already attached to this controller */ + if (serdev_ctrl->serdev) { + serdev_controller_put(serdev_ctrl); + return 0; + } + + ctx->serdev = serdev_device_alloc(serdev_ctrl); + if (!ctx->serdev) { + ret = -ENOMEM; + goto err_put_ctrl; + } + + ret = pwrseq_m2_pcie_create_bt_node(ctx, serdev_parent); + if (ret) + goto err_free_serdev; + + ret = serdev_device_add(ctx->serdev); + if (ret) { + dev_err(dev, "Failed to add serdev for WCN7850: %d\n", ret); + goto err_free_dt_node; + } + + serdev_controller_put(serdev_ctrl); + + return 0; + +err_free_dt_node: + device_remove_of_node(&ctx->serdev->dev); + of_changeset_revert(ctx->ocs); + of_changeset_destroy(ctx->ocs); + kfree(ctx->ocs); + ctx->ocs = NULL; +err_free_serdev: + serdev_device_put(ctx->serdev); + ctx->serdev = NULL; +err_put_ctrl: + serdev_controller_put(serdev_ctrl); + + return ret; +} + +static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx) +{ + if (ctx->serdev) { + device_remove_of_node(&ctx->serdev->dev); + serdev_device_remove(ctx->serdev); + ctx->serdev = NULL; + } + + if (ctx->ocs) { + of_changeset_revert(ctx->ocs); + of_changeset_destroy(ctx->ocs); + kfree(ctx->ocs); + ctx->ocs = NULL; + } +} + +static int pwrseq_m2_pcie_notify(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct pwrseq_pcie_m2_ctx *ctx = container_of(nb, struct pwrseq_pcie_m2_ctx, nb); + struct pci_dev *pdev = to_pci_dev(data); + int ret; + + /* + * Check whether the PCI device is associated with this M.2 connector or + * not, by comparing the OF node of the PCI device parent and the Port 0 + * (PCIe) remote node parent OF node. + */ + struct device_node *pci_parent __free(device_node) = + of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0); + if (!pci_parent || (pci_parent != pdev->dev.parent->of_node)) + return NOTIFY_DONE; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + /* Create serdev device for WCN7850 */ + if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107) { + ret = pwrseq_pcie_m2_create_serdev(ctx); + if (ret) + return notifier_from_errno(ret); + } + break; + case BUS_NOTIFY_REMOVED_DEVICE: + /* Destroy serdev device for WCN7850 */ + if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107) + pwrseq_pcie_m2_remove_serdev(ctx); + + break; + } + + return NOTIFY_OK; +} + +static bool pwrseq_pcie_m2_check_remote_node(struct device *dev, u8 port, u8 endpoint, + const char *node) +{ + struct device_node *remote __free(device_node) = + of_graph_get_remote_node(dev_of_node(dev), port, endpoint); + + if (remote && of_node_name_eq(remote, node)) + return true; + + return false; +} + +/* + * If the connector exposes a non-discoverable bus like UART, the respective + * protocol device needs to be created manually with the help of the notifier + * of the discoverable bus like PCIe. + */ +static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx, struct device *dev) +{ + int ret; + + /* + * Register a PCI notifier for Key E connector that has PCIe as Port + * 0/Endpoint 0 interface and Serial as Port 3/Endpoint 0 interface. + */ + if (pwrseq_pcie_m2_check_remote_node(dev, 3, 0, "serial")) { + if (pwrseq_pcie_m2_check_remote_node(dev, 0, 0, "pcie")) { + ctx->dev = dev; + ctx->nb.notifier_call = pwrseq_m2_pcie_notify; + ret = bus_register_notifier(&pci_bus_type, &ctx->nb); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register notifier for serdev\n"); + } + } + + return 0; +} + +static int pwrseq_pcie_m2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwrseq_pcie_m2_ctx *ctx; + struct pwrseq_config config = {}; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + platform_set_drvdata(pdev, ctx); + ctx->of_node = dev_of_node(dev); + ctx->pdata = device_get_match_data(dev); + if (!ctx->pdata) + return dev_err_probe(dev, -ENODEV, + "Failed to obtain platform data\n"); + + /* + * Currently, of_regulator_bulk_get_all() is the only regulator API that + * allows to get all supplies in the devicetree node without manually + * specifying them. + */ + ret = of_regulator_bulk_get_all(dev, dev_of_node(dev), &ctx->regs); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get all regulators\n"); + + ctx->num_vregs = ret; + + ctx->w_disable1_gpio = devm_gpiod_get_optional(dev, "w-disable1", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->w_disable1_gpio)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable1_gpio), + "Failed to get the W_DISABLE_1# GPIO\n"); + goto err_free_regulators; + } + + ctx->w_disable2_gpio = devm_gpiod_get_optional(dev, "w-disable2", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->w_disable2_gpio)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable2_gpio), + "Failed to get the W_DISABLE_2# GPIO\n"); + goto err_free_regulators; + } + + config.parent = dev; + config.owner = THIS_MODULE; + config.drvdata = ctx; + config.match = pwrseq_pcie_m2_match; + config.targets = ctx->pdata->targets; + + ctx->pwrseq = devm_pwrseq_device_register(dev, &config); + if (IS_ERR(ctx->pwrseq)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->pwrseq), + "Failed to register the power sequencer\n"); + goto err_free_regulators; + } + + /* + * Register a notifier for creating protocol devices for + * non-discoverable busses like UART. + */ + ret = pwrseq_pcie_m2_register_notifier(ctx, dev); + if (ret) + goto err_free_regulators; + + return 0; + +err_free_regulators: + regulator_bulk_free(ctx->num_vregs, ctx->regs); + + return ret; +} + +static void pwrseq_pcie_m2_remove(struct platform_device *pdev) +{ + struct pwrseq_pcie_m2_ctx *ctx = platform_get_drvdata(pdev); + + bus_unregister_notifier(&pci_bus_type, &ctx->nb); + pwrseq_pcie_m2_remove_serdev(ctx); + + regulator_bulk_free(ctx->num_vregs, ctx->regs); +} + +static const struct of_device_id pwrseq_pcie_m2_of_match[] = { + { + .compatible = "pcie-m2-m-connector", + .data = &pwrseq_pcie_m2_m_of_data, + }, + { + .compatible = "pcie-m2-e-connector", + .data = &pwrseq_pcie_m2_e_of_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, pwrseq_pcie_m2_of_match); + +static struct platform_driver pwrseq_pcie_m2_driver = { + .driver = { + .name = "pwrseq-pcie-m2", + .of_match_table = pwrseq_pcie_m2_of_match, + }, + .probe = pwrseq_pcie_m2_probe, + .remove = pwrseq_pcie_m2_remove, +}; +module_platform_driver(pwrseq_pcie_m2_driver); + +MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>"); +MODULE_DESCRIPTION("Power Sequencing driver for PCIe M.2 connector"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c index e8f5030f2639..b55b4317e21b 100644 --- a/drivers/power/sequencing/pwrseq-qcom-wcn.c +++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c @@ -12,6 +12,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/regulator/consumer.h> #include <linux/pwrseq/provider.h> #include <linux/string.h> @@ -23,6 +24,8 @@ struct pwrseq_qcom_wcn_pdata { unsigned int pwup_delay_ms; unsigned int gpio_enable_delay_ms; const struct pwrseq_target_data **targets; + bool has_vddio; /* separate VDD IO regulator */ + int (*match)(struct pwrseq_device *pwrseq, struct device *dev); }; struct pwrseq_qcom_wcn_ctx { @@ -30,6 +33,7 @@ struct pwrseq_qcom_wcn_ctx { struct device_node *of_node; const struct pwrseq_qcom_wcn_pdata *pdata; struct regulator_bulk_data *regs; + struct regulator *vddio; struct gpio_desc *bt_gpio; struct gpio_desc *wlan_gpio; struct gpio_desc *xo_clk_gpio; @@ -52,6 +56,26 @@ static void pwrseq_qcom_wcn_ensure_gpio_delay(struct pwrseq_qcom_wcn_ctx *ctx) msleep(ctx->pdata->gpio_enable_delay_ms - diff_msecs); } +static int pwrseq_qcom_wcn_vddio_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_enable(ctx->vddio); +} + +static int pwrseq_qcom_wcn_vddio_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_disable(ctx->vddio); +} + +static const struct pwrseq_unit_data pwrseq_qcom_wcn_vddio_unit_data = { + .name = "vddio-enable", + .enable = pwrseq_qcom_wcn_vddio_enable, + .disable = pwrseq_qcom_wcn_vddio_disable, +}; + static int pwrseq_qcom_wcn_vregs_enable(struct pwrseq_device *pwrseq) { struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); @@ -94,6 +118,19 @@ static const struct pwrseq_unit_data pwrseq_qcom_wcn_clk_unit_data = { .disable = pwrseq_qcom_wcn_clk_disable, }; +static const struct pwrseq_unit_data *pwrseq_qcom_wcn3990_unit_deps[] = { + &pwrseq_qcom_wcn_vddio_unit_data, + &pwrseq_qcom_wcn_vregs_unit_data, + NULL, +}; + +static const struct pwrseq_unit_data pwrseq_qcom_wcn3990_unit_data = { + .name = "clock-enable", + .deps = pwrseq_qcom_wcn3990_unit_deps, + .enable = pwrseq_qcom_wcn_clk_enable, + .disable = pwrseq_qcom_wcn_clk_disable, +}; + static const struct pwrseq_unit_data *pwrseq_qcom_wcn_unit_deps[] = { &pwrseq_qcom_wcn_vregs_unit_data, &pwrseq_qcom_wcn_clk_unit_data, @@ -155,7 +192,7 @@ static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = { }; static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_bt_unit_data = { - .name = "wlan-enable", + .name = "bluetooth-enable", .deps = pwrseq_qcom_wcn6855_unit_deps, .enable = pwrseq_qcom_wcn_bt_enable, .disable = pwrseq_qcom_wcn_bt_disable, @@ -229,6 +266,17 @@ static const struct pwrseq_target_data pwrseq_qcom_wcn_wlan_target_data = { .post_enable = pwrseq_qcom_wcn_pwup_delay, }; +/* There are no separate BT and WLAN enablement pins */ +static const struct pwrseq_target_data pwrseq_qcom_wcn3990_bt_target_data = { + .name = "bluetooth", + .unit = &pwrseq_qcom_wcn3990_unit_data, +}; + +static const struct pwrseq_target_data pwrseq_qcom_wcn3990_wlan_target_data = { + .name = "wlan", + .unit = &pwrseq_qcom_wcn3990_unit_data, +}; + static const struct pwrseq_target_data pwrseq_qcom_wcn6855_bt_target_data = { .name = "bluetooth", .unit = &pwrseq_qcom_wcn6855_bt_unit_data, @@ -247,6 +295,12 @@ static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = { NULL }; +static const struct pwrseq_target_data *pwrseq_qcom_wcn3990_targets[] = { + &pwrseq_qcom_wcn3990_bt_target_data, + &pwrseq_qcom_wcn3990_wlan_target_data, + NULL +}; + static const struct pwrseq_target_data *pwrseq_qcom_wcn6855_targets[] = { &pwrseq_qcom_wcn6855_bt_target_data, &pwrseq_qcom_wcn6855_wlan_target_data, @@ -272,6 +326,26 @@ static const struct pwrseq_qcom_wcn_pdata pwrseq_qca6390_of_data = { .targets = pwrseq_qcom_wcn_targets, }; +static const char *const pwrseq_wcn3990_vregs[] = { + /* vddio is handled separately */ + "vddxo", + "vddrf", + "vddch0", + "vddch1", +}; + +static int pwrseq_qcom_wcn3990_match(struct pwrseq_device *pwrseq, + struct device *dev); + +static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn3990_of_data = { + .vregs = pwrseq_wcn3990_vregs, + .num_vregs = ARRAY_SIZE(pwrseq_wcn3990_vregs), + .pwup_delay_ms = 50, + .targets = pwrseq_qcom_wcn3990_targets, + .has_vddio = true, + .match = pwrseq_qcom_wcn3990_match, +}; + static const char *const pwrseq_wcn6750_vregs[] = { "vddaon", "vddasd", @@ -328,8 +402,9 @@ static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn7850_of_data = { .targets = pwrseq_qcom_wcn_targets, }; -static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq, - struct device *dev) +static int pwrseq_qcom_wcn_match_regulator(struct pwrseq_device *pwrseq, + struct device *dev, + const char *name) { struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); struct device_node *dev_node = dev->of_node; @@ -340,13 +415,13 @@ static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq, * 'vddaon-supply' property and whether it leads us to the right * device. */ - if (!of_property_present(dev_node, "vddaon-supply")) - return 0; + if (!of_property_present(dev_node, name)) + return PWRSEQ_NO_MATCH; struct device_node *reg_node __free(device_node) = - of_parse_phandle(dev_node, "vddaon-supply", 0); + of_parse_phandle(dev_node, name, 0); if (!reg_node) - return 0; + return PWRSEQ_NO_MATCH; /* * `reg_node` is the PMU AON regulator, its parent is the `regulators` @@ -355,9 +430,29 @@ static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq, */ if (!reg_node->parent || !reg_node->parent->parent || reg_node->parent->parent != ctx->of_node) - return 0; + return PWRSEQ_NO_MATCH; + + return PWRSEQ_MATCH_OK; +} + +static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq, + struct device *dev) +{ + return pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vddaon-supply"); +} + +static int pwrseq_qcom_wcn3990_match(struct pwrseq_device *pwrseq, + struct device *dev) +{ + int ret; + + /* BT device */ + ret = pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vddio-supply"); + if (ret == PWRSEQ_MATCH_OK) + return ret; - return 1; + /* WiFi device match */ + return pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vdd-1.8-xo-supply"); } static int pwrseq_qcom_wcn_probe(struct platform_device *pdev) @@ -373,7 +468,7 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev) ctx->of_node = dev->of_node; - ctx->pdata = of_device_get_match_data(dev); + ctx->pdata = device_get_match_data(dev); if (!ctx->pdata) return dev_err_probe(dev, -ENODEV, "Failed to obtain platform data\n"); @@ -391,6 +486,12 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "Failed to get all regulators\n"); + if (ctx->pdata->has_vddio) { + ctx->vddio = devm_regulator_get(dev, "vddio"); + if (IS_ERR(ctx->vddio)) + return dev_err_probe(dev, PTR_ERR(ctx->vddio), "Failed to get VDDIO\n"); + } + ctx->bt_gpio = devm_gpiod_get_optional(dev, "bt-enable", GPIOD_OUT_LOW); if (IS_ERR(ctx->bt_gpio)) return dev_err_probe(dev, PTR_ERR(ctx->bt_gpio), @@ -432,7 +533,7 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev) config.parent = dev; config.owner = THIS_MODULE; config.drvdata = ctx; - config.match = pwrseq_qcom_wcn_match; + config.match = ctx->pdata->match ? : pwrseq_qcom_wcn_match; config.targets = ctx->pdata->targets; ctx->pwrseq = devm_pwrseq_device_register(dev, &config); @@ -445,6 +546,26 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev) static const struct of_device_id pwrseq_qcom_wcn_of_match[] = { { + .compatible = "qcom,wcn3950-pmu", + .data = &pwrseq_wcn3990_of_data, + }, + { + .compatible = "qcom,wcn3988-pmu", + .data = &pwrseq_wcn3990_of_data, + }, + { + .compatible = "qcom,wcn3990-pmu", + .data = &pwrseq_wcn3990_of_data, + }, + { + .compatible = "qcom,wcn3991-pmu", + .data = &pwrseq_wcn3990_of_data, + }, + { + .compatible = "qcom,wcn3998-pmu", + .data = &pwrseq_wcn3990_of_data, + }, + { .compatible = "qcom,qca6390-pmu", .data = &pwrseq_qca6390_of_data, }, diff --git a/drivers/power/sequencing/pwrseq-thead-gpu.c b/drivers/power/sequencing/pwrseq-thead-gpu.c new file mode 100644 index 000000000000..a45318b4b2c1 --- /dev/null +++ b/drivers/power/sequencing/pwrseq-thead-gpu.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * T-HEAD TH1520 GPU Power Sequencer Driver + * + * Copyright (c) 2025 Samsung Electronics Co., Ltd. + * Author: Michal Wilczynski <m.wilczynski@samsung.com> + * + * This driver implements the power sequence for the Imagination BXM-4-64 + * GPU on the T-HEAD TH1520 SoC. The sequence requires coordinating resources + * from both the sequencer's parent device node (clkgen_reset) and the GPU's + * device node (clocks and core reset). + * + * The `match` function is used to acquire the GPU's resources when the + * GPU driver requests the "gpu-power" sequence target. + */ + +#include <linux/auxiliary_bus.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pwrseq/provider.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#include <dt-bindings/power/thead,th1520-power.h> + +struct pwrseq_thead_gpu_ctx { + struct pwrseq_device *pwrseq; + struct reset_control *clkgen_reset; + struct device_node *aon_node; + + /* Consumer resources */ + struct device_node *consumer_node; + struct clk_bulk_data *clks; + int num_clks; + struct reset_control *gpu_reset; +}; + +static int pwrseq_thead_gpu_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + int ret; + + if (!ctx->clks || !ctx->gpu_reset) + return -ENODEV; + + ret = clk_bulk_prepare_enable(ctx->num_clks, ctx->clks); + if (ret) + return ret; + + ret = reset_control_deassert(ctx->clkgen_reset); + if (ret) + goto err_disable_clks; + + /* + * According to the hardware manual, a delay of at least 32 clock + * cycles is required between de-asserting the clkgen reset and + * de-asserting the GPU reset. Assuming a worst-case scenario with + * a very high GPU clock frequency, a delay of 1 microsecond is + * sufficient to ensure this requirement is met across all + * feasible GPU clock speeds. + */ + udelay(1); + + ret = reset_control_deassert(ctx->gpu_reset); + if (ret) + goto err_assert_clkgen; + + return 0; + +err_assert_clkgen: + reset_control_assert(ctx->clkgen_reset); +err_disable_clks: + clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks); + return ret; +} + +static int pwrseq_thead_gpu_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + int ret = 0, err; + + if (!ctx->clks || !ctx->gpu_reset) + return -ENODEV; + + err = reset_control_assert(ctx->gpu_reset); + if (err) + ret = err; + + err = reset_control_assert(ctx->clkgen_reset); + if (err && !ret) + ret = err; + + clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks); + + /* ret stores values of the first error code */ + return ret; +} + +static const struct pwrseq_unit_data pwrseq_thead_gpu_unit = { + .name = "gpu-power-sequence", + .enable = pwrseq_thead_gpu_enable, + .disable = pwrseq_thead_gpu_disable, +}; + +static const struct pwrseq_target_data pwrseq_thead_gpu_target = { + .name = "gpu-power", + .unit = &pwrseq_thead_gpu_unit, +}; + +static const struct pwrseq_target_data *pwrseq_thead_gpu_targets[] = { + &pwrseq_thead_gpu_target, + NULL +}; + +static int pwrseq_thead_gpu_match(struct pwrseq_device *pwrseq, + struct device *dev) +{ + struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + static const char *const clk_names[] = { "core", "sys" }; + struct of_phandle_args pwr_spec; + int i, ret; + + /* We only match the specific T-HEAD TH1520 GPU compatible */ + if (!of_device_is_compatible(dev->of_node, "thead,th1520-gpu")) + return PWRSEQ_NO_MATCH; + + ret = of_parse_phandle_with_args(dev->of_node, "power-domains", + "#power-domain-cells", 0, &pwr_spec); + if (ret) + return PWRSEQ_NO_MATCH; + + /* Additionally verify consumer device has AON as power-domain */ + if (pwr_spec.np != ctx->aon_node || pwr_spec.args[0] != TH1520_GPU_PD) { + of_node_put(pwr_spec.np); + return PWRSEQ_NO_MATCH; + } + + of_node_put(pwr_spec.np); + + /* If a consumer is already bound, only allow a re-match from it */ + if (ctx->consumer_node) + return ctx->consumer_node == dev->of_node ? + PWRSEQ_MATCH_OK : PWRSEQ_NO_MATCH; + + ctx->num_clks = ARRAY_SIZE(clk_names); + ctx->clks = kzalloc_objs(*ctx->clks, ctx->num_clks); + if (!ctx->clks) + return -ENOMEM; + + for (i = 0; i < ctx->num_clks; i++) + ctx->clks[i].id = clk_names[i]; + + ret = clk_bulk_get(dev, ctx->num_clks, ctx->clks); + if (ret) + goto err_free_clks; + + ctx->gpu_reset = reset_control_get_shared(dev, NULL); + if (IS_ERR(ctx->gpu_reset)) { + ret = PTR_ERR(ctx->gpu_reset); + goto err_put_clks; + } + + ctx->consumer_node = of_node_get(dev->of_node); + + return PWRSEQ_MATCH_OK; + +err_put_clks: + clk_bulk_put(ctx->num_clks, ctx->clks); +err_free_clks: + kfree(ctx->clks); + ctx->clks = NULL; + + return ret; +} + +static int pwrseq_thead_gpu_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct device *dev = &adev->dev; + struct device *parent_dev = dev->parent; + struct pwrseq_thead_gpu_ctx *ctx; + struct pwrseq_config config = {}; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->aon_node = parent_dev->of_node; + + ctx->clkgen_reset = + devm_reset_control_get_exclusive(parent_dev, "gpu-clkgen"); + if (IS_ERR(ctx->clkgen_reset)) + return dev_err_probe( + dev, PTR_ERR(ctx->clkgen_reset), + "Failed to get GPU clkgen reset from parent\n"); + + config.parent = dev; + config.owner = THIS_MODULE; + config.drvdata = ctx; + config.match = pwrseq_thead_gpu_match; + config.targets = pwrseq_thead_gpu_targets; + + ctx->pwrseq = devm_pwrseq_device_register(dev, &config); + if (IS_ERR(ctx->pwrseq)) + return dev_err_probe(dev, PTR_ERR(ctx->pwrseq), + "Failed to register power sequencer\n"); + + auxiliary_set_drvdata(adev, ctx); + + return 0; +} + +static void pwrseq_thead_gpu_remove(struct auxiliary_device *adev) +{ + struct pwrseq_thead_gpu_ctx *ctx = auxiliary_get_drvdata(adev); + + if (ctx->gpu_reset) + reset_control_put(ctx->gpu_reset); + + if (ctx->clks) { + clk_bulk_put(ctx->num_clks, ctx->clks); + kfree(ctx->clks); + } + + if (ctx->consumer_node) + of_node_put(ctx->consumer_node); +} + +static const struct auxiliary_device_id pwrseq_thead_gpu_id_table[] = { + { .name = "th1520_pm_domains.pwrseq-gpu" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, pwrseq_thead_gpu_id_table); + +static struct auxiliary_driver pwrseq_thead_gpu_driver = { + .driver = { + .name = "pwrseq-thead-gpu", + }, + .probe = pwrseq_thead_gpu_probe, + .remove = pwrseq_thead_gpu_remove, + .id_table = pwrseq_thead_gpu_id_table, +}; +module_auxiliary_driver(pwrseq_thead_gpu_driver); + +MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>"); +MODULE_DESCRIPTION("T-HEAD TH1520 GPU power sequencer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/88pm860x_charger.c b/drivers/power/supply/88pm860x_charger.c index 2b9fcb7e71d7..8d99c6ff72ed 100644 --- a/drivers/power/supply/88pm860x_charger.c +++ b/drivers/power/supply/88pm860x_charger.c @@ -284,8 +284,8 @@ static int set_charging_fsm(struct pm860x_charger_info *info) { struct power_supply *psy; union power_supply_propval data; - unsigned char fsm_state[][16] = { "init", "discharge", "precharge", - "fastcharge", + static const unsigned char fsm_state[][16] = { + "init", "discharge", "precharge", "fastcharge", }; int ret; int vbatt; @@ -313,7 +313,7 @@ static int set_charging_fsm(struct pm860x_charger_info *info) dev_dbg(info->dev, "Entering FSM:%s, Charger:%s, Battery:%s, " "Allowed:%d\n", - &fsm_state[info->state][0], + fsm_state[info->state], (info->online) ? "online" : "N/A", (info->present) ? "present" : "N/A", info->allowed); dev_dbg(info->dev, "set_charging_fsm:vbatt:%d(mV)\n", vbatt); @@ -385,7 +385,7 @@ static int set_charging_fsm(struct pm860x_charger_info *info) } dev_dbg(info->dev, "Out FSM:%s, Charger:%s, Battery:%s, Allowed:%d\n", - &fsm_state[info->state][0], + fsm_state[info->state], (info->online) ? "online" : "N/A", (info->present) ? "present" : "N/A", info->allowed); mutex_unlock(&info->lock); diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 7b18358f194a..83392ed6a8da 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -35,6 +35,9 @@ config APM_POWER Say Y here to enable support APM status emulation using battery class devices. +config ADC_BATTERY_HELPER + tristate + config GENERIC_ADC_BATTERY tristate "Generic battery support using IIO" depends on IIO @@ -107,6 +110,18 @@ config BATTERY_ACT8945A Say Y here to enable support for power supply provided by Active-semi ActivePath ACT8945A charger. +config BATTERY_CHAGALL + tristate "Pegatron Chagall battery driver" + depends on I2C + depends on LEDS_CLASS + help + Say Y to include support for Cypress CG7153AM IC based battery + fuel gauge with custom firmware found in Pegatron Chagall based + tablet line. + + This driver can also be built as a module. If so, the module will be + called chagall-battery. + config BATTERY_CPCAP tristate "Motorola CPCAP PMIC battery driver" depends on MFD_CPCAP && IIO @@ -161,6 +176,16 @@ config BATTERY_DS2782 Say Y here to enable support for the DS2782/DS2786 standalone battery gas-gauge. +config BATTERY_HUAWEI_GAOKUN + tristate "Huawei Matebook E Go power supply" + depends on EC_HUAWEI_GAOKUN + help + This driver enables battery and adapter support on the Huawei Matebook + E Go, which is a sc8280xp-based 2-in-1 tablet. + + To compile the driver as a module, choose M here: the module will be + called huawei-gaokun-battery. + config BATTERY_LEGO_EV3 tristate "LEGO MINDSTORMS EV3 battery" depends on OF && IIO && GPIOLIB && (ARCH_DAVINCI_DA850 || COMPILE_TEST) @@ -204,6 +229,17 @@ config BATTERY_SAMSUNG_SDI Say Y to enable support for Samsung SDI battery data. These batteries are used in Samsung mobile phones. +config BATTERY_S2MU005 + tristate "Samsung S2MU005 PMIC fuel gauge driver" + depends on I2C + select REGMAP_I2C + help + Say Y to enable support for the Samsung S2MU005 PMIC integrated + fuel gauge, which works indepenently of the PMIC battery charger + counterpart, and reports battery metrics. + + This driver, if built as a module, will be called s2mu005-fuel-gauge. + config BATTERY_COLLIE tristate "Sharp SL-5500 (collie) battery" depends on SA1100_COLLIE && MCP_UCB1200 @@ -222,6 +258,18 @@ config BATTERY_INGENIC This driver can also be built as a module. If so, the module will be called ingenic-battery. +config BATTERY_INTEL_DC_TI + tristate "Intel Bay / Cherry Trail Dollar Cove TI battery driver" + depends on INTEL_SOC_PMIC_CHTDC_TI && INTEL_DC_TI_ADC && IIO && ACPI + select ADC_BATTERY_HELPER + help + Choose this option if you want to monitor battery status on Intel + Bay Trail / Cherry Trail tablets using the Dollar Cove TI PMIC's + coulomb-counter as fuel-gauge. + + To compile this driver as a module, choose M here: the module will be + called intel_dc_ti_battery. + config BATTERY_IPAQ_MICRO tristate "iPAQ Atmel Micro ASIC battery driver" depends on MFD_IPAQ_MICRO @@ -449,11 +497,16 @@ config CHARGER_88PM860X help Say Y here to enable charger for Marvell 88PM860x chip. -config CHARGER_PCF50633 - tristate "NXP PCF50633 MBC" - depends on MFD_PCF50633 +config CHARGER_PF1550 + tristate "NXP PF1550 battery charger driver" + depends on MFD_PF1550 help - Say Y to include support for NXP PCF50633 Main Battery Charger. + Say Y to enable support for the NXP PF1550 battery charger. + The device is a single cell Li-Ion/Li-Polymer battery charger for + portable application. + + This driver can also be built as a module. If so, the module will be + called pf1550-charger. config BATTERY_RX51 tristate "Nokia RX-51 (N900) battery driver" @@ -583,6 +636,23 @@ config CHARGER_MAX77693 help Say Y to enable support for the Maxim MAX77693 battery charger. +config CHARGER_MAX77705 + tristate "Maxim MAX77705 battery charger driver" + depends on MFD_MAX77705 + help + Say Y to enable support for the Maxim MAX77705 battery charger. + +config CHARGER_MAX77759 + tristate "Maxim MAX77759 battery charger driver" + depends on MFD_MAX77759 && REGULATOR + default MFD_MAX77759 + help + Say M or Y here to enable the MAX77759 battery charger. MAX77759 + charger is a function of the MAX77759 PMIC. This is a dual input + switch-mode charger. This driver supports buck and OTG boost modes. + + If built as a module, it will be called max77759_charger. + config CHARGER_MAX77976 tristate "Maxim MAX77976 battery charger driver" depends on I2C @@ -595,6 +665,21 @@ config CHARGER_MAX77976 This driver can also be built as a module. If so, the module will be called max77976_charger. +config CHARGER_MAX8971 + tristate "Maxim MAX8971 battery charger driver" + depends on I2C + depends on EXTCON || !EXTCON + select REGMAP_I2C + help + The MAX8971 is a compact, high-frequency, high-efficiency switch-mode + charger for a one-cell lithium-ion (Li+) battery. It delivers up to + 1.55A of current to the battery from inputs up to 7.5V and withstands + transient inputs up to 22V. + + Say Y to enable support for the Maxim MAX8971 battery charger. + This driver can also be built as a module. If so, the module will be + called max8971_charger. + config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" depends on MFD_MAX8997 && REGULATOR_MAX8997 @@ -730,6 +815,13 @@ config CHARGER_BQ2515X rail, ADC for battery and system monitoring, and push-button controller. +config CHARGER_BQ257XX + tristate "TI BQ257XX battery charger family" + depends on MFD_BQ257XX + help + Say Y to enable support for the TI BQ257XX family of battery + charging integrated circuits. + config CHARGER_BQ25890 tristate "TI BQ25890 battery charger driver" depends on I2C @@ -872,6 +964,21 @@ config CHARGER_RT9471 This driver can also be built as a module. If so, the module will be called rt9471. +config CHARGER_RT9756 + tristate "Richtek RT9756 smart cap divider charger driver" + depends on I2C + select REGMAP_I2C + select LINEAR_RANGES + help + This adds support for Richtek RT9756 smart cap divider charger driver. + It's a high efficiency and high charge current charger. the device + integrates smart cap divider topology with 9-channel high speed + ADCs that can provide input and output voltage, current and + temperature monitoring. + + This driver can also be built as a module. If so, the module will be + called rt9756. + config CHARGER_CROS_USBPD tristate "ChromeOS EC based USBPD charger" depends on CROS_USBPD_NOTIFY @@ -937,6 +1044,15 @@ config CHARGER_UCS1002 Say Y to enable support for Microchip UCS1002 Programmable USB Port Power Controller with Charger Emulation. +config CHARGER_BD71828 + tristate "Power-supply driver for ROHM BD71828 and BD71815 PMIC" + depends on MFD_ROHM_BD71828 + help + Say Y here to enable support for charger and battery + in ROHM BD71815, BD71817, ROHM BD71828 power management + ICs. This driver gets various bits of information about battery + and charger states. + config CHARGER_BD99954 tristate "ROHM bd99954 charger driver" depends on I2C @@ -1006,6 +1122,7 @@ config CHARGER_SURFACE config BATTERY_UG3105 tristate "uPI uG3105 battery monitor driver" depends on I2C + select ADC_BATTERY_HELPER help Battery monitor driver for the uPI uG3105 battery monitor. @@ -1037,4 +1154,15 @@ config FUEL_GAUGE_MM8013 the state of charge, temperature, cycle count, actual and design capacity, etc. +config MACSMC_POWER + tristate "Apple SMC Battery and Power Driver" + depends on MFD_MACSMC + help + This driver provides support for the battery and AC adapter on + Apple Silicon machines. It exposes battery telemetry (voltage, + current, health) and AC adapter status through the standard Linux + power supply framework. + + Say Y or M here if you have an Apple Silicon based Mac. + endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index b55cc48a4c86..7ee839dca7f3 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -7,6 +7,7 @@ power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o obj-$(CONFIG_POWER_SUPPLY) += power_supply.o obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o +obj-$(CONFIG_ADC_BATTERY_HELPER) += adc-battery-helper.o obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o obj-$(CONFIG_APM_POWER) += apm_power.o @@ -23,6 +24,7 @@ obj-$(CONFIG_CHARGER_ADP5061) += adp5061.o 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_CHAGALL) += chagall-battery.o obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o obj-$(CONFIG_BATTERY_CW2015) += cw2015_battery.o obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o @@ -31,14 +33,17 @@ obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o +obj-$(CONFIG_BATTERY_HUAWEI_GAOKUN) += huawei-gaokun-battery.o obj-$(CONFIG_BATTERY_LEGO_EV3) += lego_ev3_battery.o obj-$(CONFIG_BATTERY_LENOVO_YOGA_C630) += lenovo_yoga_c630_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_QCOM_BATTMGR) += qcom_battmgr.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o obj-$(CONFIG_BATTERY_SAMSUNG_SDI) += samsung-sdi-battery.o +obj-$(CONFIG_BATTERY_S2MU005) += s2mu005-battery.o obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o obj-$(CONFIG_BATTERY_INGENIC) += ingenic-battery.o +obj-$(CONFIG_BATTERY_INTEL_DC_TI) += intel_dc_ti_battery.o obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o @@ -60,9 +65,10 @@ obj-$(CONFIG_CHARGER_RT5033) += rt5033_charger.o obj-$(CONFIG_CHARGER_RT9455) += rt9455_charger.o obj-$(CONFIG_CHARGER_RT9467) += rt9467-charger.o obj-$(CONFIG_CHARGER_RT9471) += rt9471.o +obj-$(CONFIG_CHARGER_RT9756) += rt9756.o obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o -obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o +obj-$(CONFIG_CHARGER_PF1550) += pf1550-charger.o obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o ab8500_chargalg.o obj-$(CONFIG_CHARGER_CPCAP) += cpcap-charger.o @@ -80,7 +86,9 @@ obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o +obj-$(CONFIG_CHARGER_MAX77705) += max77705_charger.o obj-$(CONFIG_CHARGER_MAX77976) += max77976_charger.o +obj-$(CONFIG_CHARGER_MAX8971) += max8971_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o obj-$(CONFIG_CHARGER_MP2629) += mp2629_charger.o @@ -94,6 +102,7 @@ obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o obj-$(CONFIG_CHARGER_BQ2515X) += bq2515x_charger.o +obj-$(CONFIG_CHARGER_BQ257XX) += bq257xx_charger.o obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o @@ -110,6 +119,7 @@ obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o obj-$(CONFIG_FUEL_GAUGE_STC3117) += stc3117_fuel_gauge.o obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o +obj-$(CONFIG_CHARGER_BD71828) += bd71828-power.o obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o @@ -117,5 +127,7 @@ obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o -obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_pmi8998_charger.o +obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_smbx.o obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o +obj-$(CONFIG_MACSMC_POWER) += macsmc-power.o +obj-$(CONFIG_CHARGER_MAX77759) += max77759_charger.o diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index b00c84fbc33c..e5202a7b6209 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -667,7 +667,8 @@ static int ab8500_btemp_bind(struct device *dev, struct device *master, /* Create a work queue for the btemp */ di->btemp_wq = - alloc_workqueue("ab8500_btemp_wq", WQ_MEM_RECLAIM, 0); + alloc_workqueue("ab8500_btemp_wq", WQ_MEM_RECLAIM | WQ_PERCPU, + 0); if (di->btemp_wq == NULL) { dev_err(dev, "failed to create work queue\n"); return -ENOMEM; diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index dc6c8b0dd1cf..d2b9880b0770 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -170,13 +170,13 @@ struct ab8500_chargalg_events { * @original_iset_ua: the non optimized/maximised charger current * @current_iset_ua: the charging current used at this moment * @condition_cnt: number of iterations needed before a new charger current - is set + * is set * @max_current_ua: maximum charger current * @wait_cnt: to avoid too fast current step down in case of charger * voltage collapse, we insert this delay between step * down * @level: tells in how many steps the charging current has been - increased + * increased */ struct ab8500_charge_curr_maximization { int original_iset_ua; @@ -199,18 +199,20 @@ enum maxim_ret { * @charge_status: battery operating status * @eoc_cnt: counter used to determine end-of_charge * @maintenance_chg: indicate if maintenance charge is active - * @t_hyst_norm temperature hysteresis when the temperature has been + * @t_hyst_norm: temperature hysteresis when the temperature has been * over or under normal limits - * @t_hyst_lowhigh temperature hysteresis when the temperature has been + * @t_hyst_lowhigh: temperature hysteresis when the temperature has been * over or under the high or low limits * @charge_state: current state of the charging algorithm - * @ccm charging current maximization parameters + * @ccm: charging current maximization parameters * @chg_info: information about connected charger types * @batt_data: data of the battery * @bm: Platform specific battery management information * @parent: pointer to the struct ab8500 * @chargalg_psy: structure that holds the battery properties exposed by * the charging algorithm + * @ac_chg: AC charger power supply + * @usb_chg: USB charger power supply * @events: structure for information about events triggered * @chargalg_wq: work queue for running the charging algorithm * @chargalg_periodic_work: work to run the charging algorithm periodically @@ -300,6 +302,7 @@ ab8500_chargalg_maintenance_timer_expired(struct hrtimer *timer) /** * ab8500_chargalg_state_to() - Change charge state * @di: pointer to the ab8500_chargalg structure + * @state: new charge algorithm state * * This function gets called when a charge state change should occur */ @@ -763,7 +766,7 @@ static void init_maxim_chg_curr(struct ab8500_chargalg *di) /** * ab8500_chargalg_chg_curr_maxim - increases the charger current to * compensate for the system load - * @di pointer to the ab8500_chargalg structure + * @di: pointer to the ab8500_chargalg structure * * This maximization function is used to raise the charger current to get the * battery current as close to the optimal value as possible. The battery diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 1042d37424f5..1813fbdfa1c1 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -3466,26 +3466,6 @@ static int ab8500_charger_probe(struct platform_device *pdev) return ret; } - /* Request interrupts */ - for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { - irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); - if (irq < 0) - return irq; - - ret = devm_request_threaded_irq(dev, - irq, NULL, ab8500_charger_irq[i].isr, - IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, - ab8500_charger_irq[i].name, di); - - if (ret != 0) { - dev_err(dev, "failed to request %s IRQ %d: %d\n" - , ab8500_charger_irq[i].name, irq, ret); - return ret; - } - dev_dbg(dev, "Requested %s IRQ %d: %d\n", - ab8500_charger_irq[i].name, irq, ret); - } - /* initialize lock */ spin_lock_init(&di->usb_state.usb_lock); mutex_init(&di->usb_ipt_crnt_lock); @@ -3494,11 +3474,11 @@ static int ab8500_charger_probe(struct platform_device *pdev) di->invalid_charger_detect_state = 0; /* AC and USB supply config */ - ac_psy_cfg.of_node = np; + ac_psy_cfg.fwnode = dev_fwnode(dev); ac_psy_cfg.supplied_to = supply_interface; ac_psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); ac_psy_cfg.drv_data = &di->ac_chg; - usb_psy_cfg.of_node = np; + usb_psy_cfg.fwnode = dev_fwnode(dev); usb_psy_cfg.supplied_to = supply_interface; usb_psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); usb_psy_cfg.drv_data = &di->usb_chg; @@ -3614,6 +3594,26 @@ static int ab8500_charger_probe(struct platform_device *pdev) return PTR_ERR(di->usb_chg.psy); } + /* Request interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, + irq, NULL, ab8500_charger_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, + ab8500_charger_irq[i].name, di); + + if (ret != 0) { + dev_err(dev, "failed to request %s IRQ %d: %d\n" + , ab8500_charger_irq[i].name, irq, ret); + return ret; + } + dev_dbg(dev, "Requested %s IRQ %d: %d\n", + ab8500_charger_irq[i].name, irq, ret); + } + /* * Check what battery we have, since we always have the USB * psy, use that as a handle. diff --git a/drivers/power/supply/acer_a500_battery.c b/drivers/power/supply/acer_a500_battery.c index 39d85b11a13c..daf01dc8025b 100644 --- a/drivers/power/supply/acer_a500_battery.c +++ b/drivers/power/supply/acer_a500_battery.c @@ -17,6 +17,7 @@ #include <linux/sched.h> #include <linux/slab.h> #include <linux/workqueue.h> +#include <linux/property.h> enum { REG_CAPACITY, @@ -231,7 +232,7 @@ static int a500_battery_probe(struct platform_device *pdev) platform_set_drvdata(pdev, bat); - psy_cfg.of_node = pdev->dev.parent->of_node; + psy_cfg.fwnode = dev_fwnode(pdev->dev.parent); psy_cfg.drv_data = bat; psy_cfg.no_wakeup_source = true; diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index b2b82f97a471..9dec4486b143 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -597,14 +597,6 @@ static int act8945a_charger_probe(struct platform_device *pdev) return irq ?: -ENXIO; } - ret = devm_request_irq(&pdev->dev, irq, act8945a_status_changed, - IRQF_TRIGGER_FALLING, "act8945a_interrupt", - charger); - if (ret) { - dev_err(&pdev->dev, "failed to request nIRQ pin IRQ\n"); - return ret; - } - charger->desc.name = "act8945a-charger"; charger->desc.get_property = act8945a_charger_get_property; charger->desc.properties = act8945a_charger_props; @@ -614,7 +606,7 @@ static int act8945a_charger_probe(struct platform_device *pdev) if (ret) return -EINVAL; - psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); psy_cfg.drv_data = charger; charger->psy = devm_power_supply_register(&pdev->dev, @@ -625,6 +617,14 @@ static int act8945a_charger_probe(struct platform_device *pdev) return PTR_ERR(charger->psy); } + ret = devm_request_irq(&pdev->dev, irq, act8945a_status_changed, + IRQF_TRIGGER_FALLING, "act8945a_interrupt", + charger); + if (ret) { + dev_err(&pdev->dev, "failed to request nIRQ pin IRQ\n"); + return ret; + } + platform_set_drvdata(pdev, charger); INIT_WORK(&charger->work, act8945a_work); diff --git a/drivers/power/supply/adc-battery-helper.c b/drivers/power/supply/adc-battery-helper.c new file mode 100644 index 000000000000..6e0f5b6d73d7 --- /dev/null +++ b/drivers/power/supply/adc-battery-helper.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Helper for batteries with accurate current and voltage measurement, but + * without temperature measurement or without a "resistance-temp-table". + * + * Some fuel-gauges are not full-featured autonomous fuel-gauges. + * These fuel-gauges offer accurate current and voltage measurements but + * their coulomb-counters are intended to work together with an always on + * micro-controller monitoring the fuel-gauge. + * + * This adc-battery-helper code offers open-circuit-voltage (ocv) and through + * that capacity estimation for devices where such limited functionality + * fuel-gauges are exposed directly to Linux. + * + * This helper requires the hw to provide accurate battery current_now and + * voltage_now measurement and this helper the provides the following properties + * based on top of those readings: + * + * POWER_SUPPLY_PROP_STATUS + * POWER_SUPPLY_PROP_VOLTAGE_OCV + * POWER_SUPPLY_PROP_VOLTAGE_NOW + * POWER_SUPPLY_PROP_CURRENT_NOW + * POWER_SUPPLY_PROP_CAPACITY + * + * As well as optional the following properties assuming an always present + * system-scope battery, allowing direct use of adc_battery_helper_get_prop() + * in this common case: + * POWER_SUPPLY_PROP_PRESENT + * POWER_SUPPLY_PROP_SCOPE + * + * Using this helper is as simple as: + * + * 1. Embed a struct adc_battery_helper this MUST be the first member of + * the battery driver's data struct. + * 2. Use adc_battery_helper_props[] or add the above properties to + * the list of properties in power_supply_desc + * 3. Call adc_battery_helper_init() after registering the power_supply and + * before returning from the probe() function + * 4. Use adc_battery_helper_get_prop() as the power-supply's get_property() + * method, or call it for the above properties. + * 5. Use adc_battery_helper_external_power_changed() as the power-supply's + * external_power_changed() method or call it from that method. + * 6. Use adc_battery_helper_[suspend|resume]() as suspend-resume methods or + * call them from the driver's suspend-resume methods. + * + * The provided get_voltage_and_current_now() method will be called by this + * helper at adc_battery_helper_init() time and later. + * + * Copyright (c) 2021-2025 Hans de Goede <hansg@kernel.org> + */ + +#include <linux/cleanup.h> +#include <linux/devm-helpers.h> +#include <linux/gpio/consumer.h> +#include <linux/mutex.h> +#include <linux/power_supply.h> +#include <linux/workqueue.h> + +#include "adc-battery-helper.h" + +#define MOV_AVG_WINDOW_SIZE ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE +#define INIT_POLL_TIME (5 * HZ) +#define POLL_TIME (30 * HZ) +#define SETTLE_TIME (1 * HZ) + +#define INIT_POLL_COUNT 30 + +#define CURR_HYST_UA 65000 + +#define LOW_BAT_UV 3700000 +#define FULL_BAT_HYST_UV 38000 + +#define AMBIENT_TEMP_CELSIUS 25 + +static int adc_battery_helper_get_status(struct adc_battery_helper *help) +{ + int full_uv = + help->psy->battery_info->constant_charge_voltage_max_uv - FULL_BAT_HYST_UV; + + if (help->curr_ua > CURR_HYST_UA) + return POWER_SUPPLY_STATUS_CHARGING; + + if (help->curr_ua < -CURR_HYST_UA) + return POWER_SUPPLY_STATUS_DISCHARGING; + + if (help->supplied) { + bool full; + + if (help->charge_finished) + full = gpiod_get_value_cansleep(help->charge_finished); + else + full = help->ocv_avg_uv > full_uv; + + if (full) + return POWER_SUPPLY_STATUS_FULL; + } + + return POWER_SUPPLY_STATUS_NOT_CHARGING; +} + +static void adc_battery_helper_work(struct work_struct *work) +{ + struct adc_battery_helper *help = container_of(work, struct adc_battery_helper, + work.work); + int i, curr_diff_ua, volt_diff_uv, res_mohm, ret, win_size; + struct device *dev = help->psy->dev.parent; + int volt_uv, prev_volt_uv = help->volt_uv; + int curr_ua, prev_curr_ua = help->curr_ua; + bool prev_supplied = help->supplied; + int prev_status = help->status; + + guard(mutex)(&help->lock); + + ret = help->get_voltage_and_current_now(help->psy, &volt_uv, &curr_ua); + if (ret) + goto out; + + help->volt_uv = volt_uv; + help->curr_ua = curr_ua; + + help->ocv_uv[help->ocv_avg_index] = + help->volt_uv - help->curr_ua * help->intern_res_avg_mohm / 1000; + dev_dbg(dev, "volt-now: %d, curr-now: %d, volt-ocv: %d\n", + help->volt_uv, help->curr_ua, help->ocv_uv[help->ocv_avg_index]); + help->ocv_avg_index = (help->ocv_avg_index + 1) % MOV_AVG_WINDOW_SIZE; + help->poll_count++; + + help->ocv_avg_uv = 0; + win_size = min(help->poll_count, MOV_AVG_WINDOW_SIZE); + for (i = 0; i < win_size; i++) + help->ocv_avg_uv += help->ocv_uv[i]; + help->ocv_avg_uv /= win_size; + + help->supplied = power_supply_am_i_supplied(help->psy); + help->status = adc_battery_helper_get_status(help); + if (help->status == POWER_SUPPLY_STATUS_FULL) + help->capacity = 100; + else + help->capacity = power_supply_batinfo_ocv2cap(help->psy->battery_info, + help->ocv_avg_uv, + AMBIENT_TEMP_CELSIUS); + + /* + * Skip internal resistance calc on charger [un]plug and + * when the battery is almost empty (voltage low). + */ + if (help->supplied != prev_supplied || + help->volt_uv < LOW_BAT_UV || + help->poll_count < 2) + goto out; + + /* + * Assuming that the OCV voltage does not change significantly + * between 2 polls, then we can calculate the internal resistance + * on a significant current change by attributing all voltage + * change between the 2 readings to the internal resistance. + */ + curr_diff_ua = abs(help->curr_ua - prev_curr_ua); + if (curr_diff_ua < CURR_HYST_UA) + goto out; + + volt_diff_uv = abs(help->volt_uv - prev_volt_uv); + res_mohm = volt_diff_uv * 1000 / curr_diff_ua; + + if ((res_mohm < (help->intern_res_avg_mohm * 2 / 3)) || + (res_mohm > (help->intern_res_avg_mohm * 4 / 3))) { + dev_dbg(dev, "Ignoring outlier internal resistance %d mOhm\n", res_mohm); + goto out; + } + + dev_dbg(dev, "Internal resistance %d mOhm\n", res_mohm); + + help->intern_res_mohm[help->intern_res_avg_index] = res_mohm; + help->intern_res_avg_index = (help->intern_res_avg_index + 1) % MOV_AVG_WINDOW_SIZE; + help->intern_res_poll_count++; + + help->intern_res_avg_mohm = 0; + win_size = min(help->intern_res_poll_count, MOV_AVG_WINDOW_SIZE); + for (i = 0; i < win_size; i++) + help->intern_res_avg_mohm += help->intern_res_mohm[i]; + help->intern_res_avg_mohm /= win_size; + +out: + queue_delayed_work(system_percpu_wq, &help->work, + (help->poll_count <= INIT_POLL_COUNT) ? + INIT_POLL_TIME : POLL_TIME); + + if (help->status != prev_status) + power_supply_changed(help->psy); +} + +const enum power_supply_property adc_battery_helper_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_SCOPE, +}; +EXPORT_SYMBOL_GPL(adc_battery_helper_properties); + +static_assert(ARRAY_SIZE(adc_battery_helper_properties) == + ADC_HELPER_NUM_PROPERTIES); + +int adc_battery_helper_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct adc_battery_helper *help = power_supply_get_drvdata(psy); + int dummy, ret = 0; + + /* + * Avoid racing with adc_battery_helper_work() while it is updating + * variables and avoid calling get_voltage_and_current_now() reentrantly. + */ + guard(mutex)(&help->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = help->status; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = help->get_voltage_and_current_now(psy, &val->intval, &dummy); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = help->ocv_avg_uv; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = help->get_voltage_and_current_now(psy, &dummy, &val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = help->capacity; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + break; + default: + return -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(adc_battery_helper_get_property); + +void adc_battery_helper_external_power_changed(struct power_supply *psy) +{ + struct adc_battery_helper *help = power_supply_get_drvdata(psy); + + dev_dbg(help->psy->dev.parent, "external power changed\n"); + mod_delayed_work(system_percpu_wq, &help->work, SETTLE_TIME); +} +EXPORT_SYMBOL_GPL(adc_battery_helper_external_power_changed); + +static void adc_battery_helper_start_work(struct adc_battery_helper *help) +{ + help->poll_count = 0; + help->ocv_avg_index = 0; + + queue_delayed_work(system_percpu_wq, &help->work, 0); + flush_delayed_work(&help->work); +} + +int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy, + adc_battery_helper_get_func get_voltage_and_current_now, + struct gpio_desc *charge_finished_gpio) +{ + struct device *dev = psy->dev.parent; + int ret; + + help->psy = psy; + help->get_voltage_and_current_now = get_voltage_and_current_now; + help->charge_finished = charge_finished_gpio; + + ret = devm_mutex_init(dev, &help->lock); + if (ret) + return ret; + + ret = devm_delayed_work_autocancel(dev, &help->work, adc_battery_helper_work); + if (ret) + return ret; + + if (!help->psy->battery_info || + help->psy->battery_info->factory_internal_resistance_uohm == -EINVAL || + help->psy->battery_info->constant_charge_voltage_max_uv == -EINVAL || + !psy->battery_info->ocv_table[0]) { + dev_err(dev, "error required properties are missing\n"); + return -ENODEV; + } + + /* Use provided internal resistance as start point (in milli-ohm) */ + help->intern_res_avg_mohm = + help->psy->battery_info->factory_internal_resistance_uohm / 1000; + /* Also add it to the internal resistance moving average window */ + help->intern_res_mohm[0] = help->intern_res_avg_mohm; + help->intern_res_avg_index = 1; + help->intern_res_poll_count = 1; + + adc_battery_helper_start_work(help); + return 0; +} +EXPORT_SYMBOL_GPL(adc_battery_helper_init); + +int adc_battery_helper_suspend(struct device *dev) +{ + struct adc_battery_helper *help = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&help->work); + return 0; +} +EXPORT_SYMBOL_GPL(adc_battery_helper_suspend); + +int adc_battery_helper_resume(struct device *dev) +{ + struct adc_battery_helper *help = dev_get_drvdata(dev); + + adc_battery_helper_start_work(help); + return 0; +} +EXPORT_SYMBOL_GPL(adc_battery_helper_resume); + +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_DESCRIPTION("ADC battery capacity estimation helper"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/adc-battery-helper.h b/drivers/power/supply/adc-battery-helper.h new file mode 100644 index 000000000000..4e42181c8983 --- /dev/null +++ b/drivers/power/supply/adc-battery-helper.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Helper for batteries with accurate current and voltage measurement, but + * without temperature measurement or without a "resistance-temp-table". + * Copyright (c) 2021-2025 Hans de Goede <hansg@kernel.org> + */ + +#include <linux/mutex.h> +#include <linux/workqueue.h> + +#define ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE 8 + +struct power_supply; +struct gpio_desc; + +/* + * The adc battery helper code needs voltage- and current-now to be sampled as + * close to each other (in sample-time) as possible. A single getter function is + * used to allow the battery driver to handle this in the best way possible. + */ +typedef int (*adc_battery_helper_get_func)(struct power_supply *psy, int *volt, int *curr); + +struct adc_battery_helper { + struct power_supply *psy; + struct gpio_desc *charge_finished; + struct delayed_work work; + struct mutex lock; + adc_battery_helper_get_func get_voltage_and_current_now; + int ocv_uv[ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE]; /* micro-volt */ + int intern_res_mohm[ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE]; /* milli-ohm */ + int poll_count; + int ocv_avg_index; + int ocv_avg_uv; /* micro-volt */ + int intern_res_poll_count; + int intern_res_avg_index; + int intern_res_avg_mohm; /* milli-ohm */ + int volt_uv; /* micro-volt */ + int curr_ua; /* micro-ampere */ + int capacity; /* percent */ + int status; + bool supplied; +}; + +extern const enum power_supply_property adc_battery_helper_properties[]; +/* Must be const cannot be an external. Asserted in adc-battery-helper.c */ +#define ADC_HELPER_NUM_PROPERTIES 7 + +int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy, + adc_battery_helper_get_func get_voltage_and_current_now, + struct gpio_desc *charge_finished_gpio); +/* + * The below functions can be directly used as power-supply / suspend-resume + * callbacks. They cast the power_supply_get_drvdata() / dev_get_drvdata() data + * directly to struct adc_battery_helper. Therefor struct adc_battery_helper + * MUST be the first member of the battery driver's data struct. + */ +int adc_battery_helper_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); +void adc_battery_helper_external_power_changed(struct power_supply *psy); +int adc_battery_helper_suspend(struct device *dev); +int adc_battery_helper_resume(struct device *dev); diff --git a/drivers/power/supply/apm_power.c b/drivers/power/supply/apm_power.c index 9236e0078578..9933cdc5c387 100644 --- a/drivers/power/supply/apm_power.c +++ b/drivers/power/supply/apm_power.c @@ -364,7 +364,8 @@ static int __init apm_battery_init(void) static void __exit apm_battery_exit(void) { - apm_get_power_status = NULL; + if (apm_get_power_status == apm_battery_apm_get_power_status) + apm_get_power_status = NULL; } module_init(apm_battery_init); diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c index e5733cb9e19e..5f6ea416fa30 100644 --- a/drivers/power/supply/axp20x_ac_power.c +++ b/drivers/power/supply/axp20x_ac_power.c @@ -364,7 +364,7 @@ static int axp20x_ac_power_probe(struct platform_device *pdev) platform_set_drvdata(pdev, power); - psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); psy_cfg.drv_data = power; power->supply = devm_power_supply_register(&pdev->dev, diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c index 3c3158f31a48..50ca8e110085 100644 --- a/drivers/power/supply/axp20x_battery.c +++ b/drivers/power/supply/axp20x_battery.c @@ -89,6 +89,8 @@ #define AXP717_BAT_CC_MIN_UA 0 #define AXP717_BAT_CC_MAX_UA 3008000 +#define AXP717_TS_PIN_DISABLE BIT(4) + struct axp20x_batt_ps; struct axp_data { @@ -117,6 +119,7 @@ struct axp20x_batt_ps { /* Maximum constant charge current */ unsigned int max_ccc; const struct axp_data *data; + bool ts_disable; }; static int axp20x_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt, @@ -984,6 +987,24 @@ static void axp717_set_battery_info(struct platform_device *pdev, int ccc = info->constant_charge_current_max_ua; int val; + axp_batt->ts_disable = (device_property_read_bool(axp_batt->dev, + "x-powers,no-thermistor")); + + /* + * Under rare conditions an incorrectly programmed efuse for + * the temp sensor on the PMIC may trigger a fault condition. + * Allow users to hard-code if the ts pin is not used to work + * around this problem. Note that this requires the battery + * be correctly defined in the device tree with a monitored + * battery node. + */ + if (axp_batt->ts_disable) { + regmap_update_bits(axp_batt->regmap, + AXP717_TS_PIN_CFG, + AXP717_TS_PIN_DISABLE, + AXP717_TS_PIN_DISABLE); + } + if (vmin > 0 && axp717_set_voltage_min_design(axp_batt, vmin)) dev_err(&pdev->dev, "couldn't set voltage_min_design\n"); @@ -1090,7 +1111,7 @@ static int axp20x_power_probe(struct platform_device *pdev) platform_set_drvdata(pdev, axp20x_batt); psy_cfg.drv_data = axp20x_batt; - psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); axp20x_batt->data = (struct axp_data *)of_device_get_match_data(dev); diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index 9722912268fe..e75d1e377ac1 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -492,7 +492,7 @@ static int axp717_usb_power_set_input_current_limit(struct axp20x_usb_power *pow if (power->max_input_cur && (intval > power->max_input_cur)) { dev_warn(power->dev, - "reqested current %d clamped to max current %d\n", + "requested current %d clamped to max current %d\n", intval, power->max_input_cur); intval = power->max_input_cur; } @@ -1011,7 +1011,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) return ret; } - psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); psy_cfg.drv_data = power; power->supply = devm_power_supply_register(&pdev->dev, diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c index ac05942e4e6a..ea0f5caee8f0 100644 --- a/drivers/power/supply/axp288_charger.c +++ b/drivers/power/supply/axp288_charger.c @@ -10,6 +10,7 @@ #include <linux/acpi.h> #include <linux/bitops.h> #include <linux/module.h> +#include <linux/devm-helpers.h> #include <linux/device.h> #include <linux/regmap.h> #include <linux/workqueue.h> @@ -821,14 +822,6 @@ static int charger_init_hw_regs(struct axp288_chrg_info *info) return 0; } -static void axp288_charger_cancel_work(void *data) -{ - struct axp288_chrg_info *info = data; - - cancel_work_sync(&info->otg.work); - cancel_work_sync(&info->cable.work); -} - static int axp288_charger_probe(struct platform_device *pdev) { int ret, i, pirq; @@ -866,12 +859,10 @@ static int axp288_charger_probe(struct platform_device *pdev) info->regmap_irqc = axp20x->regmap_irqc; info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME); - if (IS_ERR(info->cable.edev)) { - dev_err_probe(dev, PTR_ERR(info->cable.edev), - "extcon_get_extcon_dev(%s) failed\n", - AXP288_EXTCON_DEV_NAME); - return PTR_ERR(info->cable.edev); - } + if (IS_ERR(info->cable.edev)) + return dev_err_probe(dev, PTR_ERR(info->cable.edev), + "extcon_get_extcon_dev(%s) failed\n", + AXP288_EXTCON_DEV_NAME); /* * On devices with broken ACPI GPIO event handlers there also is no ACPI @@ -885,12 +876,11 @@ static int axp288_charger_probe(struct platform_device *pdev) if (extcon_name) { info->otg.cable = extcon_get_extcon_dev(extcon_name); - if (IS_ERR(info->otg.cable)) { - dev_err_probe(dev, PTR_ERR(info->otg.cable), - "extcon_get_extcon_dev(%s) failed\n", - USB_HOST_EXTCON_NAME); - return PTR_ERR(info->otg.cable); - } + if (IS_ERR(info->otg.cable)) + return dev_err_probe(dev, PTR_ERR(info->otg.cable), + "extcon_get_extcon_dev(%s) failed\n", + USB_HOST_EXTCON_NAME); + dev_info(dev, "Using " USB_HOST_EXTCON_HID " extcon for usb-id\n"); } @@ -904,38 +894,39 @@ static int axp288_charger_probe(struct platform_device *pdev) charger_cfg.drv_data = info; info->psy_usb = devm_power_supply_register(dev, &axp288_charger_desc, &charger_cfg); - if (IS_ERR(info->psy_usb)) { - ret = PTR_ERR(info->psy_usb); - dev_err(dev, "failed to register power supply: %d\n", ret); - return ret; - } + if (IS_ERR(info->psy_usb)) + return dev_err_probe(dev, PTR_ERR(info->psy_usb), + "failed to register power supply: %d\n", ret); /* Cancel our work on cleanup, register this before the notifiers */ - ret = devm_add_action(dev, axp288_charger_cancel_work, info); + ret = devm_work_autocancel(dev, &info->cable.work, + axp288_charger_extcon_evt_worker); if (ret) return ret; /* Register for extcon notification */ - INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker); info->cable.nb.notifier_call = axp288_charger_handle_cable_evt; ret = devm_extcon_register_notifier_all(dev, info->cable.edev, &info->cable.nb); - if (ret) { - dev_err(dev, "failed to register cable extcon notifier\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to register cable extcon notifier\n"); + schedule_work(&info->cable.work); + ret = devm_work_autocancel(dev, &info->otg.work, + axp288_charger_otg_evt_worker); + if (ret) + return ret; + /* Register for OTG notification */ - INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker); info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt; if (info->otg.cable) { ret = devm_extcon_register_notifier(dev, info->otg.cable, EXTCON_USB_HOST, &info->otg.id_nb); - if (ret) { - dev_err(dev, "failed to register EXTCON_USB_HOST notifier\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, + "failed to register EXTCON_USB_HOST notifier\n"); + schedule_work(&info->otg.work); } @@ -954,11 +945,9 @@ static int axp288_charger_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i], NULL, axp288_charger_irq_thread_handler, IRQF_ONESHOT, info->pdev->name, info); - if (ret) { - dev_err(dev, "failed to request interrupt=%d\n", - info->irq[i]); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to request interrupt=%d\n", + info->irq[i]); } return 0; diff --git a/drivers/power/supply/bd71828-power.c b/drivers/power/supply/bd71828-power.c new file mode 100644 index 000000000000..5e78faa0a4aa --- /dev/null +++ b/drivers/power/supply/bd71828-power.c @@ -0,0 +1,1206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* ROHM BD71815, BD71828 and BD71878 Charger driver */ + +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mfd/rohm-bd71815.h> +#include <linux/mfd/rohm-bd71828.h> +#include <linux/mfd/rohm-bd72720.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/power_supply.h> +#include <linux/slab.h> + +/* common defines */ +#define BD7182x_MASK_VBAT_U 0x1f +#define BD7182x_MASK_VDCIN_U 0x0f +#define BD7182x_MASK_IBAT_U 0x3f +#define BD7182x_MASK_CURDIR_DISCHG 0x80 +#define BD7182x_MASK_CHG_STATE 0x7f +#define BD7182x_MASK_BAT_TEMP 0x07 +#define BD7182x_MASK_DCIN_DET BIT(0) +#define BD7182x_MASK_CONF_PON BIT(0) +#define BD71815_MASK_CONF_XSTB BIT(1) +#define BD7182x_MASK_BAT_STAT 0x3f +#define BD7182x_MASK_ILIM 0x3f +#define BD7182x_MASK_DCIN_STAT 0x07 + +#define BD7182x_MASK_WDT_AUTO 0x40 +#define BD7182x_MASK_VBAT_ALM_LIMIT_U 0x01 +#define BD7182x_MASK_CHG_EN 0x01 + +#define BD7182x_DCIN_COLLAPSE_DEFAULT 0x36 + +#define MAX_CURRENT_DEFAULT 890000 /* uA */ +#define AC_NAME "bd71828_ac" +#define BAT_NAME "bd71828_bat" + +#define BAT_OPEN 0x7 + +/* + * VBAT Low voltage detection Threshold + * 0x00D4*16mV = 212*0.016 = 3.392v + */ +#define VBAT_LOW_TH 0x00D4 + +struct pwr_regs { + unsigned int vbat_avg; + unsigned int ibat; + unsigned int ibat_avg; + unsigned int ilim_stat; + unsigned int btemp_vth; + unsigned int chg_state; + unsigned int bat_temp; + unsigned int dcin_set; + unsigned int dcin_stat; + unsigned int dcin_online_mask; + unsigned int dcin_collapse_limit; + unsigned int chg_set1; + unsigned int chg_en; + unsigned int vbat_alm_limit_u; + unsigned int conf; + unsigned int vdcin; + unsigned int vdcin_himask; +}; + +static const struct pwr_regs pwr_regs_bd71828 = { + .vbat_avg = BD71828_REG_VBAT_U, + .ibat = BD71828_REG_IBAT_U, + .ibat_avg = BD71828_REG_IBAT_AVG_U, + .ilim_stat = BD71828_REG_ILIM_STAT, + .btemp_vth = BD71828_REG_VM_BTMP_U, + .chg_state = BD71828_REG_CHG_STATE, + .bat_temp = BD71828_REG_BAT_TEMP, + .dcin_set = BD71828_REG_DCIN_SET, + .dcin_stat = BD71828_REG_DCIN_STAT, + .dcin_online_mask = BD7182x_MASK_DCIN_DET, + .dcin_collapse_limit = BD71828_REG_DCIN_CLPS, + .chg_set1 = BD71828_REG_CHG_SET1, + .chg_en = BD71828_REG_CHG_EN, + .vbat_alm_limit_u = BD71828_REG_ALM_VBAT_LIMIT_U, + .conf = BD71828_REG_CONF, + .vdcin = BD71828_REG_VDCIN_U, + .vdcin_himask = BD7182x_MASK_VDCIN_U, +}; + +static const struct pwr_regs pwr_regs_bd71815 = { + .vbat_avg = BD71815_REG_VM_SA_VBAT_U, + /* BD71815 does not have separate current and current avg */ + .ibat = BD71815_REG_CC_CURCD_U, + .ibat_avg = BD71815_REG_CC_CURCD_U, + + .btemp_vth = BD71815_REG_VM_BTMP, + .chg_state = BD71815_REG_CHG_STATE, + .bat_temp = BD71815_REG_BAT_TEMP, + .dcin_stat = BD71815_REG_DCIN_STAT, + .dcin_online_mask = BD7182x_MASK_DCIN_DET, + .dcin_collapse_limit = BD71815_REG_DCIN_CLPS, + .chg_set1 = BD71815_REG_CHG_SET1, + .chg_en = BD71815_REG_CHG_SET1, + .vbat_alm_limit_u = BD71815_REG_ALM_VBAT_TH_U, + .conf = BD71815_REG_CONF, + + .vdcin = BD71815_REG_VM_DCIN_U, + .vdcin_himask = BD7182x_MASK_VDCIN_U, +}; + +static struct pwr_regs pwr_regs_bd72720 = { + .vbat_avg = BD72720_REG_VM_SA_VBAT_U, + .ibat = BD72720_REG_CC_CURCD_U, + .ibat_avg = BD72720_REG_CC_SA_CURCD_U, + .btemp_vth = BD72720_REG_VM_BTMP_U, + /* + * Note, state 0x40 IMP_CHK. not documented + * on other variants but was still handled in + * existing code. No memory traces as to why. + */ + .chg_state = BD72720_REG_CHG_STATE, + .bat_temp = BD72720_REG_CHG_BAT_TEMP_STAT, + .dcin_stat = BD72720_REG_INT_VBUS_SRC, + .dcin_online_mask = BD72720_MASK_DCIN_DET, + .dcin_collapse_limit = -1, /* Automatic. Setting not supported */ + .chg_set1 = BD72720_REG_CHG_SET_1, + .chg_en = BD72720_REG_CHG_EN, + /* 15mV note in data-sheet */ + .vbat_alm_limit_u = BD72720_REG_ALM_VBAT_TH_U, + .conf = BD72720_REG_CONF, /* o XSTB, only PON. Seprate slave addr */ + .vdcin = BD72720_REG_VM_VBUS_U, /* 10 bits not 11 as with other ICs */ + .vdcin_himask = BD72720_MASK_VDCIN_U, +}; + +struct bd71828_power { + struct regmap *regmap; + enum rohm_chip_type chip_type; + struct device *dev; + struct power_supply *ac; + struct power_supply *bat; + + const struct pwr_regs *regs; + /* Reg val to uA */ + int curr_factor; + int rsens; + int (*get_temp)(struct bd71828_power *pwr, int *temp); + int (*bat_inserted)(struct bd71828_power *pwr); +}; + +static int bd7182x_write16(struct bd71828_power *pwr, int reg, u16 val) +{ + __be16 tmp; + + tmp = cpu_to_be16(val); + + return regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp)); +} + +static int bd7182x_read16_himask(struct bd71828_power *pwr, int reg, int himask, + u16 *val) +{ + struct regmap *regmap = pwr->regmap; + int ret; + __be16 rvals; + u8 *tmp = (u8 *)&rvals; + + ret = regmap_bulk_read(regmap, reg, &rvals, sizeof(*val)); + if (!ret) { + *tmp &= himask; + *val = be16_to_cpu(rvals); + } + + return ret; +} + +static int bd71828_get_vbat(struct bd71828_power *pwr, int *vcell) +{ + u16 tmp_vcell; + int ret; + + ret = bd7182x_read16_himask(pwr, pwr->regs->vbat_avg, + BD7182x_MASK_VBAT_U, &tmp_vcell); + if (ret) + dev_err(pwr->dev, "Failed to read battery average voltage\n"); + else + *vcell = ((int)tmp_vcell) * 1000; + + return ret; +} + +static int bd71828_get_current_ds_adc(struct bd71828_power *pwr, int *curr, int *curr_avg) +{ + __be16 tmp_curr; + char *tmp = (char *)&tmp_curr; + int dir = 1; + int regs[] = { pwr->regs->ibat, pwr->regs->ibat_avg }; + int *vals[] = { curr, curr_avg }; + int ret, i; + + for (dir = 1, i = 0; i < ARRAY_SIZE(regs); i++) { + ret = regmap_bulk_read(pwr->regmap, regs[i], &tmp_curr, + sizeof(tmp_curr)); + if (ret) + break; + + if (*tmp & BD7182x_MASK_CURDIR_DISCHG) + dir = -1; + + *tmp &= BD7182x_MASK_IBAT_U; + + *vals[i] = dir * ((int)be16_to_cpu(tmp_curr)) * pwr->curr_factor; + } + + return ret; +} + +/* Unit is tenths of degree C */ +static int bd71815_get_temp(struct bd71828_power *pwr, int *temp) +{ + struct regmap *regmap = pwr->regmap; + int ret; + int t; + + ret = regmap_read(regmap, pwr->regs->btemp_vth, &t); + if (ret) + return ret; + + t = 200 - t; + + if (t > 200) { + dev_err(pwr->dev, "Failed to read battery temperature\n"); + return -ENODATA; + } + + return 0; +} + +/* Unit is tenths of degree C */ +static int bd71828_get_temp(struct bd71828_power *pwr, int *temp) +{ + u16 t; + int ret; + int tmp = 200 * 10000; + + ret = bd7182x_read16_himask(pwr, pwr->regs->btemp_vth, + BD71828_MASK_VM_BTMP_U, &t); + if (ret) + return ret; + + if (t > 3200) { + dev_err(pwr->dev, + "Failed to read battery temperature\n"); + return -ENODATA; + } + + tmp -= 625ULL * (unsigned int)t; + *temp = tmp / 1000; + + return ret; +} + +static int bd71828_charge_status(struct bd71828_power *pwr, + int *s, int *h) +{ + unsigned int state; + int status, health; + int ret = 1; + + ret = regmap_read(pwr->regmap, pwr->regs->chg_state, &state); + if (ret) { + dev_err(pwr->dev, "charger status reading failed (%d)\n", ret); + return ret; + } + + state &= BD7182x_MASK_CHG_STATE; + + dev_dbg(pwr->dev, "CHG_STATE %d\n", state); + + switch (state) { + case 0x00: + status = POWER_SUPPLY_STATUS_DISCHARGING; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x01: + case 0x02: + case 0x03: + case 0x0E: + status = POWER_SUPPLY_STATUS_CHARGING; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x0F: + status = POWER_SUPPLY_STATUS_FULL; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case 0x30: + case 0x31: + case 0x32: + case 0x40: + status = POWER_SUPPLY_STATUS_DISCHARGING; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x7f: + default: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + health = POWER_SUPPLY_HEALTH_DEAD; + break; + } + + if (s) + *s = status; + if (h) + *h = health; + + return ret; +} + +static int get_chg_online(struct bd71828_power *pwr, int *chg_online) +{ + int r, ret; + + ret = regmap_read(pwr->regmap, pwr->regs->dcin_stat, &r); + if (ret) { + dev_err(pwr->dev, "Failed to read DCIN status\n"); + return ret; + } + *chg_online = ((r & pwr->regs->dcin_online_mask) != 0); + + return 0; +} + +static int get_bat_online(struct bd71828_power *pwr, int *bat_online) +{ + int r, ret; + + ret = regmap_read(pwr->regmap, pwr->regs->bat_temp, &r); + if (ret) { + dev_err(pwr->dev, "Failed to read battery temperature\n"); + return ret; + } + *bat_online = ((r & BD7182x_MASK_BAT_TEMP) != BAT_OPEN); + + return 0; +} + +static int bd71828_bat_inserted(struct bd71828_power *pwr) +{ + int ret, val; + + ret = regmap_read(pwr->regmap, pwr->regs->conf, &val); + if (ret) { + dev_err(pwr->dev, "Failed to read CONF register\n"); + return 0; + } + ret = val & BD7182x_MASK_CONF_PON; + + if (ret) + if (regmap_update_bits(pwr->regmap, pwr->regs->conf, BD7182x_MASK_CONF_PON, 0)) + dev_err(pwr->dev, "Failed to write CONF register\n"); + + return ret; +} + +static int bd71815_bat_inserted(struct bd71828_power *pwr) +{ + int ret, val; + + ret = regmap_read(pwr->regmap, pwr->regs->conf, &val); + if (ret) { + dev_err(pwr->dev, "Failed to read CONF register\n"); + return ret; + } + + ret = !(val & BD71815_MASK_CONF_XSTB); + if (ret) + regmap_write(pwr->regmap, pwr->regs->conf, val | + BD71815_MASK_CONF_XSTB); + + return ret; +} + +static int bd71828_init_hardware(struct bd71828_power *pwr) +{ + int ret; + + /* TODO: Collapse limit should come from device-tree ? */ + if (pwr->regs->dcin_collapse_limit != (unsigned int)-1) { + ret = regmap_write(pwr->regmap, pwr->regs->dcin_collapse_limit, + BD7182x_DCIN_COLLAPSE_DEFAULT); + if (ret) { + dev_err(pwr->dev, "Failed to write DCIN collapse limit\n"); + return ret; + } + } + + ret = pwr->bat_inserted(pwr); + if (ret < 0) + return ret; + + if (ret) { + /* WDT_FST auto set */ + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_set1, + BD7182x_MASK_WDT_AUTO, + BD7182x_MASK_WDT_AUTO); + if (ret) + return ret; + + ret = bd7182x_write16(pwr, pwr->regs->vbat_alm_limit_u, + VBAT_LOW_TH); + if (ret) + return ret; + + /* + * On BD71815 "we mask the power-state" from relax detection. + * I am unsure what the impact of the power-state would be if + * we didn't - but this is what the vendor driver did - and + * that driver has been used in few projects so I just assume + * this is needed. + */ + if (pwr->chip_type == ROHM_CHIP_TYPE_BD71815) { + ret = regmap_set_bits(pwr->regmap, + BD71815_REG_REX_CTRL_1, + REX_PMU_STATE_MASK); + if (ret) + return ret; + } + } + + return 0; +} + +static int bd71828_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent); + u32 vot; + u16 tmp; + int t; + int online; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = get_chg_online(pwr, &online); + if (!ret) + val->intval = online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bd7182x_read16_himask(pwr, pwr->regs->vdcin, + pwr->regs->vdcin_himask, &tmp); + if (ret) + return ret; + + vot = tmp; + /* 5 milli volt steps */ + val->intval = 5000 * vot; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + if (!pwr->regs->ilim_stat) + return -ENODATA; + + ret = regmap_read(pwr->regmap, pwr->regs->ilim_stat, &t); + if (ret) + return ret; + + t++; + val->intval = (t & BD7182x_MASK_ILIM) * 50000; + if (val->intval > 2000000) + val->intval = 2000000; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static int bd71828_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent); + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + if (val->intval > 2000000) + return -EINVAL; + + if (val->intval < 50000) + return -EINVAL; + + if (!pwr->regs->dcin_set) + return -EINVAL; + + return regmap_update_bits(pwr->regmap, pwr->regs->dcin_set, + BD7182x_MASK_ILIM, + val->intval / 50000 - 1); + break; + default: + return -EINVAL; + } +} + +static int bd71828_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent); + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return !!(pwr->regs->dcin_set); + default: + return false; + } +} + +static int bd71828_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent); + int ret = 0; + int status, health, tmp, curr, curr_avg, chg_en; + + if (psp == POWER_SUPPLY_PROP_STATUS || + psp == POWER_SUPPLY_PROP_HEALTH || + psp == POWER_SUPPLY_PROP_CHARGE_TYPE) + ret = bd71828_charge_status(pwr, &status, &health); + else if (psp == POWER_SUPPLY_PROP_CURRENT_AVG || + psp == POWER_SUPPLY_PROP_CURRENT_NOW) + ret = bd71828_get_current_ds_adc(pwr, &curr, &curr_avg); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = health; + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = get_bat_online(pwr, &tmp); + if (!ret) + val->intval = tmp; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bd71828_get_vbat(pwr, &tmp); + val->intval = tmp; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = curr_avg; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = curr; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = MAX_CURRENT_DEFAULT; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = pwr->get_temp(pwr, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + ret = regmap_read(pwr->regmap, pwr->regs->chg_en, &chg_en); + if (ret) + return ret; + + val->intval = (chg_en & BD7182x_MASK_CHG_EN) ? + POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO : + POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int bd71828_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + if (val->intval == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en, + BD7182x_MASK_CHG_EN, + BD7182x_MASK_CHG_EN); + else + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en, + BD7182x_MASK_CHG_EN, + 0); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int bd71828_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + return true; + default: + return false; + } +} + +/** @brief ac properties */ +static const enum power_supply_property bd71828_charger_props[] = { + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static const enum power_supply_property bd71828_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, +}; + +/** @brief powers supplied by bd71828_ac */ +static char *bd71828_ac_supplied_to[] = { + BAT_NAME, +}; + +static const struct power_supply_desc bd71828_ac_desc = { + .name = AC_NAME, + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = bd71828_charger_props, + .num_properties = ARRAY_SIZE(bd71828_charger_props), + .get_property = bd71828_charger_get_property, + .set_property = bd71828_charger_set_property, + .property_is_writeable = bd71828_charger_property_is_writeable, +}; + +static const struct power_supply_desc bd71828_bat_desc = { + .name = BAT_NAME, + .type = POWER_SUPPLY_TYPE_BATTERY, + .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE), + .properties = bd71828_battery_props, + .num_properties = ARRAY_SIZE(bd71828_battery_props), + .get_property = bd71828_battery_get_property, + .set_property = bd71828_battery_set_property, + .property_is_writeable = bd71828_battery_property_is_writeable, +}; + +#define RSENS_CURR 10000000LLU + +#define BD_ISR_NAME(name) \ +bd7181x_##name##_isr + +#define BD_ISR_BAT(name, print, run_gauge) \ +static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ +{ \ + struct bd71828_power *pwr = (struct bd71828_power *)data; \ + \ + dev_dbg(pwr->dev, "%s\n", print); \ + power_supply_changed(pwr->bat); \ + \ + return IRQ_HANDLED; \ +} + +#define BD_ISR_AC(name, print, run_gauge) \ +static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ +{ \ + struct bd71828_power *pwr = (struct bd71828_power *)data; \ + \ + power_supply_changed(pwr->ac); \ + dev_dbg(pwr->dev, "%s\n", print); \ + power_supply_changed(pwr->bat); \ + \ + return IRQ_HANDLED; \ +} + +#define BD_ISR_DUMMY(name, print) \ +static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ +{ \ + struct bd71828_power *pwr = (struct bd71828_power *)data; \ + \ + dev_dbg(pwr->dev, "%s\n", print); \ + \ + return IRQ_HANDLED; \ +} + +BD_ISR_BAT(chg_state_changed, "CHG state changed", true) +/* DCIN voltage changes */ +BD_ISR_AC(dcin_removed, "DCIN removed", true) +BD_ISR_AC(clps_out, "DCIN voltage back to normal", true) +BD_ISR_AC(clps_in, "DCIN voltage collapsed", false) +BD_ISR_AC(dcin_ovp_res, "DCIN voltage normal", true) +BD_ISR_AC(dcin_ovp_det, "DCIN OVER VOLTAGE", true) + +BD_ISR_DUMMY(dcin_mon_det, "DCIN voltage below threshold") +BD_ISR_DUMMY(dcin_mon_res, "DCIN voltage above threshold") + +BD_ISR_DUMMY(vbus_curr_limit, "VBUS current limited") +BD_ISR_DUMMY(vsys_ov_res, "VSYS over-voltage cleared") +BD_ISR_DUMMY(vsys_ov_det, "VSYS over-voltage") +BD_ISR_DUMMY(vsys_uv_res, "VSYS under-voltage cleared") +BD_ISR_DUMMY(vsys_uv_det, "VSYS under-voltage") +BD_ISR_DUMMY(vsys_low_res, "'VSYS low' cleared") +BD_ISR_DUMMY(vsys_low_det, "VSYS low") +BD_ISR_DUMMY(vsys_mon_res, "VSYS mon - resumed") +BD_ISR_DUMMY(vsys_mon_det, "VSYS mon - detected") +BD_ISR_BAT(chg_wdg_temp, "charger temperature watchdog triggered", true) +BD_ISR_BAT(chg_wdg, "charging watchdog triggered", true) +BD_ISR_BAT(bat_removed, "Battery removed", true) +BD_ISR_BAT(bat_det, "Battery detected", true) +/* TODO: Verify the meaning of these interrupts */ +BD_ISR_BAT(rechg_det, "Recharging", true) +BD_ISR_BAT(rechg_res, "Recharge ending", true) +BD_ISR_DUMMY(temp_transit, "Temperature transition") +BD_ISR_BAT(therm_rmv, "bd71815-therm-rmv", false) +BD_ISR_BAT(therm_det, "bd71815-therm-det", true) +BD_ISR_BAT(bat_dead, "bd71815-bat-dead", false) +BD_ISR_BAT(bat_short_res, "bd71815-bat-short-res", true) +BD_ISR_BAT(bat_short, "bd71815-bat-short-det", false) +BD_ISR_BAT(bat_low_res, "bd71815-bat-low-res", true) +BD_ISR_BAT(bat_low, "bd71815-bat-low-det", true) +BD_ISR_BAT(bat_ov_res, "bd71815-bat-over-res", true) +/* What should we do here? */ +BD_ISR_BAT(bat_ov, "bd71815-bat-over-det", false) +BD_ISR_BAT(bat_mon_res, "bd71815-bat-mon-res", true) +BD_ISR_BAT(bat_mon, "bd71815-bat-mon-det", true) +BD_ISR_BAT(bat_cc_mon, "bd71815-bat-cc-mon2", false) +BD_ISR_BAT(bat_oc1_res, "bd71815-bat-oc1-res", true) +BD_ISR_BAT(bat_oc1, "bd71815-bat-oc1-det", false) +BD_ISR_BAT(bat_oc2_res, "bd71815-bat-oc2-res", true) +BD_ISR_BAT(bat_oc2, "bd71815-bat-oc2-det", false) +BD_ISR_BAT(bat_oc3_res, "bd71815-bat-oc3-res", true) +BD_ISR_BAT(bat_oc3, "bd71815-bat-oc3-det", false) +BD_ISR_BAT(temp_bat_low_res, "bd71815-temp-bat-low-res", true) +BD_ISR_BAT(temp_bat_low, "bd71815-temp-bat-low-det", true) +BD_ISR_BAT(temp_bat_hi_res, "bd71815-temp-bat-hi-res", true) +BD_ISR_BAT(temp_bat_hi, "bd71815-temp-bat-hi-det", true) + +static irqreturn_t bd7182x_dcin_removed(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + power_supply_changed(pwr->ac); + dev_dbg(pwr->dev, "DCIN removed\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd718x7_chg_done(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd7182x_dcin_detected(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "DCIN inserted\n"); + power_supply_changed(pwr->ac); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_vbat_low_res(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VBAT LOW Resumed\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_vbat_low_det(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VBAT LOW Detected\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_bat_hi_det(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_warn(pwr->dev, "Overtemp Detected\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_bat_hi_res(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "Overtemp Resumed\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_bat_low_det(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "Lowtemp Detected\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_bat_low_res(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "Lowtemp Resumed\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_vf_det(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VF Detected\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_vf_res(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VF Resumed\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_vf125_det(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VF125 Detected\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_vf125_res(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VF125 Resumed\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +struct bd7182x_irq_res { + const char *name; + irq_handler_t handler; +}; + +#define BDIRQ(na, hn) { .name = (na), .handler = (hn) } + +static int bd7182x_get_irqs(struct platform_device *pdev, + struct bd71828_power *pwr) +{ + int i, irq, ret; + static const struct bd7182x_irq_res bd71815_irqs[] = { + BDIRQ("bd71815-dcin-rmv", BD_ISR_NAME(dcin_removed)), + BDIRQ("bd71815-dcin-clps-out", BD_ISR_NAME(clps_out)), + BDIRQ("bd71815-dcin-clps-in", BD_ISR_NAME(clps_in)), + BDIRQ("bd71815-dcin-ovp-res", BD_ISR_NAME(dcin_ovp_res)), + BDIRQ("bd71815-dcin-ovp-det", BD_ISR_NAME(dcin_ovp_det)), + BDIRQ("bd71815-dcin-mon-res", BD_ISR_NAME(dcin_mon_res)), + BDIRQ("bd71815-dcin-mon-det", BD_ISR_NAME(dcin_mon_det)), + + BDIRQ("bd71815-vsys-uv-res", BD_ISR_NAME(vsys_uv_res)), + BDIRQ("bd71815-vsys-uv-det", BD_ISR_NAME(vsys_uv_det)), + BDIRQ("bd71815-vsys-low-res", BD_ISR_NAME(vsys_low_res)), + BDIRQ("bd71815-vsys-low-det", BD_ISR_NAME(vsys_low_det)), + BDIRQ("bd71815-vsys-mon-res", BD_ISR_NAME(vsys_mon_res)), + BDIRQ("bd71815-vsys-mon-det", BD_ISR_NAME(vsys_mon_det)), + BDIRQ("bd71815-chg-wdg-temp", BD_ISR_NAME(chg_wdg_temp)), + BDIRQ("bd71815-chg-wdg", BD_ISR_NAME(chg_wdg)), + BDIRQ("bd71815-rechg-det", BD_ISR_NAME(rechg_det)), + BDIRQ("bd71815-rechg-res", BD_ISR_NAME(rechg_res)), + BDIRQ("bd71815-ranged-temp-transit", BD_ISR_NAME(temp_transit)), + BDIRQ("bd71815-chg-state-change", BD_ISR_NAME(chg_state_changed)), + BDIRQ("bd71815-bat-temp-normal", bd71828_temp_bat_hi_res), + BDIRQ("bd71815-bat-temp-erange", bd71828_temp_bat_hi_det), + BDIRQ("bd71815-bat-rmv", BD_ISR_NAME(bat_removed)), + BDIRQ("bd71815-bat-det", BD_ISR_NAME(bat_det)), + + /* Add ISRs for these */ + BDIRQ("bd71815-therm-rmv", BD_ISR_NAME(therm_rmv)), + BDIRQ("bd71815-therm-det", BD_ISR_NAME(therm_det)), + BDIRQ("bd71815-bat-dead", BD_ISR_NAME(bat_dead)), + BDIRQ("bd71815-bat-short-res", BD_ISR_NAME(bat_short_res)), + BDIRQ("bd71815-bat-short-det", BD_ISR_NAME(bat_short)), + BDIRQ("bd71815-bat-low-res", BD_ISR_NAME(bat_low_res)), + BDIRQ("bd71815-bat-low-det", BD_ISR_NAME(bat_low)), + BDIRQ("bd71815-bat-over-res", BD_ISR_NAME(bat_ov_res)), + BDIRQ("bd71815-bat-over-det", BD_ISR_NAME(bat_ov)), + BDIRQ("bd71815-bat-mon-res", BD_ISR_NAME(bat_mon_res)), + BDIRQ("bd71815-bat-mon-det", BD_ISR_NAME(bat_mon)), + /* cc-mon 1 & 3 ? */ + BDIRQ("bd71815-bat-cc-mon2", BD_ISR_NAME(bat_cc_mon)), + BDIRQ("bd71815-bat-oc1-res", BD_ISR_NAME(bat_oc1_res)), + BDIRQ("bd71815-bat-oc1-det", BD_ISR_NAME(bat_oc1)), + BDIRQ("bd71815-bat-oc2-res", BD_ISR_NAME(bat_oc2_res)), + BDIRQ("bd71815-bat-oc2-det", BD_ISR_NAME(bat_oc2)), + BDIRQ("bd71815-bat-oc3-res", BD_ISR_NAME(bat_oc3_res)), + BDIRQ("bd71815-bat-oc3-det", BD_ISR_NAME(bat_oc3)), + BDIRQ("bd71815-temp-bat-low-res", BD_ISR_NAME(temp_bat_low_res)), + BDIRQ("bd71815-temp-bat-low-det", BD_ISR_NAME(temp_bat_low)), + BDIRQ("bd71815-temp-bat-hi-res", BD_ISR_NAME(temp_bat_hi_res)), + BDIRQ("bd71815-temp-bat-hi-det", BD_ISR_NAME(temp_bat_hi)), + /* + * TODO: add rest of the IRQs and re-check the handling. + * Check the bd71815-bat-cc-mon1, bd71815-bat-cc-mon3, + * bd71815-bat-low-res, bd71815-bat-low-det, + * bd71815-bat-hi-res, bd71815-bat-hi-det. + */ + }; + static const struct bd7182x_irq_res bd71828_irqs[] = { + BDIRQ("bd71828-chg-done", bd718x7_chg_done), + BDIRQ("bd71828-pwr-dcin-in", bd7182x_dcin_detected), + BDIRQ("bd71828-pwr-dcin-out", bd7182x_dcin_removed), + BDIRQ("bd71828-vbat-normal", bd71828_vbat_low_res), + BDIRQ("bd71828-vbat-low", bd71828_vbat_low_det), + BDIRQ("bd71828-btemp-hi", bd71828_temp_bat_hi_det), + BDIRQ("bd71828-btemp-cool", bd71828_temp_bat_hi_res), + BDIRQ("bd71828-btemp-lo", bd71828_temp_bat_low_det), + BDIRQ("bd71828-btemp-warm", bd71828_temp_bat_low_res), + BDIRQ("bd71828-temp-hi", bd71828_temp_vf_det), + BDIRQ("bd71828-temp-norm", bd71828_temp_vf_res), + BDIRQ("bd71828-temp-125-over", bd71828_temp_vf125_det), + BDIRQ("bd71828-temp-125-under", bd71828_temp_vf125_res), + }; + static const struct bd7182x_irq_res bd72720_irqs[] = { + BDIRQ("bd72720_int_vbus_rmv", BD_ISR_NAME(dcin_removed)), + BDIRQ("bd72720_int_vbus_det", bd7182x_dcin_detected), + BDIRQ("bd72720_int_vbus_mon_res", BD_ISR_NAME(dcin_mon_res)), + BDIRQ("bd72720_int_vbus_mon_det", BD_ISR_NAME(dcin_mon_det)), + BDIRQ("bd72720_int_vsys_mon_res", BD_ISR_NAME(vsys_mon_res)), + BDIRQ("bd72720_int_vsys_mon_det", BD_ISR_NAME(vsys_mon_det)), + BDIRQ("bd72720_int_vsys_uv_res", BD_ISR_NAME(vsys_uv_res)), + BDIRQ("bd72720_int_vsys_uv_det", BD_ISR_NAME(vsys_uv_det)), + BDIRQ("bd72720_int_vsys_lo_res", BD_ISR_NAME(vsys_low_res)), + BDIRQ("bd72720_int_vsys_lo_det", BD_ISR_NAME(vsys_low_det)), + BDIRQ("bd72720_int_vsys_ov_res", BD_ISR_NAME(vsys_ov_res)), + BDIRQ("bd72720_int_vsys_ov_det", BD_ISR_NAME(vsys_ov_det)), + BDIRQ("bd72720_int_bat_ilim", BD_ISR_NAME(vbus_curr_limit)), + BDIRQ("bd72720_int_chg_done", bd718x7_chg_done), + BDIRQ("bd72720_int_extemp_tout", BD_ISR_NAME(chg_wdg_temp)), + BDIRQ("bd72720_int_chg_wdt_exp", BD_ISR_NAME(chg_wdg)), + BDIRQ("bd72720_int_bat_mnt_out", BD_ISR_NAME(rechg_res)), + BDIRQ("bd72720_int_bat_mnt_in", BD_ISR_NAME(rechg_det)), + BDIRQ("bd72720_int_chg_trns", BD_ISR_NAME(chg_state_changed)), + + BDIRQ("bd72720_int_vbat_mon_res", BD_ISR_NAME(bat_mon_res)), + BDIRQ("bd72720_int_vbat_mon_det", BD_ISR_NAME(bat_mon)), + BDIRQ("bd72720_int_vbat_sht_res", BD_ISR_NAME(bat_short_res)), + BDIRQ("bd72720_int_vbat_sht_det", BD_ISR_NAME(bat_short)), + BDIRQ("bd72720_int_vbat_lo_res", BD_ISR_NAME(bat_low_res)), + BDIRQ("bd72720_int_vbat_lo_det", BD_ISR_NAME(bat_low)), + BDIRQ("bd72720_int_vbat_ov_res", BD_ISR_NAME(bat_ov_res)), + BDIRQ("bd72720_int_vbat_ov_det", BD_ISR_NAME(bat_ov)), + BDIRQ("bd72720_int_bat_rmv", BD_ISR_NAME(bat_removed)), + BDIRQ("bd72720_int_bat_det", BD_ISR_NAME(bat_det)), + BDIRQ("bd72720_int_dbat_det", BD_ISR_NAME(bat_dead)), + BDIRQ("bd72720_int_bat_temp_trns", BD_ISR_NAME(temp_transit)), + BDIRQ("bd72720_int_lobtmp_res", BD_ISR_NAME(temp_bat_low_res)), + BDIRQ("bd72720_int_lobtmp_det", BD_ISR_NAME(temp_bat_low)), + BDIRQ("bd72720_int_ovbtmp_res", BD_ISR_NAME(temp_bat_hi_res)), + BDIRQ("bd72720_int_ovbtmp_det", BD_ISR_NAME(temp_bat_hi)), + BDIRQ("bd72720_int_ocur1_res", BD_ISR_NAME(bat_oc1_res)), + BDIRQ("bd72720_int_ocur1_det", BD_ISR_NAME(bat_oc1)), + BDIRQ("bd72720_int_ocur2_res", BD_ISR_NAME(bat_oc2_res)), + BDIRQ("bd72720_int_ocur2_det", BD_ISR_NAME(bat_oc2)), + BDIRQ("bd72720_int_ocur3_res", BD_ISR_NAME(bat_oc3_res)), + BDIRQ("bd72720_int_ocur3_det", BD_ISR_NAME(bat_oc3)), + BDIRQ("bd72720_int_cc_mon2_det", BD_ISR_NAME(bat_cc_mon)), + }; + int num_irqs; + const struct bd7182x_irq_res *irqs; + + switch (pwr->chip_type) { + case ROHM_CHIP_TYPE_BD71828: + irqs = &bd71828_irqs[0]; + num_irqs = ARRAY_SIZE(bd71828_irqs); + break; + case ROHM_CHIP_TYPE_BD71815: + irqs = &bd71815_irqs[0]; + num_irqs = ARRAY_SIZE(bd71815_irqs); + break; + case ROHM_CHIP_TYPE_BD72720: + irqs = &bd72720_irqs[0]; + num_irqs = ARRAY_SIZE(bd72720_irqs); + break; + default: + return -EINVAL; + } + + for (i = 0; i < num_irqs; i++) { + irq = platform_get_irq_byname(pdev, irqs[i].name); + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + irqs[i].handler, 0, + irqs[i].name, pwr); + if (ret) + break; + } + + return ret; +} + +#define RSENS_DEFAULT_30MOHM 30000 /* 30 mOhm in uOhms*/ + +static int bd7182x_get_rsens(struct bd71828_power *pwr) +{ + u64 tmp = RSENS_CURR; + int rsens_ohm = RSENS_DEFAULT_30MOHM; + struct fwnode_handle *node = NULL; + + if (pwr->dev->parent) + node = dev_fwnode(pwr->dev->parent); + + if (node) { + int ret; + u32 rs; + + ret = fwnode_property_read_u32(node, + "rohm,charger-sense-resistor-micro-ohms", + &rs); + if (ret) { + if (ret == -EINVAL) { + rs = RSENS_DEFAULT_30MOHM; + } else { + dev_err(pwr->dev, "Bad RSENS dt property\n"); + return ret; + } + } + if (!rs) { + dev_err(pwr->dev, "Bad RSENS value\n"); + return -EINVAL; + } + + rsens_ohm = (int)rs; + } + + /* Reg val to uA */ + do_div(tmp, rsens_ohm); + + pwr->curr_factor = tmp; + pwr->rsens = rsens_ohm; + dev_dbg(pwr->dev, "Setting rsens to %u micro ohm\n", pwr->rsens); + dev_dbg(pwr->dev, "Setting curr-factor to %u\n", pwr->curr_factor); + + return 0; +} + +static int bd71828_power_probe(struct platform_device *pdev) +{ + struct bd71828_power *pwr; + struct power_supply_config ac_cfg = {}; + struct power_supply_config bat_cfg = {}; + int ret; + + pwr = devm_kzalloc(&pdev->dev, sizeof(*pwr), GFP_KERNEL); + if (!pwr) + return -ENOMEM; + + /* + * The BD72720 MFD device registers two regmaps. Power-supply driver + * uses the "wrap-map", which provides access to both of the I2C slave + * addresses used by the BD72720 + */ + pwr->chip_type = platform_get_device_id(pdev)->driver_data; + if (pwr->chip_type != ROHM_CHIP_TYPE_BD72720) + pwr->regmap = dev_get_regmap(pdev->dev.parent, NULL); + else + pwr->regmap = dev_get_regmap(pdev->dev.parent, "wrap-map"); + if (!pwr->regmap) + return dev_err_probe(&pdev->dev, -EINVAL, "No parent regmap\n"); + + pwr->dev = &pdev->dev; + + switch (pwr->chip_type) { + case ROHM_CHIP_TYPE_BD71828: + pwr->bat_inserted = bd71828_bat_inserted; + pwr->get_temp = bd71828_get_temp; + pwr->regs = &pwr_regs_bd71828; + break; + case ROHM_CHIP_TYPE_BD71815: + pwr->bat_inserted = bd71815_bat_inserted; + pwr->get_temp = bd71815_get_temp; + pwr->regs = &pwr_regs_bd71815; + break; + case ROHM_CHIP_TYPE_BD72720: + pwr->bat_inserted = bd71828_bat_inserted; + pwr->regs = &pwr_regs_bd72720; + pwr->get_temp = bd71828_get_temp; + dev_dbg(pwr->dev, "Found ROHM BD72720\n"); + break; + default: + return dev_err_probe(pwr->dev, -EINVAL, "Unknown PMIC\n"); + } + + ret = bd7182x_get_rsens(pwr); + if (ret) + return dev_err_probe(&pdev->dev, ret, "sense resistor missing\n"); + + dev_set_drvdata(&pdev->dev, pwr); + bd71828_init_hardware(pwr); + + bat_cfg.drv_data = pwr; + bat_cfg.fwnode = dev_fwnode(&pdev->dev); + + ac_cfg.supplied_to = bd71828_ac_supplied_to; + ac_cfg.num_supplicants = ARRAY_SIZE(bd71828_ac_supplied_to); + ac_cfg.drv_data = pwr; + + pwr->ac = devm_power_supply_register(&pdev->dev, &bd71828_ac_desc, + &ac_cfg); + if (IS_ERR(pwr->ac)) + return dev_err_probe(&pdev->dev, PTR_ERR(pwr->ac), + "failed to register ac\n"); + + pwr->bat = devm_power_supply_register(&pdev->dev, &bd71828_bat_desc, + &bat_cfg); + if (IS_ERR(pwr->bat)) + return dev_err_probe(&pdev->dev, PTR_ERR(pwr->bat), + "failed to register bat\n"); + + ret = bd7182x_get_irqs(pdev, pwr); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to request IRQs"); + + /* Configure wakeup capable */ + device_set_wakeup_capable(pwr->dev, 1); + device_set_wakeup_enable(pwr->dev, 1); + + return 0; +} + +static const struct platform_device_id bd71828_charger_id[] = { + { "bd71815-power", ROHM_CHIP_TYPE_BD71815 }, + { "bd71828-power", ROHM_CHIP_TYPE_BD71828 }, + { "bd72720-power", ROHM_CHIP_TYPE_BD72720 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, bd71828_charger_id); + +static struct platform_driver bd71828_power_driver = { + .driver = { + .name = "bd718xx-power", + }, + .probe = bd71828_power_probe, + .id_table = bd71828_charger_id, +}; + +module_platform_driver(bd71828_power_driver); + +MODULE_AUTHOR("Cong Pham <cpham2403@gmail.com>"); +MODULE_DESCRIPTION("ROHM BD718(15/28/78) PMIC Battery Charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/bd99954-charger.c b/drivers/power/supply/bd99954-charger.c index 54bf88262510..5c447b088223 100644 --- a/drivers/power/supply/bd99954-charger.c +++ b/drivers/power/supply/bd99954-charger.c @@ -56,7 +56,6 @@ */ #include <linux/delay.h> -#include <linux/gpio/consumer.h> #include <linux/interrupt.h> #include <linux/i2c.h> #include <linux/kernel.h> @@ -156,7 +155,7 @@ static const struct regmap_config bd9995x_regmap_config = { .reg_stride = 1, .max_register = 3 * 0x100, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .ranges = regmap_range_cfg, .num_ranges = ARRAY_SIZE(regmap_range_cfg), @@ -982,7 +981,7 @@ static int bd9995x_probe(struct i2c_client *client) bd->client = client; bd->dev = dev; psy_cfg.drv_data = bd; - psy_cfg.of_node = dev->of_node; + psy_cfg.fwnode = dev_fwnode(dev); mutex_init(&bd->lock); diff --git a/drivers/power/supply/bq2415x_charger.c b/drivers/power/supply/bq2415x_charger.c index 22f6a3b71632..b50a28b9dd38 100644 --- a/drivers/power/supply/bq2415x_charger.c +++ b/drivers/power/supply/bq2415x_charger.c @@ -842,7 +842,7 @@ static int bq2415x_notifier_call(struct notifier_block *nb, if (bq->automode < 1) return NOTIFY_OK; - mod_delayed_work(system_wq, &bq->work, 0); + mod_delayed_work(system_percpu_wq, &bq->work, 0); return NOTIFY_OK; } @@ -1497,7 +1497,7 @@ static int bq2415x_power_supply_init(struct bq2415x_device *bq) char revstr[8]; struct power_supply_config psy_cfg = { .drv_data = bq, - .of_node = bq->dev->of_node, + .fwnode = dev_fwnode(bq->dev), .attr_grp = bq2415x_sysfs_groups, }; @@ -1516,7 +1516,7 @@ static int bq2415x_power_supply_init(struct bq2415x_device *bq) ret = bq2415x_detect_revision(bq); if (ret < 0) - strcpy(revstr, "unknown"); + strscpy(revstr, "unknown", sizeof(revstr)); else sprintf(revstr, "1.%d", ret); @@ -1674,7 +1674,7 @@ static int bq2415x_probe(struct i2c_client *client) /* Query for initial reported_mode and set it */ if (bq->nb.notifier_call) { if (np) { - notify_psy = power_supply_get_by_phandle(np, + notify_psy = power_supply_get_by_reference(of_fwnode_handle(np), "ti,usb-charger-detection"); if (IS_ERR(notify_psy)) notify_psy = NULL; diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index b4ba01744368..55da91bacc3e 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -9,6 +9,7 @@ #include <linux/module.h> #include <linux/interrupt.h> #include <linux/delay.h> +#include <linux/devm-helpers.h> #include <linux/pm_runtime.h> #include <linux/power_supply.h> #include <linux/power/bq24190_charger.h> @@ -207,6 +208,7 @@ enum bq24190_chip { BQ24190, BQ24192, BQ24192i, + BQ24193, BQ24196, BQ24296, BQ24297, @@ -503,7 +505,6 @@ static ssize_t bq24190_sysfs_show(struct device *dev, else count = sysfs_emit(buf, "%hhx\n", v); - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); return count; @@ -534,7 +535,6 @@ static ssize_t bq24190_sysfs_store(struct device *dev, if (ret) count = ret; - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); return count; @@ -561,7 +561,6 @@ static int bq24190_set_otg_vbus(struct bq24190_dev_info *bdi, bool enable) else ret = bq24190_charger_set_charge_type(bdi, &val); - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); return ret; @@ -604,7 +603,6 @@ static int bq24296_set_otg_vbus(struct bq24190_dev_info *bdi, bool enable) } out: - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); return ret; @@ -637,7 +635,6 @@ static int bq24190_vbus_is_enabled(struct regulator_dev *dev) BQ24190_REG_POC_CHG_CONFIG_MASK, BQ24190_REG_POC_CHG_CONFIG_SHIFT, &val); - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); if (ret) @@ -674,7 +671,6 @@ static int bq24296_vbus_is_enabled(struct regulator_dev *dev) BQ24296_REG_POC_OTG_CONFIG_MASK, BQ24296_REG_POC_OTG_CONFIG_SHIFT, &val); - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); if (ret) @@ -1375,7 +1371,6 @@ static int bq24190_charger_get_property(struct power_supply *psy, ret = -ENODATA; } - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); return ret; @@ -1418,7 +1413,6 @@ static int bq24190_charger_set_property(struct power_supply *psy, ret = -EINVAL; } - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); return ret; @@ -1474,7 +1468,7 @@ static void bq24190_charger_external_power_changed(struct power_supply *psy) * too low default 500mA iinlim. Delay setting the input-current-limit * for 300ms to avoid this. */ - queue_delayed_work(system_wq, &bdi->input_current_limit_work, + queue_delayed_work(system_percpu_wq, &bdi->input_current_limit_work, msecs_to_jiffies(300)); } @@ -1681,7 +1675,6 @@ static int bq24190_battery_get_property(struct power_supply *psy, ret = -ENODATA; } - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); return ret; @@ -1712,7 +1705,6 @@ static int bq24190_battery_set_property(struct power_supply *psy, ret = -EINVAL; } - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); return ret; @@ -1860,7 +1852,6 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) return IRQ_NONE; } bq24190_check_status(bdi); - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); bdi->irq_event = false; @@ -1982,6 +1973,8 @@ static int bq24190_get_config(struct bq24190_dev_info *bdi) v = info->constant_charge_voltage_max_uv; if (v >= bq24190_cvc_vreg_values[0] && v <= bdi->vreg_max) bdi->vreg = bdi->vreg_max = v; + + power_supply_put_battery_info(bdi->charger, info); } return 0; @@ -2021,6 +2014,17 @@ static const struct bq24190_chip_info bq24190_chip_info_tbl[] = { .get_ntc_status = bq24190_charger_get_ntc_status, .set_otg_vbus = bq24190_set_otg_vbus, }, + [BQ24193] = { + .ichg_array_size = ARRAY_SIZE(bq24190_ccc_ichg_values), +#ifdef CONFIG_REGULATOR + .vbus_desc = &bq24190_vbus_desc, +#endif + .check_chip = bq24190_check_chip, + .set_chg_config = bq24190_battery_set_chg_config, + .ntc_fault_mask = BQ24190_REG_F_NTC_FAULT_MASK, + .get_ntc_status = bq24190_charger_get_ntc_status, + .set_otg_vbus = bq24190_set_otg_vbus, + }, [BQ24196] = { .ichg_array_size = ARRAY_SIZE(bq24190_ccc_ichg_values), #ifdef CONFIG_REGULATOR @@ -2084,8 +2088,11 @@ static int bq24190_probe(struct i2c_client *client) bdi->charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST; bdi->f_reg = 0; bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */ - INIT_DELAYED_WORK(&bdi->input_current_limit_work, - bq24190_input_current_limit_work); + + ret = devm_delayed_work_autocancel(dev, &bdi->input_current_limit_work, + bq24190_input_current_limit_work); + if (ret) + return ret; i2c_set_clientdata(client, bdi); @@ -2117,7 +2124,7 @@ static int bq24190_probe(struct i2c_client *client) #endif charger_cfg.drv_data = bdi; - charger_cfg.of_node = dev->of_node; + charger_cfg.fwnode = dev_fwnode(dev); charger_cfg.supplied_to = bq24190_charger_supplied_to; charger_cfg.num_supplicants = ARRAY_SIZE(bq24190_charger_supplied_to); bdi->charger = power_supply_register(dev, &bq24190_charger_desc, @@ -2174,7 +2181,6 @@ static int bq24190_probe(struct i2c_client *client) enable_irq_wake(client->irq); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; @@ -2196,7 +2202,6 @@ static void bq24190_remove(struct i2c_client *client) struct bq24190_dev_info *bdi = i2c_get_clientdata(client); int error; - cancel_delayed_work_sync(&bdi->input_current_limit_work); error = pm_runtime_resume_and_get(bdi->dev); if (error < 0) dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error); @@ -2261,7 +2266,6 @@ static __maybe_unused int bq24190_pm_suspend(struct device *dev) bq24190_register_reset(bdi); if (error >= 0) { - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); } @@ -2286,7 +2290,6 @@ static __maybe_unused int bq24190_pm_resume(struct device *dev) bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg); if (error >= 0) { - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); } @@ -2308,6 +2311,7 @@ static const struct i2c_device_id bq24190_i2c_ids[] = { { "bq24190", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24190] }, { "bq24192", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24192] }, { "bq24192i", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24192i] }, + { "bq24193", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24193] }, { "bq24196", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24196] }, { "bq24296", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24296] }, { "bq24297", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24297] }, @@ -2319,6 +2323,7 @@ static const struct of_device_id bq24190_of_match[] = { { .compatible = "ti,bq24190", .data = &bq24190_chip_info_tbl[BQ24190] }, { .compatible = "ti,bq24192", .data = &bq24190_chip_info_tbl[BQ24192] }, { .compatible = "ti,bq24192i", .data = &bq24190_chip_info_tbl[BQ24192i] }, + { .compatible = "ti,bq24193", .data = &bq24190_chip_info_tbl[BQ24193] }, { .compatible = "ti,bq24196", .data = &bq24190_chip_info_tbl[BQ24196] }, { .compatible = "ti,bq24296", .data = &bq24190_chip_info_tbl[BQ24296] }, { .compatible = "ti,bq24297", .data = &bq24190_chip_info_tbl[BQ24297] }, diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c index 1416586f2459..766eecb35694 100644 --- a/drivers/power/supply/bq24257_charger.c +++ b/drivers/power/supply/bq24257_charger.c @@ -113,7 +113,7 @@ static const struct regmap_config bq24257_regmap_config = { .val_bits = 8, .max_register = BQ24257_REG_7, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = bq24257_is_volatile_reg, }; diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c index 73a7fc867b03..637e0da65f87 100644 --- a/drivers/power/supply/bq24735-charger.c +++ b/drivers/power/supply/bq24735-charger.c @@ -402,7 +402,7 @@ static int bq24735_charger_probe(struct i2c_client *client) psy_cfg.supplied_to = charger->pdata->supplied_to; psy_cfg.num_supplicants = charger->pdata->num_supplicants; - psy_cfg.of_node = client->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&client->dev); psy_cfg.drv_data = charger; i2c_set_clientdata(client, charger); diff --git a/drivers/power/supply/bq2515x_charger.c b/drivers/power/supply/bq2515x_charger.c index a3424f67f2b1..437bff5bc420 100644 --- a/drivers/power/supply/bq2515x_charger.c +++ b/drivers/power/supply/bq2515x_charger.c @@ -1060,7 +1060,7 @@ static const struct regmap_config bq25150_regmap_config = { .max_register = BQ2515X_DEVICE_ID, .reg_defaults = bq25150_reg_defaults, .num_reg_defaults = ARRAY_SIZE(bq25150_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = bq2515x_volatile_register, }; @@ -1071,7 +1071,7 @@ static const struct regmap_config bq25155_regmap_config = { .max_register = BQ2515X_DEVICE_ID, .reg_defaults = bq25155_reg_defaults, .num_reg_defaults = ARRAY_SIZE(bq25155_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = bq2515x_volatile_register, }; @@ -1102,7 +1102,7 @@ static int bq2515x_probe(struct i2c_client *client) i2c_set_clientdata(client, bq2515x); charger_cfg.drv_data = bq2515x; - charger_cfg.of_node = dev->of_node; + charger_cfg.fwnode = dev_fwnode(dev); ret = bq2515x_read_properties(bq2515x); if (ret) { diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c index 5514d1896bb8..563f512709b3 100644 --- a/drivers/power/supply/bq256xx_charger.c +++ b/drivers/power/supply/bq256xx_charger.c @@ -8,7 +8,6 @@ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/gpio/consumer.h> #include <linux/power_supply.h> #include <linux/regmap.h> #include <linux/types.h> @@ -387,7 +386,7 @@ static void bq256xx_usb_work(struct work_struct *data) } } -static struct reg_default bq2560x_reg_defs[] = { +static const struct reg_default bq2560x_reg_defs[] = { {BQ256XX_INPUT_CURRENT_LIMIT, 0x17}, {BQ256XX_CHARGER_CONTROL_0, 0x1a}, {BQ256XX_CHARGE_CURRENT_LIMIT, 0xa2}, @@ -398,7 +397,7 @@ static struct reg_default bq2560x_reg_defs[] = { {BQ256XX_CHARGER_CONTROL_3, 0x4c}, }; -static struct reg_default bq25611d_reg_defs[] = { +static const struct reg_default bq25611d_reg_defs[] = { {BQ256XX_INPUT_CURRENT_LIMIT, 0x17}, {BQ256XX_CHARGER_CONTROL_0, 0x1a}, {BQ256XX_CHARGE_CURRENT_LIMIT, 0x91}, @@ -411,7 +410,7 @@ static struct reg_default bq25611d_reg_defs[] = { {BQ256XX_CHARGER_CONTROL_4, 0x75}, }; -static struct reg_default bq25618_619_reg_defs[] = { +static const struct reg_default bq25618_619_reg_defs[] = { {BQ256XX_INPUT_CURRENT_LIMIT, 0x17}, {BQ256XX_CHARGER_CONTROL_0, 0x1a}, {BQ256XX_CHARGE_CURRENT_LIMIT, 0x91}, @@ -1657,7 +1656,7 @@ static int bq256xx_parse_dt(struct bq256xx_device *bq, int ret = 0; psy_cfg->drv_data = bq; - psy_cfg->of_node = dev->of_node; + psy_cfg->fwnode = dev_fwnode(dev); ret = device_property_read_u32(bq->dev, "ti,watchdog-timeout-ms", &bq->watchdog_timer); @@ -1741,6 +1740,12 @@ static int bq256xx_probe(struct i2c_client *client) usb_register_notifier(bq->usb3_phy, &bq->usb_nb); } + ret = bq256xx_power_supply_init(bq, &psy_cfg, dev); + if (ret) { + dev_err(dev, "Failed to register power supply\n"); + return ret; + } + if (client->irq) { ret = devm_request_threaded_irq(dev, client->irq, NULL, bq256xx_irq_handler_thread, @@ -1753,12 +1758,6 @@ static int bq256xx_probe(struct i2c_client *client) } } - ret = bq256xx_power_supply_init(bq, &psy_cfg, dev); - if (ret) { - dev_err(dev, "Failed to register power supply\n"); - return ret; - } - ret = bq256xx_hw_init(bq); if (ret) { dev_err(dev, "Cannot initialize the chip.\n"); diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c new file mode 100644 index 000000000000..02c7d8b61e82 --- /dev/null +++ b/drivers/power/supply/bq257xx_charger.c @@ -0,0 +1,755 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BQ257XX Battery Charger Driver + * Copyright (C) 2025 Chris Morgan <macromorgan@hotmail.com> + */ + +#include <linux/bitfield.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mfd/bq257xx.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/property.h> +#include <linux/regmap.h> + +/* Forward declaration of driver data. */ +struct bq257xx_chg; + +/** + * struct bq257xx_chip_info - chip specific routines + * @bq257xx_hw_init: init function for hw + * @bq257xx_hw_shutdown: shutdown function for hw + * @bq257xx_get_state: get and update state of hardware + * @bq257xx_set_ichg: set maximum charge current (in uA) + * @bq257xx_set_vbatreg: set maximum charge voltage (in uV) + * @bq257xx_set_iindpm: set maximum input current (in uA) + */ +struct bq257xx_chip_info { + int (*bq257xx_hw_init)(struct bq257xx_chg *pdata); + void (*bq257xx_hw_shutdown)(struct bq257xx_chg *pdata); + int (*bq257xx_get_state)(struct bq257xx_chg *pdata); + int (*bq257xx_set_ichg)(struct bq257xx_chg *pdata, int ichg); + int (*bq257xx_set_vbatreg)(struct bq257xx_chg *pdata, int vbatreg); + int (*bq257xx_set_iindpm)(struct bq257xx_chg *pdata, int iindpm); +}; + +/** + * struct bq257xx_chg - driver data for charger + * @chip: hw specific functions + * @bq: parent MFD device + * @charger: power supply device + * @online: charger input is present + * @fast_charge: charger is in fast charge mode + * @pre_charge: charger is in pre-charge mode + * @ov_fault: charger reports over voltage fault + * @batoc_fault: charger reports battery over current fault + * @oc_fault: charger reports over current fault + * @usb_type: USB type reported from parent power supply + * @supplied: Status of parent power supply + * @iindpm_max: maximum input current limit (uA) + * @vbat_max: maximum charge voltage (uV) + * @ichg_max: maximum charge current (uA) + * @vsys_min: minimum system voltage (uV) + */ +struct bq257xx_chg { + const struct bq257xx_chip_info *chip; + struct bq257xx_device *bq; + struct power_supply *charger; + bool online; + bool fast_charge; + bool pre_charge; + bool ov_fault; + bool batoc_fault; + bool oc_fault; + int usb_type; + int supplied; + u32 iindpm_max; + u32 vbat_max; + u32 ichg_max; + u32 vsys_min; +}; + +/** + * bq25703_get_state() - Get the current state of the device + * @pdata: driver platform data + * + * Get the current state of the charger. Check if the charger is + * powered, what kind of charge state (if any) the device is in, + * and if there are any active faults. + * + * Return: Returns 0 on success, or error on failure to read device. + */ +static int bq25703_get_state(struct bq257xx_chg *pdata) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25703_CHARGER_STATUS, ®); + if (ret) + return ret; + + pdata->online = reg & BQ25703_STS_AC_STAT; + pdata->fast_charge = reg & BQ25703_STS_IN_FCHRG; + pdata->pre_charge = reg & BQ25703_STS_IN_PCHRG; + pdata->ov_fault = reg & BQ25703_STS_FAULT_ACOV; + pdata->batoc_fault = reg & BQ25703_STS_FAULT_BATOC; + pdata->oc_fault = reg & BQ25703_STS_FAULT_ACOC; + + return 0; +} + +/** + * bq25703_get_min_vsys() - Get the minimum system voltage + * @pdata: driver platform data + * @intval: value for minimum voltage + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25703_get_min_vsys(struct bq257xx_chg *pdata, int *intval) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25703_MIN_VSYS, + ®); + if (ret) + return ret; + + reg = FIELD_GET(BQ25703_MINVSYS_MASK, reg); + *intval = (reg * BQ25703_MINVSYS_STEP_UV) + BQ25703_MINVSYS_MIN_UV; + + return ret; +} + +/** + * bq25703_set_min_vsys() - Set the minimum system voltage + * @pdata: driver platform data + * @vsys: voltage value to set in uV. + * + * This function takes a requested minimum system voltage value, clamps + * it between the minimum supported value by the charger and a user + * defined minimum system value, and then writes the value to the + * appropriate register. + * + * Return: Returns 0 on success or error if an error occurs. + */ +static int bq25703_set_min_vsys(struct bq257xx_chg *pdata, int vsys) +{ + unsigned int reg; + int vsys_min = pdata->vsys_min; + + vsys = clamp(vsys, BQ25703_MINVSYS_MIN_UV, vsys_min); + reg = ((vsys - BQ25703_MINVSYS_MIN_UV) / BQ25703_MINVSYS_STEP_UV); + reg = FIELD_PREP(BQ25703_MINVSYS_MASK, reg); + + return regmap_write(pdata->bq->regmap, BQ25703_MIN_VSYS, + reg); +} + +/** + * bq25703_get_cur() - Get the reported current from the battery + * @pdata: driver platform data + * @intval: value of reported battery current + * + * Read the reported current from the battery. Since value is always + * positive set sign to negative if discharging. + * + * Return: Returns 0 on success or error if unable to read value. + */ +static int bq25703_get_cur(struct bq257xx_chg *pdata, int *intval) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25703_ADCIBAT_CHG, ®); + if (ret < 0) + return ret; + + if (pdata->online) + *intval = FIELD_GET(BQ25703_ADCIBAT_CHG_MASK, reg) * + BQ25703_ADCIBAT_CHG_STEP_UA; + else + *intval = -(FIELD_GET(BQ25703_ADCIBAT_DISCHG_MASK, reg) * + BQ25703_ADCIBAT_DIS_STEP_UA); + + return ret; +} + +/** + * bq25703_get_ichg_cur() - Get the maximum reported charge current + * @pdata: driver platform data + * @intval: value of maximum reported charge current + * + * Get the maximum reported charge current from the battery. + * + * Return: Returns 0 on success or error if unable to read value. + */ +static int bq25703_get_ichg_cur(struct bq257xx_chg *pdata, int *intval) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25703_CHARGE_CURRENT, ®); + if (ret) + return ret; + + *intval = FIELD_GET(BQ25703_ICHG_MASK, reg) * BQ25703_ICHG_STEP_UA; + + return ret; +} + +/** + * bq25703_set_ichg_cur() - Set the maximum charge current + * @pdata: driver platform data + * @ichg: current value to set in uA. + * + * This function takes a requested maximum charge current value, clamps + * it between the minimum supported value by the charger and a user + * defined maximum charging value, and then writes the value to the + * appropriate register. + * + * Return: Returns 0 on success or error if an error occurs. + */ +static int bq25703_set_ichg_cur(struct bq257xx_chg *pdata, int ichg) +{ + unsigned int reg; + int ichg_max = pdata->ichg_max; + + ichg = clamp(ichg, BQ25703_ICHG_MIN_UA, ichg_max); + reg = FIELD_PREP(BQ25703_ICHG_MASK, (ichg / BQ25703_ICHG_STEP_UA)); + + return regmap_write(pdata->bq->regmap, BQ25703_CHARGE_CURRENT, + reg); +} + +/** + * bq25703_get_chrg_volt() - Get the maximum set charge voltage + * @pdata: driver platform data + * @intval: maximum charge voltage value + * + * Return: Returns 0 on success or error if unable to read value. + */ +static int bq25703_get_chrg_volt(struct bq257xx_chg *pdata, int *intval) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25703_MAX_CHARGE_VOLT, + ®); + if (ret) + return ret; + + *intval = FIELD_GET(BQ25703_MAX_CHARGE_VOLT_MASK, reg) * + BQ25703_VBATREG_STEP_UV; + + return ret; +} + +/** + * bq25703_set_chrg_volt() - Set the maximum charge voltage + * @pdata: driver platform data + * @vbat: voltage value to set in uV. + * + * This function takes a requested maximum charge voltage value, clamps + * it between the minimum supported value by the charger and a user + * defined maximum charging value, and then writes the value to the + * appropriate register. + * + * Return: Returns 0 on success or error if an error occurs. + */ +static int bq25703_set_chrg_volt(struct bq257xx_chg *pdata, int vbat) +{ + unsigned int reg; + int vbat_max = pdata->vbat_max; + + vbat = clamp(vbat, BQ25703_VBATREG_MIN_UV, vbat_max); + + reg = FIELD_PREP(BQ25703_MAX_CHARGE_VOLT_MASK, + (vbat / BQ25703_VBATREG_STEP_UV)); + + return regmap_write(pdata->bq->regmap, BQ25703_MAX_CHARGE_VOLT, + reg); +} + +/** + * bq25703_get_iindpm() - Get the maximum set input current + * @pdata: driver platform data + * @intval: maximum input current value + * + * Read the actual input current limit from the device into intval. + * This can differ from the value programmed due to some autonomous + * functions that may be enabled (but are not currently). This is why + * there is a different register used. + * + * Return: Returns 0 on success or error if unable to read register + * value. + */ +static int bq25703_get_iindpm(struct bq257xx_chg *pdata, int *intval) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25703_IIN_DPM, ®); + if (ret) + return ret; + + reg = FIELD_GET(BQ25703_IINDPM_MASK, reg); + *intval = (reg * BQ25703_IINDPM_STEP_UA) + BQ25703_IINDPM_OFFSET_UA; + + return ret; +} + +/** + * bq25703_set_iindpm() - Set the maximum input current + * @pdata: driver platform data + * @iindpm: current value in uA. + * + * This function takes a requested maximum input current value, clamps + * it between the minimum supported value by the charger and a user + * defined maximum input value, and then writes the value to the + * appropriate register. + * + * Return: Returns 0 on success or error if an error occurs. + */ +static int bq25703_set_iindpm(struct bq257xx_chg *pdata, int iindpm) +{ + unsigned int reg; + int iindpm_max = pdata->iindpm_max; + + iindpm = clamp(iindpm, BQ25703_IINDPM_MIN_UA, iindpm_max); + + reg = ((iindpm - BQ25703_IINDPM_OFFSET_UA) / BQ25703_IINDPM_STEP_UA); + + return regmap_write(pdata->bq->regmap, BQ25703_IIN_HOST, + FIELD_PREP(BQ25703_IINDPM_MASK, reg)); +} + +/** + * bq25703_get_vbat() - Get the reported voltage from the battery + * @pdata: driver platform data + * @intval: value of reported battery voltage + * + * Read value of battery voltage into intval. + * + * Return: Returns 0 on success or error if unable to read value. + */ +static int bq25703_get_vbat(struct bq257xx_chg *pdata, int *intval) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25703_ADCVSYSVBAT, ®); + if (ret) + return ret; + + reg = FIELD_GET(BQ25703_ADCVBAT_MASK, reg); + *intval = (reg * BQ25703_ADCVSYSVBAT_STEP) + BQ25703_ADCVSYSVBAT_OFFSET_UV; + + return ret; +} + +/** + * bq25703_hw_init() - Set all the required registers to init the charger + * @pdata: driver platform data + * + * Initialize the BQ25703 by first disabling the watchdog timer (which + * shuts off the charger in the absence of periodic writes). Then, set + * the charge current, charge voltage, minimum system voltage, and + * input current limit. Disable low power mode to allow ADCs and + * interrupts. Enable the ADC, start the ADC, set the ADC scale to + * full, and enable each individual ADC channel. + * + * Return: Returns 0 on success or error code on error. + */ +static int bq25703_hw_init(struct bq257xx_chg *pdata) +{ + struct regmap *regmap = pdata->bq->regmap; + int ret = 0; + + regmap_update_bits(regmap, BQ25703_CHARGE_OPTION_0, + BQ25703_WDTMR_ADJ_MASK, + FIELD_PREP(BQ25703_WDTMR_ADJ_MASK, + BQ25703_WDTMR_DISABLE)); + + ret = pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max); + if (ret) + return ret; + + ret = bq25703_set_min_vsys(pdata, pdata->vsys_min); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_iindpm(pdata, pdata->iindpm_max); + if (ret) + return ret; + + /* Disable low power mode by writing 0 to the register. */ + regmap_update_bits(regmap, BQ25703_CHARGE_OPTION_0, + BQ25703_EN_LWPWR, 0); + + /* Enable the ADC. */ + regmap_update_bits(regmap, BQ25703_ADC_OPTION, + BQ25703_ADC_CONV_EN, BQ25703_ADC_CONV_EN); + + /* Start the ADC. */ + regmap_update_bits(regmap, BQ25703_ADC_OPTION, + BQ25703_ADC_START, BQ25703_ADC_START); + + /* Set the scale of the ADC. */ + regmap_update_bits(regmap, BQ25703_ADC_OPTION, + BQ25703_ADC_FULL_SCALE, BQ25703_ADC_FULL_SCALE); + + /* Enable each of the ADC channels available. */ + regmap_update_bits(regmap, BQ25703_ADC_OPTION, + BQ25703_ADC_CH_MASK, + (BQ25703_ADC_CMPIN_EN | BQ25703_ADC_VBUS_EN | + BQ25703_ADC_PSYS_EN | BQ25703_ADC_IIN_EN | + BQ25703_ADC_IDCHG_EN | BQ25703_ADC_ICHG_EN | + BQ25703_ADC_VSYS_EN | BQ25703_ADC_VBAT_EN)); + + return ret; +} + +/** + * bq25703_hw_shutdown() - Set registers for shutdown + * @pdata: driver platform data + * + * Enable low power mode for the device while in shutdown. + */ +static void bq25703_hw_shutdown(struct bq257xx_chg *pdata) +{ + regmap_update_bits(pdata->bq->regmap, BQ25703_CHARGE_OPTION_0, + BQ25703_EN_LWPWR, BQ25703_EN_LWPWR); +} + +static int bq257xx_set_charger_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct bq257xx_chg *pdata = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return pdata->chip->bq257xx_set_iindpm(pdata, val->intval); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return pdata->chip->bq257xx_set_vbatreg(pdata, val->intval); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return pdata->chip->bq257xx_set_ichg(pdata, val->intval); + + default: + break; + } + + return -EINVAL; +} + +static int bq257xx_get_charger_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bq257xx_chg *pdata = power_supply_get_drvdata(psy); + int ret = 0; + + ret = pdata->chip->bq257xx_get_state(pdata); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!pdata->online) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (pdata->fast_charge || pdata->pre_charge) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + + case POWER_SUPPLY_PROP_HEALTH: + if (pdata->ov_fault || pdata->batoc_fault) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (pdata->oc_fault) + val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "Texas Instruments"; + break; + + case POWER_SUPPLY_PROP_ONLINE: + val->intval = pdata->online; + break; + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return bq25703_get_iindpm(pdata, &val->intval); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return bq25703_get_chrg_volt(pdata, &val->intval); + + case POWER_SUPPLY_PROP_CURRENT_NOW: + return bq25703_get_cur(pdata, &val->intval); + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return bq25703_get_vbat(pdata, &val->intval); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return bq25703_get_ichg_cur(pdata, &val->intval); + + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + return bq25703_get_min_vsys(pdata, &val->intval); + + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = pdata->usb_type; + break; + + default: + return -EINVAL; + } + + return ret; +} + +static enum power_supply_property bq257xx_power_supply_props[] = { + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_USB_TYPE, +}; + +static int bq257xx_property_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return true; + default: + return false; + } +} + +/** + * bq257xx_external_power_changed() - Handler for external power change + * @psy: Power supply data + * + * When the external power into the charger is changed, check the USB + * type so that it can be reported. Additionally, update the max input + * current and max charging current to the value reported if it is a + * USB PD charger, otherwise use the default value. Note that each time + * a charger is removed the max charge current register is erased, so + * it must be set again each time the input changes or the device will + * not charge. + */ +static void bq257xx_external_power_changed(struct power_supply *psy) +{ + struct bq257xx_chg *pdata = power_supply_get_drvdata(psy); + union power_supply_propval val; + int ret; + int imax = pdata->iindpm_max; + + pdata->chip->bq257xx_get_state(pdata); + + pdata->supplied = power_supply_am_i_supplied(pdata->charger); + if (pdata->supplied < 0) + return; + + if (pdata->supplied == 0) + goto out; + + ret = power_supply_get_property_from_supplier(psy, + POWER_SUPPLY_PROP_USB_TYPE, + &val); + if (ret) + return; + + pdata->usb_type = val.intval; + + if ((pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD) || + (pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD_DRP) || + (pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD_PPS)) { + ret = power_supply_get_property_from_supplier(psy, + POWER_SUPPLY_PROP_CURRENT_MAX, + &val); + if (ret) + return; + + if (val.intval) + imax = val.intval; + } + + if (pdata->supplied) { + pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max); + pdata->chip->bq257xx_set_iindpm(pdata, imax); + pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max); + } + +out: + power_supply_changed(psy); +} + +static irqreturn_t bq257xx_irq_handler_thread(int irq, void *private) +{ + struct bq257xx_chg *pdata = private; + + bq257xx_external_power_changed(pdata->charger); + return IRQ_HANDLED; +} + +static const struct power_supply_desc bq257xx_power_supply_desc = { + .name = "bq257xx-charger", + .type = POWER_SUPPLY_TYPE_USB, + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_DRP) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), + .properties = bq257xx_power_supply_props, + .num_properties = ARRAY_SIZE(bq257xx_power_supply_props), + .get_property = bq257xx_get_charger_property, + .set_property = bq257xx_set_charger_property, + .property_is_writeable = bq257xx_property_is_writeable, + .external_power_changed = bq257xx_external_power_changed, +}; + +static const struct bq257xx_chip_info bq25703_chip_info = { + .bq257xx_hw_init = &bq25703_hw_init, + .bq257xx_hw_shutdown = &bq25703_hw_shutdown, + .bq257xx_get_state = &bq25703_get_state, + .bq257xx_set_ichg = &bq25703_set_ichg_cur, + .bq257xx_set_vbatreg = &bq25703_set_chrg_volt, + .bq257xx_set_iindpm = &bq25703_set_iindpm, +}; + +/** + * bq257xx_parse_dt() - Parse the device tree for required properties + * @pdata: driver platform data + * @psy_cfg: power supply config data + * @dev: device struct + * + * Read the device tree to identify the minimum system voltage, the + * maximum charge current, the maximum charge voltage, and the maximum + * input current. + * + * Return: Returns 0 on success or error code on error. + */ +static int bq257xx_parse_dt(struct bq257xx_chg *pdata, + struct power_supply_config *psy_cfg, struct device *dev) +{ + struct power_supply_battery_info *bat_info; + int ret; + + ret = power_supply_get_battery_info(pdata->charger, + &bat_info); + if (ret) + return dev_err_probe(dev, ret, + "Unable to get battery info\n"); + + if ((bat_info->voltage_min_design_uv <= 0) || + (bat_info->constant_charge_voltage_max_uv <= 0) || + (bat_info->constant_charge_current_max_ua <= 0)) + return dev_err_probe(dev, -EINVAL, + "Required bat info missing or invalid\n"); + + pdata->vsys_min = bat_info->voltage_min_design_uv; + pdata->vbat_max = bat_info->constant_charge_voltage_max_uv; + pdata->ichg_max = bat_info->constant_charge_current_max_ua; + + power_supply_put_battery_info(pdata->charger, bat_info); + + ret = device_property_read_u32(dev, + "input-current-limit-microamp", + &pdata->iindpm_max); + if (ret) + pdata->iindpm_max = BQ25703_IINDPM_DEFAULT_UA; + + return 0; +} + +static int bq257xx_charger_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bq257xx_device *bq = dev_get_drvdata(pdev->dev.parent); + struct bq257xx_chg *pdata; + struct power_supply_config psy_cfg = { }; + int ret; + + device_set_of_node_from_dev(dev, pdev->dev.parent); + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->bq = bq; + pdata->chip = &bq25703_chip_info; + + platform_set_drvdata(pdev, pdata); + + psy_cfg.drv_data = pdata; + psy_cfg.fwnode = dev_fwnode(dev); + + pdata->charger = devm_power_supply_register(dev, + &bq257xx_power_supply_desc, + &psy_cfg); + if (IS_ERR(pdata->charger)) + return dev_err_probe(dev, PTR_ERR(pdata->charger), + "Power supply register charger failed\n"); + + ret = bq257xx_parse_dt(pdata, &psy_cfg, dev); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_hw_init(pdata); + if (ret) + return dev_err_probe(dev, ret, "Cannot initialize the charger\n"); + + platform_set_drvdata(pdev, pdata); + + if (bq->client->irq) { + ret = devm_request_threaded_irq(dev, bq->client->irq, NULL, + bq257xx_irq_handler_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + dev_name(&bq->client->dev), pdata); + if (ret < 0) + dev_err_probe(dev, ret, "Charger get irq failed\n"); + } + + return ret; +} + +static void bq257xx_charger_shutdown(struct platform_device *pdev) +{ + struct bq257xx_chg *pdata = platform_get_drvdata(pdev); + + pdata->chip->bq257xx_hw_shutdown(pdata); +} + +static struct platform_driver bq257xx_chg_driver = { + .driver = { + .name = "bq257xx-charger", + }, + .probe = bq257xx_charger_probe, + .shutdown = bq257xx_charger_shutdown, +}; +module_platform_driver(bq257xx_chg_driver); + +MODULE_DESCRIPTION("bq257xx charger driver"); +MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 2f5ceaf00b94..868e86e1749b 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -164,7 +164,7 @@ static const struct regmap_config bq25890_regmap_config = { .val_bits = 8, .max_register = 0x14, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .wr_table = &bq25890_writeable_regs, .volatile_table = &bq25890_volatile_regs, diff --git a/drivers/power/supply/bq25980_charger.c b/drivers/power/supply/bq25980_charger.c index 0c5e2938bb36..44af3a0c92e5 100644 --- a/drivers/power/supply/bq25980_charger.c +++ b/drivers/power/supply/bq25980_charger.c @@ -8,7 +8,6 @@ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/gpio/consumer.h> #include <linux/power_supply.h> #include <linux/regmap.h> #include <linux/types.h> @@ -104,7 +103,7 @@ struct bq25980_device { int watchdog_timer; }; -static struct reg_default bq25980_reg_defs[] = { +static const struct reg_default bq25980_reg_defs[] = { {BQ25980_BATOVP, 0x5A}, {BQ25980_BATOVP_ALM, 0x46}, {BQ25980_BATOCP, 0x51}, @@ -159,7 +158,7 @@ static struct reg_default bq25980_reg_defs[] = { {BQ25980_CHRGR_CTRL_6, 0x0}, }; -static struct reg_default bq25975_reg_defs[] = { +static const struct reg_default bq25975_reg_defs[] = { {BQ25980_BATOVP, 0x5A}, {BQ25980_BATOVP_ALM, 0x46}, {BQ25980_BATOCP, 0x51}, @@ -214,7 +213,7 @@ static struct reg_default bq25975_reg_defs[] = { {BQ25980_CHRGR_CTRL_6, 0x0}, }; -static struct reg_default bq25960_reg_defs[] = { +static const struct reg_default bq25960_reg_defs[] = { {BQ25980_BATOVP, 0x5A}, {BQ25980_BATOVP_ALM, 0x46}, {BQ25980_BATOCP, 0x51}, @@ -932,7 +931,7 @@ static const struct regmap_config bq25980_regmap_config = { .max_register = BQ25980_CHRGR_CTRL_6, .reg_defaults = bq25980_reg_defs, .num_reg_defaults = ARRAY_SIZE(bq25980_reg_defs), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = bq25980_is_volatile_reg, }; @@ -943,7 +942,7 @@ static const struct regmap_config bq25975_regmap_config = { .max_register = BQ25980_CHRGR_CTRL_6, .reg_defaults = bq25975_reg_defs, .num_reg_defaults = ARRAY_SIZE(bq25975_reg_defs), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = bq25980_is_volatile_reg, }; @@ -954,7 +953,7 @@ static const struct regmap_config bq25960_regmap_config = { .max_register = BQ25980_CHRGR_CTRL_6, .reg_defaults = bq25960_reg_defs, .num_reg_defaults = ARRAY_SIZE(bq25960_reg_defs), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = bq25980_is_volatile_reg, }; @@ -1057,7 +1056,7 @@ static int bq25980_power_supply_init(struct bq25980_device *bq, struct device *dev) { struct power_supply_config psy_cfg = { .drv_data = bq, - .of_node = dev->of_node, }; + .fwnode = dev_fwnode(dev), }; psy_cfg.supplied_to = bq25980_charger_supplied_to; psy_cfg.num_supplicants = ARRAY_SIZE(bq25980_charger_supplied_to); @@ -1241,6 +1240,12 @@ static int bq25980_probe(struct i2c_client *client) return ret; } + ret = bq25980_power_supply_init(bq, dev); + if (ret) { + dev_err(dev, "Failed to register power supply\n"); + return ret; + } + if (client->irq) { ret = devm_request_threaded_irq(dev, client->irq, NULL, bq25980_irq_handler_thread, @@ -1251,12 +1256,6 @@ static int bq25980_probe(struct i2c_client *client) return ret; } - ret = bq25980_power_supply_init(bq, dev); - if (ret) { - dev_err(dev, "Failed to register power supply\n"); - return ret; - } - ret = bq25980_hw_init(bq); if (ret) { dev_err(dev, "Cannot initialize the chip.\n"); diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 90a5bccfc6b9..45f0e39b8c2d 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -124,6 +124,7 @@ enum bq27xxx_reg_index { BQ27XXX_DM_DATA, /* Block Data */ BQ27XXX_DM_CKSUM, /* Block Data Checksum */ BQ27XXX_REG_SEDVF, /* End-of-discharge Voltage */ + BQ27XXX_REG_PKCFG, /* Pack Configuration */ BQ27XXX_REG_MAX, /* sentinel */ }; @@ -161,6 +162,7 @@ static u8 [BQ27XXX_DM_DATA] = INVALID_REG_ADDR, [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR, [BQ27XXX_REG_SEDVF] = 0x77, + [BQ27XXX_REG_PKCFG] = 0x7C, }, bq27010_regs[BQ27XXX_REG_MAX] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -187,6 +189,7 @@ static u8 [BQ27XXX_DM_DATA] = INVALID_REG_ADDR, [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR, [BQ27XXX_REG_SEDVF] = 0x77, + [BQ27XXX_REG_PKCFG] = 0x7C, }, bq2750x_regs[BQ27XXX_REG_MAX] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -583,6 +586,7 @@ static enum power_supply_property bq27000_props[] = { POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, }; static enum power_supply_property bq27010_props[] = { @@ -604,6 +608,7 @@ static enum power_supply_property bq27010_props[] = { POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, }; #define bq2750x_props bq27510g3_props @@ -1122,7 +1127,7 @@ static int poll_interval_param_set(const char *val, const struct kernel_param *k mutex_lock(&bq27xxx_list_lock); list_for_each_entry(di, &bq27xxx_battery_devices, list) - mod_delayed_work(system_wq, &di->work, 0); + mod_delayed_work(system_percpu_wq, &di->work, 0); mutex_unlock(&bq27xxx_list_lock); return ret; @@ -1167,7 +1172,7 @@ static inline int bq27xxx_write(struct bq27xxx_device_info *di, int reg_index, return -EINVAL; if (!di->bus.write) - return -EPERM; + return -EOPNOTSUPP; ret = di->bus.write(di, di->regs[reg_index], value, single); if (ret < 0) @@ -1186,7 +1191,7 @@ static inline int bq27xxx_read_block(struct bq27xxx_device_info *di, int reg_ind return -EINVAL; if (!di->bus.read_bulk) - return -EPERM; + return -EOPNOTSUPP; ret = di->bus.read_bulk(di, di->regs[reg_index], data, len); if (ret < 0) @@ -1205,7 +1210,7 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in return -EINVAL; if (!di->bus.write_bulk) - return -EPERM; + return -EOPNOTSUPP; ret = di->bus.write_bulk(di, di->regs[reg_index], data, len); if (ret < 0) @@ -1914,11 +1919,10 @@ static void bq27xxx_battery_update_unlocked(struct bq27xxx_device_info *di) bool has_singe_flag = di->opts & BQ27XXX_O_ZERO; cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag); - if ((cache.flags & 0xff) == 0xff) - cache.flags = -1; /* read error */ + if (di->chip == BQ27000 && (cache.flags & 0xff) == 0xff) + cache.flags = -ENODEV; /* bq27000 hdq read error */ if (cache.flags >= 0) { cache.capacity = bq27xxx_battery_read_soc(di); - di->cache.flags = cache.flags; /* * On gauges with signed current reporting the current must be @@ -1941,7 +1945,7 @@ static void bq27xxx_battery_update_unlocked(struct bq27xxx_device_info *di) di->last_update = jiffies; if (!di->removed && poll_interval > 0) - mod_delayed_work(system_wq, &di->work, poll_interval * HZ); + mod_delayed_work(system_percpu_wq, &di->work, poll_interval * HZ); } void bq27xxx_battery_update(struct bq27xxx_device_info *di) @@ -2045,6 +2049,35 @@ static int bq27xxx_battery_voltage(struct bq27xxx_device_info *di, } /* + * Return the design maximum battery Voltage in microvolts, or < 0 if something + * fails. The programmed value of the maximum battery voltage is determined by + * QV0 and QV1 (bits 5 and 6) in the Pack Configuration register. + */ +static int bq27xxx_battery_read_dmax_volt(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int reg_val, qv; + + if (di->voltage_max_design > 0) { + val->intval = di->voltage_max_design; + return 0; + } + + reg_val = bq27xxx_read(di, BQ27XXX_REG_PKCFG, true); + if (reg_val < 0) { + dev_err(di->dev, "error reading design max voltage\n"); + return reg_val; + } + + qv = (reg_val >> 5) & 0x3; + val->intval = 3968000 + 48000 * qv; + + di->voltage_max_design = val->intval; + + return 0; +} + +/* * Return the design minimum battery Voltage in microvolts * Or < 0 if something fails. */ @@ -2098,7 +2131,7 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, mutex_unlock(&di->lock); if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0) - return -ENODEV; + return di->cache.flags; switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -2158,6 +2191,9 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: ret = bq27xxx_battery_read_dmin_volt(di, val); break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + ret = bq27xxx_battery_read_dmax_volt(di, val); + break; case POWER_SUPPLY_PROP_CYCLE_COUNT: ret = bq27xxx_battery_read_cyct(di, val); break; @@ -2185,30 +2221,21 @@ static void bq27xxx_external_power_changed(struct power_supply *psy) struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); /* After charger plug in/out wait 0.5s for things to stabilize */ - mod_delayed_work(system_wq, &di->work, HZ / 2); -} - -static void bq27xxx_battery_mutex_destroy(void *data) -{ - struct mutex *lock = data; - - mutex_destroy(lock); + mod_delayed_work(system_percpu_wq, &di->work, HZ / 2); } int bq27xxx_battery_setup(struct bq27xxx_device_info *di) { struct power_supply_desc *psy_desc; struct power_supply_config psy_cfg = { - .of_node = di->dev->of_node, + .fwnode = dev_fwnode(di->dev), .drv_data = di, .no_wakeup_source = true, }; int ret; INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll); - mutex_init(&di->lock); - ret = devm_add_action_or_reset(di->dev, bq27xxx_battery_mutex_destroy, - &di->lock); + ret = devm_mutex_init(di->dev, &di->lock); if (ret) return ret; diff --git a/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c index ba0d22d90429..868e95f0887e 100644 --- a/drivers/power/supply/bq27xxx_battery_i2c.c +++ b/drivers/power/supply/bq27xxx_battery_i2c.c @@ -6,6 +6,7 @@ * Andrew F. Davis <afd@ti.com> */ +#include <linux/delay.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/module.h> @@ -31,6 +32,7 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, struct i2c_msg msg[2]; u8 data[2]; int ret; + int retry = 0; if (!client->adapter) return -ENODEV; @@ -47,7 +49,16 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, else msg[1].len = 2; - ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + do { + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret == -EBUSY && ++retry < 3) { + /* sleep 10 milliseconds when busy */ + usleep_range(10000, 11000); + continue; + } + break; + } while (1); + if (ret < 0) return ret; diff --git a/drivers/power/supply/chagall-battery.c b/drivers/power/supply/chagall-battery.c new file mode 100644 index 000000000000..8b05422aca6f --- /dev/null +++ b/drivers/power/supply/chagall-battery.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/array_size.h> +#include <linux/delay.h> +#include <linux/devm-helpers.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> + +#define CHAGALL_REG_LED_AMBER 0x60 +#define CHAGALL_REG_LED_WHITE 0x70 +#define CHAGALL_REG_BATTERY_TEMPERATURE 0xa2 +#define CHAGALL_REG_BATTERY_VOLTAGE 0xa4 +#define CHAGALL_REG_BATTERY_CURRENT 0xa6 +#define CHAGALL_REG_BATTERY_CAPACITY 0xa8 +#define CHAGALL_REG_BATTERY_CHARGING_CURRENT 0xaa +#define CHAGALL_REG_BATTERY_CHARGING_VOLTAGE 0xac +#define CHAGALL_REG_BATTERY_STATUS 0xae +#define BATTERY_DISCHARGING BIT(6) +#define BATTERY_FULL_CHARGED BIT(5) +#define BATTERY_FULL_DISCHARGED BIT(4) +#define CHAGALL_REG_BATTERY_REMAIN_CAPACITY 0xb0 +#define CHAGALL_REG_BATTERY_FULL_CAPACITY 0xb2 +#define CHAGALL_REG_MAX_COUNT 0xb4 + +#define CHAGALL_BATTERY_DATA_REFRESH 5000 +#define TEMP_CELSIUS_OFFSET 2731 + +static const struct regmap_config chagall_battery_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = CHAGALL_REG_MAX_COUNT, + .reg_format_endian = REGMAP_ENDIAN_LITTLE, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +struct chagall_battery_data { + struct regmap *regmap; + struct led_classdev amber_led; + struct led_classdev white_led; + struct power_supply *battery; + struct delayed_work poll_work; + u16 last_state; +}; + +static void chagall_led_set_brightness_amber(struct led_classdev *led, + enum led_brightness brightness) +{ + struct chagall_battery_data *cg = + container_of(led, struct chagall_battery_data, amber_led); + + regmap_write(cg->regmap, CHAGALL_REG_LED_AMBER, brightness); +} + +static void chagall_led_set_brightness_white(struct led_classdev *led, + enum led_brightness brightness) +{ + struct chagall_battery_data *cg = + container_of(led, struct chagall_battery_data, white_led); + + regmap_write(cg->regmap, CHAGALL_REG_LED_WHITE, brightness); +} + +static const enum power_supply_property chagall_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, +}; + +static const unsigned int chagall_battery_prop_offs[] = { + [POWER_SUPPLY_PROP_STATUS] = CHAGALL_REG_BATTERY_STATUS, + [POWER_SUPPLY_PROP_VOLTAGE_NOW] = CHAGALL_REG_BATTERY_VOLTAGE, + [POWER_SUPPLY_PROP_VOLTAGE_MAX] = CHAGALL_REG_BATTERY_CHARGING_VOLTAGE, + [POWER_SUPPLY_PROP_CURRENT_NOW] = CHAGALL_REG_BATTERY_CURRENT, + [POWER_SUPPLY_PROP_CURRENT_MAX] = CHAGALL_REG_BATTERY_CHARGING_CURRENT, + [POWER_SUPPLY_PROP_CAPACITY] = CHAGALL_REG_BATTERY_CAPACITY, + [POWER_SUPPLY_PROP_TEMP] = CHAGALL_REG_BATTERY_TEMPERATURE, + [POWER_SUPPLY_PROP_CHARGE_FULL] = CHAGALL_REG_BATTERY_FULL_CAPACITY, + [POWER_SUPPLY_PROP_CHARGE_NOW] = CHAGALL_REG_BATTERY_REMAIN_CAPACITY, +}; + +static int chagall_battery_get_value(struct chagall_battery_data *cg, + enum power_supply_property psp, u32 *val) +{ + if (psp >= ARRAY_SIZE(chagall_battery_prop_offs)) + return -EINVAL; + if (!chagall_battery_prop_offs[psp]) + return -EINVAL; + + /* Battery data is stored in 2 consecutive registers with little-endian */ + return regmap_bulk_read(cg->regmap, chagall_battery_prop_offs[psp], val, 2); +} + +static int chagall_battery_get_status(u32 status_reg) +{ + if (status_reg & BATTERY_FULL_CHARGED) + return POWER_SUPPLY_STATUS_FULL; + else if (status_reg & BATTERY_DISCHARGING) + return POWER_SUPPLY_STATUS_DISCHARGING; + else + return POWER_SUPPLY_STATUS_CHARGING; +} + +static int chagall_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct chagall_battery_data *cg = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + + default: + ret = chagall_battery_get_value(cg, psp, &val->intval); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP: + val->intval -= TEMP_CELSIUS_OFFSET; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval *= 1000; + break; + + case POWER_SUPPLY_PROP_STATUS: + val->intval = chagall_battery_get_status(val->intval); + break; + + default: + break; + } + + break; + } + + return 0; +} + +static void chagall_battery_poll_work(struct work_struct *work) +{ + struct chagall_battery_data *cg = + container_of(work, struct chagall_battery_data, poll_work.work); + u32 state; + int ret; + + ret = chagall_battery_get_value(cg, POWER_SUPPLY_PROP_STATUS, &state); + if (ret) + return; + + state = chagall_battery_get_status(state); + + if (cg->last_state != state) { + cg->last_state = state; + power_supply_changed(cg->battery); + } + + /* continuously send uevent notification */ + schedule_delayed_work(&cg->poll_work, + msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH)); +} + +static const struct power_supply_desc chagall_battery_desc = { + .name = "chagall-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = chagall_battery_properties, + .num_properties = ARRAY_SIZE(chagall_battery_properties), + .get_property = chagall_battery_get_property, + .external_power_changed = power_supply_changed, +}; + +static int chagall_battery_probe(struct i2c_client *client) +{ + struct chagall_battery_data *cg; + struct device *dev = &client->dev; + struct power_supply_config cfg = { }; + int ret; + + cg = devm_kzalloc(dev, sizeof(*cg), GFP_KERNEL); + if (!cg) + return -ENOMEM; + + cfg.drv_data = cg; + cfg.fwnode = dev_fwnode(dev); + + i2c_set_clientdata(client, cg); + + cg->regmap = devm_regmap_init_i2c(client, &chagall_battery_regmap_config); + if (IS_ERR(cg->regmap)) + return dev_err_probe(dev, PTR_ERR(cg->regmap), "cannot allocate regmap\n"); + + cg->last_state = POWER_SUPPLY_STATUS_UNKNOWN; + cg->battery = devm_power_supply_register(dev, &chagall_battery_desc, &cfg); + if (IS_ERR(cg->battery)) + return dev_err_probe(dev, PTR_ERR(cg->battery), + "failed to register power supply\n"); + + cg->amber_led.name = "power::amber"; + cg->amber_led.max_brightness = 1; + cg->amber_led.flags = LED_CORE_SUSPENDRESUME; + cg->amber_led.brightness_set = chagall_led_set_brightness_amber; + cg->amber_led.default_trigger = "chagall-battery-charging"; + + ret = devm_led_classdev_register(dev, &cg->amber_led); + if (ret) + return dev_err_probe(dev, ret, "failed to register amber LED\n"); + + cg->white_led.name = "power::white"; + cg->white_led.max_brightness = 1; + cg->white_led.flags = LED_CORE_SUSPENDRESUME; + cg->white_led.brightness_set = chagall_led_set_brightness_white; + cg->white_led.default_trigger = "chagall-battery-full"; + + ret = devm_led_classdev_register(dev, &cg->white_led); + if (ret) + return dev_err_probe(dev, ret, "failed to register white LED\n"); + + led_set_brightness(&cg->amber_led, LED_OFF); + led_set_brightness(&cg->white_led, LED_OFF); + + ret = devm_delayed_work_autocancel(dev, &cg->poll_work, chagall_battery_poll_work); + if (ret) + return ret; + + schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH)); + + return 0; +} + +static int __maybe_unused chagall_battery_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct chagall_battery_data *cg = i2c_get_clientdata(client); + + cancel_delayed_work_sync(&cg->poll_work); + + return 0; +} + +static int __maybe_unused chagall_battery_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct chagall_battery_data *cg = i2c_get_clientdata(client); + + schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH)); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(chagall_battery_pm_ops, + chagall_battery_suspend, chagall_battery_resume); + +static const struct of_device_id chagall_of_match[] = { + { .compatible = "pegatron,chagall-ec" }, + { } +}; +MODULE_DEVICE_TABLE(of, chagall_of_match); + +static struct i2c_driver chagall_battery_driver = { + .driver = { + .name = "chagall-battery", + .pm = &chagall_battery_pm_ops, + .of_match_table = chagall_of_match, + }, + .probe = chagall_battery_probe, +}; +module_i2c_driver(chagall_battery_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); +MODULE_DESCRIPTION("Pegatron Chagall fuel gauge driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/collie_battery.c b/drivers/power/supply/collie_battery.c index 68390bd1004f..3daf7befc0bf 100644 --- a/drivers/power/supply/collie_battery.c +++ b/drivers/power/supply/collie_battery.c @@ -440,6 +440,7 @@ err_put_gpio_full: static void collie_bat_remove(struct ucb1x00_dev *dev) { + device_init_wakeup(&ucb->dev, 0); free_irq(gpiod_to_irq(collie_bat_main.gpio_full), &collie_bat_main); power_supply_unregister(collie_bat_bu.psy); power_supply_unregister(collie_bat_main.psy); diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c index 813037c00ded..7b7bdce3162f 100644 --- a/drivers/power/supply/cpcap-battery.c +++ b/drivers/power/supply/cpcap-battery.c @@ -387,7 +387,7 @@ static const struct cpcap_battery_config cpcap_battery_bw8x_data = { * Safe values for any lipo battery likely to fit into a mapphone * battery bay. */ -static const struct cpcap_battery_config cpcap_battery_unkown_data = { +static const struct cpcap_battery_config cpcap_battery_unknown_data = { .cd_factor = 0x3cc, .info.technology = POWER_SUPPLY_TECHNOLOGY_LION, .info.voltage_max_design = 4200000, @@ -404,6 +404,30 @@ static int cpcap_battery_match_nvmem(struct device *dev, const void *data) return 0; } +static void cpcap_battery_update_battery_data(struct cpcap_battery_ddata *ddata) +{ + struct power_supply_battery_info *info; + + if (power_supply_get_battery_info(ddata->psy, &info) < 0) + return; + + if (info->technology > 0) + ddata->config.info.technology = info->technology; + + if (info->voltage_max_design_uv > 0) + ddata->config.info.voltage_max_design = info->voltage_max_design_uv; + + if (info->voltage_min_design_uv > 0) + ddata->config.info.voltage_min_design = info->voltage_min_design_uv; + + if (info->charge_full_design_uah > 0) + ddata->config.info.charge_full_design = info->charge_full_design_uah; + + if (info->constant_charge_voltage_max_uv > 0) + ddata->config.bat.constant_charge_voltage_max_uv = + info->constant_charge_voltage_max_uv; +} + static void cpcap_battery_detect_battery_type(struct cpcap_battery_ddata *ddata) { struct nvmem_device *nvmem; @@ -429,8 +453,11 @@ static void cpcap_battery_detect_battery_type(struct cpcap_battery_ddata *ddata) ddata->config = cpcap_battery_bw8x_data; break; default: - ddata->config = cpcap_battery_unkown_data; + ddata->config = cpcap_battery_unknown_data; } + + if (ddata->psy) + cpcap_battery_update_battery_data(ddata); } /** @@ -1122,15 +1149,11 @@ static int cpcap_battery_probe(struct platform_device *pdev) platform_set_drvdata(pdev, ddata); - error = cpcap_battery_init_interrupts(pdev, ddata); - if (error) - return error; - error = cpcap_battery_init_iio(ddata); if (error) return error; - psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); psy_cfg.drv_data = ddata; ddata->psy = devm_power_supply_register(ddata->dev, @@ -1142,6 +1165,10 @@ static int cpcap_battery_probe(struct platform_device *pdev) return error; } + error = cpcap_battery_init_interrupts(pdev, ddata); + if (error) + return error; + atomic_set(&ddata->active, 1); error = cpcap_battery_calibrate(ddata); diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index 6625d539d9ae..d0c3008db534 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -689,9 +689,8 @@ static void cpcap_usb_detect(struct work_struct *work) struct power_supply *battery; battery = power_supply_get_by_name("battery"); - if (IS_ERR_OR_NULL(battery)) { - dev_err(ddata->dev, "battery power_supply not available %li\n", - PTR_ERR(battery)); + if (!battery) { + dev_err(ddata->dev, "battery power_supply not available\n"); return; } @@ -902,7 +901,7 @@ static int cpcap_charger_probe(struct platform_device *pdev) atomic_set(&ddata->active, 1); - psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); psy_cfg.drv_data = ddata; psy_cfg.supplied_to = cpcap_charger_supplied_to; psy_cfg.num_supplicants = ARRAY_SIZE(cpcap_charger_supplied_to); diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c index 02d5bdbe2e8d..53e6a77e03fc 100644 --- a/drivers/power/supply/cros_charge-control.c +++ b/drivers/power/supply/cros_charge-control.c @@ -47,29 +47,20 @@ struct cros_chctl_priv { static int cros_chctl_send_charge_control_cmd(struct cros_ec_device *cros_ec, u8 cmd_version, struct ec_params_charge_control *req) { + int ret; static const u8 outsizes[] = { [1] = offsetof(struct ec_params_charge_control, cmd), [2] = sizeof(struct ec_params_charge_control), [3] = sizeof(struct ec_params_charge_control), }; - struct { - struct cros_ec_command msg; - union { - struct ec_params_charge_control req; - struct ec_response_charge_control resp; - } __packed data; - } __packed buf = { - .msg = { - .command = EC_CMD_CHARGE_CONTROL, - .version = cmd_version, - .insize = 0, - .outsize = outsizes[cmd_version], - }, - .data.req = *req, - }; + ret = cros_ec_cmd(cros_ec, cmd_version, EC_CMD_CHARGE_CONTROL, req, + outsizes[cmd_version], NULL, 0); + + if (ret < 0) + return ret; - return cros_ec_cmd_xfer_status(cros_ec, &buf.msg); + return 0; } static int cros_chctl_configure_ec(struct cros_chctl_priv *priv) diff --git a/drivers/power/supply/cros_peripheral_charger.c b/drivers/power/supply/cros_peripheral_charger.c index 962a6fd29832..f132fad288cb 100644 --- a/drivers/power/supply/cros_peripheral_charger.c +++ b/drivers/power/supply/cros_peripheral_charger.c @@ -64,7 +64,7 @@ static int cros_pchg_ec_command(const struct charger_data *charger, struct cros_ec_command *msg; int ret; - msg = kzalloc(struct_size(msg, data, max(outsize, insize)), GFP_KERNEL); + msg = kzalloc_flex(*msg, data, max(outsize, insize)); if (!msg) return -ENOMEM; diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c index 47d3f58aa15c..7d3e676a951c 100644 --- a/drivers/power/supply/cros_usbpd-charger.c +++ b/drivers/power/supply/cros_usbpd-charger.c @@ -94,7 +94,7 @@ static int cros_usbpd_charger_ec_command(struct charger_data *charger, struct cros_ec_command *msg; int ret; - msg = kzalloc(struct_size(msg, data, max(outsize, insize)), GFP_KERNEL); + msg = kzalloc_flex(*msg, data, max(outsize, insize)); if (!msg) return -ENOMEM; diff --git a/drivers/power/supply/cw2015_battery.c b/drivers/power/supply/cw2015_battery.c index f63c3c410451..286524d2318c 100644 --- a/drivers/power/supply/cw2015_battery.c +++ b/drivers/power/supply/cw2015_battery.c @@ -13,7 +13,6 @@ #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> @@ -506,10 +505,7 @@ static int cw_battery_get_property(struct power_supply *psy, 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; + val->intval = max(cw_bat->battery->charge_full_design_uah, 0); break; case POWER_SUPPLY_PROP_CHARGE_NOW: @@ -698,12 +694,18 @@ static int cw_bat_probe(struct i2c_client *client) "No monitored battery, some properties will be missing\n"); } - cw_bat->battery_workqueue = create_singlethread_workqueue("rk_battery"); + cw_bat->battery_workqueue = devm_alloc_ordered_workqueue(&client->dev, + "rk_battery", 0); if (!cw_bat->battery_workqueue) return -ENOMEM; - devm_delayed_work_autocancel(&client->dev, - &cw_bat->battery_delay_work, cw_bat_work); + ret = devm_delayed_work_autocancel(&client->dev, &cw_bat->battery_delay_work, cw_bat_work); + if (ret) { + dev_err_probe(&client->dev, ret, + "Failed to register delayed work\n"); + return ret; + } + queue_delayed_work(cw_bat->battery_workqueue, &cw_bat->battery_delay_work, msecs_to_jiffies(10)); return 0; diff --git a/drivers/power/supply/da9030_battery.c b/drivers/power/supply/da9030_battery.c index ac2e319e9517..d25279c26030 100644 --- a/drivers/power/supply/da9030_battery.c +++ b/drivers/power/supply/da9030_battery.c @@ -502,8 +502,7 @@ static int da9030_battery_probe(struct platform_device *pdev) /* 10 seconds between monitor runs unless platform defines other interval */ - charger->interval = msecs_to_jiffies( - (pdata->batmon_interval ? : 10) * 1000); + charger->interval = secs_to_jiffies(pdata->batmon_interval ? : 10); charger->charge_milliamp = pdata->charge_milliamp; charger->charge_millivolt = pdata->charge_millivolt; diff --git a/drivers/power/supply/ds2760_battery.c b/drivers/power/supply/ds2760_battery.c index 83bdec5a2bda..142c7492c3c2 100644 --- a/drivers/power/supply/ds2760_battery.c +++ b/drivers/power/supply/ds2760_battery.c @@ -112,7 +112,6 @@ struct ds2760_device_info { struct power_supply_desc bat_desc; struct workqueue_struct *monitor_wqueue; struct delayed_work monitor_work; - struct delayed_work set_charged_work; struct notifier_block pm_notifier; }; @@ -210,7 +209,7 @@ static const struct bin_attribute *const w1_ds2760_bin_attrs[] = { }; static const struct attribute_group w1_ds2760_group = { - .bin_attrs_new = w1_ds2760_bin_attrs, + .bin_attrs = w1_ds2760_bin_attrs, }; static const struct attribute_group *w1_ds2760_groups[] = { @@ -489,50 +488,6 @@ static void ds2760_battery_external_power_changed(struct power_supply *psy) } -static void ds2760_battery_set_charged_work(struct work_struct *work) -{ - char bias; - struct ds2760_device_info *di = container_of(work, - struct ds2760_device_info, set_charged_work.work); - - dev_dbg(di->dev, "%s\n", __func__); - - ds2760_battery_read_status(di); - - /* When we get notified by external circuitry that the battery is - * considered fully charged now, we know that there is no current - * flow any more. However, the ds2760's internal current meter is - * too inaccurate to rely on - spec say something ~15% failure. - * Hence, we use the current offset bias register to compensate - * that error. - */ - - if (!power_supply_am_i_supplied(di->bat)) - return; - - bias = (signed char) di->current_raw + - (signed char) di->raw[DS2760_CURRENT_OFFSET_BIAS]; - - dev_dbg(di->dev, "%s: bias = %d\n", __func__, bias); - - w1_ds2760_write(di->dev, &bias, DS2760_CURRENT_OFFSET_BIAS, 1); - w1_ds2760_store_eeprom(di->dev, DS2760_EEPROM_BLOCK1); - w1_ds2760_recall_eeprom(di->dev, DS2760_EEPROM_BLOCK1); - - /* Write to the di->raw[] buffer directly - the CURRENT_OFFSET_BIAS - * value won't be read back by ds2760_battery_read_status() */ - di->raw[DS2760_CURRENT_OFFSET_BIAS] = bias; -} - -static void ds2760_battery_set_charged(struct power_supply *psy) -{ - struct ds2760_device_info *di = power_supply_get_drvdata(psy); - - /* postpone the actual work by 20 secs. This is for debouncing GPIO - * signals and to let the current value settle. See AN4188. */ - mod_delayed_work(di->monitor_wqueue, &di->set_charged_work, HZ * 20); -} - static int ds2760_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -692,17 +647,15 @@ static int w1_ds2760_add_slave(struct w1_slave *sl) di->bat_desc.set_property = ds2760_battery_set_property; di->bat_desc.property_is_writeable = ds2760_battery_property_is_writeable; - di->bat_desc.set_charged = ds2760_battery_set_charged; di->bat_desc.external_power_changed = ds2760_battery_external_power_changed; psy_cfg.drv_data = di; + psy_cfg.fwnode = dev_fwnode(dev); if (dev->of_node) { u32 tmp; - psy_cfg.of_node = dev->of_node; - if (!of_property_read_bool(dev->of_node, "maxim,pmod-enabled")) pmod_enabled = true; @@ -747,8 +700,6 @@ static int w1_ds2760_add_slave(struct w1_slave *sl) } INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work); - INIT_DELAYED_WORK(&di->set_charged_work, - ds2760_battery_set_charged_work); di->monitor_wqueue = alloc_ordered_workqueue(name, WQ_MEM_RECLAIM); if (!di->monitor_wqueue) { retval = -ESRCH; @@ -774,7 +725,6 @@ static void w1_ds2760_remove_slave(struct w1_slave *sl) unregister_pm_notifier(&di->pm_notifier); cancel_delayed_work_sync(&di->monitor_work); - cancel_delayed_work_sync(&di->set_charged_work); destroy_workqueue(di->monitor_wqueue); } diff --git a/drivers/power/supply/ds2780_battery.c b/drivers/power/supply/ds2780_battery.c index dd9ac7a32967..5b57bbba79d4 100644 --- a/drivers/power/supply/ds2780_battery.c +++ b/drivers/power/supply/ds2780_battery.c @@ -660,8 +660,8 @@ static const struct bin_attribute ds2780_param_eeprom_bin_attr = { .mode = S_IRUGO | S_IWUSR, }, .size = DS2780_PARAM_EEPROM_SIZE, - .read_new = ds2780_read_param_eeprom_bin, - .write_new = ds2780_write_param_eeprom_bin, + .read = ds2780_read_param_eeprom_bin, + .write = ds2780_write_param_eeprom_bin, }; static ssize_t ds2780_read_user_eeprom_bin(struct file *filp, @@ -705,8 +705,8 @@ static const struct bin_attribute ds2780_user_eeprom_bin_attr = { .mode = S_IRUGO | S_IWUSR, }, .size = DS2780_USER_EEPROM_SIZE, - .read_new = ds2780_read_user_eeprom_bin, - .write_new = ds2780_write_user_eeprom_bin, + .read = ds2780_read_user_eeprom_bin, + .write = ds2780_write_user_eeprom_bin, }; static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2780_get_pmod_enabled, @@ -734,7 +734,7 @@ static const struct bin_attribute *const ds2780_sysfs_bin_attrs[] = { static const struct attribute_group ds2780_sysfs_group = { .attrs = ds2780_sysfs_attrs, - .bin_attrs_new = ds2780_sysfs_bin_attrs, + .bin_attrs = ds2780_sysfs_bin_attrs, }; static const struct attribute_group *ds2780_sysfs_groups[] = { diff --git a/drivers/power/supply/ds2781_battery.c b/drivers/power/supply/ds2781_battery.c index 8a1f1f9835e0..1319e02f3f95 100644 --- a/drivers/power/supply/ds2781_battery.c +++ b/drivers/power/supply/ds2781_battery.c @@ -662,8 +662,8 @@ static const struct bin_attribute ds2781_param_eeprom_bin_attr = { .mode = S_IRUGO | S_IWUSR, }, .size = DS2781_PARAM_EEPROM_SIZE, - .read_new = ds2781_read_param_eeprom_bin, - .write_new = ds2781_write_param_eeprom_bin, + .read = ds2781_read_param_eeprom_bin, + .write = ds2781_write_param_eeprom_bin, }; static ssize_t ds2781_read_user_eeprom_bin(struct file *filp, @@ -708,8 +708,8 @@ static const struct bin_attribute ds2781_user_eeprom_bin_attr = { .mode = S_IRUGO | S_IWUSR, }, .size = DS2781_USER_EEPROM_SIZE, - .read_new = ds2781_read_user_eeprom_bin, - .write_new = ds2781_write_user_eeprom_bin, + .read = ds2781_read_user_eeprom_bin, + .write = ds2781_write_user_eeprom_bin, }; static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2781_get_pmod_enabled, @@ -737,7 +737,7 @@ static const struct bin_attribute *const ds2781_sysfs_bin_attrs[] = { static const struct attribute_group ds2781_sysfs_group = { .attrs = ds2781_sysfs_attrs, - .bin_attrs_new = ds2781_sysfs_bin_attrs, + .bin_attrs = ds2781_sysfs_bin_attrs, }; diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c index d5d215f5ad8b..f5f2566b3a32 100644 --- a/drivers/power/supply/generic-adc-battery.c +++ b/drivers/power/supply/generic-adc-battery.c @@ -166,7 +166,7 @@ static int gab_probe(struct platform_device *pdev) if (!adc_bat) return -ENOMEM; - psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); psy_cfg.drv_data = adc_bat; psy_desc = &adc_bat->psy_desc; psy_desc->name = dev_name(&pdev->dev); diff --git a/drivers/power/supply/goldfish_battery.c b/drivers/power/supply/goldfish_battery.c index 479195e35d73..5aa24e4dc445 100644 --- a/drivers/power/supply/goldfish_battery.c +++ b/drivers/power/supply/goldfish_battery.c @@ -224,12 +224,6 @@ static int goldfish_battery_probe(struct platform_device *pdev) if (data->irq < 0) return -ENODEV; - ret = devm_request_irq(&pdev->dev, data->irq, - goldfish_battery_interrupt, - IRQF_SHARED, pdev->name, data); - if (ret) - return ret; - psy_cfg.drv_data = data; data->ac = devm_power_supply_register(&pdev->dev, @@ -244,6 +238,12 @@ static int goldfish_battery_probe(struct platform_device *pdev) if (IS_ERR(data->battery)) return PTR_ERR(data->battery); + ret = devm_request_irq(&pdev->dev, data->irq, + goldfish_battery_interrupt, + IRQF_SHARED, pdev->name, data); + if (ret) + return ret; + GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK); return 0; } diff --git a/drivers/power/supply/gpio-charger.c b/drivers/power/supply/gpio-charger.c index 46d18ce6a739..2504190eba82 100644 --- a/drivers/power/supply/gpio-charger.c +++ b/drivers/power/supply/gpio-charger.c @@ -79,7 +79,8 @@ static int set_charge_current_limit(struct gpio_charger *gpio_charger, int val) for (i = 0; i < ndescs; i++) { bool val = (mapping.gpiodata >> i) & 1; - gpiod_set_value_cansleep(gpios[ndescs-i-1], val); + + gpiod_set_value_cansleep(gpios[ndescs - i - 1], val); } gpio_charger->charge_current_limit = mapping.limit_ua; @@ -226,14 +227,14 @@ static int init_charge_current_limit(struct device *dev, gpio_charger->current_limit_map_size = len / 2; len = device_property_read_u32_array(dev, "charge-current-limit-mapping", - (u32*) gpio_charger->current_limit_map, len); + (u32 *) gpio_charger->current_limit_map, len); if (len < 0) return len; set_def_limit = !device_property_read_u32(dev, "charge-current-limit-default-microamp", &def_limit); - for (i=0; i < gpio_charger->current_limit_map_size; i++) { + for (i = 0; i < gpio_charger->current_limit_map_size; i++) { if (gpio_charger->current_limit_map[i].limit_ua > cur_limit) { dev_err(dev, "charge-current-limit-mapping not sorted by current in descending order\n"); return -EINVAL; @@ -333,7 +334,7 @@ static int gpio_charger_probe(struct platform_device *pdev) charger_desc->property_is_writeable = gpio_charger_property_is_writeable; - psy_cfg.of_node = dev->of_node; + psy_cfg.fwnode = dev_fwnode(dev); psy_cfg.drv_data = gpio_charger; if (pdata) { @@ -366,7 +367,9 @@ static int gpio_charger_probe(struct platform_device *pdev) platform_set_drvdata(pdev, gpio_charger); - device_init_wakeup(dev, 1); + ret = devm_device_init_wakeup(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to init wakeup\n"); return 0; } diff --git a/drivers/power/supply/huawei-gaokun-battery.c b/drivers/power/supply/huawei-gaokun-battery.c new file mode 100644 index 000000000000..e4dfec3b4241 --- /dev/null +++ b/drivers/power/supply/huawei-gaokun-battery.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * huawei-gaokun-battery - A power supply driver for HUAWEI Matebook E Go + * + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com> + */ + +#include <linux/auxiliary_bus.h> +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/platform_data/huawei-gaokun-ec.h> +#include <linux/power_supply.h> +#include <linux/sprintf.h> + +/* -------------------------------------------------------------------------- */ +/* String Data Reg */ + +#define EC_BAT_VENDOR 0x01 /* from 0x01 to 0x0F, SUNWODA */ +#define EC_BAT_MODEL 0x11 /* from 0x11 to 0x1F, HB30A8P9ECW-22T */ + +#define EC_ADP_STATUS 0x81 +#define EC_AC_STATUS BIT(0) +#define EC_BAT_PRESENT BIT(1) /* BATC._STA */ + +#define EC_BAT_STATUS 0x82 /* _BST */ +#define EC_BAT_DISCHARGING BIT(0) +#define EC_BAT_CHARGING BIT(1) +#define EC_BAT_CRITICAL BIT(2) /* Low Battery Level */ +#define EC_BAT_FULL BIT(3) + +/* -------------------------------------------------------------------------- */ +/* Word Data Reg */ + +/* 0x5A: ? + * 0x5C: ? + * 0x5E: ? + * 0X60: ? + * 0x84: ? + */ + +#define EC_BAT_STATUS_START 0x90 +#define EC_BAT_PERCENTAGE 0x90 +#define EC_BAT_VOLTAGE 0x92 +#define EC_BAT_CAPACITY 0x94 +#define EC_BAT_FULL_CAPACITY 0x96 +/* 0x98: ? */ +#define EC_BAT_CURRENT 0x9A +/* 0x9C: ? */ + +#define EC_BAT_INFO_START 0xA0 +/* 0xA0: POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT? */ +#define EC_BAT_DESIGN_CAPACITY 0xA2 +#define EC_BAT_DESIGN_VOLTAGE 0xA4 +#define EC_BAT_SERIAL_NUMBER 0xA6 +#define EC_BAT_CYCLE_COUNT 0xAA + +/* -------------------------------------------------------------------------- */ +/* Battery Event ID */ + +#define EC_EVENT_BAT_A0 0xA0 +#define EC_EVENT_BAT_A1 0xA1 +#define EC_EVENT_BAT_A2 0xA2 +#define EC_EVENT_BAT_A3 0xA3 +#define EC_EVENT_BAT_B1 0xB1 +/* EVENT B1 A0 A1 repeat about every 1s 2s 3s respectively */ + +/* ACPI _BIX field, Min sampling time, the duration between two _BST */ +#define CACHE_TIME 2000 /* cache time in milliseconds */ + +#define MILLI_TO_MICRO 1000 + +#define SMART_CHARGE_MODE 0 +#define SMART_CHARGE_DELAY 1 +#define SMART_CHARGE_START 2 +#define SMART_CHARGE_END 3 + +#define NO_DELAY_MODE 1 +#define DELAY_MODE 4 + +struct gaokun_psy_bat_status { + __le16 percentage_now; /* 0x90 */ + __le16 voltage_now; + __le16 capacity_now; + __le16 full_capacity; + __le16 unknown1; + __le16 rate_now; + __le16 unknown2; /* 0x9C */ +} __packed; + +struct gaokun_psy_bat_info { + __le16 unknown3; /* 0xA0 */ + __le16 design_capacity; + __le16 design_voltage; + __le16 serial_number; + __le16 padding2; + __le16 cycle_count; /* 0xAA */ +} __packed; + +struct gaokun_psy { + struct gaokun_ec *ec; + struct device *dev; + struct notifier_block nb; + + struct power_supply *bat_psy; + struct power_supply *adp_psy; + + unsigned long update_time; + struct gaokun_psy_bat_status status; + struct gaokun_psy_bat_info info; + + char battery_model[0x10]; /* HB30A8P9ECW-22T, the real one is XXX-22A */ + char battery_serial[0x10]; + char battery_vendor[0x10]; + + int charge_now; + int online; + int bat_present; +}; + +/* -------------------------------------------------------------------------- */ +/* Adapter */ + +static int gaokun_psy_get_adp_status(struct gaokun_psy *ecbat) +{ + /* _PSR */ + int ret; + u8 online; + + ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &online); + if (ret) + return ret; + + ecbat->online = !!(online & EC_AC_STATUS); + + return 0; +} + +static int gaokun_psy_get_adp_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); + int ret; + + ret = gaokun_psy_get_adp_status(ecbat); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ecbat->online; + break; + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = POWER_SUPPLY_USB_TYPE_C; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property gaokun_psy_adp_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_USB_TYPE, +}; + +static const struct power_supply_desc gaokun_psy_adp_desc = { + .name = "gaokun-ec-adapter", + .type = POWER_SUPPLY_TYPE_USB, + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_C), + .get_property = gaokun_psy_get_adp_property, + .properties = gaokun_psy_adp_props, + .num_properties = ARRAY_SIZE(gaokun_psy_adp_props), +}; + +/* -------------------------------------------------------------------------- */ +/* Battery */ + +static inline void gaokun_psy_get_bat_present(struct gaokun_psy *ecbat) +{ + int ret; + u8 present; + + /* Some kind of initialization */ + gaokun_ec_write(ecbat->ec, (u8 []){0x02, 0xB2, 1, 0x90}); + + ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &present); + + ecbat->bat_present = ret ? false : !!(present & EC_BAT_PRESENT); +} + +static inline int gaokun_psy_bat_present(struct gaokun_psy *ecbat) +{ + return ecbat->bat_present; +} + +static int gaokun_psy_get_bat_info(struct gaokun_psy *ecbat) +{ + /* _BIX */ + if (!gaokun_psy_bat_present(ecbat)) + return 0; + + return gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_INFO_START, + sizeof(ecbat->info), (u8 *)&ecbat->info); +} + +static void gaokun_psy_update_bat_charge(struct gaokun_psy *ecbat) +{ + u8 charge; + + gaokun_ec_psy_read_byte(ecbat->ec, EC_BAT_STATUS, &charge); + + switch (charge) { + case EC_BAT_CHARGING: + ecbat->charge_now = POWER_SUPPLY_STATUS_CHARGING; + break; + case EC_BAT_DISCHARGING: + ecbat->charge_now = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case EC_BAT_FULL: + ecbat->charge_now = POWER_SUPPLY_STATUS_FULL; + break; + default: + dev_warn(ecbat->dev, "unknown charge state %d\n", charge); + } +} + +static int gaokun_psy_get_bat_status(struct gaokun_psy *ecbat) +{ + /* _BST */ + int ret; + + if (time_before(jiffies, ecbat->update_time + + msecs_to_jiffies(CACHE_TIME))) + return 0; + + gaokun_psy_update_bat_charge(ecbat); + ret = gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_STATUS_START, + sizeof(ecbat->status), (u8 *)&ecbat->status); + + ecbat->update_time = jiffies; + + return ret; +} + +static void gaokun_psy_init(struct gaokun_psy *ecbat) +{ + gaokun_psy_get_bat_present(ecbat); + if (!gaokun_psy_bat_present(ecbat)) + return; + + gaokun_psy_get_bat_info(ecbat); + + snprintf(ecbat->battery_serial, sizeof(ecbat->battery_serial), + "%d", le16_to_cpu(ecbat->info.serial_number)); + + gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_VENDOR, + sizeof(ecbat->battery_vendor) - 1, + ecbat->battery_vendor); + + gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_MODEL, + sizeof(ecbat->battery_model) - 1, + ecbat->battery_model); + + ecbat->battery_model[14] = 'A'; /* FIX UP */ +} + +static int gaokun_psy_get_bat_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); + u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE]; + int ret; + + if (gaokun_psy_bat_present(ecbat)) + gaokun_psy_get_bat_status(ecbat); + else if (psp != POWER_SUPPLY_PROP_PRESENT) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = ecbat->charge_now; + break; + + case POWER_SUPPLY_PROP_PRESENT: + val->intval = ecbat->bat_present; + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + + case POWER_SUPPLY_PROP_CYCLE_COUNT: + val->intval = le16_to_cpu(ecbat->info.cycle_count); + break; + + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = le16_to_cpu(ecbat->info.design_voltage) * MILLI_TO_MICRO; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = le16_to_cpu(ecbat->status.voltage_now) * MILLI_TO_MICRO; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = (s16)le16_to_cpu(ecbat->status.rate_now) * MILLI_TO_MICRO; + break; + + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = le16_to_cpu(ecbat->info.design_capacity) * MILLI_TO_MICRO; + break; + + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = le16_to_cpu(ecbat->status.full_capacity) * MILLI_TO_MICRO; + break; + + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = le16_to_cpu(ecbat->status.capacity_now) * MILLI_TO_MICRO; + break; + + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, buf); + if (ret) + return ret; + + if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD) + val->intval = buf[SMART_CHARGE_START]; + else + val->intval = buf[SMART_CHARGE_END]; + break; + + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = le16_to_cpu(ecbat->status.percentage_now); + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = ecbat->battery_model; + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = ecbat->battery_vendor; + break; + + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = ecbat->battery_serial; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int gaokun_psy_set_bat_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); + u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE]; + int ret; + + if (!gaokun_psy_bat_present(ecbat)) + return -ENODEV; + + ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, buf); + if (ret) + return ret; + + switch (psp) { + /* + * Resetting another thershold makes single thersold setting more likely + * to succeed. But setting start = end makes thing strange(failure). + */ + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + buf[SMART_CHARGE_START] = val->intval; + if (buf[SMART_CHARGE_START] > buf[SMART_CHARGE_END]) + buf[SMART_CHARGE_END] = buf[SMART_CHARGE_START] + 1; + return gaokun_ec_psy_set_smart_charge(ecbat->ec, buf); + + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + buf[SMART_CHARGE_END] = val->intval; + if (buf[SMART_CHARGE_END] < buf[SMART_CHARGE_START]) + buf[SMART_CHARGE_START] = buf[SMART_CHARGE_END] - 1; + return gaokun_ec_psy_set_smart_charge(ecbat->ec, buf); + + default: + return -EINVAL; + } + + return 0; +} + +static int gaokun_psy_is_bat_property_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD || + psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD; +} + +static enum power_supply_property gaokun_psy_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static const struct power_supply_desc gaokun_psy_bat_desc = { + .name = "gaokun-ec-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = gaokun_psy_get_bat_property, + .set_property = gaokun_psy_set_bat_property, + .property_is_writeable = gaokun_psy_is_bat_property_writeable, + .properties = gaokun_psy_bat_props, + .num_properties = ARRAY_SIZE(gaokun_psy_bat_props), +}; + +/* -------------------------------------------------------------------------- */ +/* Sysfs */ + +/* + * Note that, HUAWEI calls them SBAC/GBAC and SBCM/GBCM in DSDT, they are likely + * Set/Get Battery Adaptive Charging and Set/Get Battery Charging Mode. + */ + +/* battery adaptive charge */ +static ssize_t battery_adaptive_charge_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = to_power_supply(dev); + struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); + int ret; + bool on; + + ret = gaokun_ec_psy_get_smart_charge_enable(ecbat->ec, &on); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", on); +} + +static ssize_t battery_adaptive_charge_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct power_supply *psy = to_power_supply(dev); + struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); + int ret; + bool on; + + if (kstrtobool(buf, &on)) + return -EINVAL; + + ret = gaokun_ec_psy_set_smart_charge_enable(ecbat->ec, on); + if (ret) + return ret; + + return size; +} + +static DEVICE_ATTR_RW(battery_adaptive_charge); + +static inline int get_charge_delay(u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE]) +{ + return buf[SMART_CHARGE_MODE] == NO_DELAY_MODE ? 0 : buf[SMART_CHARGE_DELAY]; +} + +static inline void +set_charge_delay(u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE], u8 delay) +{ + if (delay) { + buf[SMART_CHARGE_DELAY] = delay; + buf[SMART_CHARGE_MODE] = DELAY_MODE; + } else { + /* No writing zero, there is a specific mode for it. */ + buf[SMART_CHARGE_MODE] = NO_DELAY_MODE; + } +} + +/* Smart charge */ +static ssize_t smart_charge_delay_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = to_power_supply(dev); + struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); + u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE]; + int ret; + + ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, bf); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", get_charge_delay(bf)); +} + +static ssize_t smart_charge_delay_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct power_supply *psy = to_power_supply(dev); + struct gaokun_psy *ecbat = power_supply_get_drvdata(psy); + u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE]; + u8 delay; + int ret; + + if (kstrtou8(buf, 10, &delay)) + return -EINVAL; + + ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, bf); + if (ret) + return ret; + + set_charge_delay(bf, delay); + + ret = gaokun_ec_psy_set_smart_charge(ecbat->ec, bf); + if (ret) + return ret; + + return size; +} + +static DEVICE_ATTR_RW(smart_charge_delay); + +static struct attribute *gaokun_psy_features_attrs[] = { + &dev_attr_battery_adaptive_charge.attr, + &dev_attr_smart_charge_delay.attr, + NULL, +}; +ATTRIBUTE_GROUPS(gaokun_psy_features); + +static int gaokun_psy_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct gaokun_psy *ecbat = container_of(nb, struct gaokun_psy, nb); + + switch (action) { + case EC_EVENT_BAT_A2: + case EC_EVENT_BAT_B1: + gaokun_psy_get_bat_info(ecbat); + return NOTIFY_OK; + + case EC_EVENT_BAT_A0: + gaokun_psy_get_adp_status(ecbat); + power_supply_changed(ecbat->adp_psy); + msleep(10); + fallthrough; + + case EC_EVENT_BAT_A1: + case EC_EVENT_BAT_A3: + if (action == EC_EVENT_BAT_A3) { + gaokun_psy_get_bat_info(ecbat); + msleep(100); + } + gaokun_psy_get_bat_status(ecbat); + power_supply_changed(ecbat->bat_psy); + return NOTIFY_OK; + + default: + return NOTIFY_DONE; + } +} + +static int gaokun_psy_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct gaokun_ec *ec = adev->dev.platform_data; + struct power_supply_config psy_cfg = {}; + struct device *dev = &adev->dev; + struct gaokun_psy *ecbat; + + ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL); + if (!ecbat) + return -ENOMEM; + + ecbat->ec = ec; + ecbat->dev = dev; + ecbat->nb.notifier_call = gaokun_psy_notify; + + auxiliary_set_drvdata(adev, ecbat); + + psy_cfg.drv_data = ecbat; + ecbat->adp_psy = devm_power_supply_register(dev, &gaokun_psy_adp_desc, + &psy_cfg); + if (IS_ERR(ecbat->adp_psy)) + return dev_err_probe(dev, PTR_ERR(ecbat->adp_psy), + "Failed to register AC power supply\n"); + + psy_cfg.supplied_to = (char **)&gaokun_psy_bat_desc.name; + psy_cfg.num_supplicants = 1; + psy_cfg.no_wakeup_source = true; + psy_cfg.attr_grp = gaokun_psy_features_groups; + ecbat->bat_psy = devm_power_supply_register(dev, &gaokun_psy_bat_desc, + &psy_cfg); + if (IS_ERR(ecbat->bat_psy)) + return dev_err_probe(dev, PTR_ERR(ecbat->bat_psy), + "Failed to register battery power supply\n"); + gaokun_psy_init(ecbat); + + return gaokun_ec_register_notify(ec, &ecbat->nb); +} + +static void gaokun_psy_remove(struct auxiliary_device *adev) +{ + struct gaokun_psy *ecbat = auxiliary_get_drvdata(adev); + + gaokun_ec_unregister_notify(ecbat->ec, &ecbat->nb); +} + +static const struct auxiliary_device_id gaokun_psy_id_table[] = { + { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_PSY, }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, gaokun_psy_id_table); + +static struct auxiliary_driver gaokun_psy_driver = { + .name = GAOKUN_DEV_PSY, + .id_table = gaokun_psy_id_table, + .probe = gaokun_psy_probe, + .remove = gaokun_psy_remove, +}; + +module_auxiliary_driver(gaokun_psy_driver); + +MODULE_DESCRIPTION("HUAWEI Matebook E Go psy driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/ingenic-battery.c b/drivers/power/supply/ingenic-battery.c index 0a40f425c277..b111c7ce2be3 100644 --- a/drivers/power/supply/ingenic-battery.c +++ b/drivers/power/supply/ingenic-battery.c @@ -146,7 +146,7 @@ static int ingenic_battery_probe(struct platform_device *pdev) desc->num_properties = ARRAY_SIZE(ingenic_battery_properties); desc->get_property = ingenic_battery_get_property; psy_cfg.drv_data = bat; - psy_cfg.of_node = dev->of_node; + psy_cfg.fwnode = dev_fwnode(dev); bat->battery = devm_power_supply_register(dev, desc, &psy_cfg); if (IS_ERR(bat->battery)) diff --git a/drivers/power/supply/intel_dc_ti_battery.c b/drivers/power/supply/intel_dc_ti_battery.c new file mode 100644 index 000000000000..67a75281b0ac --- /dev/null +++ b/drivers/power/supply/intel_dc_ti_battery.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Battery driver for the coulomb-counter of the Intel Dollar Cove TI PMIC + * + * Note the Intel Dollar Cove TI PMIC coulomb-counter is not a full-featured + * autonomous fuel-gauge. It is intended to work together with an always on + * micro-controller monitoring it. + * + * Since Linux does not monitor coulomb-counter changes while the device + * is off or suspended, voltage based capacity estimation from + * the adc-battery-helper code is used. + * + * Copyright (C) 2024 Hans de Goede <hansg@kernel.org> + * + * Register definitions and calibration code was taken from + * kernel/drivers/platform/x86/dc_ti_cc.c from the Acer A1-840 Android kernel + * which has the following copyright header: + * + * Copyright (C) 2014 Intel Corporation + * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com> + * + * dc_ti_cc.c is part of the Acer A1-840 Android kernel source-code archive + * named: "App. Guide_Acer_20151221_A_A.zip" + * which is distributed by Acer from the Acer A1-840 support page: + * https://www.acer.com/us-en/support/product-support/A1-840/downloads + */ + +#include <linux/acpi.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/iio/consumer.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/power_supply.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/timekeeping.h> + +#include "adc-battery-helper.h" + +#define DC_TI_PMIC_VERSION_REG 0x00 +#define PMIC_VERSION_A0 0xC0 +#define PMIC_VERSION_A1 0xC1 + +#define DC_TI_CC_CNTL_REG 0x60 +#define CC_CNTL_CC_CTR_EN BIT(0) +#define CC_CNTL_CC_CLR_EN BIT(1) +#define CC_CNTL_CC_CAL_EN BIT(2) +#define CC_CNTL_CC_OFFSET_EN BIT(3) +#define CC_CNTL_SMPL_INTVL GENMASK(5, 4) +#define CC_CNTL_SMPL_INTVL_15MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 0) +#define CC_CNTL_SMPL_INTVL_62MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 1) +#define CC_CNTL_SMPL_INTVL_125MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 2) +#define CC_CNTL_SMPL_INTVL_250MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 3) + +#define DC_TI_SMPL_CTR0_REG 0x69 +#define DC_TI_SMPL_CTR1_REG 0x68 +#define DC_TI_SMPL_CTR2_REG 0x67 + +#define DC_TI_CC_OFFSET_HI_REG 0x61 +#define CC_OFFSET_HI_MASK 0x3F +#define DC_TI_CC_OFFSET_LO_REG 0x62 + +#define DC_TI_SW_OFFSET_REG 0x6C + +#define DC_TI_CC_ACC3_REG 0x63 +#define DC_TI_CC_ACC2_REG 0x64 +#define DC_TI_CC_ACC1_REG 0x65 +#define DC_TI_CC_ACC0_REG 0x66 + +#define DC_TI_CC_INTG1_REG 0x6A +#define DC_TI_CC_INTG1_MASK 0x3F +#define DC_TI_CC_INTG0_REG 0x6B + +#define DC_TI_EEPROM_ACCESS_CONTROL 0x88 +#define EEPROM_UNLOCK 0xDA +#define EEPROM_LOCK 0x00 + +#define DC_TI_EEPROM_CC_GAIN_REG 0xF4 +#define CC_TRIM_REVISION GENMASK(3, 0) +#define CC_GAIN_CORRECTION GENMASK(7, 4) + +#define PMIC_VERSION_A0_TRIM_REV 3 +#define PMIC_VERSION_A1_MIN_TRIM_REV 1 + +#define DC_TI_EEPROM_CC_OFFSET_REG 0xFD + +#define DC_TI_EEPROM_CTRL 0xFE +#define EEPROM_BANK0_SEL 0x01 +#define EEPROM_BANK1_SEL 0x02 + +#define SMPL_INTVL_US 15000 +#define SMPL_INTVL_MS (SMPL_INTVL_US / USEC_PER_MSEC) +#define CALIBRATION_TIME_US (10 * SMPL_INTVL_US) +#define SLEEP_SLACK_US 2500 + +/* CC gain correction is in 0.0025 increments */ +#define CC_GAIN_STEP 25 +#define CC_GAIN_DIV 10000 + +/* CC offset is in 0.5 units per 250ms (default sample interval) */ +#define CC_OFFSET_DIV 2 +#define CC_OFFSET_SMPL_INTVL_MS 250 + +/* CC accumulator scale is 366.2 ųCoulumb / unit */ +#define CC_ACC_TO_UA(acc, smpl_ctr) \ + ((acc) * (3662 * MSEC_PER_SEC / 10) / ((smpl_ctr) * SMPL_INTVL_MS)) + +#define DEV_NAME "chtdc_ti_battery" + +struct dc_ti_battery_chip { + /* Must be the first member see adc-battery-helper documentation */ + struct adc_battery_helper helper; + struct device *dev; + struct regmap *regmap; + struct iio_channel *vbat_channel; + struct power_supply *psy; + int cc_gain; + int cc_offset; +}; + +static int dc_ti_battery_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr) +{ + struct dc_ti_battery_chip *chip = power_supply_get_drvdata(psy); + ktime_t ktime; + s64 sleep_usec; + unsigned int reg_val; + s32 acc, smpl_ctr; + int ret; + + /* + * Enable coulomb-counter before reading Vbat from ADC, so that the CC + * samples are from the same time period as the Vbat reading. + */ + ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG, + CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN | CC_CNTL_CC_CTR_EN); + if (ret) + goto out_err; + + ktime = ktime_get(); + + /* Read Vbat, convert IIO mV to power-supply ųV */ + ret = iio_read_channel_processed_scale(chip->vbat_channel, volt, 1000); + if (ret < 0) + goto out_err; + + ktime = ktime_sub(ktime_get(), ktime); + + /* Sleep at least 3 sample-times + slack to get 3+ CC samples */ + sleep_usec = 3 * SMPL_INTVL_US + SLEEP_SLACK_US - ktime_to_us(ktime); + if (sleep_usec > 0 && sleep_usec < 1000000) + usleep_range(sleep_usec, sleep_usec + SLEEP_SLACK_US); + + /* + * The PMIC latches the coulomb- and sample-counters upon reading the + * CC_ACC0 register. Reading multiple registers at once is not supported. + * + * Step 1: Read CC_ACC0 - CC_ACC3 + */ + ret = regmap_read(chip->regmap, DC_TI_CC_ACC0_REG, ®_val); + if (ret) + goto out_err; + + acc = reg_val; + + ret = regmap_read(chip->regmap, DC_TI_CC_ACC1_REG, ®_val); + if (ret) + goto out_err; + + acc |= reg_val << 8; + + ret = regmap_read(chip->regmap, DC_TI_CC_ACC2_REG, ®_val); + if (ret) + goto out_err; + + acc |= reg_val << 16; + + ret = regmap_read(chip->regmap, DC_TI_CC_ACC3_REG, ®_val); + if (ret) + goto out_err; + + acc |= reg_val << 24; + + /* Step 2: Read SMPL_CTR0 - SMPL_CTR2 */ + ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR0_REG, ®_val); + if (ret) + goto out_err; + + smpl_ctr = reg_val; + + ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR1_REG, ®_val); + if (ret) + goto out_err; + + smpl_ctr |= reg_val << 8; + + ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR2_REG, ®_val); + if (ret) + goto out_err; + + smpl_ctr |= reg_val << 16; + + /* Disable the coulumb-counter again */ + ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG, + CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN); + if (ret) + goto out_err; + + /* Apply calibration */ + acc -= chip->cc_offset * smpl_ctr * SMPL_INTVL_MS / + (CC_OFFSET_SMPL_INTVL_MS * CC_OFFSET_DIV); + acc = acc * (CC_GAIN_DIV - chip->cc_gain * CC_GAIN_STEP) / CC_GAIN_DIV; + *curr = CC_ACC_TO_UA(acc, smpl_ctr); + + return 0; + +out_err: + dev_err(chip->dev, "IO-error %d communicating with PMIC\n", ret); + return ret; +} + +static const struct power_supply_desc dc_ti_battery_psy_desc = { + .name = "intel_dc_ti_battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = adc_battery_helper_get_property, + .external_power_changed = adc_battery_helper_external_power_changed, + .properties = adc_battery_helper_properties, + .num_properties = ADC_HELPER_NUM_PROPERTIES, +}; + +static int dc_ti_battery_hw_init(struct dc_ti_battery_chip *chip) +{ + u8 pmic_version, cc_trim_rev; + unsigned int reg_val; + int ret; + + /* Set sample rate to 15 ms and calibrate the coulomb-counter */ + ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG, + CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN | + CC_CNTL_CC_CAL_EN | CC_CNTL_CC_CTR_EN); + if (ret) + goto out; + + fsleep(CALIBRATION_TIME_US); + + /* Disable coulomb-counter it is only used while getting the current */ + ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG, + CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN); + if (ret) + goto out; + + ret = regmap_read(chip->regmap, DC_TI_PMIC_VERSION_REG, ®_val); + if (ret) + goto out; + + pmic_version = reg_val; + + /* + * As per the PMIC vendor (TI), the calibration offset and gain err + * values are stored in EEPROM Bank 0 and Bank 1 of the PMIC. + * We need to read the stored offset and gain margins and need + * to apply the corrections to the raw coulomb counter value. + */ + + /* Unlock the EEPROM Access */ + ret = regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_UNLOCK); + if (ret) + goto out; + + /* Select Bank 1 to read CC GAIN Err correction */ + ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK1_SEL); + if (ret) + goto out; + + ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_GAIN_REG, ®_val); + if (ret) + goto out; + + cc_trim_rev = FIELD_GET(CC_TRIM_REVISION, reg_val); + + dev_dbg(chip->dev, "pmic-ver 0x%02x trim-rev %d\n", pmic_version, cc_trim_rev); + + if (!(pmic_version == PMIC_VERSION_A0 && cc_trim_rev == PMIC_VERSION_A0_TRIM_REV) && + !(pmic_version == PMIC_VERSION_A1 && cc_trim_rev >= PMIC_VERSION_A1_MIN_TRIM_REV)) { + dev_dbg(chip->dev, "unsupported trim-revision, using uncalibrated CC values\n"); + goto out_relock; + } + + chip->cc_gain = 1 - (int)FIELD_GET(CC_GAIN_CORRECTION, reg_val); + + /* Select Bank 0 to read CC OFFSET Correction */ + ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK0_SEL); + if (ret) + goto out_relock; + + ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_OFFSET_REG, ®_val); + if (ret) + goto out_relock; + + chip->cc_offset = (s8)reg_val; + + dev_dbg(chip->dev, "cc-offset %d cc-gain %d\n", chip->cc_offset, chip->cc_gain); + +out_relock: + /* Re-lock the EEPROM Access */ + regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_LOCK); +out: + if (ret) + dev_err(chip->dev, "IO-error %d initializing PMIC\n", ret); + + return ret; +} + +static int dc_ti_battery_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent); + struct power_supply_config psy_cfg = {}; + struct fwnode_reference_args args; + struct gpio_desc *charge_finished; + struct dc_ti_battery_chip *chip; + int ret; + + /* On most devices with a Dollar Cove TI the battery is handled by ACPI */ + if (!acpi_quirk_skip_acpi_ac_and_battery()) + return -ENODEV; + + /* ACPI glue code adds a "monitored-battery" fwnode, wait for this */ + ret = fwnode_property_get_reference_args(dev_fwnode(dev), "monitored-battery", + NULL, 0, 0, &args); + if (ret) { + dev_dbg(dev, "fwnode_property_get_ref() ret %d\n", ret); + return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for monitored-battery fwnode\n"); + } + + fwnode_handle_put(args.fwnode); + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = dev; + chip->regmap = pmic->regmap; + + chip->vbat_channel = devm_iio_channel_get(dev, "VBAT"); + if (IS_ERR(chip->vbat_channel)) { + dev_dbg(dev, "devm_iio_channel_get() ret %ld\n", PTR_ERR(chip->vbat_channel)); + return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for VBAT IIO channel\n"); + } + + charge_finished = devm_gpiod_get_optional(dev, "charged", GPIOD_IN); + if (IS_ERR(charge_finished)) + return dev_err_probe(dev, PTR_ERR(charge_finished), "Getting charged GPIO\n"); + + ret = dc_ti_battery_hw_init(chip); + if (ret) + return ret; + + platform_set_drvdata(pdev, chip); + + psy_cfg.drv_data = chip; + chip->psy = devm_power_supply_register(dev, &dc_ti_battery_psy_desc, &psy_cfg); + if (IS_ERR(chip->psy)) + return PTR_ERR(chip->psy); + + return adc_battery_helper_init(&chip->helper, chip->psy, + dc_ti_battery_get_voltage_and_current_now, + charge_finished); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(dc_ti_battery_pm_ops, adc_battery_helper_suspend, + adc_battery_helper_resume, NULL); + +static struct platform_driver dc_ti_battery_driver = { + .driver = { + .name = DEV_NAME, + .pm = pm_sleep_ptr(&dc_ti_battery_pm_ops), + }, + .probe = dc_ti_battery_probe, +}; +module_platform_driver(dc_ti_battery_driver); + +MODULE_ALIAS("platform:" DEV_NAME); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_DESCRIPTION("Intel Dollar Cove (TI) battery driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/ip5xxx_power.c b/drivers/power/supply/ip5xxx_power.c index c448e0ac0dfa..24eea7a91b30 100644 --- a/drivers/power/supply/ip5xxx_power.c +++ b/drivers/power/supply/ip5xxx_power.c @@ -828,10 +828,9 @@ static void ip5xxx_setup_regs(struct device *dev, struct ip5xxx *ip5xxx, static int ip5xxx_power_probe(struct i2c_client *client) { - const struct ip5xxx_regfield_config *fields = &ip51xx_fields; + const struct ip5xxx_regfield_config *fields; struct power_supply_config psy_cfg = {}; struct device *dev = &client->dev; - const struct of_device_id *of_id; struct power_supply *psy; struct ip5xxx *ip5xxx; @@ -843,12 +842,10 @@ static int ip5xxx_power_probe(struct i2c_client *client) if (IS_ERR(ip5xxx->regmap)) return PTR_ERR(ip5xxx->regmap); - of_id = i2c_of_match_device(dev->driver->of_match_table, client); - if (of_id) - fields = (const struct ip5xxx_regfield_config *)of_id->data; + fields = i2c_get_match_data(client) ?: &ip51xx_fields; ip5xxx_setup_regs(dev, ip5xxx, fields); - psy_cfg.of_node = dev->of_node; + psy_cfg.fwnode = dev_fwnode(dev); psy_cfg.drv_data = ip5xxx; psy = devm_power_supply_register(dev, &ip5xxx_battery_desc, &psy_cfg); diff --git a/drivers/power/supply/ipaq_micro_battery.c b/drivers/power/supply/ipaq_micro_battery.c index 7e0568a5353f..5e3fe3852d0e 100644 --- a/drivers/power/supply/ipaq_micro_battery.c +++ b/drivers/power/supply/ipaq_micro_battery.c @@ -7,6 +7,7 @@ * Author : Linus Walleij <linus.walleij@linaro.org> */ +#include <linux/devm-helpers.h> #include <linux/module.h> #include <linux/init.h> #include <linux/platform_device.h> @@ -232,48 +233,31 @@ static int micro_batt_probe(struct platform_device *pdev) return -ENOMEM; mb->micro = dev_get_drvdata(pdev->dev.parent); - mb->wq = alloc_workqueue("ipaq-battery-wq", WQ_MEM_RECLAIM, 0); + mb->wq = devm_alloc_workqueue(&pdev->dev, "ipaq-battery-wq", + WQ_MEM_RECLAIM | WQ_PERCPU, 0); if (!mb->wq) return -ENOMEM; - INIT_DELAYED_WORK(&mb->update, micro_battery_work); + ret = devm_delayed_work_autocancel(&pdev->dev, &mb->update, micro_battery_work); + if (ret) + return ret; + platform_set_drvdata(pdev, mb); queue_delayed_work(mb->wq, &mb->update, 1); - micro_batt_power = power_supply_register(&pdev->dev, - µ_batt_power_desc, NULL); - if (IS_ERR(micro_batt_power)) { - ret = PTR_ERR(micro_batt_power); - goto batt_err; - } + micro_batt_power = devm_power_supply_register(&pdev->dev, + µ_batt_power_desc, + NULL); + if (IS_ERR(micro_batt_power)) + return PTR_ERR(micro_batt_power); - micro_ac_power = power_supply_register(&pdev->dev, - µ_ac_power_desc, NULL); - if (IS_ERR(micro_ac_power)) { - ret = PTR_ERR(micro_ac_power); - goto ac_err; - } + micro_ac_power = devm_power_supply_register(&pdev->dev, + µ_ac_power_desc, NULL); + if (IS_ERR(micro_ac_power)) + return PTR_ERR(micro_ac_power); dev_info(&pdev->dev, "iPAQ micro battery driver\n"); return 0; - -ac_err: - power_supply_unregister(micro_batt_power); -batt_err: - cancel_delayed_work_sync(&mb->update); - destroy_workqueue(mb->wq); - return ret; -} - -static void micro_batt_remove(struct platform_device *pdev) - -{ - struct micro_battery *mb = platform_get_drvdata(pdev); - - power_supply_unregister(micro_ac_power); - power_supply_unregister(micro_batt_power); - cancel_delayed_work_sync(&mb->update); - destroy_workqueue(mb->wq); } static int __maybe_unused micro_batt_suspend(struct device *dev) @@ -302,7 +286,6 @@ static struct platform_driver micro_batt_device_driver = { .pm = µ_batt_dev_pm_ops, }, .probe = micro_batt_probe, - .remove = micro_batt_remove, }; module_platform_driver(micro_batt_device_driver); diff --git a/drivers/power/supply/lego_ev3_battery.c b/drivers/power/supply/lego_ev3_battery.c index 9085de0ae1b2..28454de05761 100644 --- a/drivers/power/supply/lego_ev3_battery.c +++ b/drivers/power/supply/lego_ev3_battery.c @@ -23,6 +23,7 @@ #include <linux/mod_devicetable.h> #include <linux/platform_device.h> #include <linux/power_supply.h> +#include <linux/property.h> struct lego_ev3_battery { struct iio_channel *iio_v; @@ -198,7 +199,7 @@ static int lego_ev3_battery_probe(struct platform_device *pdev) batt->v_min = 48000000; } - psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); psy_cfg.drv_data = batt; batt->psy = devm_power_supply_register(dev, &lego_ev3_battery_desc, diff --git a/drivers/power/supply/lt3651-charger.c b/drivers/power/supply/lt3651-charger.c index 8de500ffad95..ebfbdbcb7683 100644 --- a/drivers/power/supply/lt3651-charger.c +++ b/drivers/power/supply/lt3651-charger.c @@ -131,7 +131,7 @@ static int lt3651_charger_probe(struct platform_device *pdev) charger_desc->properties = lt3651_charger_properties; charger_desc->num_properties = ARRAY_SIZE(lt3651_charger_properties); charger_desc->get_property = lt3651_charger_get_property; - psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); psy_cfg.drv_data = lt3651_charger; lt3651_charger->charger = devm_power_supply_register(&pdev->dev, diff --git a/drivers/power/supply/ltc4162-l-charger.c b/drivers/power/supply/ltc4162-l-charger.c index 23eb426295db..9dd74fa9552d 100644 --- a/drivers/power/supply/ltc4162-l-charger.c +++ b/drivers/power/supply/ltc4162-l-charger.c @@ -1119,7 +1119,7 @@ static const struct regmap_config ltc4162l_regmap_config = { .writeable_reg = ltc4162l_is_writeable_reg, .volatile_reg = ltc4162l_is_volatile_reg, .max_register = LTC4162L_INPUT_UNDERVOLTAGE_DAC, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, }; static void ltc4162l_clear_interrupts(struct ltc4162l_info *info) @@ -1185,7 +1185,7 @@ static int ltc4162l_probe(struct i2c_client *client) if (!device_property_read_u32(dev, "lltc,cell-count", &value)) info->cell_count = value; - ltc4162l_config.of_node = dev->of_node; + ltc4162l_config.fwnode = dev_fwnode(dev); ltc4162l_config.drv_data = info; ltc4162l_config.attr_grp = ltc4162l_attr_groups; diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c new file mode 100644 index 000000000000..33ca07460f3a --- /dev/null +++ b/drivers/power/supply/macsmc-power.c @@ -0,0 +1,855 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC Power/Battery Management Driver + * + * This driver exposes battery telemetry (voltage, current, temperature, health) + * and AC adapter status provided by the Apple SMC (System Management Controller) + * on Apple Silicon systems. + * + * Copyright The Asahi Linux Contributors + */ + +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/devm-helpers.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/mfd/macsmc.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/reboot.h> +#include <linux/workqueue.h> + +#define MAX_STRING_LENGTH 256 + +/* + * The SMC reports charge in mAh (Coulombs) but energy in mWh (Joules). + * We lack a register for "Nominal Voltage" or "Energy Accumulator". + * We use a fixed 3.8V/cell constant to approximate energy stats for userspace, + * derived from empirical data across supported MacBook models. + */ +#define MACSMC_NOMINAL_CELL_VOLTAGE_MV 3800 + +/* SMC Key Flags */ +#define CHNC_BATTERY_FULL BIT(0) +#define CHNC_NO_CHARGER BIT(7) +#define CHNC_NOCHG_CH0C BIT(14) +#define CHNC_NOCHG_CH0B_CH0K BIT(15) +#define CHNC_BATTERY_FULL_2 BIT(18) +#define CHNC_BMS_BUSY BIT(23) +#define CHNC_CHLS_LIMIT BIT(24) +#define CHNC_NOAC_CH0J BIT(53) +#define CHNC_NOAC_CH0I BIT(54) + +#define CH0R_LOWER_FLAGS GENMASK(15, 0) +#define CH0R_NOAC_CH0I BIT(0) +#define CH0R_NOAC_DISCONNECTED BIT(4) +#define CH0R_NOAC_CH0J BIT(5) +#define CH0R_BMS_BUSY BIT(8) +#define CH0R_NOAC_CH0K BIT(9) +#define CH0R_NOAC_CHWA BIT(11) + +#define CH0X_CH0C BIT(0) +#define CH0X_CH0B BIT(1) + +#define ACSt_CAN_BOOT_AP BIT(2) +#define ACSt_CAN_BOOT_IBOOT BIT(1) + +#define CHWA_CHLS_FIXED_START_OFFSET 5 +#define CHLS_MIN_END_THRESHOLD 10 +#define CHLS_FORCE_DISCHARGE 0x100 +#define CHWA_FIXED_END_THRESHOLD 80 +#define CHWA_PROP_WRITE_THRESHOLD 95 + +#define MACSMC_MAX_BATT_PROPS 50 +#define MACSMC_MAX_AC_PROPS 10 + +struct macsmc_power { + struct device *dev; + struct apple_smc *smc; + + struct power_supply_desc ac_desc; + struct power_supply_desc batt_desc; + + struct power_supply *batt; + struct power_supply *ac; + + char model_name[MAX_STRING_LENGTH]; + char serial_number[MAX_STRING_LENGTH]; + char mfg_date[MAX_STRING_LENGTH]; + + /* Supported feature flags based on SMC key presence */ + bool has_chwa; /* Charge limit (Modern firmware) */ + bool has_chls; /* Charge limit (Older firmware) */ + bool has_ch0i; /* Force discharge (Older firmware) */ + bool has_ch0c; /* Inhibit charge (Older firmware) */ + bool has_chte; /* Inhibit charge (Modern firmware) */ + + u8 num_cells; + int nominal_voltage_mv; + + struct notifier_block nb; + struct work_struct critical_work; + bool emergency_shutdown_triggered; + bool orderly_shutdown_triggered; +}; + +static int macsmc_battery_get_status(struct macsmc_power *power) +{ + u64 nocharge_flags; + u32 nopower_flags; + u16 ac_current; + int charge_limit = 0; + bool limited = false; + bool flag; + int ret; + + /* + * B0AV (Voltage) is fundamental. If we can't read it, we assume the + * battery is gone. CHCE (Hardware charger present) / CHCC (Hardware + * charger capable) are fundamental status flags. + * BSFC (System full charge) / CHSC (System charging) are fundamental + * status flags. + */ + + /* Check if power input is inhibited (e.g. BMS balancing cycle) */ + ret = apple_smc_read_u32(power->smc, SMC_KEY(CH0R), &nopower_flags); + if (!ret && (nopower_flags & CH0R_LOWER_FLAGS & ~CH0R_BMS_BUSY)) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* Check if charger is present */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCE), &flag); + if (ret < 0) + return ret; + if (!flag) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* Check if AC is charge capable */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCC), &flag); + if (ret < 0) + return ret; + if (!flag) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* Check if AC input limit is too low */ + ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &ac_current); + if (!ret && ac_current < 100) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* Check if battery is full */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC), &flag); + if (ret < 0) + return ret; + if (flag) + return POWER_SUPPLY_STATUS_FULL; + + /* Check for user-defined charge limits */ + if (power->has_chls) { + u16 vu16; + + ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16); + if (ret == 0 && (vu16 & 0xff) >= CHLS_MIN_END_THRESHOLD) + charge_limit = (vu16 & 0xff) - CHWA_CHLS_FIXED_START_OFFSET; + } else if (power->has_chwa) { + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag); + if (ret == 0 && flag) + charge_limit = CHWA_FIXED_END_THRESHOLD - CHWA_CHLS_FIXED_START_OFFSET; + } + + if (charge_limit > 0) { + u8 buic = 0; + + if (apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &buic) >= 0 && + buic >= charge_limit) + limited = true; + } + + /* Check charging inhibitors */ + ret = apple_smc_read_u64(power->smc, SMC_KEY(CHNC), &nocharge_flags); + if (!ret) { + if (nocharge_flags & CHNC_BATTERY_FULL) + return POWER_SUPPLY_STATUS_FULL; + /* BMS busy shows up as inhibit, but we treat it as charging */ + else if (nocharge_flags == CHNC_BMS_BUSY && !limited) + return POWER_SUPPLY_STATUS_CHARGING; + else if (nocharge_flags) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + else + return POWER_SUPPLY_STATUS_CHARGING; + } + + /* Fallback: System charging flag */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHSC), &flag); + if (ret < 0) + return ret; + if (!flag) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + + return POWER_SUPPLY_STATUS_CHARGING; +} + +static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power) +{ + int ret; + u8 val8; + u8 chte_buf[4]; + + if (power->has_ch0i) { + ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val8); + if (ret) + return ret; + if (val8 & CH0R_NOAC_CH0I) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE; + } + + if (power->has_chte) { + ret = apple_smc_read(power->smc, SMC_KEY(CHTE), chte_buf, 4); + if (ret < 0) + return ret; + + if (chte_buf[0] == 0x01) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + } else if (power->has_ch0c) { + ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val8); + if (ret) + return ret; + if (val8 & CH0X_CH0C) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + } + + return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; +} + +static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int val) +{ + int ret; + + switch (val) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: + /* Reset all inhibitors to a known-good 'auto' state */ + if (power->has_ch0i) { + ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0); + if (ret) + return ret; + } + + if (power->has_chte) { + ret = apple_smc_write_u32(power->smc, SMC_KEY(CHTE), 0); + if (ret) + return ret; + } else if (power->has_ch0c) { + ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0); + if (ret) + return ret; + } + return 0; + + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: + if (power->has_chte) + return apple_smc_write_u32(power->smc, SMC_KEY(CHTE), 1); + else if (power->has_ch0c) + return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 1); + else + return -EOPNOTSUPP; + + case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE: + if (!power->has_ch0i) + return -EOPNOTSUPP; + return apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 1); + + default: + return -EINVAL; + } +} + +static int macsmc_battery_get_date(const char *s, int *out) +{ + if (!isdigit(s[0]) || !isdigit(s[1])) + return -EOPNOTSUPP; + + *out = (s[0] - '0') * 10 + s[1] - '0'; + return 0; +} + +static int macsmc_battery_get_capacity_level(struct macsmc_power *power) +{ + bool flag; + u32 val; + int ret; + + /* Check for emergency shutdown condition */ + if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val) >= 0 && val) + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + + /* Check AC status for whether we could boot in this state */ + if (apple_smc_read_u32(power->smc, SMC_KEY(ACSt), &val) >= 0) { + if (!(val & ACSt_CAN_BOOT_IBOOT)) + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + + if (!(val & ACSt_CAN_BOOT_AP)) + return POWER_SUPPLY_CAPACITY_LEVEL_LOW; + } + + /* BSFC = Battery System Full Charge */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC), &flag); + if (ret < 0) + return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + + if (flag) + return POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else + return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; +} + +static int macsmc_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct macsmc_power *power = power_supply_get_drvdata(psy); + int ret = 0; + u8 vu8; + u16 vu16; + s16 vs16; + s32 vs32; + s64 vs64; + bool flag; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = macsmc_battery_get_status(power); + ret = val->intval < 0 ? val->intval : 0; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + val->intval = macsmc_battery_get_charge_behaviour(power); + ret = val->intval < 0 ? val->intval : 0; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16); + val->intval = vu16 == 0xffff ? 0 : vu16 * 60; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TF), &vu16); + val->intval = vu16 == 0xffff ? 0 : vu16 * 60; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8); + val->intval = vu8; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = macsmc_battery_get_capacity_level(power); + ret = val->intval < 0 ? val->intval : 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = apple_smc_read_s16(power->smc, SMC_KEY(B0AC), &vs16); + val->intval = vs16 * 1000; + break; + case POWER_SUPPLY_PROP_POWER_NOW: + ret = apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &vs32); + val->intval = vs32 * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + ret = apple_smc_read_u16(power->smc, SMC_KEY(BITV), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + /* Calculate total max design voltage from per-cell maximum voltage */ + ret = apple_smc_read_u16(power->smc, SMC_KEY(BVVN), &vu16); + val->intval = vu16 * 1000 * power->num_cells; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + /* Lifetime min */ + ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPM), &vs16); + val->intval = vs16 * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + /* Lifetime max */ + ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPX), &vs16); + val->intval = vs16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RV), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16); + /* B0RM is Big Endian, likely pass through from TI gas gauge */ + val->intval = (s16)swab16(vu16) * 1000; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16); + val->intval = vu16 * power->nominal_voltage_mv; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16); + val->intval = vu16 * power->nominal_voltage_mv; + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16); + /* B0RM is Big Endian, likely pass through from TI gas gauge */ + val->intval = (s16)swab16(vu16) * power->nominal_voltage_mv; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16); + val->intval = vu16 - 2732; /* Kelvin x10 to Celsius x10 */ + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = apple_smc_read_s64(power->smc, SMC_KEY(BAAC), &vs64); + val->intval = vs64; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0CT), &vu16); + val->intval = vu16; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + break; + case POWER_SUPPLY_PROP_HEALTH: + flag = false; + ret = apple_smc_read_flag(power->smc, SMC_KEY(BBAD), &flag); + val->intval = flag ? POWER_SUPPLY_HEALTH_DEAD : POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = power->model_name; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = power->serial_number; + break; + case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: + ret = macsmc_battery_get_date(&power->mfg_date[0], &val->intval); + /* The SMC reports the manufacture year as an offset from 1992. */ + val->intval += 1992; + break; + case POWER_SUPPLY_PROP_MANUFACTURE_MONTH: + ret = macsmc_battery_get_date(&power->mfg_date[2], &val->intval); + break; + case POWER_SUPPLY_PROP_MANUFACTURE_DAY: + ret = macsmc_battery_get_date(&power->mfg_date[4], &val->intval); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int macsmc_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct macsmc_power *power = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + return macsmc_battery_set_charge_behaviour(power, val->intval); + default: + return -EINVAL; + } +} + +static int macsmc_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + return true; + default: + return false; + } +} + +static const struct power_supply_desc macsmc_battery_desc_template = { + .name = "macsmc-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = macsmc_battery_get_property, + .set_property = macsmc_battery_set_property, + .property_is_writeable = macsmc_battery_property_is_writeable, +}; + +static int macsmc_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct macsmc_power *power = power_supply_get_drvdata(psy); + int ret = 0; + u16 vu16; + u32 vu32; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = apple_smc_read_u32(power->smc, SMC_KEY(CHIS), &vu32); + val->intval = !!vu32; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT: + ret = apple_smc_read_u32(power->smc, SMC_KEY(ACPW), &vu32); + val->intval = vu32 * 1000; + break; + default: + return -EINVAL; + } + + return ret; +} + +static const struct power_supply_desc macsmc_ac_desc_template = { + .name = "macsmc-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .get_property = macsmc_ac_get_property, +}; + +static void macsmc_power_critical_work(struct work_struct *wrk) +{ + struct macsmc_power *power = container_of(wrk, struct macsmc_power, critical_work); + u16 bitv, b0av; + u32 bcf0; + + if (!power->batt) + return; + + /* + * Avoid duplicate atempts at emergency shutdown + */ + if (power->emergency_shutdown_triggered || system_state > SYSTEM_RUNNING) + return; + + /* + * EMERGENCY: Check voltage vs design minimum. + * If we are below BITV, the battery is physically exhausted. + * We must shut down NOW to protect the filesystem. + */ + if (apple_smc_read_u16(power->smc, SMC_KEY(BITV), &bitv) >= 0 && + apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &b0av) >= 0 && + b0av < bitv) { + power->emergency_shutdown_triggered = true; + dev_emerg(power->dev, + "Battery voltage (%d mV) below design minimum (%d mV)! Emergency shutdown.\n", + b0av, bitv); + + /* + * Shutdown is now imminent. Kick userspace again and give it some + * brief time to (hopefully) flush what's needed, before forcing. + */ + hw_protection_trigger("Battery voltage below design minimum", 1500); + } + + /* + * Avoid duplicate attempts at orderly shutdown. + * Voltage check is above this as we may want to + * "upgrade" an orderly shutdown to a critical power + * off if voltage drops. + */ + if (power->orderly_shutdown_triggered || system_state > SYSTEM_RUNNING) + return; + + /* + * Check if SMC flagged the battery as empty. + * We trigger a graceful shutdown to let the OS save data. + */ + if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &bcf0) == 0 && bcf0 != 0) { + power->orderly_shutdown_triggered = true; + dev_crit(power->dev, "Battery critical (empty flag set). Triggering orderly shutdown.\n"); + orderly_poweroff(true); + } +} + +static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data) +{ + struct macsmc_power *power = container_of(nb, struct macsmc_power, nb); + + /* + * SMC Event IDs are correlated to physical events (e.g. charger + * connect/disconnect) but the exact meaning of each ID is predicted. + * 0x71... indicates power/battery events. + */ + if ((event & 0xffffff00) == 0x71010100 || /* Charger status change */ + (event & 0xffff0000) == 0x71060000 || /* Port charge state change */ + (event & 0xffff0000) == 0x71130000) { /* Connector insert/remove event */ + if (power->batt) + power_supply_changed(power->batt); + if (power->ac) + power_supply_changed(power->ac); + return NOTIFY_OK; + } else if (event == 0x71020000) { + /* Critical battery warning */ + if (power->batt) + schedule_work(&power->critical_work); + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} + +static int macsmc_power_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = {}; + struct macsmc_power *power; + bool has_battery = false; + bool has_ac_adapter = false; + int ret = -ENODEV; + bool flag; + u16 vu16; + u32 val32; + enum power_supply_property *props; + size_t nprops; + + if (!smc) + return -ENODEV; + + power = devm_kzalloc(dev, sizeof(*power), GFP_KERNEL); + if (!power) + return -ENOMEM; + + power->dev = dev; + power->smc = smc; + dev_set_drvdata(dev, power); + + INIT_WORK(&power->critical_work, macsmc_power_critical_work); + ret = devm_work_autocancel(dev, &power->critical_work, macsmc_power_critical_work); + if (ret) + return ret; + + /* + * Check for battery presence. + * B0AV is a fundamental key. + */ + if (apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16) == 0 && + macsmc_battery_get_status(power) > POWER_SUPPLY_STATUS_UNKNOWN) + has_battery = true; + + /* + * Check for AC adapter presence. + * CHIS is a fundamental key. + */ + if (apple_smc_key_exists(smc, SMC_KEY(CHIS))) + has_ac_adapter = true; + + if (!has_battery && !has_ac_adapter) + return -ENODEV; + + if (has_battery) { + power->batt_desc = macsmc_battery_desc_template; + props = devm_kcalloc(dev, MACSMC_MAX_BATT_PROPS, + sizeof(enum power_supply_property), + GFP_KERNEL); + if (!props) + return -ENOMEM; + + nprops = 0; + + /* Fundamental properties */ + props[nprops++] = POWER_SUPPLY_PROP_STATUS; + props[nprops++] = POWER_SUPPLY_PROP_PRESENT; + props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; + props[nprops++] = POWER_SUPPLY_PROP_CURRENT_NOW; + props[nprops++] = POWER_SUPPLY_PROP_POWER_NOW; + props[nprops++] = POWER_SUPPLY_PROP_CAPACITY; + props[nprops++] = POWER_SUPPLY_PROP_CAPACITY_LEVEL; + props[nprops++] = POWER_SUPPLY_PROP_TEMP; + props[nprops++] = POWER_SUPPLY_PROP_CYCLE_COUNT; + props[nprops++] = POWER_SUPPLY_PROP_HEALTH; + props[nprops++] = POWER_SUPPLY_PROP_SCOPE; + props[nprops++] = POWER_SUPPLY_PROP_MODEL_NAME; + props[nprops++] = POWER_SUPPLY_PROP_SERIAL_NUMBER; + props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_YEAR; + props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_MONTH; + props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_DAY; + + /* Extended properties usually present */ + props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW; + props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_FULL_NOW; + props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; + props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; + props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; + props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; + props[nprops++] = POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT; + props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; + props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE; + props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; + props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL; + props[nprops++] = POWER_SUPPLY_PROP_CHARGE_NOW; + props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; + props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL; + props[nprops++] = POWER_SUPPLY_PROP_ENERGY_NOW; + props[nprops++] = POWER_SUPPLY_PROP_CHARGE_COUNTER; + + /* Detect features based on key availability */ + if (apple_smc_key_exists(smc, SMC_KEY(CHTE))) + power->has_chte = true; + if (apple_smc_key_exists(smc, SMC_KEY(CH0C))) + power->has_ch0c = true; + if (apple_smc_key_exists(smc, SMC_KEY(CH0I))) + power->has_ch0i = true; + + /* Reset "Optimised Battery Charging" flags to default state */ + if (power->has_chte) + apple_smc_write_u32(smc, SMC_KEY(CHTE), 0); + else if (power->has_ch0c) + apple_smc_write_u8(smc, SMC_KEY(CH0C), 0); + + if (power->has_ch0i) + apple_smc_write_u8(smc, SMC_KEY(CH0I), 0); + + apple_smc_write_u8(smc, SMC_KEY(CH0K), 0); + apple_smc_write_u8(smc, SMC_KEY(CH0B), 0); + + /* Configure charge behaviour if supported */ + if (power->has_ch0i || power->has_ch0c || power->has_chte) { + props[nprops++] = POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR; + + power->batt_desc.charge_behaviours = + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO); + + if (power->has_ch0i) + power->batt_desc.charge_behaviours |= + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE); + + if (power->has_chte || power->has_ch0c) + power->batt_desc.charge_behaviours |= + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE); + } + + /* Detect charge limit method (CHWA vs CHLS) */ + if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0) + power->has_chwa = true; + else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0) + power->has_chls = true; + + if (nprops > MACSMC_MAX_BATT_PROPS) + return -ENOMEM; + + power->batt_desc.properties = props; + power->batt_desc.num_properties = nprops; + + /* Fetch identity strings */ + apple_smc_read(smc, SMC_KEY(BMDN), power->model_name, + sizeof(power->model_name) - 1); + apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, + sizeof(power->serial_number) - 1); + apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date, + sizeof(power->mfg_date) - 1); + + apple_smc_read_u8(power->smc, SMC_KEY(BNCB), &power->num_cells); + power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells; + + /* Enable critical shutdown notifications by reading status once */ + apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val32); + + psy_cfg.drv_data = power; + power->batt = devm_power_supply_register(dev, &power->batt_desc, &psy_cfg); + if (IS_ERR(power->batt)) { + dev_err_probe(dev, PTR_ERR(power->batt), + "Failed to register battery\n"); + /* Don't return failure yet; try AC registration first */ + power->batt = NULL; + } + } + + if (has_ac_adapter) { + power->ac_desc = macsmc_ac_desc_template; + props = devm_kcalloc(dev, MACSMC_MAX_AC_PROPS, + sizeof(enum power_supply_property), + GFP_KERNEL); + if (!props) + return -ENOMEM; + + nprops = 0; + + /* Online status is fundamental */ + props[nprops++] = POWER_SUPPLY_PROP_ONLINE; + + /* Input power limits are usually available */ + if (apple_smc_key_exists(power->smc, SMC_KEY(ACPW))) + props[nprops++] = POWER_SUPPLY_PROP_INPUT_POWER_LIMIT; + + /* macOS 15.4+ firmware dropped legacy AC keys (AC-n, AC-i) */ + if (apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16) >= 0) { + props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; + props[nprops++] = POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT; + } + + if (nprops > MACSMC_MAX_AC_PROPS) + return -ENOMEM; + + power->ac_desc.properties = props; + power->ac_desc.num_properties = nprops; + + psy_cfg.drv_data = power; + power->ac = devm_power_supply_register(dev, &power->ac_desc, &psy_cfg); + if (IS_ERR(power->ac)) { + dev_err_probe(dev, PTR_ERR(power->ac), + "Failed to register AC adapter\n"); + power->ac = NULL; + } + } + + /* Final check: did we register anything? */ + if (!power->batt && !power->ac) + return -ENODEV; + + power->nb.notifier_call = macsmc_power_event; + blocking_notifier_chain_register(&smc->event_handlers, &power->nb); + + return 0; +} + +static void macsmc_power_remove(struct platform_device *pdev) +{ + struct macsmc_power *power = dev_get_drvdata(&pdev->dev); + + blocking_notifier_chain_unregister(&power->smc->event_handlers, &power->nb); +} + +static const struct platform_device_id macsmc_power_id[] = { + { "macsmc-power" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, macsmc_power_id); + +static struct platform_driver macsmc_power_driver = { + .driver = { + .name = "macsmc-power", + }, + .id_table = macsmc_power_id, + .probe = macsmc_power_probe, + .remove = macsmc_power_remove, +}; +module_platform_driver(macsmc_power_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC battery and power management driver"); +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_AUTHOR("Michael Reeves <michael.reeves077@gmail.com>"); diff --git a/drivers/power/supply/max14577_charger.c b/drivers/power/supply/max14577_charger.c index 1cef2f860b5f..63077d38ea30 100644 --- a/drivers/power/supply/max14577_charger.c +++ b/drivers/power/supply/max14577_charger.c @@ -501,7 +501,7 @@ static struct max14577_charger_platform_data *max14577_charger_dt_init( static struct max14577_charger_platform_data *max14577_charger_dt_init( struct platform_device *pdev) { - return NULL; + return ERR_PTR(-ENODATA); } #endif /* CONFIG_OF */ @@ -572,7 +572,7 @@ static int max14577_charger_probe(struct platform_device *pdev) chg->max14577 = max14577; chg->pdata = max14577_charger_dt_init(pdev); - if (IS_ERR_OR_NULL(chg->pdata)) + if (IS_ERR(chg->pdata)) return PTR_ERR(chg->pdata); ret = max14577_charger_reg_init(chg); diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index 51310f6e4803..48453508688a 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -388,6 +388,7 @@ static int max17040_get_property(struct power_supply *psy, union power_supply_propval *val) { struct max17040_chip *chip = power_supply_get_drvdata(psy); + int ret; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: @@ -410,8 +411,12 @@ static int max17040_get_property(struct power_supply *psy, if (!chip->channel_temp) return -ENODATA; - iio_read_channel_processed_scale(chip->channel_temp, - &val->intval, 10); + ret = iio_read_channel_processed(chip->channel_temp, &val->intval); + if (ret) + return ret; + + val->intval /= 100; /* Convert from milli- to deci-degree */ + break; default: return -EINVAL; diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index 655b3f25dbd7..167fb3fb3732 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -61,6 +61,7 @@ struct max17042_chip { struct work_struct work; int init_complete; int irq; + int task_period; }; static enum power_supply_property max17042_battery_props[] = { @@ -88,6 +89,7 @@ static enum power_supply_property max17042_battery_props[] = { POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, // these two have to be at the end on the list POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, @@ -131,7 +133,7 @@ static int max17042_get_status(struct max17042_chip *chip, int *status) * FullCAP to match RepCap when it detects end of charging. * * When this cycle the battery gets charged to a higher (calculated) - * capacity then the previous cycle then FullCAP will get updated + * capacity than the previous cycle then FullCAP will get updated * continuously once end-of-charge detection kicks in, so allow the * 2 to differ a bit. */ @@ -201,7 +203,7 @@ static int max17042_get_battery_health(struct max17042_chip *chip, int *health) goto out; } - if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) { + if (vbatt > size_add(chip->pdata->vmax, MAX17042_VMAX_TOLERANCE)) { *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; goto out; } @@ -331,6 +333,8 @@ static int max17042_get_property(struct power_supply *psy, return ret; data64 = data * 5000000ll; + data64 *= chip->task_period; + do_div(data64, MAX17042_DEFAULT_TASK_PERIOD); do_div(data64, chip->pdata->r_sns); val->intval = data64; break; @@ -340,6 +344,8 @@ static int max17042_get_property(struct power_supply *psy, return ret; data64 = data * 5000000ll; + data64 *= chip->task_period; + do_div(data64, MAX17042_DEFAULT_TASK_PERIOD); do_div(data64, chip->pdata->r_sns); val->intval = data64; break; @@ -349,6 +355,8 @@ static int max17042_get_property(struct power_supply *psy, return ret; data64 = data * 5000000ll; + data64 *= chip->task_period; + do_div(data64, MAX17042_DEFAULT_TASK_PERIOD); do_div(data64, chip->pdata->r_sns); val->intval = data64; break; @@ -358,6 +366,8 @@ static int max17042_get_property(struct power_supply *psy, return ret; data64 = sign_extend64(data, 15) * 5000000ll; + data64 *= chip->task_period; + data64 = div_s64(data64, MAX17042_DEFAULT_TASK_PERIOD); val->intval = div_s64(data64, chip->pdata->r_sns); break; case POWER_SUPPLY_PROP_TEMP: @@ -430,6 +440,25 @@ static int max17042_get_property(struct power_supply *psy, if (ret < 0) return ret; + /* when charging, the value is not meaningful */ + if (data == U16_MAX) + return -ENODATA; + + val->intval = data * 5625 / 1000; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055 && + chip->chip_type != MAXIM_DEVICE_TYPE_MAX77759) + return -EINVAL; + + ret = regmap_read(map, MAX17055_TTF, &data); + if (ret < 0) + return ret; + + /* when discharging, the value is not meaningful */ + if (data == U16_MAX) + return -ENODATA; + val->intval = data * 5625 / 1000; break; default: @@ -646,7 +675,8 @@ static void max17042_write_config_regs(struct max17042_chip *chip) regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg); if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 || chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050 || - chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) + chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 || + chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759) regmap_write(map, MAX17047_FullSOCThr, config->full_soc_thresh); } @@ -783,7 +813,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) || (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) || - (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)) { + (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050) || + (chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759)) { max17042_override_por(map, MAX17042_IAvg_empty, config->iavg_empty); max17042_override_por(map, MAX17042_TempNom, config->temp_nom); max17042_override_por(map, MAX17042_TempLim, config->temp_lim); @@ -792,7 +823,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) || (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050) || - (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)) { + (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) || + (chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759)) { max17042_override_por(map, MAX17047_V_empty, config->vempty); } } @@ -921,8 +953,12 @@ max17042_get_of_pdata(struct max17042_chip *chip) /* * Require current sense resistor value to be specified for * current-sense functionality to be enabled at all. + * maxim,rsns-microohm is the property name used by older DTs and kept + * for compatibility. */ - if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) { + if ((of_property_read_u32(np, "shunt-resistor-micro-ohms", + &prop) == 0) || + (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0)) { pdata->r_sns = prop; pdata->enable_current_sense = true; } @@ -1011,6 +1047,45 @@ static const struct regmap_config max17042_regmap_config = { .val_format_endian = REGMAP_ENDIAN_NATIVE, }; +static const struct regmap_range max77759_fg_registers[] = { + regmap_reg_range(MAX17042_STATUS, MAX77759_MixAtFull), + regmap_reg_range(MAX17042_VFSOC0Enable, MAX17042_VFSOC0Enable), + regmap_reg_range(MAX17042_MLOCKReg1, MAX17042_MLOCKReg2), + regmap_reg_range(MAX17042_MODELChrTbl, MAX17055_TimerH), + regmap_reg_range(MAX77759_IIn, MAX77759_IIn), + regmap_reg_range(MAX17055_AtQResidual, MAX17055_AtAvCap), + regmap_reg_range(MAX17042_OCVInternal, MAX17042_OCVInternal), + regmap_reg_range(MAX17042_VFSOC, MAX17042_VFSOC), +}; + +static const struct regmap_range max77759_fg_ro_registers[] = { + regmap_reg_range(MAX17042_FSTAT, MAX17042_FSTAT), + regmap_reg_range(MAX17042_OCVInternal, MAX17042_OCVInternal), + regmap_reg_range(MAX17042_VFSOC, MAX17042_VFSOC), +}; + +static const struct regmap_access_table max77759_fg_write_table = { + .yes_ranges = max77759_fg_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_fg_registers), + .no_ranges = max77759_fg_ro_registers, + .n_no_ranges = ARRAY_SIZE(max77759_fg_ro_registers), +}; + +static const struct regmap_access_table max77759_fg_rd_table = { + .yes_ranges = max77759_fg_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_fg_registers), +}; + +static const struct regmap_config max77759_fg_regmap_cfg = { + .reg_bits = 8, + .val_bits = 16, + .max_register = 0xff, + .wr_table = &max77759_fg_write_table, + .rd_table = &max77759_fg_rd_table, + .val_format_endian = REGMAP_ENDIAN_NATIVE, + .cache_type = REGCACHE_NONE, +}; + static const struct power_supply_desc max17042_psy_desc = { .name = "max170xx_battery", .type = POWER_SUPPLY_TYPE_BATTERY, @@ -1037,6 +1112,7 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq { struct i2c_adapter *adapter = client->adapter; const struct power_supply_desc *max17042_desc = &max17042_psy_desc; + const struct regmap_config *regmap_config; struct power_supply_config psy_cfg = {}; struct max17042_chip *chip; int ret; @@ -1052,21 +1128,24 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq chip->dev = dev; chip->chip_type = chip_type; - chip->regmap = devm_regmap_init_i2c(client, &max17042_regmap_config); - if (IS_ERR(chip->regmap)) { - dev_err(dev, "Failed to initialize regmap\n"); - return -EINVAL; - } + + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759) + regmap_config = &max77759_fg_regmap_cfg; + else + regmap_config = &max17042_regmap_config; + chip->regmap = devm_regmap_init_i2c(client, regmap_config); + if (IS_ERR(chip->regmap)) + return dev_err_probe(dev, PTR_ERR(chip->regmap), + "Failed to initialize regmap\n"); chip->pdata = max17042_get_pdata(chip); - if (!chip->pdata) { - dev_err(dev, "no platform data provided\n"); - return -EINVAL; - } + if (!chip->pdata) + return dev_err_probe(dev, -EINVAL, + "no platform data provided\n"); dev_set_drvdata(dev, chip); psy_cfg.drv_data = chip; - psy_cfg.of_node = dev->of_node; + psy_cfg.fwnode = dev_fwnode(dev); /* When current is not measured, * CURRENT_NOW and CURRENT_AVG properties should be invisible. */ @@ -1088,12 +1167,22 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007); } + chip->task_period = MAX17042_DEFAULT_TASK_PERIOD; + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759) { + ret = regmap_read(chip->regmap, MAX17042_TaskPeriod, &val); + if (ret) + return dev_err_probe(dev, ret, + "failed to read task period\n"); + chip->task_period = val; + } + dev_dbg(dev, "task period: %#.4x (%d)\n", chip->task_period, + chip->task_period); + chip->battery = devm_power_supply_register(dev, max17042_desc, &psy_cfg); - if (IS_ERR(chip->battery)) { - dev_err(dev, "failed: power supply register\n"); - return PTR_ERR(chip->battery); - } + if (IS_ERR(chip->battery)) + return dev_err_probe(dev, PTR_ERR(chip->battery), + "failed: power supply register\n"); if (irq) { unsigned int flags = IRQF_ONESHOT | IRQF_SHARED | IRQF_PROBE_SHARED; @@ -1236,6 +1325,8 @@ static const struct of_device_id max17042_dt_match[] __used = { .data = (void *) MAXIM_DEVICE_TYPE_MAX17055 }, { .compatible = "maxim,max77705-battery", .data = (void *) MAXIM_DEVICE_TYPE_MAX17047 }, + { .compatible = "maxim,max77759-fg", + .data = (void *) MAXIM_DEVICE_TYPE_MAX77759 }, { .compatible = "maxim,max77849-battery", .data = (void *) MAXIM_DEVICE_TYPE_MAX17047 }, { }, @@ -1248,6 +1339,7 @@ static const struct i2c_device_id max17042_id[] = { { "max17047", MAXIM_DEVICE_TYPE_MAX17047 }, { "max17050", MAXIM_DEVICE_TYPE_MAX17050 }, { "max17055", MAXIM_DEVICE_TYPE_MAX17055 }, + { "max77759-fg", MAXIM_DEVICE_TYPE_MAX77759 }, { "max77849-battery", MAXIM_DEVICE_TYPE_MAX17047 }, { } }; diff --git a/drivers/power/supply/max1720x_battery.c b/drivers/power/supply/max1720x_battery.c index 11580e414713..e2bd54ee3970 100644 --- a/drivers/power/supply/max1720x_battery.c +++ b/drivers/power/supply/max1720x_battery.c @@ -29,6 +29,11 @@ /* ModelGauge m5 */ #define MAX172XX_STATUS 0x00 /* Status */ #define MAX172XX_STATUS_BAT_ABSENT BIT(3) /* Battery absent */ +#define MAX172XX_STATUS_IMX BIT(6) /* Maximum Current Alert Threshold Exceeded */ +#define MAX172XX_STATUS_VMN BIT(8) /* Minimum Voltage Alert Threshold Exceeded */ +#define MAX172XX_STATUS_TMN BIT(9) /* Minimum Temperature Alert Threshold Exceeded */ +#define MAX172XX_STATUS_VMX BIT(12) /* Maximum Voltage Alert Threshold Exceeded */ +#define MAX172XX_STATUS_TMX BIT(13) /* Maximum Temperature Alert Threshold Exceeded */ #define MAX172XX_REPCAP 0x05 /* Average capacity */ #define MAX172XX_REPSOC 0x06 /* Percentage of charge */ #define MAX172XX_TEMP 0x08 /* Temperature */ @@ -114,7 +119,7 @@ static const struct regmap_config max1720x_regmap_cfg = { .val_format_endian = REGMAP_ENDIAN_LITTLE, .rd_table = &max1720x_readable_regs, .volatile_table = &max1720x_volatile_regs, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, }; static const struct regmap_range max1720x_nvmem_allow[] = { @@ -250,6 +255,7 @@ static const struct nvmem_cell_info max1720x_nvmem_cells[] = { }; static const enum power_supply_property max1720x_battery_props[] = { + POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_VOLTAGE_NOW, @@ -282,9 +288,10 @@ static int max172xx_voltage_to_ps(unsigned int reg) return reg * 1250; /* in uV */ } -static int max172xx_capacity_to_ps(unsigned int reg) +static int max172xx_capacity_to_ps(unsigned int reg, + struct max1720x_device_info *info) { - return reg * 500; /* in uAh */ + return reg * (500000 / info->rsense); /* in uAh */ } /* @@ -302,7 +309,7 @@ static int max172xx_temperature_to_ps(unsigned int reg) /* * Calculating current registers resolution: * - * RSense stored in 10^-5 Ohm, so mesaurment voltage must be + * RSense stored in 10^-5 Ohm, so measurement voltage must be * in 10^-11 Volts for get current in uA. * 16 bit current reg fullscale +/-51.2mV is 102400 uV. * So: 102400 / 65535 * 10^5 = 156252 @@ -314,6 +321,43 @@ static int max172xx_current_to_voltage(unsigned int reg) return val * 156252; } +static int max172xx_battery_health(struct max1720x_device_info *info, + unsigned int *health) +{ + unsigned int status; + int ret; + + ret = regmap_read(info->regmap, MAX172XX_STATUS, &status); + if (ret < 0) + return ret; + + if (status & MAX172XX_STATUS_VMN) + *health = POWER_SUPPLY_HEALTH_DEAD; + else if (status & MAX172XX_STATUS_VMX) + *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (status & MAX172XX_STATUS_TMN) + *health = POWER_SUPPLY_HEALTH_COLD; + else if (status & MAX172XX_STATUS_TMX) + *health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (status & MAX172XX_STATUS_IMX) + *health = POWER_SUPPLY_HEALTH_OVERCURRENT; + else + *health = POWER_SUPPLY_HEALTH_GOOD; + + /* Clear events which are not self-clearing to detect next events */ + if (status > 0 && status != MAX172XX_STATUS_IMX) { + ret = regmap_set_bits(info->regmap, MAX172XX_STATUS, + MAX172XX_STATUS_VMN | + MAX172XX_STATUS_VMX | + MAX172XX_STATUS_TMN | + MAX172XX_STATUS_TMX); + if (ret < 0) + return ret; + } + + return 0; +} + static int max1720x_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -323,6 +367,10 @@ static int max1720x_battery_get_property(struct power_supply *psy, int ret = 0; switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + ret = max172xx_battery_health(info, ®_val); + val->intval = reg_val; + break; case POWER_SUPPLY_PROP_PRESENT: /* * POWER_SUPPLY_PROP_PRESENT will always readable via @@ -347,11 +395,11 @@ static int max1720x_battery_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: ret = regmap_read(info->regmap, MAX172XX_DESIGN_CAP, ®_val); - val->intval = max172xx_capacity_to_ps(reg_val); + val->intval = max172xx_capacity_to_ps(reg_val, info); break; case POWER_SUPPLY_PROP_CHARGE_AVG: ret = regmap_read(info->regmap, MAX172XX_REPCAP, ®_val); - val->intval = max172xx_capacity_to_ps(reg_val); + val->intval = max172xx_capacity_to_ps(reg_val, info); break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: ret = regmap_read(info->regmap, MAX172XX_TTE, ®_val); @@ -375,10 +423,12 @@ static int max1720x_battery_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CHARGE_FULL: ret = regmap_read(info->regmap, MAX172XX_FULL_CAP, ®_val); - val->intval = max172xx_capacity_to_ps(reg_val); + val->intval = max172xx_capacity_to_ps(reg_val, info); break; case POWER_SUPPLY_PROP_MODEL_NAME: ret = regmap_read(info->regmap, MAX172XX_DEV_NAME, ®_val); + if (ret) + return ret; reg_val = FIELD_GET(MAX172XX_DEV_NAME_TYPE_MASK, reg_val); if (reg_val == MAX172XX_DEV_NAME_TYPE_MAX17201) val->strval = max17201_model; diff --git a/drivers/power/supply/max77650-charger.c b/drivers/power/supply/max77650-charger.c index 5f58c0c24b4d..4ae43668992e 100644 --- a/drivers/power/supply/max77650-charger.c +++ b/drivers/power/supply/max77650-charger.c @@ -298,7 +298,7 @@ static int max77650_charger_probe(struct platform_device *pdev) chg->dev = dev; - pscfg.of_node = dev->of_node; + pscfg.fwnode = dev_fwnode(dev); pscfg.drv_data = chg; chg_irq = platform_get_irq_byname(pdev, "CHG"); diff --git a/drivers/power/supply/max77693_charger.c b/drivers/power/supply/max77693_charger.c index cdea35c0d1de..027d6a539b65 100644 --- a/drivers/power/supply/max77693_charger.c +++ b/drivers/power/supply/max77693_charger.c @@ -608,7 +608,7 @@ static int max77693_set_charge_input_threshold_volt(struct max77693_charger *chg case 4700000: case 4800000: case 4900000: - data = (uvolt - 4700000) / 100000; + data = ((uvolt - 4700000) / 100000) + 1; break; default: dev_err(chg->dev, "Wrong value for charge input voltage regulation threshold\n"); diff --git a/drivers/power/supply/max77705_charger.c b/drivers/power/supply/max77705_charger.c new file mode 100644 index 000000000000..63b0b4f0cd21 --- /dev/null +++ b/drivers/power/supply/max77705_charger.c @@ -0,0 +1,699 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Based on max77650-charger.c + * + * Copyright (C) 2025 Dzmitry Sankouski <dsankouski@gmail.org> + * + * Battery charger driver for MAXIM 77705 charger/power-supply. + */ + +#include <linux/devm-helpers.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mfd/max77693-common.h> +#include <linux/mfd/max77705-private.h> +#include <linux/power/max77705_charger.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> + +static const char *max77705_charger_model = "max77705"; +static const char *max77705_charger_manufacturer = "Maxim Integrated"; + +static const struct regmap_config max77705_chg_regmap_config = { + .reg_base = MAX77705_CHG_REG_BASE, + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77705_CHG_REG_SAFEOUT_CTRL, +}; + +static enum power_supply_property max77705_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, +}; + +static irqreturn_t max77705_aicl_irq(int irq, void *irq_drv_data) +{ + struct max77705_charger_data *chg = irq_drv_data; + unsigned int regval, irq_status; + int err; + + err = regmap_read(chg->regmap, MAX77705_CHG_REG_INT_OK, &irq_status); + if (err < 0) + return IRQ_HANDLED; + + // irq is fiered at the end of current decrease sequence too + // early check AICL_I bit to guard against that excess irq call + while (!(irq_status & BIT(MAX77705_AICL_I))) { + err = regmap_field_read(chg->rfield[MAX77705_CHG_CHGIN_LIM], ®val); + if (err < 0) + return IRQ_HANDLED; + + regval--; + + err = regmap_field_write(chg->rfield[MAX77705_CHG_CHGIN_LIM], regval); + if (err < 0) + return IRQ_HANDLED; + + msleep(AICL_WORK_DELAY_MS); + + err = regmap_read(chg->regmap, MAX77705_CHG_REG_INT_OK, &irq_status); + if (err < 0) + return IRQ_HANDLED; + } + + return IRQ_HANDLED; +} + +static irqreturn_t max77705_chgin_irq(int irq, void *irq_drv_data) +{ + struct max77705_charger_data *chg = irq_drv_data; + + queue_work(chg->wqueue, &chg->chgin_work); + + return IRQ_HANDLED; +} + +static const struct regmap_irq max77705_charger_irqs[] = { + REGMAP_IRQ_REG_LINE(MAX77705_BYP_I, BITS_PER_BYTE), + REGMAP_IRQ_REG_LINE(MAX77705_INP_LIMIT_I, BITS_PER_BYTE), + REGMAP_IRQ_REG_LINE(MAX77705_BATP_I, BITS_PER_BYTE), + REGMAP_IRQ_REG_LINE(MAX77705_BAT_I, BITS_PER_BYTE), + REGMAP_IRQ_REG_LINE(MAX77705_CHG_I, BITS_PER_BYTE), + REGMAP_IRQ_REG_LINE(MAX77705_WCIN_I, BITS_PER_BYTE), + REGMAP_IRQ_REG_LINE(MAX77705_CHGIN_I, BITS_PER_BYTE), + REGMAP_IRQ_REG_LINE(MAX77705_AICL_I, BITS_PER_BYTE), +}; + +static const struct regmap_irq_chip max77705_charger_irq_chip = { + .name = "max77705-charger", + .status_base = MAX77705_CHG_REG_INT, + .mask_base = MAX77705_CHG_REG_INT_MASK, + .num_regs = 1, + .irqs = max77705_charger_irqs, + .num_irqs = ARRAY_SIZE(max77705_charger_irqs), +}; + +static int max77705_charger_enable(struct max77705_charger_data *chg) +{ + int rv; + + rv = regmap_field_write(chg->rfield[MAX77705_CHG_EN], 1); + if (rv) + dev_err(chg->dev, "unable to enable the charger: %d\n", rv); + + return rv; +} + +static void max77705_charger_disable(void *data) +{ + struct max77705_charger_data *chg = data; + int rv; + + rv = regmap_field_write(chg->rfield[MAX77705_CHG_EN], MAX77705_CHG_DISABLE); + if (rv) + dev_err(chg->dev, "unable to disable the charger: %d\n", rv); +} + +static int max77705_get_online(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, MAX77705_CHG_REG_INT_OK, &data); + if (ret < 0) + return ret; + + *val = !!(data & MAX77705_CHGIN_OK); + + return 0; +} + +static int max77705_set_integer(struct max77705_charger_data *chg, enum max77705_field_idx fidx, + unsigned int clamp_min, unsigned int clamp_max, + unsigned int div, int val) +{ + unsigned int regval; + + regval = clamp_val(val, clamp_min, clamp_max) / div; + + return regmap_field_write(chg->rfield[fidx], regval); +} + +static int max77705_check_battery(struct max77705_charger_data *chg, int *val) +{ + unsigned int reg_data; + unsigned int reg_data2; + struct regmap *regmap = chg->regmap; + + regmap_read(regmap, MAX77705_CHG_REG_INT_OK, ®_data); + + dev_dbg(chg->dev, "CHG_INT_OK(0x%x)\n", reg_data); + + regmap_read(regmap, MAX77705_CHG_REG_DETAILS_00, ®_data2); + + dev_dbg(chg->dev, "CHG_DETAILS00(0x%x)\n", reg_data2); + + if ((reg_data & MAX77705_BATP_OK) || !(reg_data2 & MAX77705_BATP_DTLS)) + *val = true; + else + *val = false; + + return 0; +} + +static int max77705_get_charge_type(struct max77705_charger_data *chg, int *val) +{ + struct regmap *regmap = chg->regmap; + unsigned int reg_data, chg_en; + + regmap_field_read(chg->rfield[MAX77705_CHG_EN], &chg_en); + if (!chg_en) { + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + return 0; + } + + regmap_read(regmap, MAX77705_CHG_REG_DETAILS_01, ®_data); + reg_data &= MAX77705_CHG_DTLS; + + switch (reg_data) { + case 0x0: + case MAX77705_CHARGER_CONSTANT_CURRENT: + case MAX77705_CHARGER_CONSTANT_VOLTAGE: + *val = POWER_SUPPLY_CHARGE_TYPE_FAST; + return 0; + default: + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + return 0; + } + + return 0; +} + +static int max77705_get_status(struct max77705_charger_data *chg, int *val) +{ + struct regmap *regmap = chg->regmap; + unsigned int reg_data, chg_en; + + regmap_field_read(chg->rfield[MAX77705_CHG_EN], &chg_en); + if (!chg_en) { + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + return 0; + } + + regmap_read(regmap, MAX77705_CHG_REG_DETAILS_01, ®_data); + reg_data &= MAX77705_CHG_DTLS; + + switch (reg_data) { + case 0x0: + case MAX77705_CHARGER_CONSTANT_CURRENT: + case MAX77705_CHARGER_CONSTANT_VOLTAGE: + *val = POWER_SUPPLY_STATUS_CHARGING; + return 0; + case MAX77705_CHARGER_END_OF_CHARGE: + case MAX77705_CHARGER_DONE: + *val = POWER_SUPPLY_STATUS_FULL; + return 0; + /* those values hard coded as in vendor kernel, because of */ + /* failure to determine it's actual meaning. */ + case 0x05: + case 0x06: + case 0x07: + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + return 0; + case 0x08: + case 0xA: + case 0xB: + *val = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + default: + *val = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + } + + return 0; +} + +static int max77705_get_vbus_state(struct regmap *regmap, int *value) +{ + int ret; + unsigned int charge_dtls; + + ret = regmap_read(regmap, MAX77705_CHG_REG_DETAILS_00, &charge_dtls); + if (ret) + return ret; + + charge_dtls = ((charge_dtls & MAX77705_CHGIN_DTLS) >> + MAX77705_CHGIN_DTLS_SHIFT); + + switch (charge_dtls) { + case 0x00: + *value = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + break; + case 0x01: + *value = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + break; + case 0x02: + *value = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + case 0x03: + *value = POWER_SUPPLY_HEALTH_GOOD; + break; + default: + return 0; + } + return 0; +} + +static int max77705_get_battery_health(struct max77705_charger_data *chg, + int *value) +{ + struct regmap *regmap = chg->regmap; + unsigned int bat_dtls; + + regmap_read(regmap, MAX77705_CHG_REG_DETAILS_01, &bat_dtls); + bat_dtls = ((bat_dtls & MAX77705_BAT_DTLS) >> MAX77705_BAT_DTLS_SHIFT); + + switch (bat_dtls) { + case MAX77705_BATTERY_NOBAT: + dev_dbg(chg->dev, "%s: No battery and the chg is suspended\n", + __func__); + *value = POWER_SUPPLY_HEALTH_NO_BATTERY; + break; + case MAX77705_BATTERY_PREQUALIFICATION: + dev_dbg(chg->dev, "%s: battery is okay but its voltage is low(~VPQLB)\n", + __func__); + break; + case MAX77705_BATTERY_DEAD: + dev_dbg(chg->dev, "%s: battery dead\n", __func__); + *value = POWER_SUPPLY_HEALTH_DEAD; + break; + case MAX77705_BATTERY_GOOD: + case MAX77705_BATTERY_LOWVOLTAGE: + *value = POWER_SUPPLY_HEALTH_GOOD; + break; + case MAX77705_BATTERY_OVERVOLTAGE: + dev_dbg(chg->dev, "%s: battery ovp\n", __func__); + *value = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + default: + dev_dbg(chg->dev, "%s: battery unknown\n", __func__); + *value = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + } + + return 0; +} + +static int max77705_get_health(struct max77705_charger_data *chg, int *val) +{ + struct regmap *regmap = chg->regmap; + int ret, is_online = 0; + + ret = max77705_get_online(regmap, &is_online); + if (ret) + return ret; + if (is_online) { + ret = max77705_get_vbus_state(regmap, val); + if (ret || (*val != POWER_SUPPLY_HEALTH_GOOD)) + return ret; + } + return max77705_get_battery_health(chg, val); +} + +static int max77705_get_input_current(struct max77705_charger_data *chg, + int *val) +{ + unsigned int reg_data; + int get_current = 0; + + regmap_field_read(chg->rfield[MAX77705_CHG_CHGIN_LIM], ®_data); + + if (reg_data <= 3) + get_current = MAX77705_CURRENT_CHGIN_MIN; + else + get_current = (reg_data + 1) * MAX77705_CURRENT_CHGIN_STEP; + + *val = get_current; + + return 0; +} + +static int max77705_get_charge_current(struct max77705_charger_data *chg, + int *val) +{ + unsigned int reg_data; + + regmap_field_read(chg->rfield[MAX77705_CHG_CC_LIM], ®_data); + + *val = reg_data <= 0x2 ? MAX77705_CURRENT_CHGIN_MIN : reg_data * MAX77705_CURRENT_CHG_STEP; + + return 0; +} + +static int max77705_set_float_voltage(struct max77705_charger_data *chg, + int float_voltage) +{ + int float_voltage_mv; + unsigned int reg_data = 0; + + float_voltage_mv = float_voltage / 1000; + reg_data = float_voltage_mv <= 4000 ? 0x0 : + float_voltage_mv >= 4500 ? 0x23 : + (float_voltage_mv <= 4200) ? (float_voltage_mv - 4000) / 50 : + (((float_voltage_mv - 4200) / 10) + 0x04); + + return regmap_field_write(chg->rfield[MAX77705_CHG_CV_PRM], reg_data); +} + +static int max77705_get_float_voltage(struct max77705_charger_data *chg, + int *val) +{ + unsigned int reg_data = 0; + int voltage_mv; + + regmap_field_read(chg->rfield[MAX77705_CHG_CV_PRM], ®_data); + voltage_mv = reg_data <= 0x04 ? reg_data * 50 + 4000 : + (reg_data - 4) * 10 + 4200; + *val = voltage_mv * 1000; + + return 0; +} + +static int max77705_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77705_charger_data *chg = power_supply_get_drvdata(psy); + struct regmap *regmap = chg->regmap; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + return max77705_get_online(regmap, &val->intval); + case POWER_SUPPLY_PROP_PRESENT: + return max77705_check_battery(chg, &val->intval); + case POWER_SUPPLY_PROP_STATUS: + return max77705_get_status(chg, &val->intval); + case POWER_SUPPLY_PROP_CHARGE_TYPE: + return max77705_get_charge_type(chg, &val->intval); + case POWER_SUPPLY_PROP_HEALTH: + return max77705_get_health(chg, &val->intval); + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return max77705_get_input_current(chg, &val->intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return max77705_get_charge_current(chg, &val->intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + return max77705_get_float_voltage(chg, &val->intval); + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = chg->bat_info->voltage_max_design_uv; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = max77705_charger_model; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = max77705_charger_manufacturer; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max77705_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77705_charger_data *chg = power_supply_get_drvdata(psy); + int err = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + err = max77705_set_integer(chg, MAX77705_CHG_CC_LIM, + MAX77705_CURRENT_CHGIN_MIN, + MAX77705_CURRENT_CHGIN_MAX, + MAX77705_CURRENT_CHG_STEP, + val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + err = max77705_set_integer(chg, MAX77705_CHG_CHGIN_LIM, + MAX77705_CURRENT_CHGIN_MIN, + MAX77705_CURRENT_CHGIN_MAX, + MAX77705_CURRENT_CHGIN_STEP, + val->intval); + break; + default: + err = -EINVAL; + } + + return err; +}; + +static int max77705_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return true; + default: + return false; + } +} + +static const struct power_supply_desc max77705_charger_psy_desc = { + .name = "max77705-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = max77705_charger_props, + .property_is_writeable = max77705_property_is_writeable, + .num_properties = ARRAY_SIZE(max77705_charger_props), + .get_property = max77705_chg_get_property, + .set_property = max77705_set_property, +}; + +static void max77705_chgin_isr_work(struct work_struct *work) +{ + struct max77705_charger_data *chg = + container_of(work, struct max77705_charger_data, chgin_work); + + power_supply_changed(chg->psy_chg); +} + +static int max77705_charger_initialize(struct max77705_charger_data *chg) +{ + struct power_supply_battery_info *info; + struct regmap *regmap = chg->regmap; + int err; + + err = power_supply_get_battery_info(chg->psy_chg, &info); + if (err) + return dev_err_probe(chg->dev, err, "error on getting battery info"); + + chg->bat_info = info; + + /* unlock charger setting protect */ + /* slowest LX slope */ + err = regmap_field_write(chg->rfield[MAX77705_CHGPROT], MAX77705_CHGPROT_UNLOCKED); + if (err) + goto err; + + err = regmap_field_write(chg->rfield[MAX77705_LX_SLOPE], MAX77705_SLOWEST_LX_SLOPE); + if (err) + goto err; + + /* fast charge timer disable */ + /* restart threshold disable */ + /* pre-qual charge disable */ + err = regmap_field_write(chg->rfield[MAX77705_FCHGTIME], MAX77705_FCHGTIME_DISABLE); + if (err) + goto err; + + err = regmap_field_write(chg->rfield[MAX77705_CHG_RSTRT], MAX77705_CHG_RSTRT_DISABLE); + if (err) + goto err; + + err = regmap_field_write(chg->rfield[MAX77705_CHG_PQEN], MAX77705_CHG_PQEN_DISABLE); + if (err) + goto err; + + err = regmap_field_write(chg->rfield[MAX77705_MODE], + MAX77705_CHG_MASK | MAX77705_BUCK_MASK); + if (err) + goto err; + + /* charge current 450mA(default) */ + /* otg current limit 900mA */ + err = regmap_field_write(chg->rfield[MAX77705_OTG_ILIM], MAX77705_OTG_ILIM_900); + if (err) + goto err; + + /* BAT to SYS OCP 4.80A */ + err = regmap_field_write(chg->rfield[MAX77705_REG_B2SOVRC], MAX77705_B2SOVRC_4_8A); + if (err) + goto err; + + /* top off current 150mA */ + /* top off timer 30min */ + err = regmap_field_write(chg->rfield[MAX77705_TO], MAX77705_TO_ITH_150MA); + if (err) + goto err; + + err = regmap_field_write(chg->rfield[MAX77705_TO_TIME], MAX77705_TO_TIME_30M); + if (err) + goto err; + + err = regmap_field_write(chg->rfield[MAX77705_SYS_TRACK], MAX77705_SYS_TRACK_DISABLE); + if (err) + goto err; + + /* cv voltage 4.2V or 4.35V */ + /* MINVSYS 3.6V(default) */ + if (info->voltage_max_design_uv < 0) { + dev_warn(chg->dev, "missing battery:voltage-max-design-microvolt\n"); + max77705_set_float_voltage(chg, 4200000); + } else { + max77705_set_float_voltage(chg, info->voltage_max_design_uv); + } + + err = regmap_field_write(chg->rfield[MAX77705_VCHGIN], MAX77705_VCHGIN_4_5); + if (err) + goto err; + + err = regmap_field_write(chg->rfield[MAX77705_WCIN], MAX77705_WCIN_4_5); + if (err) + goto err; + + /* Watchdog timer */ + regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_00, + MAX77705_WDTEN_MASK, 0); + + /* VBYPSET=5.0V */ + err = regmap_field_write(chg->rfield[MAX77705_VBYPSET], 0); + if (err) + goto err; + + /* Switching Frequency : 1.5MHz */ + err = regmap_field_write(chg->rfield[MAX77705_REG_FSW], MAX77705_CHG_FSW_1_5MHz); + if (err) + goto err; + + /* Auto skip mode */ + err = regmap_field_write(chg->rfield[MAX77705_REG_DISKIP], MAX77705_AUTO_SKIP); + if (err) + goto err; + + return 0; + +err: + return dev_err_probe(chg->dev, err, "error while configuring"); + +} + +static int max77705_charger_probe(struct i2c_client *i2c) +{ + struct power_supply_config pscfg = {}; + struct max77705_charger_data *chg; + struct regmap_irq_chip *chip_desc; + struct device *dev; + struct regmap_irq_chip_data *irq_data; + int ret; + + dev = &i2c->dev; + + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + chg->dev = dev; + i2c_set_clientdata(i2c, chg); + + chip_desc = devm_kmemdup(dev, &max77705_charger_irq_chip, + sizeof(max77705_charger_irq_chip), + GFP_KERNEL); + if (!chip_desc) + return -ENOMEM; + chip_desc->irq_drv_data = chg; + + chg->regmap = devm_regmap_init_i2c(i2c, &max77705_chg_regmap_config); + if (IS_ERR(chg->regmap)) + return PTR_ERR(chg->regmap); + + for (int i = 0; i < MAX77705_N_REGMAP_FIELDS; i++) { + chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap, + max77705_reg_field[i]); + if (IS_ERR(chg->rfield[i])) + return dev_err_probe(dev, PTR_ERR(chg->rfield[i]), + "cannot allocate regmap field\n"); + } + + pscfg.fwnode = dev_fwnode(dev); + pscfg.drv_data = chg; + + chg->psy_chg = devm_power_supply_register(dev, &max77705_charger_psy_desc, &pscfg); + if (IS_ERR(chg->psy_chg)) + return PTR_ERR(chg->psy_chg); + + ret = devm_regmap_add_irq_chip(chg->dev, chg->regmap, i2c->irq, + IRQF_ONESHOT, 0, + chip_desc, &irq_data); + if (ret) + return dev_err_probe(dev, ret, "failed to add irq chip\n"); + + chg->wqueue = devm_alloc_ordered_workqueue(dev, "%s", 0, dev_name(dev)); + if (!chg->wqueue) + return -ENOMEM; + + ret = devm_work_autocancel(dev, &chg->chgin_work, max77705_chgin_isr_work); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize interrupt work\n"); + + ret = max77705_charger_initialize(chg); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize charger IC\n"); + + ret = devm_request_threaded_irq(dev, regmap_irq_get_virq(irq_data, MAX77705_CHGIN_I), + NULL, max77705_chgin_irq, + IRQF_TRIGGER_NONE, + "chgin-irq", chg); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, regmap_irq_get_virq(irq_data, MAX77705_AICL_I), + NULL, max77705_aicl_irq, + IRQF_TRIGGER_NONE, + "aicl-irq", chg); + if (ret) + return ret; + + ret = max77705_charger_enable(chg); + if (ret) + return dev_err_probe(dev, ret, "failed to enable charge\n"); + + return devm_add_action_or_reset(dev, max77705_charger_disable, chg); +} + +static const struct of_device_id max77705_charger_of_match[] = { + { .compatible = "maxim,max77705-charger" }, + { } +}; +MODULE_DEVICE_TABLE(of, max77705_charger_of_match); + +static struct i2c_driver max77705_charger_driver = { + .driver = { + .name = "max77705-charger", + .of_match_table = max77705_charger_of_match, + }, + .probe = max77705_charger_probe, +}; +module_i2c_driver(max77705_charger_driver); + +MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>"); +MODULE_DESCRIPTION("Maxim MAX77705 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/max77759_charger.c b/drivers/power/supply/max77759_charger.c new file mode 100644 index 000000000000..9bb414599f16 --- /dev/null +++ b/drivers/power/supply/max77759_charger.c @@ -0,0 +1,774 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * max77759_charger.c - Battery charger driver for MAX77759 charger device. + * + * Copyright 2025 Google LLC. + */ + +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/devm-helpers.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/linear_range.h> +#include <linux/mfd/max77759.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/string_choices.h> +#include <linux/workqueue.h> + +/* Default values for Fast Charge Current & Float Voltage */ +#define CHG_CC_DEFAULT_UA 2266770 +#define CHG_FV_DEFAULT_MV 4300 + +#define MAX_NUM_RETRIES 3 +#define PSY_WORK_RETRY_DELAY_MS 10 + +#define FOREACH_IRQ(S) \ + S(AICL), \ + S(CHGIN), \ + S(CHG), \ + S(INLIM), \ + S(BAT_OILO), \ + S(CHG_STA_CC), \ + S(CHG_STA_CV), \ + S(CHG_STA_TO), \ + S(CHG_STA_DONE) + +#define GENERATE_ENUM(e) e +#define GENERATE_STRING(s) #s + +enum { + FOREACH_IRQ(GENERATE_ENUM) +}; + +static const char *const chgr_irqs_str[] = { + FOREACH_IRQ(GENERATE_STRING) +}; + +#define NUM_IRQS ARRAY_SIZE(chgr_irqs_str) + +/* Fast charge current limits (in uA) */ +static const struct linear_range chgcc_limit_ranges[] = { + LINEAR_RANGE(133330, 0x0, 0x2, 0), + LINEAR_RANGE(200000, 0x3, 0x3C, 66670), +}; + +/* Charge Termination Voltage Limits (in mV) */ +static const struct linear_range chg_cv_prm_ranges[] = { + LINEAR_RANGE(3800, 0x38, 0x39, 100), + LINEAR_RANGE(4000, 0x0, 0x32, 10), +}; + +/* USB input current limits (in uA) */ +static const struct linear_range chgin_ilim_ranges[] = { + LINEAR_RANGE(100000, 0x3, 0x7F, 25000), +}; + +struct max77759_charger { + struct device *dev; + struct regmap *regmap; + struct power_supply *psy; + struct regulator_dev *chgin_otg_rdev; + struct notifier_block nb; + struct power_supply *tcpm_psy; + struct delayed_work psy_work; + struct mutex retry_lock; /* Protects psy_work_retry_cnt */ + u32 psy_work_retry_cnt; + int irqs[NUM_IRQS]; + struct mutex lock; /* protects the state below */ + enum max77759_chgr_mode mode; +}; + +static inline int unlock_prot_regs(struct max77759_charger *chg, bool unlock) +{ + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_06, + MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT, unlock + ? MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT : 0); +} + +static int charger_input_valid(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_INT_OK, &val); + if (ret) + return ret; + + return (val & MAX77759_CHGR_REG_CHG_INT_CHG) && + (val & MAX77759_CHGR_REG_CHG_INT_CHGIN); +} + +static int get_online(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = charger_input_valid(chg); + if (ret <= 0) + return ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_02, &val); + if (ret) + return ret; + + guard(mutex)(&chg->lock); + + return (val & MAX77759_CHGR_REG_CHG_DETAILS_02_CHGIN_STS) && + (chg->mode == MAX77759_CHGR_MODE_CHG_BUCK_ON); +} + +static int get_status(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { + case MAX77759_CHGR_CHG_DTLS_PREQUAL: + case MAX77759_CHGR_CHG_DTLS_CC: + case MAX77759_CHGR_CHG_DTLS_CV: + case MAX77759_CHGR_CHG_DTLS_TO: + return POWER_SUPPLY_STATUS_CHARGING; + case MAX77759_CHGR_CHG_DTLS_DONE: + return POWER_SUPPLY_STATUS_FULL; + case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: + case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: + case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: + case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: + return POWER_SUPPLY_STATUS_NOT_CHARGING; + case MAX77759_CHGR_CHG_DTLS_OFF: + return POWER_SUPPLY_STATUS_DISCHARGING; + default: + break; + } + + return POWER_SUPPLY_STATUS_UNKNOWN; +} + +static int get_charge_type(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { + case MAX77759_CHGR_CHG_DTLS_PREQUAL: + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + case MAX77759_CHGR_CHG_DTLS_CC: + case MAX77759_CHGR_CHG_DTLS_CV: + return POWER_SUPPLY_CHARGE_TYPE_FAST; + case MAX77759_CHGR_CHG_DTLS_TO: + return POWER_SUPPLY_CHARGE_TYPE_STANDARD; + case MAX77759_CHGR_CHG_DTLS_DONE: + case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: + case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: + case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: + case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: + case MAX77759_CHGR_CHG_DTLS_OFF: + return POWER_SUPPLY_CHARGE_TYPE_NONE; + default: + break; + } + + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; +} + +static int get_chg_health(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_00, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_00_CHGIN_DTLS, val)) { + case MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE: + case MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE: + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + case MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE: + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; + case MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID: + return POWER_SUPPLY_HEALTH_GOOD; + default: + break; + } + + return POWER_SUPPLY_HEALTH_UNKNOWN; +} + +static int get_batt_health(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_BAT_DTLS, val)) { + case MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP: + return POWER_SUPPLY_HEALTH_NO_BATTERY; + case MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY: + return POWER_SUPPLY_HEALTH_DEAD; + case MAX77759_CHGR_BAT_DTLS_BAT_CHG_TIMER_FAULT: + return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + case MAX77759_CHGR_BAT_DTLS_BAT_OKAY: + case MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE: + return POWER_SUPPLY_HEALTH_GOOD; + case MAX77759_CHGR_BAT_DTLS_BAT_UNDERVOLTAGE: + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + case MAX77759_CHGR_BAT_DTLS_BAT_OVERVOLTAGE: + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; + case MAX77759_CHGR_BAT_DTLS_BAT_OVERCURRENT: + return POWER_SUPPLY_HEALTH_OVERCURRENT; + default: + break; + } + + return POWER_SUPPLY_HEALTH_UNKNOWN; +} + +static int get_health(struct max77759_charger *chg) +{ + int ret; + + ret = get_online(chg); + if (ret < 0) + return ret; + + if (ret) { + ret = get_chg_health(chg); + if (ret < 0 || ret != POWER_SUPPLY_HEALTH_GOOD) + return ret; + } + + return get_batt_health(chg); +} + +static int get_fast_charge_current(struct max77759_charger *chg) +{ + u32 regval, val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, ®val); + if (ret) + return ret; + + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); + ret = linear_range_get_value_array(chgcc_limit_ranges, + ARRAY_SIZE(chgcc_limit_ranges), + regval, &val); + return ret ? ret : val; +} + +static int set_fast_charge_current_limit(struct max77759_charger *chg, + u32 cc_max_ua) +{ + bool found; + u32 regval; + + linear_range_get_selector_high_array(chgcc_limit_ranges, + ARRAY_SIZE(chgcc_limit_ranges), + cc_max_ua, ®val, &found); + if (!found) + return -EINVAL; + + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, + MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); +} + +static int get_float_voltage(struct max77759_charger *chg) +{ + u32 regval, val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, ®val); + if (ret) + return ret; + + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, regval); + ret = linear_range_get_value_array(chg_cv_prm_ranges, + ARRAY_SIZE(chg_cv_prm_ranges), + regval, &val); + + return ret ? ret : val; +} + +static int set_float_voltage_limit(struct max77759_charger *chg, u32 fv_mv) +{ + u32 regval; + bool found; + + linear_range_get_selector_high_array(chg_cv_prm_ranges, + ARRAY_SIZE(chg_cv_prm_ranges), + fv_mv, ®val, &found); + if (!found) + return -EINVAL; + + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, + MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, + regval); +} + +static int get_input_current_limit(struct max77759_charger *chg) +{ + u32 regval, val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, ®val); + if (ret) + return ret; + + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, regval); + regval = umax(regval, chgin_ilim_ranges[0].min_sel); + + ret = linear_range_get_value_array(chgin_ilim_ranges, + ARRAY_SIZE(chgin_ilim_ranges), + regval, &val); + + return ret ? ret : val; +} + +static int set_input_current_limit(struct max77759_charger *chg, int ilim_ua) +{ + u32 regval; + + if (ilim_ua < 0) + return -EINVAL; + + linear_range_get_selector_within(chgin_ilim_ranges, ilim_ua, ®val); + + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, + MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, + regval); +} + +static const enum power_supply_property max77759_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, +}; + +static int max77759_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *pval) +{ + struct max77759_charger *chg = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = get_online(chg); + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = charger_input_valid(chg); + break; + case POWER_SUPPLY_PROP_STATUS: + ret = get_status(chg); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = get_charge_type(chg); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = get_health(chg); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = get_fast_charge_current(chg); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + ret = get_float_voltage(chg); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = get_input_current_limit(chg); + break; + default: + ret = -EINVAL; + } + + pval->intval = ret; + return ret < 0 ? ret : 0; +} + +static const struct power_supply_desc max77759_charger_desc = { + .name = "max77759-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = max77759_charger_props, + .num_properties = ARRAY_SIZE(max77759_charger_props), + .get_property = max77759_charger_get_property, +}; + +static int charger_set_mode(struct max77759_charger *chg, + enum max77759_chgr_mode mode) +{ + int ret; + + guard(mutex)(&chg->lock); + + if (chg->mode == mode) + return 0; + + if ((mode == MAX77759_CHGR_MODE_CHG_BUCK_ON || + mode == MAX77759_CHGR_MODE_OTG_BOOST_ON) && + chg->mode != MAX77759_CHGR_MODE_OFF) { + dev_err(chg->dev, "Invalid mode transition from %d to %d\n", + chg->mode, mode); + return -EINVAL; + } + + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, + MAX77759_CHGR_REG_CHG_CNFG_00_MODE, mode); + if (ret) + return ret; + + chg->mode = mode; + return 0; +} + +static int enable_chgin_otg(struct regulator_dev *rdev) +{ + struct max77759_charger *chg = rdev_get_drvdata(rdev); + + return charger_set_mode(chg, MAX77759_CHGR_MODE_OTG_BOOST_ON); +} + +static int disable_chgin_otg(struct regulator_dev *rdev) +{ + struct max77759_charger *chg = rdev_get_drvdata(rdev); + + return charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); +} + +static int chgin_otg_status(struct regulator_dev *rdev) +{ + struct max77759_charger *chg = rdev_get_drvdata(rdev); + + guard(mutex)(&chg->lock); + + return chg->mode == MAX77759_CHGR_MODE_OTG_BOOST_ON; +} + +static const struct regulator_ops chgin_otg_reg_ops = { + .enable = enable_chgin_otg, + .disable = disable_chgin_otg, + .is_enabled = chgin_otg_status, +}; + +static const struct regulator_desc chgin_otg_reg_desc = { + .name = "chgin-otg", + .of_match = of_match_ptr("chgin-otg-regulator"), + .owner = THIS_MODULE, + .ops = &chgin_otg_reg_ops, + .fixed_uV = 5000000, + .n_voltages = 1, +}; + +static irqreturn_t irq_handler(int irq, void *data) +{ + struct max77759_charger *chg = data; + + power_supply_changed(chg->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t bat_oilo_irq_handler(int irq, void *data) +{ + struct max77759_charger *chg = data; + + dev_warn_ratelimited(chg->dev, + "Battery over-current threshold crossed\n"); + + return irq_handler(irq, data); +} + +static int max77759_init_irqhandler(struct max77759_charger *chg) +{ + struct device *dev = chg->dev; + irq_handler_t thread_fn; + char *name; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(chgr_irqs_str); i++) { + ret = platform_get_irq_byname(to_platform_device(dev), + chgr_irqs_str[i]); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get irq resource for %s\n", + chgr_irqs_str[i]); + + chg->irqs[i] = ret; + name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", dev_name(dev), + chgr_irqs_str[i]); + if (!name) + return dev_err_probe(dev, -ENOMEM, + "Failed to allocate space for irqname: %s\n", + chgr_irqs_str[i]); + + if (i == BAT_OILO) + thread_fn = bat_oilo_irq_handler; + else + thread_fn = irq_handler; + + ret = devm_request_threaded_irq(dev, chg->irqs[i], NULL, + thread_fn, 0, name, chg); + if (ret) + return dev_err_probe(dev, ret, + "Unable to register irq handler for %s\n", + chgr_irqs_str[i]); + } + + return 0; +} + +static int max77759_charger_init(struct max77759_charger *chg) +{ + struct power_supply_battery_info *info; + u32 regval, fast_chg_curr, fv; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, ®val); + if (ret) + return ret; + + chg->mode = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_00_MODE, regval); + ret = charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); + if (ret) + return ret; + + if (power_supply_get_battery_info(chg->psy, &info)) { + fv = CHG_FV_DEFAULT_MV; + fast_chg_curr = CHG_CC_DEFAULT_UA; + } else { + fv = info->constant_charge_voltage_max_uv / 1000; + fast_chg_curr = info->constant_charge_current_max_ua; + } + + ret = set_fast_charge_current_limit(chg, fast_chg_curr); + if (ret) + return ret; + + ret = set_float_voltage_limit(chg, fv); + if (ret) + return ret; + + ret = unlock_prot_regs(chg, true); + if (ret) + return ret; + + /* Disable wireless charging input */ + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_12, + MAX77759_CHGR_REG_CHG_CNFG_12_WCINSEL, 0); + if (ret) + goto relock; + + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_18, + MAX77759_CHGR_REG_CHG_CNFG_18_WDTEN, 0); + if (ret) + goto relock; + + return unlock_prot_regs(chg, false); + +relock: + (void)unlock_prot_regs(chg, false); + return ret; +} + +static void psy_work_item(struct work_struct *work) +{ + struct max77759_charger *chg = + container_of(work, struct max77759_charger, psy_work.work); + union power_supply_propval current_limit, online; + int ret; + + ret = power_supply_get_property(chg->tcpm_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, + ¤t_limit); + if (ret) { + dev_err(chg->dev, + "Failed to get CURRENT_MAX psy property, ret=%d\n", + ret); + goto err; + } + + ret = power_supply_get_property(chg->tcpm_psy, POWER_SUPPLY_PROP_ONLINE, + &online); + if (ret) { + dev_err(chg->dev, + "Failed to get ONLINE psy property, ret=%d\n", + ret); + goto err; + } + + if (online.intval && current_limit.intval) { + ret = set_input_current_limit(chg, current_limit.intval); + if (ret) { + dev_err(chg->dev, + "Unable to set current limit, ret=%d\n", ret); + goto err; + } + + charger_set_mode(chg, MAX77759_CHGR_MODE_CHG_BUCK_ON); + } else { + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); + } + + scoped_guard(mutex, &chg->retry_lock) { + if (chg->psy_work_retry_cnt) + dev_dbg(chg->dev, + "chg psy_work succeeded after %u tries\n", + chg->psy_work_retry_cnt); + chg->psy_work_retry_cnt = 0; + } + + return; + +err: + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); + scoped_guard(mutex, &chg->retry_lock) { + if (chg->psy_work_retry_cnt >= MAX_NUM_RETRIES) { + dev_err(chg->dev, "chg psy work failed, giving up\n"); + return; + } + + ++chg->psy_work_retry_cnt; + dev_dbg(chg->dev, "Retrying %u/%u chg psy_work\n", + chg->psy_work_retry_cnt, MAX_NUM_RETRIES); + schedule_delayed_work(&chg->psy_work, + msecs_to_jiffies(PSY_WORK_RETRY_DELAY_MS)); + } +} + +static int psy_changed(struct notifier_block *nb, unsigned long evt, void *data) +{ + struct max77759_charger *chg = container_of(nb, struct max77759_charger, + nb); + static const char *psy_name = "tcpm-source"; + struct power_supply *psy = data; + + if (!strnstr(psy->desc->name, psy_name, strlen(psy_name)) || + evt != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + chg->tcpm_psy = psy; + scoped_guard(mutex, &chg->retry_lock) + chg->psy_work_retry_cnt = 0; + + schedule_delayed_work(&chg->psy_work, 0); + + return NOTIFY_OK; +} + +static void max_tcpci_unregister_psy_notifier(void *nb) +{ + power_supply_unreg_notifier(nb); +} + +static int max77759_charger_probe(struct platform_device *pdev) +{ + struct regulator_config chgin_otg_reg_cfg; + struct power_supply_config psy_cfg; + struct device *dev = &pdev->dev; + struct max77759_charger *chg; + int ret; + + device_set_of_node_from_dev(dev, dev->parent); + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + platform_set_drvdata(pdev, chg); + chg->dev = dev; + chg->regmap = dev_get_regmap(dev->parent, "charger"); + if (!chg->regmap) + return dev_err_probe(dev, -ENODEV, "Missing regmap\n"); + + ret = devm_mutex_init(dev, &chg->lock); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize lock\n"); + + ret = devm_mutex_init(dev, &chg->retry_lock); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize retry_lock\n"); + + psy_cfg.fwnode = dev_fwnode(dev); + psy_cfg.drv_data = chg; + chg->psy = devm_power_supply_register(dev, &max77759_charger_desc, + &psy_cfg); + if (IS_ERR(chg->psy)) + return dev_err_probe(dev, PTR_ERR(chg->psy), + "Failed to register psy\n"); + + ret = max77759_charger_init(chg); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize max77759 charger\n"); + + chgin_otg_reg_cfg.dev = dev; + chgin_otg_reg_cfg.driver_data = chg; + chgin_otg_reg_cfg.of_node = dev_of_node(dev); + chg->chgin_otg_rdev = devm_regulator_register(dev, &chgin_otg_reg_desc, + &chgin_otg_reg_cfg); + if (IS_ERR(chg->chgin_otg_rdev)) + return dev_err_probe(dev, PTR_ERR(chg->chgin_otg_rdev), + "Failed to register chgin otg regulator\n"); + + ret = devm_delayed_work_autocancel(dev, &chg->psy_work, psy_work_item); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize psy work\n"); + + chg->nb.notifier_call = psy_changed; + ret = power_supply_reg_notifier(&chg->nb); + if (ret) + return dev_err_probe(dev, ret, + "Unable to register psy notifier\n"); + + ret = devm_add_action_or_reset(dev, max_tcpci_unregister_psy_notifier, + &chg->nb); + if (ret) + return dev_err_probe(dev, ret, + "Failed to add devm action to unregister psy notifier\n"); + + return max77759_init_irqhandler(chg); +} + +static const struct platform_device_id max77759_charger_id[] = { + { .name = "max77759-charger", }, + { } +}; +MODULE_DEVICE_TABLE(platform, max77759_charger_id); + +static struct platform_driver max77759_charger_driver = { + .driver = { + .name = "max77759-charger", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = max77759_charger_probe, + .id_table = max77759_charger_id, +}; +module_platform_driver(max77759_charger_driver); + +MODULE_AUTHOR("Amit Sunil Dhamne <amitsd@google.com>"); +MODULE_DESCRIPTION("Maxim MAX77759 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/max77976_charger.c b/drivers/power/supply/max77976_charger.c index e6fe68cebc32..3d6ff4005533 100644 --- a/drivers/power/supply/max77976_charger.c +++ b/drivers/power/supply/max77976_charger.c @@ -292,10 +292,10 @@ static int max77976_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_ONLINE: err = max77976_get_online(chg, &val->intval); break; - case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: val->intval = MAX77976_CHG_CC_MAX; break; - case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: err = max77976_get_integer(chg, CHG_CC, MAX77976_CHG_CC_MIN, MAX77976_CHG_CC_MAX, @@ -330,7 +330,7 @@ static int max77976_set_property(struct power_supply *psy, int err = 0; switch (psp) { - case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: err = max77976_set_integer(chg, CHG_CC, MAX77976_CHG_CC_MIN, MAX77976_CHG_CC_MAX, @@ -355,7 +355,7 @@ static int max77976_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { - case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return true; default: @@ -368,8 +368,8 @@ static enum power_supply_property max77976_psy_props[] = { POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, - POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, diff --git a/drivers/power/supply/max8903_charger.c b/drivers/power/supply/max8903_charger.c index e65d0141f260..45fbaad6c647 100644 --- a/drivers/power/supply/max8903_charger.c +++ b/drivers/power/supply/max8903_charger.c @@ -349,7 +349,7 @@ static int max8903_probe(struct platform_device *pdev) data->psy_desc.properties = max8903_charger_props; data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props); - psy_cfg.of_node = dev->of_node; + psy_cfg.fwnode = dev_fwnode(dev); psy_cfg.drv_data = data; data->psy = devm_power_supply_register(dev, &data->psy_desc, &psy_cfg); diff --git a/drivers/power/supply/max8971_charger.c b/drivers/power/supply/max8971_charger.c new file mode 100644 index 000000000000..26416d26f235 --- /dev/null +++ b/drivers/power/supply/max8971_charger.c @@ -0,0 +1,752 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/devm-helpers.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/extcon.h> +#include <linux/i2c.h> +#include <linux/mod_devicetable.h> +#include <linux/of_graph.h> +#include <linux/property.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/sysfs.h> +#include <linux/types.h> + +#define MAX8971_REG_CHGINT 0x0f +#define MAX8971_REG_CHG_RST BIT(0) +#define MAX8971_REG_CHGINT_MASK 0x01 +#define MAX8971_AICL_MASK BIT(7) +#define MAX8971_REG_CHG_STAT 0x02 +#define MAX8971_CHG_MASK BIT(3) +#define MAX8971_REG_DETAILS1 0x03 +#define MAX8971_REG_DETAILS2 0x04 +#define MAX8971_REG_CHGCNTL1 0x05 +#define MAX8971_REG_FCHGCRNT 0x06 +#define MAX8971_REG_DCCRNT 0x07 +#define MAX8971_CHGRSTRT_MASK BIT(6) +#define MAX8971_REG_TOPOFF 0x08 +#define MAX8971_REG_TEMPREG 0x09 +#define MAX8971_REG_PROTCMD 0x0a +#define MAX8971_CHGPROT_LOCKED 0x00 +#define MAX8971_CHGPROT_UNLOCKED 0x03 + +#define MAX8971_FCHGT_DEFAULT 2 +#define MAX8971_TOPOFFT_DEFAULT 3 + +static const char *max8971_manufacturer = "Maxim Integrated"; +static const char *max8971_model = "MAX8971"; + +enum max8971_charging_state { + MAX8971_CHARGING_DEAD_BATTERY, + MAX8971_CHARGING_PREQUALIFICATION, + MAX8971_CHARGING_FAST_CONST_CURRENT, + MAX8971_CHARGING_FAST_CONST_VOLTAGE, + MAX8971_CHARGING_TOP_OFF, + MAX8971_CHARGING_DONE, + MAX8971_CHARGING_TIMER_FAULT, + MAX8971_CHARGING_SUSPENDED_THERMAL, + MAX8971_CHARGING_OFF, + MAX8971_CHARGING_THERMAL_LOOP, +}; + +enum max8971_health_state { + MAX8971_HEALTH_UNKNOWN, + MAX8971_HEALTH_COLD, + MAX8971_HEALTH_COOL, + MAX8971_HEALTH_WARM, + MAX8971_HEALTH_HOT, + MAX8971_HEALTH_OVERHEAT, +}; + +/* Fast-Charge current limit, 250..1550 mA, 50 mA steps */ +#define MAX8971_CHG_CC_STEP 50000U +#define MAX8971_CHG_CC_MIN 250000U +#define MAX8971_CHG_CC_MAX 1550000U + +/* Input current limit, 250..1500 mA, 25 mA steps */ +#define MAX8971_DCILMT_STEP 25000U +#define MAX8971_DCILMT_MIN 250000U +#define MAX8971_DCILMT_MAX 1500000U + +enum max8971_field_idx { + THM_DTLS, /* DETAILS1 */ + BAT_DTLS, CHG_DTLS, /* DETAILS2 */ + CHG_CC, FCHG_T, /* FCHGCRNT */ + DCI_LMT, /* DCCRNT */ + TOPOFF_T, TOPOFF_S, /* TOPOFF */ + CPROT, /* PROTCMD */ + MAX8971_N_REGMAP_FIELDS +}; + +static const struct reg_field max8971_reg_field[MAX8971_N_REGMAP_FIELDS] = { + [THM_DTLS] = REG_FIELD(MAX8971_REG_DETAILS1, 0, 2), + [BAT_DTLS] = REG_FIELD(MAX8971_REG_DETAILS2, 4, 5), + [CHG_DTLS] = REG_FIELD(MAX8971_REG_DETAILS2, 0, 3), + [CHG_CC] = REG_FIELD(MAX8971_REG_FCHGCRNT, 0, 4), + [FCHG_T] = REG_FIELD(MAX8971_REG_FCHGCRNT, 5, 7), + [DCI_LMT] = REG_FIELD(MAX8971_REG_DCCRNT, 0, 5), + [TOPOFF_T] = REG_FIELD(MAX8971_REG_TOPOFF, 5, 7), + [TOPOFF_S] = REG_FIELD(MAX8971_REG_TOPOFF, 2, 3), + [CPROT] = REG_FIELD(MAX8971_REG_PROTCMD, 2, 3), +}; + +static const struct regmap_config max8971_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX8971_REG_CHGINT, +}; + +struct max8971_data { + struct device *dev; + struct power_supply *psy_mains; + + struct extcon_dev *edev; + struct notifier_block extcon_nb; + struct delayed_work extcon_work; + + struct regmap *regmap; + struct regmap_field *rfield[MAX8971_N_REGMAP_FIELDS]; + + enum power_supply_usb_type usb_type; + + u32 fchgt; + u32 tofft; + u32 toffs; + + bool present; +}; + +static int max8971_get_status(struct max8971_data *priv, int *val) +{ + u32 regval; + int err; + + err = regmap_field_read(priv->rfield[CHG_DTLS], ®val); + if (err) + return err; + + switch (regval) { + case MAX8971_CHARGING_DEAD_BATTERY: + case MAX8971_CHARGING_PREQUALIFICATION: + case MAX8971_CHARGING_FAST_CONST_CURRENT: + case MAX8971_CHARGING_FAST_CONST_VOLTAGE: + case MAX8971_CHARGING_TOP_OFF: + case MAX8971_CHARGING_THERMAL_LOOP: + *val = POWER_SUPPLY_STATUS_CHARGING; + break; + case MAX8971_CHARGING_DONE: + *val = POWER_SUPPLY_STATUS_FULL; + break; + case MAX8971_CHARGING_TIMER_FAULT: + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case MAX8971_CHARGING_OFF: + case MAX8971_CHARGING_SUSPENDED_THERMAL: + *val = POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + *val = POWER_SUPPLY_STATUS_UNKNOWN; + } + + return 0; +} + +static int max8971_get_charge_type(struct max8971_data *priv, int *val) +{ + u32 regval; + int err; + + err = regmap_field_read(priv->rfield[CHG_DTLS], ®val); + if (err) + return err; + + switch (regval) { + case MAX8971_CHARGING_DEAD_BATTERY: + case MAX8971_CHARGING_PREQUALIFICATION: + *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case MAX8971_CHARGING_FAST_CONST_CURRENT: + case MAX8971_CHARGING_FAST_CONST_VOLTAGE: + *val = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case MAX8971_CHARGING_TOP_OFF: + case MAX8971_CHARGING_THERMAL_LOOP: + *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + break; + case MAX8971_CHARGING_DONE: + case MAX8971_CHARGING_TIMER_FAULT: + case MAX8971_CHARGING_SUSPENDED_THERMAL: + case MAX8971_CHARGING_OFF: + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + default: + *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + + return 0; +} + +static int max8971_get_health(struct max8971_data *priv, int *val) +{ + u32 regval; + int err; + + err = regmap_field_read(priv->rfield[THM_DTLS], ®val); + if (err) + return err; + + switch (regval) { + case MAX8971_HEALTH_COLD: + *val = POWER_SUPPLY_HEALTH_COLD; + break; + case MAX8971_HEALTH_COOL: + *val = POWER_SUPPLY_HEALTH_COOL; + break; + case MAX8971_HEALTH_WARM: + *val = POWER_SUPPLY_HEALTH_GOOD; + break; + case MAX8971_HEALTH_HOT: + *val = POWER_SUPPLY_HEALTH_HOT; + break; + case MAX8971_HEALTH_OVERHEAT: + *val = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case MAX8971_HEALTH_UNKNOWN: + default: + *val = POWER_SUPPLY_HEALTH_UNKNOWN; + } + + return 0; +} + +static int max8971_get_online(struct max8971_data *priv, int *val) +{ + u32 regval; + int err; + + err = regmap_read(priv->regmap, MAX8971_REG_CHG_STAT, ®val); + if (err) + return err; + + if (priv->present) + /* CHG_OK bit is 0 when charger is online */ + *val = !(regval & MAX8971_CHG_MASK); + else + *val = priv->present; + + return 0; +} + +static int max8971_get_integer(struct max8971_data *priv, enum max8971_field_idx fidx, + u32 clamp_min, u32 clamp_max, u32 mult, int *val) +{ + u32 regval; + int err; + + err = regmap_field_read(priv->rfield[fidx], ®val); + if (err) + return err; + + *val = clamp_val(regval * mult, clamp_min, clamp_max); + + return 0; +} + +static int max8971_set_integer(struct max8971_data *priv, enum max8971_field_idx fidx, + u32 clamp_min, u32 clamp_max, u32 div, int val) +{ + u32 regval; + + regval = clamp_val(val, clamp_min, clamp_max) / div; + + return regmap_field_write(priv->rfield[fidx], regval); +} + +static int max8971_get_property(struct power_supply *psy, enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8971_data *priv = power_supply_get_drvdata(psy); + int err = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + err = max8971_get_status(priv, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + err = max8971_get_charge_type(priv, &val->intval); + break; + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = priv->usb_type; + break; + case POWER_SUPPLY_PROP_HEALTH: + err = max8971_get_health(priv, &val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + err = max8971_get_online(priv, &val->intval); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = priv->present; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + val->intval = MAX8971_CHG_CC_MAX; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + err = max8971_get_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX, + MAX8971_CHG_CC_STEP, &val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + err = max8971_get_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX, + MAX8971_DCILMT_STEP, &val->intval); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = max8971_model; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = max8971_manufacturer; + break; + default: + err = -EINVAL; + } + + return err; +} + +static int max8971_set_property(struct power_supply *psy, enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max8971_data *priv = power_supply_get_drvdata(psy); + int err = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + err = max8971_set_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX, + MAX8971_CHG_CC_STEP, val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + err = max8971_set_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX, + MAX8971_DCILMT_STEP, val->intval); + break; + default: + err = -EINVAL; + } + + return err; +}; + +static int max8971_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return true; + default: + return false; + } +} + +static enum power_supply_property max8971_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static const struct power_supply_desc max8971_charger_desc = { + .name = "max8971-charger", + .type = POWER_SUPPLY_TYPE_USB, + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | + BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_ACA), + .properties = max8971_properties, + .num_properties = ARRAY_SIZE(max8971_properties), + .get_property = max8971_get_property, + .set_property = max8971_set_property, + .property_is_writeable = max8971_property_is_writeable, +}; + +static void max8971_update_config(struct max8971_data *priv) +{ + regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_UNLOCKED); + + if (priv->fchgt != MAX8971_FCHGT_DEFAULT) + regmap_field_write(priv->rfield[FCHG_T], priv->fchgt); + + regmap_write_bits(priv->regmap, MAX8971_REG_DCCRNT, MAX8971_CHGRSTRT_MASK, + MAX8971_CHGRSTRT_MASK); + + if (priv->tofft != MAX8971_TOPOFFT_DEFAULT) + regmap_field_write(priv->rfield[TOPOFF_T], priv->tofft); + + if (priv->toffs) + regmap_field_write(priv->rfield[TOPOFF_S], priv->toffs); + + regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_LOCKED); +} + +static ssize_t fast_charge_timer_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = to_power_supply(dev); + struct max8971_data *priv = power_supply_get_drvdata(psy); + u32 regval; + int err; + + err = regmap_field_read(priv->rfield[FCHG_T], ®val); + if (err) + return err; + + switch (regval) { + case 0x1 ... 0x7: + /* Time is off by 3 hours comparing to value */ + regval += 3; + break; + case 0x0: + default: + regval = 0; + break; + } + + return sysfs_emit(buf, "%u\n", regval); +} + +static ssize_t fast_charge_timer_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = to_power_supply(dev); + struct max8971_data *priv = power_supply_get_drvdata(psy); + unsigned long hours; + int val, err; + + err = kstrtoul(buf, 10, &hours); + if (err) + return err; + + val = hours - 3; + if (val <= 0 || val > 7) + priv->fchgt = 0; + else + priv->fchgt = val; + + max8971_update_config(priv); + + return count; +} + +static ssize_t top_off_threshold_current_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = to_power_supply(dev); + struct max8971_data *priv = power_supply_get_drvdata(psy); + u32 regval, val; + int err; + + err = regmap_field_read(priv->rfield[TOPOFF_S], ®val); + if (err) + return err; + + /* 50uA start with 50uA step */ + val = regval * 50 + 50; + val *= 1000; + + return sysfs_emit(buf, "%u\n", val); +} + +static ssize_t top_off_threshold_current_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = to_power_supply(dev); + struct max8971_data *priv = power_supply_get_drvdata(psy); + unsigned long uamp; + int err; + + err = kstrtoul(buf, 10, &uamp); + if (err) + return err; + + if (uamp < 50000 || uamp > 200000) + return -EINVAL; + + priv->toffs = uamp / 50000 - 1; + + max8971_update_config(priv); + + return count; +} + +static ssize_t top_off_timer_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = to_power_supply(dev); + struct max8971_data *priv = power_supply_get_drvdata(psy); + u32 regval; + int err; + + err = regmap_field_read(priv->rfield[TOPOFF_T], ®val); + if (err) + return err; + + /* 10 min intervals */ + regval *= 10; + + return sysfs_emit(buf, "%u\n", regval); +} + +static ssize_t top_off_timer_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = to_power_supply(dev); + struct max8971_data *priv = power_supply_get_drvdata(psy); + unsigned long minutes; + int err; + + err = kstrtoul(buf, 10, &minutes); + if (err) + return err; + + if (minutes > 70) + return -EINVAL; + + priv->tofft = minutes / 10; + + max8971_update_config(priv); + + return count; +} + +static DEVICE_ATTR_RW(fast_charge_timer); +static DEVICE_ATTR_RW(top_off_threshold_current); +static DEVICE_ATTR_RW(top_off_timer); + +static struct attribute *max8971_attrs[] = { + &dev_attr_fast_charge_timer.attr, + &dev_attr_top_off_threshold_current.attr, + &dev_attr_top_off_timer.attr, + NULL +}; +ATTRIBUTE_GROUPS(max8971); + +static void max8971_extcon_evt_worker(struct work_struct *work) +{ + struct max8971_data *priv = + container_of(work, struct max8971_data, extcon_work.work); + struct device *dev = priv->dev; + struct extcon_dev *edev = priv->edev; + u32 chgcc, dcilmt; + + if (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0) { + dev_dbg(dev, "USB SDP charger is connected\n"); + priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP; + chgcc = 500000; + dcilmt = 500000; + } else if (extcon_get_state(edev, EXTCON_USB) > 0) { + dev_dbg(dev, "USB charger is connected\n"); + priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP; + chgcc = 500000; + dcilmt = 500000; + } else if (extcon_get_state(edev, EXTCON_DISP_MHL) > 0) { + dev_dbg(dev, "MHL plug is connected\n"); + priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP; + chgcc = 500000; + dcilmt = 500000; + } else if (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0) { + dev_dbg(dev, "USB DCP charger is connected\n"); + priv->usb_type = POWER_SUPPLY_USB_TYPE_DCP; + chgcc = 900000; + dcilmt = 1200000; + } else if (extcon_get_state(edev, EXTCON_CHG_USB_FAST) > 0) { + dev_dbg(dev, "USB FAST charger is connected\n"); + priv->usb_type = POWER_SUPPLY_USB_TYPE_ACA; + chgcc = 900000; + dcilmt = 1200000; + } else if (extcon_get_state(edev, EXTCON_CHG_USB_SLOW) > 0) { + dev_dbg(dev, "USB SLOW charger is connected\n"); + priv->usb_type = POWER_SUPPLY_USB_TYPE_ACA; + chgcc = 900000; + dcilmt = 1200000; + } else if (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0) { + dev_dbg(dev, "USB CDP charger is connected\n"); + priv->usb_type = POWER_SUPPLY_USB_TYPE_CDP; + chgcc = 900000; + dcilmt = 1200000; + } else { + dev_dbg(dev, "USB state is unknown\n"); + priv->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + return; + } + + regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_UNLOCKED); + + max8971_set_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX, + MAX8971_CHG_CC_STEP, chgcc); + max8971_set_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX, + MAX8971_DCILMT_STEP, dcilmt); + + regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_LOCKED); +} + +static int extcon_get_charger_type(struct notifier_block *nb, + unsigned long state, void *data) +{ + struct max8971_data *priv = + container_of(nb, struct max8971_data, extcon_nb); + schedule_delayed_work(&priv->extcon_work, 0); + + return NOTIFY_OK; +} + +static irqreturn_t max8971_interrupt(int irq, void *dev_id) +{ + struct max8971_data *priv = dev_id; + struct device *dev = priv->dev; + int err, state; + + err = regmap_read(priv->regmap, MAX8971_REG_CHGINT, &state); + if (err) + dev_err(dev, "interrupt reg read failed %d\n", err); + + err = regmap_write_bits(priv->regmap, MAX8971_REG_CHGINT_MASK, + MAX8971_AICL_MASK, MAX8971_AICL_MASK); + if (err) + dev_err(dev, "failed to mask IRQ\n"); + + /* set presence prop */ + priv->present = state & MAX8971_REG_CHG_RST; + + /* on every plug chip resets to default */ + if (priv->present) + max8971_update_config(priv); + + /* update supply status */ + power_supply_changed(priv->psy_mains); + + return IRQ_HANDLED; +} + +static int max8971_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max8971_data *priv; + struct device_node *extcon; + struct power_supply_config cfg = { }; + int err, i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + + i2c_set_clientdata(client, priv); + + priv->regmap = devm_regmap_init_i2c(client, &max8971_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), "cannot allocate regmap\n"); + + for (i = 0; i < MAX8971_N_REGMAP_FIELDS; i++) { + priv->rfield[i] = devm_regmap_field_alloc(dev, priv->regmap, max8971_reg_field[i]); + if (IS_ERR(priv->rfield[i])) + return dev_err_probe(dev, PTR_ERR(priv->rfield[i]), + "cannot allocate regmap field\n"); + } + + cfg.attr_grp = max8971_groups; + cfg.drv_data = priv; + cfg.fwnode = dev_fwnode(dev); + + priv->psy_mains = devm_power_supply_register(dev, &max8971_charger_desc, &cfg); + if (IS_ERR(priv->psy_mains)) + return dev_err_probe(dev, PTR_ERR(priv->psy_mains), + "failed to register mains supply\n"); + + err = regmap_write_bits(priv->regmap, MAX8971_REG_CHGINT_MASK, MAX8971_AICL_MASK, + MAX8971_AICL_MASK); + if (err) + return dev_err_probe(dev, err, "failed to mask IRQ\n"); + + err = devm_request_threaded_irq(dev, client->irq, NULL, &max8971_interrupt, + IRQF_ONESHOT | IRQF_SHARED, client->name, priv); + if (err) + return dev_err_probe(dev, err, "failed to register IRQ %d\n", client->irq); + + extcon = of_graph_get_remote_node(dev->of_node, -1, -1); + if (!extcon) + return 0; + + priv->edev = extcon_find_edev_by_node(extcon); + of_node_put(extcon); + if (IS_ERR(priv->edev)) + return dev_err_probe(dev, PTR_ERR(priv->edev), "failed to find extcon\n"); + + err = devm_delayed_work_autocancel(dev, &priv->extcon_work, + max8971_extcon_evt_worker); + if (err) + return dev_err_probe(dev, err, "failed to add extcon evt stop action\n"); + + priv->extcon_nb.notifier_call = extcon_get_charger_type; + + err = devm_extcon_register_notifier_all(dev, priv->edev, &priv->extcon_nb); + if (err) + return dev_err_probe(dev, err, "failed to register notifier\n"); + + /* Initial configuration work with 1 sec delay */ + schedule_delayed_work(&priv->extcon_work, msecs_to_jiffies(1000)); + + return 0; +} + +static int __maybe_unused max8971_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max8971_data *priv = i2c_get_clientdata(client); + + irq_wake_thread(client->irq, priv); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(max8971_pm_ops, NULL, max8971_resume); + +static const struct of_device_id max8971_match_ids[] = { + { .compatible = "maxim,max8971" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, max8971_match_ids); + +static const struct i2c_device_id max8971_i2c_id[] = { + { "max8971" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max8971_i2c_id); + +static struct i2c_driver max8971_driver = { + .driver = { + .name = "max8971-charger", + .of_match_table = max8971_match_ids, + .pm = &max8971_pm_ops, + }, + .probe = max8971_probe, + .id_table = max8971_i2c_id, +}; +module_i2c_driver(max8971_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); +MODULE_DESCRIPTION("MAX8971 Charger Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/mm8013.c b/drivers/power/supply/mm8013.c index 4adf2acc2779..93c50cff31bc 100644 --- a/drivers/power/supply/mm8013.c +++ b/drivers/power/supply/mm8013.c @@ -274,7 +274,7 @@ static int mm8013_probe(struct i2c_client *client) return dev_err_probe(dev, ret, "MM8013 not found\n"); psy_cfg.drv_data = chip; - psy_cfg.of_node = dev->of_node; + psy_cfg.fwnode = dev_fwnode(dev); psy = devm_power_supply_register(dev, &mm8013_desc, &psy_cfg); if (IS_ERR(psy)) diff --git a/drivers/power/supply/mt6360_charger.c b/drivers/power/supply/mt6360_charger.c index e99e55148976..77747eb51667 100644 --- a/drivers/power/supply/mt6360_charger.c +++ b/drivers/power/supply/mt6360_charger.c @@ -810,7 +810,7 @@ static int mt6360_charger_probe(struct platform_device *pdev) memcpy(&mci->psy_desc, &mt6360_charger_desc, sizeof(mci->psy_desc)); mci->psy_desc.name = dev_name(&pdev->dev); charger_cfg.drv_data = mci; - charger_cfg.of_node = pdev->dev.of_node; + charger_cfg.fwnode = dev_fwnode(&pdev->dev); mci->psy = devm_power_supply_register(&pdev->dev, &mci->psy_desc, &charger_cfg); if (IS_ERR(mci->psy)) diff --git a/drivers/power/supply/mt6370-charger.c b/drivers/power/supply/mt6370-charger.c index ad8793bf997e..916556baa854 100644 --- a/drivers/power/supply/mt6370-charger.c +++ b/drivers/power/supply/mt6370-charger.c @@ -752,7 +752,7 @@ static int mt6370_chg_init_psy(struct mt6370_priv *priv) { struct power_supply_config cfg = { .drv_data = priv, - .of_node = dev_of_node(priv->dev), + .fwnode = dev_fwnode(priv->dev), }; priv->psy = devm_power_supply_register(priv->dev, &mt6370_chg_psy_desc, @@ -761,21 +761,6 @@ static int mt6370_chg_init_psy(struct mt6370_priv *priv) return PTR_ERR_OR_ZERO(priv->psy); } -static void mt6370_chg_destroy_attach_lock(void *data) -{ - struct mutex *attach_lock = data; - - mutex_destroy(attach_lock); -} - -static void mt6370_chg_destroy_wq(void *data) -{ - struct workqueue_struct *wq = data; - - flush_workqueue(wq); - destroy_workqueue(wq); -} - static irqreturn_t mt6370_attach_i_handler(int irq, void *data) { struct mt6370_priv *priv = data; @@ -895,22 +880,15 @@ static int mt6370_chg_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "Failed to init psy\n"); - mutex_init(&priv->attach_lock); - ret = devm_add_action_or_reset(dev, mt6370_chg_destroy_attach_lock, - &priv->attach_lock); + ret = devm_mutex_init(dev, &priv->attach_lock); if (ret) - return dev_err_probe(dev, ret, "Failed to init attach lock\n"); + return ret; priv->attach = MT6370_ATTACH_STAT_DETACH; - priv->wq = create_singlethread_workqueue(dev_name(priv->dev)); + priv->wq = devm_alloc_ordered_workqueue(dev, "%s", 0, dev_name(priv->dev)); if (!priv->wq) - return dev_err_probe(dev, -ENOMEM, - "Failed to create workqueue\n"); - - ret = devm_add_action_or_reset(dev, mt6370_chg_destroy_wq, priv->wq); - if (ret) - return dev_err_probe(dev, ret, "Failed to init wq\n"); + return -ENOMEM; ret = devm_work_autocancel(dev, &priv->bc12_work, mt6370_chg_bc12_work_func); if (ret) diff --git a/drivers/power/supply/olpc_battery.c b/drivers/power/supply/olpc_battery.c index 849f63e89ba0..202c4fa9b903 100644 --- a/drivers/power/supply/olpc_battery.c +++ b/drivers/power/supply/olpc_battery.c @@ -553,7 +553,7 @@ static const struct bin_attribute olpc_bat_eeprom = { .mode = S_IRUGO, }, .size = EEPROM_SIZE, - .read_new = olpc_bat_eeprom_read, + .read = olpc_bat_eeprom_read, }; /* Allow userspace to see the specific error value pulled from the EC */ @@ -591,7 +591,7 @@ static const struct bin_attribute *const olpc_bat_sysfs_bin_attrs[] = { static const struct attribute_group olpc_bat_sysfs_group = { .attrs = olpc_bat_sysfs_attrs, - .bin_attrs_new = olpc_bat_sysfs_bin_attrs, + .bin_attrs = olpc_bat_sysfs_bin_attrs, }; static const struct attribute_group *olpc_bat_sysfs_groups[] = { @@ -674,7 +674,7 @@ static int olpc_battery_probe(struct platform_device *pdev) /* Ignore the status. It doesn't actually matter */ - ac_psy_cfg.of_node = pdev->dev.of_node; + ac_psy_cfg.fwnode = dev_fwnode(&pdev->dev); ac_psy_cfg.drv_data = data; data->olpc_ac = devm_power_supply_register(&pdev->dev, &olpc_ac_desc, @@ -692,7 +692,7 @@ static int olpc_battery_probe(struct platform_device *pdev) olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo1_bat_props); } - bat_psy_cfg.of_node = pdev->dev.of_node; + bat_psy_cfg.fwnode = dev_fwnode(&pdev->dev); bat_psy_cfg.drv_data = data; bat_psy_cfg.attr_grp = olpc_bat_sysfs_groups; diff --git a/drivers/power/supply/pcf50633-charger.c b/drivers/power/supply/pcf50633-charger.c deleted file mode 100644 index 0136bc87b105..000000000000 --- a/drivers/power/supply/pcf50633-charger.c +++ /dev/null @@ -1,466 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* NXP PCF50633 Main Battery Charger Driver - * - * (C) 2006-2008 by Openmoko, Inc. - * Author: Balaji Rao <balajirrao@openmoko.org> - * All rights reserved. - * - * Broken down from monstrous PCF50633 driver mainly by - * Harald Welte, Andy Green and Werner Almesberger - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/slab.h> -#include <linux/init.h> -#include <linux/types.h> -#include <linux/device.h> -#include <linux/sysfs.h> -#include <linux/platform_device.h> -#include <linux/power_supply.h> - -#include <linux/mfd/pcf50633/core.h> -#include <linux/mfd/pcf50633/mbc.h> - -struct pcf50633_mbc { - struct pcf50633 *pcf; - - int adapter_online; - int usb_online; - - struct power_supply *usb; - struct power_supply *adapter; - struct power_supply *ac; -}; - -int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) -{ - struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); - int ret = 0; - u8 bits; - u8 mbcs2, chgmod; - unsigned int mbcc5; - - if (ma >= 1000) { - bits = PCF50633_MBCC7_USB_1000mA; - ma = 1000; - } else if (ma >= 500) { - bits = PCF50633_MBCC7_USB_500mA; - ma = 500; - } else if (ma >= 100) { - bits = PCF50633_MBCC7_USB_100mA; - ma = 100; - } else { - bits = PCF50633_MBCC7_USB_SUSPEND; - ma = 0; - } - - ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, - PCF50633_MBCC7_USB_MASK, bits); - if (ret) - dev_err(pcf->dev, "error setting usb curlim to %d mA\n", ma); - else - dev_info(pcf->dev, "usb curlim to %d mA\n", ma); - - /* - * We limit the charging current to be the USB current limit. - * The reason is that on pcf50633, when it enters PMU Standby mode, - * which it does when the device goes "off", the USB current limit - * reverts to the variant default. In at least one common case, that - * default is 500mA. By setting the charging current to be the same - * as the USB limit we set here before PMU standby, we enforce it only - * using the correct amount of current even when the USB current limit - * gets reset to the wrong thing - */ - - if (mbc->pcf->pdata->charger_reference_current_ma) { - mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma; - if (mbcc5 > 255) - mbcc5 = 255; - pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5); - } - - mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); - chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); - - /* If chgmod == BATFULL, setting chgena has no effect. - * Datasheet says we need to set resume instead but when autoresume is - * used resume doesn't work. Clear and set chgena instead. - */ - if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL) - pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, - PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA); - else { - pcf50633_reg_clear_bits(pcf, PCF50633_REG_MBCC1, - PCF50633_MBCC1_CHGENA); - pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, - PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA); - } - - power_supply_changed(mbc->usb); - - return ret; -} -EXPORT_SYMBOL_GPL(pcf50633_mbc_usb_curlim_set); - -int pcf50633_mbc_get_status(struct pcf50633 *pcf) -{ - struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); - int status = 0; - u8 chgmod; - - if (!mbc) - return 0; - - chgmod = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2) - & PCF50633_MBCS2_MBC_MASK; - - if (mbc->usb_online) - status |= PCF50633_MBC_USB_ONLINE; - if (chgmod == PCF50633_MBCS2_MBC_USB_PRE || - chgmod == PCF50633_MBCS2_MBC_USB_PRE_WAIT || - chgmod == PCF50633_MBCS2_MBC_USB_FAST || - chgmod == PCF50633_MBCS2_MBC_USB_FAST_WAIT) - status |= PCF50633_MBC_USB_ACTIVE; - if (mbc->adapter_online) - status |= PCF50633_MBC_ADAPTER_ONLINE; - if (chgmod == PCF50633_MBCS2_MBC_ADP_PRE || - chgmod == PCF50633_MBCS2_MBC_ADP_PRE_WAIT || - chgmod == PCF50633_MBCS2_MBC_ADP_FAST || - chgmod == PCF50633_MBCS2_MBC_ADP_FAST_WAIT) - status |= PCF50633_MBC_ADAPTER_ACTIVE; - - return status; -} -EXPORT_SYMBOL_GPL(pcf50633_mbc_get_status); - -int pcf50633_mbc_get_usb_online_status(struct pcf50633 *pcf) -{ - struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); - - if (!mbc) - return 0; - - return mbc->usb_online; -} -EXPORT_SYMBOL_GPL(pcf50633_mbc_get_usb_online_status); - -static ssize_t -show_chgmode(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct pcf50633_mbc *mbc = dev_get_drvdata(dev); - - u8 mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); - u8 chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); - - return sysfs_emit(buf, "%d\n", chgmod); -} -static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, NULL); - -static ssize_t -show_usblim(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct pcf50633_mbc *mbc = dev_get_drvdata(dev); - u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & - PCF50633_MBCC7_USB_MASK; - unsigned int ma; - - if (usblim == PCF50633_MBCC7_USB_1000mA) - ma = 1000; - else if (usblim == PCF50633_MBCC7_USB_500mA) - ma = 500; - else if (usblim == PCF50633_MBCC7_USB_100mA) - ma = 100; - else - ma = 0; - - return sysfs_emit(buf, "%u\n", ma); -} - -static ssize_t set_usblim(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct pcf50633_mbc *mbc = dev_get_drvdata(dev); - unsigned long ma; - int ret; - - ret = kstrtoul(buf, 10, &ma); - if (ret) - return ret; - - pcf50633_mbc_usb_curlim_set(mbc->pcf, ma); - - return count; -} - -static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim); - -static ssize_t -show_chglim(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct pcf50633_mbc *mbc = dev_get_drvdata(dev); - u8 mbcc5 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC5); - unsigned int ma; - - if (!mbc->pcf->pdata->charger_reference_current_ma) - return -ENODEV; - - ma = (mbc->pcf->pdata->charger_reference_current_ma * mbcc5) >> 8; - - return sysfs_emit(buf, "%u\n", ma); -} - -static ssize_t set_chglim(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct pcf50633_mbc *mbc = dev_get_drvdata(dev); - unsigned long ma; - unsigned int mbcc5; - int ret; - - if (!mbc->pcf->pdata->charger_reference_current_ma) - return -ENODEV; - - ret = kstrtoul(buf, 10, &ma); - if (ret) - return ret; - - mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma; - if (mbcc5 > 255) - mbcc5 = 255; - pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5); - - return count; -} - -/* - * This attribute allows to change MBC charging limit on the fly - * independently of usb current limit. It also gets set automatically every - * time usb current limit is changed. - */ -static DEVICE_ATTR(chg_curlim, S_IRUGO | S_IWUSR, show_chglim, set_chglim); - -static struct attribute *pcf50633_mbc_sysfs_attrs[] = { - &dev_attr_chgmode.attr, - &dev_attr_usb_curlim.attr, - &dev_attr_chg_curlim.attr, - NULL, -}; - -ATTRIBUTE_GROUPS(pcf50633_mbc_sysfs); - -static void -pcf50633_mbc_irq_handler(int irq, void *data) -{ - struct pcf50633_mbc *mbc = data; - - /* USB */ - if (irq == PCF50633_IRQ_USBINS) { - mbc->usb_online = 1; - } else if (irq == PCF50633_IRQ_USBREM) { - mbc->usb_online = 0; - pcf50633_mbc_usb_curlim_set(mbc->pcf, 0); - } - - /* Adapter */ - if (irq == PCF50633_IRQ_ADPINS) - mbc->adapter_online = 1; - else if (irq == PCF50633_IRQ_ADPREM) - mbc->adapter_online = 0; - - power_supply_changed(mbc->ac); - power_supply_changed(mbc->usb); - power_supply_changed(mbc->adapter); - - if (mbc->pcf->pdata->mbc_event_callback) - mbc->pcf->pdata->mbc_event_callback(mbc->pcf, irq); -} - -static int adapter_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = mbc->adapter_online; - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int usb_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); - int ret = 0; - u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & - PCF50633_MBCC7_USB_MASK; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = mbc->usb_online && - (usblim <= PCF50633_MBCC7_USB_500mA); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int ac_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy); - int ret = 0; - u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & - PCF50633_MBCC7_USB_MASK; - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = mbc->usb_online && - (usblim == PCF50633_MBCC7_USB_1000mA); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static enum power_supply_property power_props[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static const u8 mbc_irq_handlers[] = { - PCF50633_IRQ_ADPINS, - PCF50633_IRQ_ADPREM, - PCF50633_IRQ_USBINS, - PCF50633_IRQ_USBREM, - PCF50633_IRQ_BATFULL, - PCF50633_IRQ_CHGHALT, - PCF50633_IRQ_THLIMON, - PCF50633_IRQ_THLIMOFF, - PCF50633_IRQ_USBLIMON, - PCF50633_IRQ_USBLIMOFF, - PCF50633_IRQ_LOWSYS, - PCF50633_IRQ_LOWBAT, -}; - -static const struct power_supply_desc pcf50633_mbc_adapter_desc = { - .name = "adapter", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = power_props, - .num_properties = ARRAY_SIZE(power_props), - .get_property = &adapter_get_property, -}; - -static const struct power_supply_desc pcf50633_mbc_usb_desc = { - .name = "usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = power_props, - .num_properties = ARRAY_SIZE(power_props), - .get_property = usb_get_property, -}; - -static const struct power_supply_desc pcf50633_mbc_ac_desc = { - .name = "ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = power_props, - .num_properties = ARRAY_SIZE(power_props), - .get_property = ac_get_property, -}; - -static int pcf50633_mbc_probe(struct platform_device *pdev) -{ - struct power_supply_config psy_cfg = {}; - struct power_supply_config usb_psy_cfg; - struct pcf50633_mbc *mbc; - int i; - u8 mbcs1; - - mbc = devm_kzalloc(&pdev->dev, sizeof(*mbc), GFP_KERNEL); - if (!mbc) - return -ENOMEM; - - platform_set_drvdata(pdev, mbc); - mbc->pcf = dev_to_pcf50633(pdev->dev.parent); - - /* Set up IRQ handlers */ - for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) - pcf50633_register_irq(mbc->pcf, mbc_irq_handlers[i], - pcf50633_mbc_irq_handler, mbc); - - psy_cfg.supplied_to = mbc->pcf->pdata->batteries; - psy_cfg.num_supplicants = mbc->pcf->pdata->num_batteries; - psy_cfg.drv_data = mbc; - - /* Create power supplies */ - mbc->adapter = devm_power_supply_register(&pdev->dev, - &pcf50633_mbc_adapter_desc, - &psy_cfg); - if (IS_ERR(mbc->adapter)) { - dev_err(mbc->pcf->dev, "failed to register adapter\n"); - return PTR_ERR(mbc->adapter); - } - - usb_psy_cfg = psy_cfg; - usb_psy_cfg.attr_grp = pcf50633_mbc_sysfs_groups; - - mbc->usb = devm_power_supply_register(&pdev->dev, - &pcf50633_mbc_usb_desc, - &usb_psy_cfg); - if (IS_ERR(mbc->usb)) { - dev_err(mbc->pcf->dev, "failed to register usb\n"); - return PTR_ERR(mbc->usb); - } - - mbc->ac = devm_power_supply_register(&pdev->dev, - &pcf50633_mbc_ac_desc, - &psy_cfg); - if (IS_ERR(mbc->ac)) { - dev_err(mbc->pcf->dev, "failed to register ac\n"); - return PTR_ERR(mbc->ac); - } - - mbcs1 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS1); - if (mbcs1 & PCF50633_MBCS1_USBPRES) - pcf50633_mbc_irq_handler(PCF50633_IRQ_USBINS, mbc); - if (mbcs1 & PCF50633_MBCS1_ADAPTPRES) - pcf50633_mbc_irq_handler(PCF50633_IRQ_ADPINS, mbc); - - return 0; -} - -static void pcf50633_mbc_remove(struct platform_device *pdev) -{ - struct pcf50633_mbc *mbc = platform_get_drvdata(pdev); - int i; - - /* Remove IRQ handlers */ - for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) - pcf50633_free_irq(mbc->pcf, mbc_irq_handlers[i]); -} - -static struct platform_driver pcf50633_mbc_driver = { - .driver = { - .name = "pcf50633-mbc", - }, - .probe = pcf50633_mbc_probe, - .remove = pcf50633_mbc_remove, -}; - -module_platform_driver(pcf50633_mbc_driver); - -MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>"); -MODULE_DESCRIPTION("PCF50633 mbc driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:pcf50633-mbc"); diff --git a/drivers/power/supply/pf1550-charger.c b/drivers/power/supply/pf1550-charger.c new file mode 100644 index 000000000000..a457862ef461 --- /dev/null +++ b/drivers/power/supply/pf1550-charger.c @@ -0,0 +1,641 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * charger driver for the PF1550 + * + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Robin Gong <yibin.gong@freescale.com> + * + * Portions Copyright (c) 2025 Savoir-faire Linux Inc. + * Samuel Kayode <samuel.kayode@savoirfairelinux.com> + */ + +#include <linux/devm-helpers.h> +#include <linux/interrupt.h> +#include <linux/mfd/pf1550.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> + +#define PF1550_DEFAULT_CONSTANT_VOLT 4200000 +#define PF1550_DEFAULT_MIN_SYSTEM_VOLT 3500000 +#define PF1550_DEFAULT_THERMAL_TEMP 95 +#define PF1550_CHARGER_IRQ_NR 5 + +struct pf1550_charger { + struct device *dev; + const struct pf1550_ddata *pf1550; + struct power_supply *charger; + struct power_supply *battery; + struct delayed_work vbus_sense_work; + struct delayed_work chg_sense_work; + struct delayed_work bat_sense_work; + int virqs[PF1550_CHARGER_IRQ_NR]; + + u32 constant_volt; + u32 min_system_volt; + u32 thermal_regulation_temp; +}; + +static int pf1550_get_charger_state(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, PF1550_CHARG_REG_CHG_SNS, &data); + if (ret < 0) + return ret; + + data &= PF1550_CHG_SNS_MASK; + + switch (data) { + case PF1550_CHG_PRECHARGE: + case PF1550_CHG_CONSTANT_CURRENT: + case PF1550_CHG_CONSTANT_VOL: + case PF1550_CHG_EOC: + *val = POWER_SUPPLY_STATUS_CHARGING; + break; + case PF1550_CHG_DONE: + *val = POWER_SUPPLY_STATUS_FULL; + break; + case PF1550_CHG_TIMER_FAULT: + case PF1550_CHG_SUSPEND: + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case PF1550_CHG_OFF_INV: + case PF1550_CHG_OFF_TEMP: + case PF1550_CHG_LINEAR_ONLY: + *val = POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + *val = POWER_SUPPLY_STATUS_UNKNOWN; + } + + return 0; +} + +static int pf1550_get_charge_type(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, PF1550_CHARG_REG_CHG_SNS, &data); + if (ret < 0) + return ret; + + data &= PF1550_CHG_SNS_MASK; + + switch (data) { + case PF1550_CHG_SNS_MASK: + *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case PF1550_CHG_CONSTANT_CURRENT: + case PF1550_CHG_CONSTANT_VOL: + case PF1550_CHG_EOC: + *val = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case PF1550_CHG_DONE: + case PF1550_CHG_TIMER_FAULT: + case PF1550_CHG_SUSPEND: + case PF1550_CHG_OFF_INV: + case PF1550_CHG_BAT_OVER: + case PF1550_CHG_OFF_TEMP: + case PF1550_CHG_LINEAR_ONLY: + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + default: + *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + + return 0; +} + +/* + * Supported health statuses: + * - POWER_SUPPLY_HEALTH_DEAD + * - POWER_SUPPLY_HEALTH_GOOD + * - POWER_SUPPLY_HEALTH_OVERVOLTAGE + * - POWER_SUPPLY_HEALTH_UNKNOWN + */ +static int pf1550_get_battery_health(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, PF1550_CHARG_REG_BATT_SNS, &data); + if (ret < 0) + return ret; + + data &= PF1550_BAT_SNS_MASK; + + switch (data) { + case PF1550_BAT_NO_DETECT: + *val = POWER_SUPPLY_HEALTH_NO_BATTERY; + break; + case PF1550_BAT_NO_VBUS: + case PF1550_BAT_LOW_THAN_PRECHARG: + case PF1550_BAT_CHARG_FAIL: + case PF1550_BAT_HIGH_THAN_PRECHARG: + *val = POWER_SUPPLY_HEALTH_GOOD; + break; + case PF1550_BAT_OVER_VOL: + *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + default: + *val = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + } + + return 0; +} + +static int pf1550_get_present(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, PF1550_CHARG_REG_BATT_SNS, &data); + if (ret < 0) + return ret; + + data &= PF1550_BAT_SNS_MASK; + *val = (data == PF1550_BAT_NO_DETECT) ? 0 : 1; + + return 0; +} + +static int pf1550_get_online(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, PF1550_CHARG_REG_VBUS_SNS, &data); + if (ret < 0) + return ret; + + *val = (data & PF1550_VBUS_VALID) ? 1 : 0; + + return 0; +} + +static void pf1550_chg_bat_work(struct work_struct *work) +{ + struct pf1550_charger *chg = container_of(to_delayed_work(work), + struct pf1550_charger, + bat_sense_work); + unsigned int data; + + if (regmap_read(chg->pf1550->regmap, PF1550_CHARG_REG_BATT_SNS, &data)) { + dev_err(chg->dev, "Read BATT_SNS error.\n"); + return; + } + + switch (data & PF1550_BAT_SNS_MASK) { + case PF1550_BAT_NO_VBUS: + dev_dbg(chg->dev, "No valid VBUS input.\n"); + break; + case PF1550_BAT_LOW_THAN_PRECHARG: + dev_dbg(chg->dev, "VBAT < VPRECHG.LB.\n"); + break; + case PF1550_BAT_CHARG_FAIL: + dev_dbg(chg->dev, "Battery charging failed.\n"); + break; + case PF1550_BAT_HIGH_THAN_PRECHARG: + dev_dbg(chg->dev, "VBAT > VPRECHG.LB.\n"); + break; + case PF1550_BAT_OVER_VOL: + dev_dbg(chg->dev, "VBAT > VBATOV.\n"); + break; + case PF1550_BAT_NO_DETECT: + dev_dbg(chg->dev, "Battery not detected.\n"); + break; + default: + dev_err(chg->dev, "Unknown value read:%x\n", + data & PF1550_CHG_SNS_MASK); + } +} + +static void pf1550_chg_chg_work(struct work_struct *work) +{ + struct pf1550_charger *chg = container_of(to_delayed_work(work), + struct pf1550_charger, + chg_sense_work); + unsigned int data; + + if (regmap_read(chg->pf1550->regmap, PF1550_CHARG_REG_CHG_SNS, &data)) { + dev_err(chg->dev, "Read CHG_SNS error.\n"); + return; + } + + switch (data & PF1550_CHG_SNS_MASK) { + case PF1550_CHG_PRECHARGE: + dev_dbg(chg->dev, "In pre-charger mode.\n"); + break; + case PF1550_CHG_CONSTANT_CURRENT: + dev_dbg(chg->dev, "In fast-charge constant current mode.\n"); + break; + case PF1550_CHG_CONSTANT_VOL: + dev_dbg(chg->dev, "In fast-charge constant voltage mode.\n"); + break; + case PF1550_CHG_EOC: + dev_dbg(chg->dev, "In EOC mode.\n"); + break; + case PF1550_CHG_DONE: + dev_dbg(chg->dev, "In DONE mode.\n"); + break; + case PF1550_CHG_TIMER_FAULT: + dev_info(chg->dev, "In timer fault mode.\n"); + break; + case PF1550_CHG_SUSPEND: + dev_info(chg->dev, "In thermistor suspend mode.\n"); + break; + case PF1550_CHG_OFF_INV: + dev_info(chg->dev, "Input invalid, charger off.\n"); + break; + case PF1550_CHG_BAT_OVER: + dev_warn(chg->dev, "Battery over-voltage.\n"); + break; + case PF1550_CHG_OFF_TEMP: + dev_info(chg->dev, "Temp high, charger off.\n"); + break; + case PF1550_CHG_LINEAR_ONLY: + dev_dbg(chg->dev, "In Linear mode, not charging.\n"); + break; + default: + dev_err(chg->dev, "Unknown value read:%x\n", + data & PF1550_CHG_SNS_MASK); + } +} + +static void pf1550_chg_vbus_work(struct work_struct *work) +{ + struct pf1550_charger *chg = container_of(to_delayed_work(work), + struct pf1550_charger, + vbus_sense_work); + unsigned int data; + + if (regmap_read(chg->pf1550->regmap, PF1550_CHARG_REG_VBUS_SNS, &data)) { + dev_err(chg->dev, "Read VBUS_SNS error.\n"); + return; + } + + if (data & PF1550_VBUS_UVLO) { + dev_dbg(chg->dev, "VBUS detached.\n"); + power_supply_changed(chg->battery); + } + if (data & PF1550_VBUS_IN2SYS) + dev_dbg(chg->dev, "VBUS_IN2SYS_SNS.\n"); + if (data & PF1550_VBUS_OVLO) + dev_dbg(chg->dev, "VBUS_OVLO_SNS.\n"); + if (data & PF1550_VBUS_VALID) { + dev_dbg(chg->dev, "VBUS attached.\n"); + power_supply_changed(chg->charger); + } +} + +static irqreturn_t pf1550_charger_irq_handler(int irq, void *data) +{ + struct pf1550_charger *chg = data; + struct device *dev = chg->dev; + int i, irq_type = -1; + + for (i = 0; i < PF1550_CHARGER_IRQ_NR; i++) + if (irq == chg->virqs[i]) + irq_type = i; + + switch (irq_type) { + case PF1550_CHARG_IRQ_BAT2SOCI: + dev_info(dev, "BAT to SYS Overcurrent interrupt.\n"); + break; + case PF1550_CHARG_IRQ_BATI: + schedule_delayed_work(&chg->bat_sense_work, + msecs_to_jiffies(10)); + break; + case PF1550_CHARG_IRQ_CHGI: + schedule_delayed_work(&chg->chg_sense_work, + msecs_to_jiffies(10)); + break; + case PF1550_CHARG_IRQ_VBUSI: + schedule_delayed_work(&chg->vbus_sense_work, + msecs_to_jiffies(10)); + break; + case PF1550_CHARG_IRQ_THMI: + dev_info(dev, "Thermal interrupt.\n"); + break; + default: + dev_err(dev, "unknown interrupt occurred.\n"); + } + + return IRQ_HANDLED; +} + +static enum power_supply_property pf1550_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property pf1550_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static int pf1550_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pf1550_charger *chg = power_supply_get_drvdata(psy); + struct regmap *regmap = chg->pf1550->regmap; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = pf1550_get_charger_state(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = pf1550_get_charge_type(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = pf1550_get_battery_health(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = pf1550_get_present(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = pf1550_get_online(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "PF1550"; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "NXP"; + break; + default: + return -EINVAL; + } + + return ret; +} + +static const struct power_supply_desc pf1550_charger_desc = { + .name = "pf1550-charger", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = pf1550_charger_props, + .num_properties = ARRAY_SIZE(pf1550_charger_props), + .get_property = pf1550_charger_get_property, +}; + +static const struct power_supply_desc pf1550_battery_desc = { + .name = "pf1550-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = pf1550_battery_props, + .num_properties = ARRAY_SIZE(pf1550_battery_props), + .get_property = pf1550_charger_get_property, +}; + +static int pf1550_set_constant_volt(struct pf1550_charger *chg, + unsigned int uvolt) +{ + unsigned int data; + + if (uvolt >= 3500000 && uvolt <= 4440000) + data = 8 + (uvolt - 3500000) / 20000; + else + return dev_err_probe(chg->dev, -EINVAL, + "Wrong value for constant voltage\n"); + + dev_dbg(chg->dev, "Charging constant voltage: %u (0x%x)\n", uvolt, + data); + + return regmap_update_bits(chg->pf1550->regmap, + PF1550_CHARG_REG_BATT_REG, + PF1550_CHARG_REG_BATT_REG_CHGCV_MASK, data); +} + +static int pf1550_set_min_system_volt(struct pf1550_charger *chg, + unsigned int uvolt) +{ + unsigned int data; + + switch (uvolt) { + case 3500000: + data = 0x0; + break; + case 3700000: + data = 0x1; + break; + case 4300000: + data = 0x2; + break; + default: + return dev_err_probe(chg->dev, -EINVAL, + "Wrong value for minimum system voltage\n"); + } + + data <<= PF1550_CHARG_REG_BATT_REG_VMINSYS_SHIFT; + + dev_dbg(chg->dev, "Minimum system regulation voltage: %u (0x%x)\n", + uvolt, data); + + return regmap_update_bits(chg->pf1550->regmap, + PF1550_CHARG_REG_BATT_REG, + PF1550_CHARG_REG_BATT_REG_VMINSYS_MASK, data); +} + +static int pf1550_set_thermal_regulation_temp(struct pf1550_charger *chg, + unsigned int cells) +{ + unsigned int data; + + switch (cells) { + case 80: + data = 0x0; + break; + case 95: + data = 0x1; + break; + case 110: + data = 0x2; + break; + case 125: + data = 0x3; + break; + default: + return dev_err_probe(chg->dev, -EINVAL, + "Wrong value for thermal temperature\n"); + } + + data <<= PF1550_CHARG_REG_THM_REG_CNFG_REGTEMP_SHIFT; + + dev_dbg(chg->dev, "Thermal regulation loop temperature: %u (0x%x)\n", + cells, data); + + return regmap_update_bits(chg->pf1550->regmap, + PF1550_CHARG_REG_THM_REG_CNFG, + PF1550_CHARG_REG_THM_REG_CNFG_REGTEMP_MASK, + data); +} + +/* + * Sets charger registers to proper and safe default values. + */ +static int pf1550_reg_init(struct pf1550_charger *chg) +{ + struct power_supply_battery_info *info; + struct device *dev = chg->dev; + int ret; + + /* Unmask charger interrupt, mask DPMI and reserved bit */ + ret = regmap_write(chg->pf1550->regmap, PF1550_CHARG_REG_CHG_INT_MASK, + PF1550_CHG_INT_MASK); + if (ret) + return dev_err_probe(dev, ret, + "Error unmask charger interrupt\n"); + + ret = pf1550_set_constant_volt(chg, chg->constant_volt); + if (ret) + return ret; + + ret = pf1550_set_min_system_volt(chg, chg->min_system_volt); + if (ret) + return ret; + + ret = pf1550_set_thermal_regulation_temp(chg, + chg->thermal_regulation_temp); + if (ret) + return ret; + + /* + * The PF1550 charger has 3 modes of operation. By default, the charger + * is in mode 1; it remains off. Appropriate for applications not using + * a battery. The other supported mode is mode 2, the charger is turned + * on to charge a battery when present. + */ + if (power_supply_get_battery_info(chg->charger, &info)) { + ret = regmap_write(chg->pf1550->regmap, + PF1550_CHARG_REG_CHG_OPER, + PF1550_CHG_BAT_ON); + if (ret) + return dev_err_probe(dev, ret, + "Error turn on charger\n"); + } + + return 0; +} + +static void pf1550_dt_parse_dev_info(struct pf1550_charger *chg) +{ + struct power_supply_battery_info *info; + struct device *dev = chg->dev; + + if (device_property_read_u32(dev->parent, "nxp,min-system-microvolt", + &chg->min_system_volt)) + chg->min_system_volt = PF1550_DEFAULT_MIN_SYSTEM_VOLT; + + if (device_property_read_u32(dev->parent, + "nxp,thermal-regulation-celsius", + &chg->thermal_regulation_temp)) + chg->thermal_regulation_temp = PF1550_DEFAULT_THERMAL_TEMP; + + if (power_supply_get_battery_info(chg->charger, &info)) + chg->constant_volt = PF1550_DEFAULT_CONSTANT_VOLT; + else + chg->constant_volt = info->constant_charge_voltage_max_uv; +} + +static int pf1550_charger_probe(struct platform_device *pdev) +{ + const struct pf1550_ddata *pf1550 = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = {}; + struct pf1550_charger *chg; + int i, irq, ret; + + chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + chg->dev = &pdev->dev; + chg->pf1550 = pf1550; + + if (!chg->pf1550->regmap) + return dev_err_probe(&pdev->dev, -ENODEV, + "failed to get regmap\n"); + + platform_set_drvdata(pdev, chg); + + ret = devm_delayed_work_autocancel(chg->dev, &chg->vbus_sense_work, + pf1550_chg_vbus_work); + if (ret) + return dev_err_probe(chg->dev, ret, + "failed to add vbus sense work\n"); + + ret = devm_delayed_work_autocancel(chg->dev, &chg->chg_sense_work, + pf1550_chg_chg_work); + if (ret) + return dev_err_probe(chg->dev, ret, + "failed to add charger sense work\n"); + + ret = devm_delayed_work_autocancel(chg->dev, &chg->bat_sense_work, + pf1550_chg_bat_work); + if (ret) + return dev_err_probe(chg->dev, ret, + "failed to add battery sense work\n"); + + psy_cfg.drv_data = chg; + + chg->charger = devm_power_supply_register(&pdev->dev, + &pf1550_charger_desc, + &psy_cfg); + if (IS_ERR(chg->charger)) + return dev_err_probe(&pdev->dev, PTR_ERR(chg->charger), + "failed: power supply register\n"); + + chg->battery = devm_power_supply_register(&pdev->dev, + &pf1550_battery_desc, + &psy_cfg); + if (IS_ERR(chg->battery)) + return dev_err_probe(&pdev->dev, PTR_ERR(chg->battery), + "failed: power supply register\n"); + + for (i = 0; i < PF1550_CHARGER_IRQ_NR; i++) { + irq = platform_get_irq(pdev, i); + if (irq < 0) + return irq; + + chg->virqs[i] = irq; + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + pf1550_charger_irq_handler, + IRQF_NO_SUSPEND, + "pf1550-charger", chg); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed irq request\n"); + } + + pf1550_dt_parse_dev_info(chg); + + return pf1550_reg_init(chg); +} + +static const struct platform_device_id pf1550_charger_id[] = { + { "pf1550-charger", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, pf1550_charger_id); + +static struct platform_driver pf1550_charger_driver = { + .driver = { + .name = "pf1550-charger", + }, + .probe = pf1550_charger_probe, + .id_table = pf1550_charger_id, +}; +module_platform_driver(pf1550_charger_driver); + +MODULE_AUTHOR("Robin Gong <yibin.gong@freescale.com>"); +MODULE_DESCRIPTION("PF1550 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/pm8916_bms_vm.c b/drivers/power/supply/pm8916_bms_vm.c index 5d0dd842509c..de5d571c03e2 100644 --- a/drivers/power/supply/pm8916_bms_vm.c +++ b/drivers/power/supply/pm8916_bms_vm.c @@ -167,15 +167,6 @@ static int pm8916_bms_vm_battery_probe(struct platform_device *pdev) if (ret < 0) return -EINVAL; - irq = platform_get_irq_byname(pdev, "fifo"); - if (irq < 0) - return irq; - - ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_bms_vm_fifo_update_done_irq, - IRQF_ONESHOT, "pm8916_vm_bms", bat); - if (ret) - return ret; - ret = regmap_bulk_read(bat->regmap, bat->reg + PM8916_PERPH_TYPE, &tmp, 2); if (ret) goto comm_error; @@ -210,7 +201,7 @@ static int pm8916_bms_vm_battery_probe(struct platform_device *pdev) bat->vbat_now = bat->last_ocv; psy_cfg.drv_data = bat; - psy_cfg.of_node = dev->of_node; + psy_cfg.fwnode = dev_fwnode(dev); bat->battery = devm_power_supply_register(dev, &pm8916_bms_vm_battery_psy_desc, &psy_cfg); if (IS_ERR(bat->battery)) @@ -220,6 +211,15 @@ static int pm8916_bms_vm_battery_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "Unable to get battery info\n"); + irq = platform_get_irq_byname(pdev, "fifo"); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_bms_vm_fifo_update_done_irq, + IRQF_ONESHOT, "pm8916_vm_bms", bat); + if (ret) + return ret; + platform_set_drvdata(pdev, bat); return 0; diff --git a/drivers/power/supply/pm8916_lbc.c b/drivers/power/supply/pm8916_lbc.c index 6d92e98cbecc..6b631012a795 100644 --- a/drivers/power/supply/pm8916_lbc.c +++ b/drivers/power/supply/pm8916_lbc.c @@ -274,15 +274,6 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev) return dev_err_probe(dev, -EINVAL, "Wrong amount of reg values: %d (4 expected)\n", len); - irq = platform_get_irq_byname(pdev, "usb_vbus"); - if (irq < 0) - return irq; - - ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq, - IRQF_ONESHOT, "pm8916_lbc", chg); - if (ret) - return ret; - ret = device_property_read_u32_array(dev, "reg", chg->reg, len); if (ret) return ret; @@ -322,7 +313,7 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev) dev_err_probe(dev, ret, "Error while parsing device tree\n"); psy_cfg.drv_data = chg; - psy_cfg.of_node = dev->of_node; + psy_cfg.fwnode = dev_fwnode(dev); chg->charger = devm_power_supply_register(dev, &pm8916_lbc_charger_psy_desc, &psy_cfg); if (IS_ERR(chg->charger)) @@ -332,6 +323,10 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "Unable to get battery info\n"); + irq = platform_get_irq_byname(pdev, "usb_vbus"); + if (irq < 0) + return irq; + chg->edev = devm_extcon_dev_allocate(dev, pm8916_lbc_charger_cable); if (IS_ERR(chg->edev)) return PTR_ERR(chg->edev); @@ -340,6 +335,11 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev) if (ret < 0) return dev_err_probe(dev, ret, "failed to register extcon device\n"); + ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq, + IRQF_ONESHOT, "pm8916_lbc", chg); + if (ret) + return ret; + ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp); if (ret) goto comm_error; diff --git a/drivers/power/supply/pmu_battery.c b/drivers/power/supply/pmu_battery.c index ed83c5e05ca3..b5ffe2da14dc 100644 --- a/drivers/power/supply/pmu_battery.c +++ b/drivers/power/supply/pmu_battery.c @@ -160,8 +160,7 @@ static int __init pmu_bat_init(void) for (i = 0; i < pmu_battery_count; i++) { struct power_supply_config psy_cfg = {}; - struct pmu_battery_dev *pbat = kzalloc(sizeof(*pbat), - GFP_KERNEL); + struct pmu_battery_dev *pbat = kzalloc_obj(*pbat); if (!pbat) break; diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 76c340b38015..a446d3d086fc 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -18,7 +18,6 @@ #include <linux/device.h> #include <linux/notifier.h> #include <linux/err.h> -#include <linux/of.h> #include <linux/power_supply.h> #include <linux/property.h> #include <linux/thermal.h> @@ -196,24 +195,24 @@ static int __power_supply_populate_supplied_from(struct power_supply *epsy, void *data) { struct power_supply *psy = data; - struct device_node *np; + struct fwnode_handle *np; int i = 0; do { - np = of_parse_phandle(psy->of_node, "power-supplies", i++); - if (!np) + np = fwnode_find_reference(psy->dev.fwnode, "power-supplies", i++); + if (IS_ERR(np)) break; - if (np == epsy->of_node) { + if (np == epsy->dev.fwnode) { dev_dbg(&psy->dev, "%s: Found supply : %s\n", psy->desc->name, epsy->desc->name); psy->supplied_from[i-1] = (char *)epsy->desc->name; psy->num_supplies++; - of_node_put(np); + fwnode_handle_put(np); break; } - of_node_put(np); - } while (np); + fwnode_handle_put(np); + } while (true); return 0; } @@ -232,16 +231,16 @@ static int power_supply_populate_supplied_from(struct power_supply *psy) static int __power_supply_find_supply_from_node(struct power_supply *epsy, void *data) { - struct device_node *np = data; + struct fwnode_handle *fwnode = data; /* returning non-zero breaks out of power_supply_for_each_psy loop */ - if (epsy->of_node == np) + if (epsy->dev.fwnode == fwnode) return 1; return 0; } -static int power_supply_find_supply_from_node(struct device_node *supply_node) +static int power_supply_find_supply_from_fwnode(struct fwnode_handle *supply_node) { int error; @@ -249,7 +248,7 @@ static int power_supply_find_supply_from_node(struct device_node *supply_node) * power_supply_for_each_psy() either returns its own errors or values * returned by __power_supply_find_supply_from_node(). * - * __power_supply_find_supply_from_node() will return 0 (no match) + * __power_supply_find_supply_from_fwnode() will return 0 (no match) * or 1 (match). * * We return 0 if power_supply_for_each_psy() returned 1, -EPROBE_DEFER if @@ -262,7 +261,7 @@ static int power_supply_find_supply_from_node(struct device_node *supply_node) static int power_supply_check_supplies(struct power_supply *psy) { - struct device_node *np; + struct fwnode_handle *np; int cnt = 0; /* If there is already a list honor it */ @@ -270,24 +269,24 @@ static int power_supply_check_supplies(struct power_supply *psy) return 0; /* No device node found, nothing to do */ - if (!psy->of_node) + if (!psy->dev.fwnode) return 0; do { int ret; - np = of_parse_phandle(psy->of_node, "power-supplies", cnt++); - if (!np) + np = fwnode_find_reference(psy->dev.fwnode, "power-supplies", cnt++); + if (IS_ERR(np)) break; - ret = power_supply_find_supply_from_node(np); - of_node_put(np); + ret = power_supply_find_supply_from_fwnode(np); + fwnode_handle_put(np); if (ret) { dev_dbg(&psy->dev, "Failed to find supply!\n"); return ret; } - } while (np); + } while (!IS_ERR(np)); /* Missing valid "power-supplies" entries */ if (cnt == 1) @@ -449,19 +448,6 @@ int power_supply_get_property_from_supplier(struct power_supply *psy, } EXPORT_SYMBOL_GPL(power_supply_get_property_from_supplier); -int power_supply_set_battery_charged(struct power_supply *psy) -{ - if (atomic_read(&psy->use_cnt) >= 0 && - psy->desc->type == POWER_SUPPLY_TYPE_BATTERY && - psy->desc->set_charged) { - psy->desc->set_charged(psy); - return 0; - } - - return -EINVAL; -} -EXPORT_SYMBOL_GPL(power_supply_set_battery_charged); - static int power_supply_match_device_by_name(struct device *dev, const void *data) { const char *name = data; @@ -510,15 +496,14 @@ void power_supply_put(struct power_supply *psy) } EXPORT_SYMBOL_GPL(power_supply_put); -#ifdef CONFIG_OF -static int power_supply_match_device_node(struct device *dev, const void *data) +static int power_supply_match_device_fwnode(struct device *dev, const void *data) { - return dev->parent && dev->parent->of_node == data; + return dev->parent && dev_fwnode(dev->parent) == data; } /** - * power_supply_get_by_phandle() - Search for a power supply and returns its ref - * @np: Pointer to device node holding phandle property + * power_supply_get_by_reference() - Search for a power supply and returns its ref + * @fwnode: Pointer to fwnode holding phandle property * @property: Name of property holding a power supply name * * If power supply was found, it increases reference count for the @@ -528,21 +513,21 @@ static int power_supply_match_device_node(struct device *dev, const void *data) * Return: On success returns a reference to a power supply with * matching name equals to value under @property, NULL or ERR_PTR otherwise. */ -struct power_supply *power_supply_get_by_phandle(struct device_node *np, - const char *property) +struct power_supply *power_supply_get_by_reference(struct fwnode_handle *fwnode, + const char *property) { - struct device_node *power_supply_np; + struct fwnode_handle *power_supply_fwnode; struct power_supply *psy = NULL; struct device *dev; - power_supply_np = of_parse_phandle(np, property, 0); - if (!power_supply_np) - return ERR_PTR(-ENODEV); + power_supply_fwnode = fwnode_find_reference(fwnode, property, 0); + if (IS_ERR(power_supply_fwnode)) + return ERR_CAST(power_supply_fwnode); - dev = class_find_device(&power_supply_class, NULL, power_supply_np, - power_supply_match_device_node); + dev = class_find_device(&power_supply_class, NULL, power_supply_fwnode, + power_supply_match_device_fwnode); - of_node_put(power_supply_np); + fwnode_handle_put(power_supply_fwnode); if (dev) { psy = dev_to_psy(dev); @@ -551,7 +536,7 @@ struct power_supply *power_supply_get_by_phandle(struct device_node *np, return psy; } -EXPORT_SYMBOL_GPL(power_supply_get_by_phandle); +EXPORT_SYMBOL_GPL(power_supply_get_by_reference); static void devm_power_supply_put(struct device *dev, void *res) { @@ -561,27 +546,27 @@ static void devm_power_supply_put(struct device *dev, void *res) } /** - * devm_power_supply_get_by_phandle() - Resource managed version of - * power_supply_get_by_phandle() + * devm_power_supply_get_by_reference() - Resource managed version of + * power_supply_get_by_reference() * @dev: Pointer to device holding phandle property * @property: Name of property holding a power supply phandle * * Return: On success returns a reference to a power supply with * matching name equals to value under @property, NULL or ERR_PTR otherwise. */ -struct power_supply *devm_power_supply_get_by_phandle(struct device *dev, - const char *property) +struct power_supply *devm_power_supply_get_by_reference(struct device *dev, + const char *property) { struct power_supply **ptr, *psy; - if (!dev->of_node) + if (!dev_fwnode(dev)) return ERR_PTR(-ENODEV); ptr = devres_alloc(devm_power_supply_put, sizeof(*ptr), GFP_KERNEL); if (!ptr) return ERR_PTR(-ENOMEM); - psy = power_supply_get_by_phandle(dev->of_node, property); + psy = power_supply_get_by_reference(dev_fwnode(dev), property); if (IS_ERR_OR_NULL(psy)) { devres_free(ptr); } else { @@ -590,40 +575,26 @@ struct power_supply *devm_power_supply_get_by_phandle(struct device *dev, } return psy; } -EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle); -#endif /* CONFIG_OF */ +EXPORT_SYMBOL_GPL(devm_power_supply_get_by_reference); int power_supply_get_battery_info(struct power_supply *psy, struct power_supply_battery_info **info_out) { struct power_supply_resistance_temp_table *resist_table; struct power_supply_battery_info *info; - struct device_node *battery_np = NULL; - struct fwnode_reference_args args; - struct fwnode_handle *fwnode = NULL; + struct fwnode_handle *srcnode, *fwnode; const char *value; - int err, len, index; - const __be32 *list; + int err, len, index, proplen; + u32 *propdata __free(kfree) = NULL; u32 min_max[2]; - if (psy->of_node) { - battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0); - if (!battery_np) - return -ENODEV; - - fwnode = fwnode_handle_get(of_fwnode_handle(battery_np)); - } else if (psy->dev.parent) { - err = fwnode_property_get_reference_args( - dev_fwnode(psy->dev.parent), - "monitored-battery", NULL, 0, 0, &args); - if (err) - return err; - - fwnode = args.fwnode; - } + srcnode = dev_fwnode(&psy->dev); + if (!srcnode && psy->dev.parent) + srcnode = dev_fwnode(psy->dev.parent); - if (!fwnode) - return -ENOENT; + fwnode = fwnode_find_reference(srcnode, "monitored-battery", 0); + if (IS_ERR(fwnode)) + return PTR_ERR(fwnode); err = fwnode_property_read_string(fwnode, "compatible", &value); if (err) @@ -753,15 +724,7 @@ int power_supply_get_battery_info(struct power_supply *psy, info->temp_max = min_max[1]; } - /* - * The below code uses raw of-data parsing to parse - * /schemas/types.yaml#/definitions/uint32-matrix - * data, so for now this is only support with of. - */ - if (!battery_np) - goto out_ret_pointer; - - len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius"); + len = fwnode_property_count_u32(fwnode, "ocv-capacity-celsius"); if (len < 0 && len != -EINVAL) { err = len; goto out_put_node; @@ -770,13 +733,13 @@ int power_supply_get_battery_info(struct power_supply *psy, err = -EINVAL; goto out_put_node; } else if (len > 0) { - of_property_read_u32_array(battery_np, "ocv-capacity-celsius", + fwnode_property_read_u32_array(fwnode, "ocv-capacity-celsius", info->ocv_temp, len); } for (index = 0; index < len; index++) { struct power_supply_battery_ocv_table *table; - int i, tab_len, size; + int i, tab_len; char *propname __free(kfree) = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", index); @@ -785,15 +748,28 @@ int power_supply_get_battery_info(struct power_supply *psy, err = -ENOMEM; goto out_put_node; } - list = of_get_property(battery_np, propname, &size); - if (!list || !size) { + proplen = fwnode_property_count_u32(fwnode, propname); + if (proplen < 0 || proplen % 2 != 0) { dev_err(&psy->dev, "failed to get %s\n", propname); power_supply_put_battery_info(psy, info); err = -EINVAL; goto out_put_node; } - tab_len = size / (2 * sizeof(__be32)); + u32 *propdata __free(kfree) = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL); + if (!propdata) { + power_supply_put_battery_info(psy, info); + err = -EINVAL; + goto out_put_node; + } + err = fwnode_property_read_u32_array(fwnode, propname, propdata, proplen); + if (err < 0) { + dev_err(&psy->dev, "failed to get %s\n", propname); + power_supply_put_battery_info(psy, info); + goto out_put_node; + } + + tab_len = proplen / 2; info->ocv_table_size[index] = tab_len; info->ocv_table[index] = table = @@ -805,18 +781,36 @@ int power_supply_get_battery_info(struct power_supply *psy, } for (i = 0; i < tab_len; i++) { - table[i].ocv = be32_to_cpu(*list); - list++; - table[i].capacity = be32_to_cpu(*list); - list++; + table[i].ocv = propdata[i*2]; + table[i].capacity = propdata[i*2+1]; } } - list = of_get_property(battery_np, "resistance-temp-table", &len); - if (!list || !len) + proplen = fwnode_property_count_u32(fwnode, "resistance-temp-table"); + if (proplen == 0 || proplen == -EINVAL) { + err = 0; goto out_ret_pointer; + } else if (proplen < 0 || proplen % 2 != 0) { + power_supply_put_battery_info(psy, info); + err = (proplen < 0) ? proplen : -EINVAL; + goto out_put_node; + } - info->resist_table_size = len / (2 * sizeof(__be32)); + propdata = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL); + if (!propdata) { + power_supply_put_battery_info(psy, info); + err = -ENOMEM; + goto out_put_node; + } + + err = fwnode_property_read_u32_array(fwnode, "resistance-temp-table", + propdata, proplen); + if (err < 0) { + power_supply_put_battery_info(psy, info); + goto out_put_node; + } + + info->resist_table_size = proplen / 2; info->resist_table = resist_table = devm_kcalloc(&psy->dev, info->resist_table_size, sizeof(*resist_table), @@ -828,8 +822,8 @@ int power_supply_get_battery_info(struct power_supply *psy, } for (index = 0; index < info->resist_table_size; index++) { - resist_table[index].temp = be32_to_cpu(*list++); - resist_table[index].resistance = be32_to_cpu(*list++); + resist_table[index].temp = propdata[index*2]; + resist_table[index].resistance = propdata[index*2+1]; } out_ret_pointer: @@ -838,7 +832,6 @@ out_ret_pointer: out_put_node: fwnode_handle_put(fwnode); - of_node_put(battery_np); return err; } EXPORT_SYMBOL_GPL(power_supply_get_battery_info); @@ -1248,9 +1241,8 @@ bool power_supply_has_property(struct power_supply *psy, return false; } -int power_supply_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) +static int __power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, + union power_supply_propval *val, bool use_extensions) { struct power_supply_ext_registration *reg; @@ -1260,10 +1252,14 @@ int power_supply_get_property(struct power_supply *psy, return -ENODEV; } - scoped_guard(rwsem_read, &psy->extensions_sem) { - power_supply_for_each_extension(reg, psy) { - if (power_supply_ext_has_property(reg->ext, psp)) + if (use_extensions) { + scoped_guard(rwsem_read, &psy->extensions_sem) { + power_supply_for_each_extension(reg, psy) { + if (!power_supply_ext_has_property(reg->ext, psp)) + continue; + return reg->ext->get_property(psy, reg->ext, reg->data, psp, val); + } } } @@ -1274,20 +1270,49 @@ int power_supply_get_property(struct power_supply *psy, else return -EINVAL; } + +int power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, + union power_supply_propval *val) +{ + return __power_supply_get_property(psy, psp, val, true); +} EXPORT_SYMBOL_GPL(power_supply_get_property); -int power_supply_set_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) +/** + * power_supply_get_property_direct - Read a power supply property without checking for extensions + * @psy: The power supply + * @psp: The power supply property to read + * @val: The resulting value of the power supply property + * + * Read a power supply property without taking into account any power supply extensions registered + * on the given power supply. This is mostly useful for power supply extensions that want to access + * their own power supply as using power_supply_get_property() directly will result in a potential + * deadlock. + * + * Return: 0 on success or negative error code on failure. + */ +int power_supply_get_property_direct(struct power_supply *psy, enum power_supply_property psp, + union power_supply_propval *val) +{ + return __power_supply_get_property(psy, psp, val, false); +} +EXPORT_SYMBOL_GPL(power_supply_get_property_direct); + + +static int __power_supply_set_property(struct power_supply *psy, enum power_supply_property psp, + const union power_supply_propval *val, bool use_extensions) { struct power_supply_ext_registration *reg; if (atomic_read(&psy->use_cnt) <= 0) return -ENODEV; - scoped_guard(rwsem_read, &psy->extensions_sem) { - power_supply_for_each_extension(reg, psy) { - if (power_supply_ext_has_property(reg->ext, psp)) { + if (use_extensions) { + scoped_guard(rwsem_read, &psy->extensions_sem) { + power_supply_for_each_extension(reg, psy) { + if (!power_supply_ext_has_property(reg->ext, psp)) + continue; + if (reg->ext->set_property) return reg->ext->set_property(psy, reg->ext, reg->data, psp, val); @@ -1302,8 +1327,34 @@ int power_supply_set_property(struct power_supply *psy, return psy->desc->set_property(psy, psp, val); } + +int power_supply_set_property(struct power_supply *psy, enum power_supply_property psp, + const union power_supply_propval *val) +{ + return __power_supply_set_property(psy, psp, val, true); +} EXPORT_SYMBOL_GPL(power_supply_set_property); +/** + * power_supply_set_property_direct - Write a power supply property without checking for extensions + * @psy: The power supply + * @psp: The power supply property to write + * @val: The value to write to the power supply property + * + * Write a power supply property without taking into account any power supply extensions registered + * on the given power supply. This is mostly useful for power supply extensions that want to access + * their own power supply as using power_supply_set_property() directly will result in a potential + * deadlock. + * + * Return: 0 on success or negative error code on failure. + */ +int power_supply_set_property_direct(struct power_supply *psy, enum power_supply_property psp, + const union power_supply_propval *val) +{ + return __power_supply_set_property(psy, psp, val, false); +} +EXPORT_SYMBOL_GPL(power_supply_set_property_direct); + int power_supply_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { @@ -1375,7 +1426,7 @@ int power_supply_register_extension(struct power_supply *psy, const struct power if (power_supply_has_property(psy, ext->properties[i])) return -EEXIST; - reg = kmalloc(sizeof(*reg), GFP_KERNEL); + reg = kmalloc_obj(*reg); if (!reg) return -ENOMEM; @@ -1542,11 +1593,9 @@ __power_supply_register(struct device *parent, dev_set_drvdata(dev, psy); psy->desc = desc; if (cfg) { + device_set_node(dev, cfg->fwnode); dev->groups = cfg->attr_grp; psy->drv_data = cfg->drv_data; - psy->of_node = - cfg->fwnode ? to_of_node(cfg->fwnode) : cfg->of_node; - dev->of_node = psy->of_node; psy->supplied_to = cfg->supplied_to; psy->num_supplicants = cfg->num_supplicants; } diff --git a/drivers/power/supply/power_supply_leds.c b/drivers/power/supply/power_supply_leds.c index f4a7e566bea1..1548aaba3362 100644 --- a/drivers/power/supply/power_supply_leds.c +++ b/drivers/power/supply/power_supply_leds.c @@ -48,7 +48,7 @@ static int power_supply_register_led_trigger(struct power_supply *psy, if (err && *err) return *err; - psy_trig = kzalloc(sizeof(*psy_trig), GFP_KERNEL); + psy_trig = kzalloc_obj(*psy_trig); if (!psy_trig) goto err_free_trigger; diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index edb058c19c9c..f30a7b9ccd5e 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -33,7 +33,7 @@ struct power_supply_attr { [POWER_SUPPLY_PROP_ ## _name] = \ { \ .prop_name = #_name, \ - .attr_name = #_name "\0", \ + .attr_name = #_name, \ .text_values = _text, \ .text_values_len = _len, \ } @@ -70,6 +70,8 @@ static const char * const POWER_SUPPLY_USB_TYPE_TEXT[] = { [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_PD_SPR_AVS] = "PD_SPR_AVS", + [POWER_SUPPLY_USB_TYPE_PD_PPS_SPR_AVS] = "PD_PPS_SPR_AVS", [POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID] = "BrickID", }; @@ -110,6 +112,8 @@ static const char * const POWER_SUPPLY_HEALTH_TEXT[] = { [POWER_SUPPLY_HEALTH_COOL] = "Cool", [POWER_SUPPLY_HEALTH_HOT] = "Hot", [POWER_SUPPLY_HEALTH_NO_BATTERY] = "No battery", + [POWER_SUPPLY_HEALTH_BLOWN_FUSE] = "Blown fuse", + [POWER_SUPPLY_HEALTH_CELL_IMBALANCE] = "Cell imbalance", }; static const char * const POWER_SUPPLY_TECHNOLOGY_TEXT[] = { @@ -138,9 +142,10 @@ static const char * const POWER_SUPPLY_SCOPE_TEXT[] = { }; static const char * const POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[] = { - [POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO] = "auto", - [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE] = "inhibit-charge", - [POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge", + [POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO] = "auto", + [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE] = "inhibit-charge", + [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE] = "inhibit-charge-awake", + [POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge", }; static struct power_supply_attr power_supply_attrs[] __ro_after_init = { @@ -220,6 +225,8 @@ static struct power_supply_attr power_supply_attrs[] __ro_after_init = { POWER_SUPPLY_ATTR(MANUFACTURE_YEAR), POWER_SUPPLY_ATTR(MANUFACTURE_MONTH), POWER_SUPPLY_ATTR(MANUFACTURE_DAY), + POWER_SUPPLY_ATTR(INTERNAL_RESISTANCE), + POWER_SUPPLY_ATTR(STATE_OF_HEALTH), /* Properties of type `const char *' */ POWER_SUPPLY_ATTR(MODEL_NAME), POWER_SUPPLY_ATTR(MANUFACTURER), @@ -230,12 +237,12 @@ static struct power_supply_attr power_supply_attrs[] __ro_after_init = { static struct attribute * __power_supply_attrs[POWER_SUPPLY_ATTR_CNT + 1] __ro_after_init; -static const struct power_supply_attr *to_ps_attr(struct device_attribute *attr) +static const struct power_supply_attr *to_ps_attr(const struct device_attribute *attr) { - return container_of(attr, struct power_supply_attr, dev_attr); + return container_of_const(attr, struct power_supply_attr, dev_attr); } -static enum power_supply_property dev_attr_psp(struct device_attribute *attr) +static enum power_supply_property dev_attr_psp(const struct device_attribute *attr) { return to_ps_attr(attr) - power_supply_attrs; } @@ -321,6 +328,27 @@ static ssize_t power_supply_show_charge_behaviour(struct device *dev, value->intval, buf); } +static ssize_t power_supply_show_charge_types(struct device *dev, + struct power_supply *psy, + enum power_supply_charge_type current_type, + char *buf) +{ + struct power_supply_ext_registration *reg; + + scoped_guard(rwsem_read, &psy->extensions_sem) { + power_supply_for_each_extension(reg, psy) { + if (power_supply_ext_has_property(reg->ext, + POWER_SUPPLY_PROP_CHARGE_TYPES)) + return power_supply_charge_types_show(dev, + reg->ext->charge_types, + current_type, buf); + } + } + + return power_supply_charge_types_show(dev, psy->desc->charge_types, + current_type, buf); +} + static ssize_t power_supply_format_property(struct device *dev, bool uevent, struct device_attribute *attr, @@ -365,7 +393,7 @@ static ssize_t power_supply_format_property(struct device *dev, case POWER_SUPPLY_PROP_CHARGE_TYPES: if (uevent) /* no possible values in uevents */ goto default_format; - ret = power_supply_charge_types_show(dev, psy->desc->charge_types, + ret = power_supply_show_charge_types(dev, psy, value.intval, buf); break; case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER: diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index 47d29271ddf4..490137a23d00 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -2,12 +2,15 @@ /* * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. * Copyright (c) 2022, Linaro Ltd + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include <linux/auxiliary_bus.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/nvmem-consumer.h> #include <linux/of_device.h> #include <linux/power_supply.h> +#include <linux/property.h> #include <linux/soc/qcom/pdr.h> #include <linux/soc/qcom/pmic_glink.h> #include <linux/math.h> @@ -17,8 +20,10 @@ #define BATTMGR_STRING_LEN 128 enum qcom_battmgr_variant { - QCOM_BATTMGR_SM8350, QCOM_BATTMGR_SC8280XP, + QCOM_BATTMGR_SM8350, + QCOM_BATTMGR_SM8550, + QCOM_BATTMGR_X1E80100, }; #define BATTMGR_BAT_STATUS 0x1 @@ -29,8 +34,9 @@ enum qcom_battmgr_variant { #define NOTIF_BAT_PROPERTY 0x30 #define NOTIF_USB_PROPERTY 0x32 #define NOTIF_WLS_PROPERTY 0x34 -#define NOTIF_BAT_INFO 0x81 #define NOTIF_BAT_STATUS 0x80 +#define NOTIF_BAT_INFO 0x81 +#define NOTIF_BAT_CHARGING_STATE 0x83 #define BATTMGR_BAT_INFO 0x9 @@ -64,6 +70,9 @@ enum qcom_battmgr_variant { #define BATT_RESISTANCE 21 #define BATT_POWER_NOW 22 #define BATT_POWER_AVG 23 +#define BATT_CHG_CTRL_EN 24 +#define BATT_CHG_CTRL_START_THR 25 +#define BATT_CHG_CTRL_END_THR 26 #define BATTMGR_USB_PROPERTY_GET 0x32 #define BATTMGR_USB_PROPERTY_SET 0x33 @@ -88,6 +97,13 @@ enum qcom_battmgr_variant { #define WLS_TYPE 5 #define WLS_BOOST_EN 6 +#define BATTMGR_CHG_CTRL_LIMIT_EN 0x48 +#define CHARGE_CTRL_START_THR_MIN 50 +#define CHARGE_CTRL_START_THR_MAX 95 +#define CHARGE_CTRL_END_THR_MIN 55 +#define CHARGE_CTRL_END_THR_MAX 100 +#define CHARGE_CTRL_DELTA_SOC 5 + struct qcom_battmgr_enable_request { struct pmic_glink_hdr hdr; __le32 battery_id; @@ -122,6 +138,13 @@ struct qcom_battmgr_discharge_time_request { __le32 reserved; }; +struct qcom_battmgr_charge_ctrl_request { + struct pmic_glink_hdr hdr; + __le32 enable; + __le32 target_soc; + __le32 delta_soc; +}; + struct qcom_battmgr_message { struct pmic_glink_hdr hdr; union { @@ -234,6 +257,8 @@ struct qcom_battmgr_info { unsigned int capacity_warning; unsigned int cycle_count; unsigned int charge_count; + unsigned int charge_ctrl_start; + unsigned int charge_ctrl_end; char model_number[BATTMGR_STRING_LEN]; char serial_number[BATTMGR_STRING_LEN]; char oem_info[BATTMGR_STRING_LEN]; @@ -253,6 +278,8 @@ struct qcom_battmgr_status { unsigned int voltage_now; unsigned int voltage_ocv; unsigned int temperature; + unsigned int resistance; + unsigned int soh_percent; unsigned int discharge_time; unsigned int charge_time; @@ -417,7 +444,11 @@ static const u8 sm8350_bat_prop_map[] = { [POWER_SUPPLY_PROP_MODEL_NAME] = BATT_MODEL_NAME, [POWER_SUPPLY_PROP_TIME_TO_FULL_AVG] = BATT_TTF_AVG, [POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG] = BATT_TTE_AVG, + [POWER_SUPPLY_PROP_INTERNAL_RESISTANCE] = BATT_RESISTANCE, + [POWER_SUPPLY_PROP_STATE_OF_HEALTH] = BATT_SOH, [POWER_SUPPLY_PROP_POWER_NOW] = BATT_POWER_NOW, + [POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD] = BATT_CHG_CTRL_START_THR, + [POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD] = BATT_CHG_CTRL_END_THR, }; static int qcom_battmgr_bat_sm8350_update(struct qcom_battmgr *battmgr, @@ -488,7 +519,8 @@ static int qcom_battmgr_bat_get_property(struct power_supply *psy, if (!battmgr->service_up) return -EAGAIN; - if (battmgr->variant == QCOM_BATTMGR_SC8280XP) + if (battmgr->variant == QCOM_BATTMGR_SC8280XP || + battmgr->variant == QCOM_BATTMGR_X1E80100) ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp); else ret = qcom_battmgr_bat_sm8350_update(battmgr, psp); @@ -576,17 +608,31 @@ static int qcom_battmgr_bat_get_property(struct power_supply *psy, val->intval = battmgr->status.capacity; break; case POWER_SUPPLY_PROP_CAPACITY: + if (battmgr->status.percent == (unsigned int)-1) + return -ENODATA; val->intval = battmgr->status.percent; break; case POWER_SUPPLY_PROP_TEMP: val->intval = battmgr->status.temperature; break; + case POWER_SUPPLY_PROP_INTERNAL_RESISTANCE: + val->intval = battmgr->status.resistance; + break; + case POWER_SUPPLY_PROP_STATE_OF_HEALTH: + val->intval = battmgr->status.soh_percent; + break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: val->intval = battmgr->status.discharge_time; break; case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: val->intval = battmgr->status.charge_time; break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + val->intval = battmgr->info.charge_ctrl_start; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + val->intval = battmgr->info.charge_ctrl_end; + break; case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: val->intval = battmgr->info.year; break; @@ -612,10 +658,144 @@ static int qcom_battmgr_bat_get_property(struct power_supply *psy, return 0; } +static int qcom_battmgr_set_charge_control(struct qcom_battmgr *battmgr, + u32 target_soc, u32 delta_soc) +{ + struct qcom_battmgr_charge_ctrl_request request = { + .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_BATTMGR), + .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP), + .hdr.opcode = cpu_to_le32(BATTMGR_CHG_CTRL_LIMIT_EN), + .enable = cpu_to_le32(1), + .target_soc = cpu_to_le32(target_soc), + .delta_soc = cpu_to_le32(delta_soc), + }; + + return qcom_battmgr_request(battmgr, &request, sizeof(request)); +} + +static int qcom_battmgr_set_charge_start_threshold(struct qcom_battmgr *battmgr, int start_soc) +{ + u32 target_soc, delta_soc; + int ret; + + start_soc = clamp(start_soc, CHARGE_CTRL_START_THR_MIN, CHARGE_CTRL_START_THR_MAX); + + /* + * If the new start threshold is larger than the old end threshold, + * move the end threshold one step (DELTA_SOC) after the new start + * threshold. + */ + if (start_soc > battmgr->info.charge_ctrl_end) { + target_soc = start_soc + CHARGE_CTRL_DELTA_SOC; + target_soc = min_t(u32, target_soc, CHARGE_CTRL_END_THR_MAX); + delta_soc = target_soc - start_soc; + delta_soc = min_t(u32, delta_soc, CHARGE_CTRL_DELTA_SOC); + } else { + target_soc = battmgr->info.charge_ctrl_end; + delta_soc = battmgr->info.charge_ctrl_end - start_soc; + } + + mutex_lock(&battmgr->lock); + ret = qcom_battmgr_set_charge_control(battmgr, target_soc, delta_soc); + mutex_unlock(&battmgr->lock); + if (!ret) { + battmgr->info.charge_ctrl_start = start_soc; + battmgr->info.charge_ctrl_end = target_soc; + } + + return 0; +} + +static int qcom_battmgr_set_charge_end_threshold(struct qcom_battmgr *battmgr, int end_soc) +{ + u32 delta_soc = CHARGE_CTRL_DELTA_SOC; + int ret; + + end_soc = clamp(end_soc, CHARGE_CTRL_END_THR_MIN, CHARGE_CTRL_END_THR_MAX); + + if (battmgr->info.charge_ctrl_start && end_soc > battmgr->info.charge_ctrl_start) + delta_soc = end_soc - battmgr->info.charge_ctrl_start; + + mutex_lock(&battmgr->lock); + ret = qcom_battmgr_set_charge_control(battmgr, end_soc, delta_soc); + mutex_unlock(&battmgr->lock); + if (!ret) { + battmgr->info.charge_ctrl_start = end_soc - delta_soc; + battmgr->info.charge_ctrl_end = end_soc; + } + + return 0; +} + +static int qcom_battmgr_charge_control_thresholds_init(struct qcom_battmgr *battmgr) +{ + int ret; + u8 en, end_soc, start_soc, delta_soc; + + ret = nvmem_cell_read_u8(battmgr->dev->parent, "charge_limit_en", &en); + if (!ret && en != 0) { + ret = nvmem_cell_read_u8(battmgr->dev->parent, "charge_limit_end", &end_soc); + if (ret < 0) + return ret; + + ret = nvmem_cell_read_u8(battmgr->dev->parent, "charge_limit_delta", &delta_soc); + if (ret < 0) + return ret; + + if (delta_soc >= end_soc) + return -EINVAL; + + start_soc = end_soc - delta_soc; + end_soc = clamp(end_soc, CHARGE_CTRL_END_THR_MIN, CHARGE_CTRL_END_THR_MAX); + start_soc = clamp(start_soc, CHARGE_CTRL_START_THR_MIN, CHARGE_CTRL_START_THR_MAX); + + battmgr->info.charge_ctrl_start = start_soc; + battmgr->info.charge_ctrl_end = end_soc; + } + + return 0; +} + +static int qcom_battmgr_bat_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + return 1; + default: + return 0; + } + + return 0; +} + +static int qcom_battmgr_bat_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *pval) +{ + struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy); + + if (!battmgr->service_up) + return -EAGAIN; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + return qcom_battmgr_set_charge_start_threshold(battmgr, pval->intval); + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + return qcom_battmgr_set_charge_end_threshold(battmgr, pval->intval); + default: + return -EINVAL; + } + + return 0; +} + static const enum power_supply_property sc8280xp_bat_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CYCLE_COUNT, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_NOW, @@ -645,6 +825,43 @@ static const struct power_supply_desc sc8280xp_bat_psy_desc = { .get_property = qcom_battmgr_bat_get_property, }; +static const enum power_supply_property x1e80100_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_EMPTY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_EMPTY, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MANUFACTURE_YEAR, + POWER_SUPPLY_PROP_MANUFACTURE_MONTH, + POWER_SUPPLY_PROP_MANUFACTURE_DAY, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, +}; + +static const struct power_supply_desc x1e80100_bat_psy_desc = { + .name = "qcom-battmgr-bat", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = x1e80100_bat_props, + .num_properties = ARRAY_SIZE(x1e80100_bat_props), + .get_property = qcom_battmgr_bat_get_property, + .set_property = qcom_battmgr_bat_set_property, + .property_is_writeable = qcom_battmgr_bat_is_writeable, +}; + static const enum power_supply_property sm8350_bat_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_HEALTH, @@ -664,6 +881,8 @@ static const enum power_supply_property sm8350_bat_props[] = { POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_INTERNAL_RESISTANCE, + POWER_SUPPLY_PROP_STATE_OF_HEALTH, POWER_SUPPLY_PROP_POWER_NOW, }; @@ -675,6 +894,42 @@ static const struct power_supply_desc sm8350_bat_psy_desc = { .get_property = qcom_battmgr_bat_get_property, }; +static const enum power_supply_property sm8550_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_INTERNAL_RESISTANCE, + POWER_SUPPLY_PROP_STATE_OF_HEALTH, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, +}; + +static const struct power_supply_desc sm8550_bat_psy_desc = { + .name = "qcom-battmgr-bat", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = sm8550_bat_props, + .num_properties = ARRAY_SIZE(sm8550_bat_props), + .get_property = qcom_battmgr_bat_get_property, + .set_property = qcom_battmgr_bat_set_property, + .property_is_writeable = qcom_battmgr_bat_is_writeable, +}; + static int qcom_battmgr_ac_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -750,7 +1005,8 @@ static int qcom_battmgr_usb_get_property(struct power_supply *psy, if (!battmgr->service_up) return -EAGAIN; - if (battmgr->variant == QCOM_BATTMGR_SC8280XP) + if (battmgr->variant == QCOM_BATTMGR_SC8280XP || + battmgr->variant == QCOM_BATTMGR_X1E80100) ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp); else ret = qcom_battmgr_usb_sm8350_update(battmgr, psp); @@ -872,7 +1128,8 @@ static int qcom_battmgr_wls_get_property(struct power_supply *psy, if (!battmgr->service_up) return -EAGAIN; - if (battmgr->variant == QCOM_BATTMGR_SC8280XP) + if (battmgr->variant == QCOM_BATTMGR_SC8280XP || + battmgr->variant == QCOM_BATTMGR_X1E80100) ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp); else ret = qcom_battmgr_wls_sm8350_update(battmgr, psp); @@ -943,12 +1200,14 @@ static void qcom_battmgr_notification(struct qcom_battmgr *battmgr, } notification = le32_to_cpu(msg->notification); + notification &= 0xff; switch (notification) { case NOTIF_BAT_INFO: battmgr->info.valid = false; fallthrough; case NOTIF_BAT_STATUS: case NOTIF_BAT_PROPERTY: + case NOTIF_BAT_CHARGING_STATE: power_supply_changed(battmgr->bat_psy); break; case NOTIF_USB_PROPERTY: @@ -978,8 +1237,12 @@ static void qcom_battmgr_sc8280xp_strcpy(char *dest, const char *src) static unsigned int qcom_battmgr_sc8280xp_parse_technology(const char *chemistry) { - if (!strncmp(chemistry, "LIO", BATTMGR_CHEMISTRY_LEN)) + if ((!strncmp(chemistry, "LIO", BATTMGR_CHEMISTRY_LEN)) || + (!strncmp(chemistry, "OOI", BATTMGR_CHEMISTRY_LEN))) return POWER_SUPPLY_TECHNOLOGY_LION; + if (!strncmp(chemistry, "LIP", BATTMGR_CHEMISTRY_LEN) || + !strncmp(chemistry, "LiP", BATTMGR_CHEMISTRY_LEN)) + return POWER_SUPPLY_TECHNOLOGY_LIPO; pr_err("Unknown battery technology '%s'\n", chemistry); return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; @@ -1062,6 +1325,26 @@ static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr, battmgr->ac.online = source == BATTMGR_CHARGING_SOURCE_AC; battmgr->usb.online = source == BATTMGR_CHARGING_SOURCE_USB; battmgr->wireless.online = source == BATTMGR_CHARGING_SOURCE_WIRELESS; + if (battmgr->info.last_full_capacity != 0) { + /* + * 100 * battmgr->status.capacity can overflow a 32bit + * unsigned integer. FW readings are in m{W/A}h, which + * are multiplied by 1000 converting them to u{W/A}h, + * the format the power_supply API expects. + * To avoid overflow use the original value for dividend + * and convert the divider back to m{W/A}h, which can be + * done without any loss of precision. + */ + battmgr->status.percent = + (100 * le32_to_cpu(resp->status.capacity)) / + (battmgr->info.last_full_capacity / 1000); + } else { + /* + * Let the sysfs handler know no data is available at + * this time. + */ + battmgr->status.percent = (unsigned int)-1; + } break; case BATTMGR_BAT_DISCHARGE_TIME: battmgr->status.discharge_time = le32_to_cpu(resp->time); @@ -1069,6 +1352,9 @@ static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr, case BATTMGR_BAT_CHARGE_TIME: battmgr->status.charge_time = le32_to_cpu(resp->time); break; + case BATTMGR_CHG_CTRL_LIMIT_EN: + battmgr->error = 0; + break; default: dev_warn(battmgr->dev, "unknown message %#x\n", opcode); break; @@ -1133,6 +1419,9 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr, case BATT_CAPACITY: battmgr->status.percent = le32_to_cpu(resp->intval.value) / 100; break; + case BATT_SOH: + battmgr->status.soh_percent = le32_to_cpu(resp->intval.value); + break; case BATT_VOLT_OCV: battmgr->status.voltage_ocv = le32_to_cpu(resp->intval.value); break; @@ -1173,9 +1462,18 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr, case BATT_TTE_AVG: battmgr->status.discharge_time = le32_to_cpu(resp->intval.value); break; + case BATT_RESISTANCE: + battmgr->status.resistance = le32_to_cpu(resp->intval.value); + break; case BATT_POWER_NOW: battmgr->status.power_now = le32_to_cpu(resp->intval.value); break; + case BATT_CHG_CTRL_START_THR: + battmgr->info.charge_ctrl_start = le32_to_cpu(resp->intval.value); + break; + case BATT_CHG_CTRL_END_THR: + battmgr->info.charge_ctrl_end = le32_to_cpu(resp->intval.value); + break; default: dev_warn(battmgr->dev, "unknown property %#x\n", property); break; @@ -1258,6 +1556,7 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr, } break; case BATTMGR_REQUEST_NOTIFICATION: + case BATTMGR_CHG_CTRL_LIMIT_EN: battmgr->error = 0; break; default: @@ -1277,7 +1576,8 @@ static void qcom_battmgr_callback(const void *data, size_t len, void *priv) if (opcode == BATTMGR_NOTIFICATION) qcom_battmgr_notification(battmgr, data, len); - else if (battmgr->variant == QCOM_BATTMGR_SC8280XP) + else if (battmgr->variant == QCOM_BATTMGR_SC8280XP || + battmgr->variant == QCOM_BATTMGR_X1E80100) qcom_battmgr_sc8280xp_callback(battmgr, data, len); else qcom_battmgr_sm8350_callback(battmgr, data, len); @@ -1311,9 +1611,12 @@ static void qcom_battmgr_pdr_notify(void *priv, int state) } static const struct of_device_id qcom_battmgr_of_variants[] = { + { .compatible = "qcom,glymur-pmic-glink", .data = (void *)QCOM_BATTMGR_X1E80100 }, + { .compatible = "qcom,kaanapali-pmic-glink", .data = (void *)QCOM_BATTMGR_SM8550 }, { .compatible = "qcom,sc8180x-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP }, { .compatible = "qcom,sc8280xp-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP }, - { .compatible = "qcom,x1e80100-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP }, + { .compatible = "qcom,sm8550-pmic-glink", .data = (void *)QCOM_BATTMGR_SM8550 }, + { .compatible = "qcom,x1e80100-pmic-glink", .data = (void *)QCOM_BATTMGR_X1E80100 }, /* Unmatched devices falls back to QCOM_BATTMGR_SM8350 */ {} }; @@ -1323,11 +1626,13 @@ static char *qcom_battmgr_battery[] = { "battery" }; static int qcom_battmgr_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id) { + const struct power_supply_desc *psy_desc; struct power_supply_config psy_cfg_supply = {}; struct power_supply_config psy_cfg = {}; const struct of_device_id *match; struct qcom_battmgr *battmgr; struct device *dev = &adev->dev; + int ret; battmgr = devm_kzalloc(dev, sizeof(*battmgr), GFP_KERNEL); if (!battmgr) @@ -1336,10 +1641,10 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev, battmgr->dev = dev; psy_cfg.drv_data = battmgr; - psy_cfg.of_node = adev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&adev->dev); psy_cfg_supply.drv_data = battmgr; - psy_cfg_supply.of_node = adev->dev.of_node; + psy_cfg_supply.fwnode = dev_fwnode(&adev->dev); psy_cfg_supply.supplied_to = qcom_battmgr_battery; psy_cfg_supply.num_supplicants = 1; @@ -1353,8 +1658,19 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev, else battmgr->variant = QCOM_BATTMGR_SM8350; - if (battmgr->variant == QCOM_BATTMGR_SC8280XP) { - battmgr->bat_psy = devm_power_supply_register(dev, &sc8280xp_bat_psy_desc, &psy_cfg); + ret = qcom_battmgr_charge_control_thresholds_init(battmgr); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to init battery charge control thresholds\n"); + + if (battmgr->variant == QCOM_BATTMGR_SC8280XP || + battmgr->variant == QCOM_BATTMGR_X1E80100) { + if (battmgr->variant == QCOM_BATTMGR_X1E80100) + psy_desc = &x1e80100_bat_psy_desc; + else + psy_desc = &sc8280xp_bat_psy_desc; + + battmgr->bat_psy = devm_power_supply_register(dev, psy_desc, &psy_cfg); if (IS_ERR(battmgr->bat_psy)) return dev_err_probe(dev, PTR_ERR(battmgr->bat_psy), "failed to register battery power supply\n"); @@ -1374,7 +1690,12 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev, return dev_err_probe(dev, PTR_ERR(battmgr->wls_psy), "failed to register wireless charing power supply\n"); } else { - battmgr->bat_psy = devm_power_supply_register(dev, &sm8350_bat_psy_desc, &psy_cfg); + if (battmgr->variant == QCOM_BATTMGR_SM8550) + psy_desc = &sm8550_bat_psy_desc; + else + psy_desc = &sm8350_bat_psy_desc; + + battmgr->bat_psy = devm_power_supply_register(dev, psy_desc, &psy_cfg); if (IS_ERR(battmgr->bat_psy)) return dev_err_probe(dev, PTR_ERR(battmgr->bat_psy), "failed to register battery power supply\n"); diff --git a/drivers/power/supply/qcom_smbb.c b/drivers/power/supply/qcom_smbb.c index a79563f6ff7a..28afe758a2da 100644 --- a/drivers/power/supply/qcom_smbb.c +++ b/drivers/power/supply/qcom_smbb.c @@ -880,7 +880,7 @@ static int smbb_charger_probe(struct platform_device *pdev) } bat_cfg.drv_data = chg; - bat_cfg.of_node = pdev->dev.of_node; + bat_cfg.fwnode = dev_fwnode(&pdev->dev); chg->bat_psy = devm_power_supply_register(&pdev->dev, &bat_psy_desc, &bat_cfg); diff --git a/drivers/power/supply/qcom_pmi8998_charger.c b/drivers/power/supply/qcom_smbx.c index 3b4132376649..bf2e2ccc454a 100644 --- a/drivers/power/supply/qcom_pmi8998_charger.c +++ b/drivers/power/supply/qcom_smbx.c @@ -2,7 +2,7 @@ /* * Copyright (c) 2016-2019 The Linux Foundation. All rights reserved. * Copyright (c) 2023, Linaro Ltd. - * Author: Caleb Connolly <caleb.connolly@linaro.org> + * Author: Casey Connolly <casey.connolly@linaro.org> * * This driver is for the switch-mode battery charger and boost * hardware found in pmi8998 and related PMICs. @@ -134,6 +134,9 @@ #define OCP_CHARGER_BIT BIT(1) #define SDP_CHARGER_BIT BIT(0) +#define USBIN_CMD_IL 0x340 +#define USBIN_SUSPEND_BIT BIT(0) + #define TYPE_C_STATUS_1 0x30B #define UFP_TYPEC_MASK GENMASK(7, 5) #define UFP_TYPEC_RDSTD_BIT BIT(7) @@ -362,17 +365,17 @@ enum charger_status { DISABLE_CHARGE, }; -struct smb2_register { +struct smb_init_register { u16 addr; u8 mask; u8 val; }; /** - * struct smb2_chip - smb2 chip structure + * struct smb_chip - smb chip structure * @dev: Device reference for power_supply * @name: The platform device name - * @base: Base address for smb2 registers + * @base: Base address for smb registers * @regmap: Register map * @batt_info: Battery data from DT * @status_change_work: Worker to handle plug/unplug events @@ -382,7 +385,7 @@ struct smb2_register { * @usb_in_v_chan: USB_IN voltage measurement channel * @chg_psy: Charger power supply instance */ -struct smb2_chip { +struct smb_chip { struct device *dev; const char *name; unsigned int base; @@ -399,7 +402,7 @@ struct smb2_chip { struct power_supply *chg_psy; }; -static enum power_supply_property smb2_properties[] = { +static enum power_supply_property smb_properties[] = { POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_CURRENT_MAX, @@ -411,7 +414,7 @@ static enum power_supply_property smb2_properties[] = { POWER_SUPPLY_PROP_USB_TYPE, }; -static int smb2_get_prop_usb_online(struct smb2_chip *chip, int *val) +static int smb_get_prop_usb_online(struct smb_chip *chip, int *val) { unsigned int stat; int rc; @@ -431,13 +434,13 @@ static int smb2_get_prop_usb_online(struct smb2_chip *chip, int *val) * Qualcomm "automatic power source detection" aka APSD * tells us what type of charger we're connected to. */ -static int smb2_apsd_get_charger_type(struct smb2_chip *chip, int *val) +static int smb_apsd_get_charger_type(struct smb_chip *chip, int *val) { unsigned int apsd_stat, stat; int usb_online = 0; int rc; - rc = smb2_get_prop_usb_online(chip, &usb_online); + rc = smb_get_prop_usb_online(chip, &usb_online); if (!usb_online) { *val = POWER_SUPPLY_USB_TYPE_UNKNOWN; return rc; @@ -471,13 +474,13 @@ static int smb2_apsd_get_charger_type(struct smb2_chip *chip, int *val) return 0; } -static int smb2_get_prop_status(struct smb2_chip *chip, int *val) +static int smb_get_prop_status(struct smb_chip *chip, int *val) { unsigned char stat[2]; int usb_online = 0; int rc; - rc = smb2_get_prop_usb_online(chip, &usb_online); + rc = smb_get_prop_usb_online(chip, &usb_online); if (!usb_online) { *val = POWER_SUPPLY_STATUS_DISCHARGING; return rc; @@ -519,7 +522,7 @@ static int smb2_get_prop_status(struct smb2_chip *chip, int *val) } } -static inline int smb2_get_current_limit(struct smb2_chip *chip, +static inline int smb_get_current_limit(struct smb_chip *chip, unsigned int *val) { int rc = regmap_read(chip->regmap, chip->base + ICL_STATUS, val); @@ -529,7 +532,7 @@ static inline int smb2_get_current_limit(struct smb2_chip *chip, return rc; } -static int smb2_set_current_limit(struct smb2_chip *chip, unsigned int val) +static int smb_set_current_limit(struct smb_chip *chip, unsigned int val) { unsigned char val_raw; @@ -544,22 +547,22 @@ static int smb2_set_current_limit(struct smb2_chip *chip, unsigned int val) val_raw); } -static void smb2_status_change_work(struct work_struct *work) +static void smb_status_change_work(struct work_struct *work) { unsigned int charger_type, current_ua; int usb_online = 0; int count, rc; - struct smb2_chip *chip; + struct smb_chip *chip; - chip = container_of(work, struct smb2_chip, status_change_work.work); + chip = container_of(work, struct smb_chip, status_change_work.work); - smb2_get_prop_usb_online(chip, &usb_online); + smb_get_prop_usb_online(chip, &usb_online); if (!usb_online) return; for (count = 0; count < 3; count++) { dev_dbg(chip->dev, "get charger type retry %d\n", count); - rc = smb2_apsd_get_charger_type(chip, &charger_type); + rc = smb_apsd_get_charger_type(chip, &charger_type); if (rc != -EAGAIN) break; msleep(100); @@ -592,11 +595,11 @@ static void smb2_status_change_work(struct work_struct *work) break; } - smb2_set_current_limit(chip, current_ua); + smb_set_current_limit(chip, current_ua); power_supply_changed(chip->chg_psy); } -static int smb2_get_iio_chan(struct smb2_chip *chip, struct iio_channel *chan, +static int smb_get_iio_chan(struct smb_chip *chip, struct iio_channel *chan, int *val) { int rc; @@ -617,7 +620,7 @@ static int smb2_get_iio_chan(struct smb2_chip *chip, struct iio_channel *chan, return iio_read_channel_processed(chan, val); } -static int smb2_get_prop_health(struct smb2_chip *chip, int *val) +static int smb_get_prop_health(struct smb_chip *chip, int *val) { int rc; unsigned int stat; @@ -651,11 +654,11 @@ static int smb2_get_prop_health(struct smb2_chip *chip, int *val) } } -static int smb2_get_property(struct power_supply *psy, +static int smb_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - struct smb2_chip *chip = power_supply_get_drvdata(psy); + struct smb_chip *chip = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_MANUFACTURER: @@ -665,46 +668,50 @@ static int smb2_get_property(struct power_supply *psy, val->strval = chip->name; return 0; case POWER_SUPPLY_PROP_CURRENT_MAX: - return smb2_get_current_limit(chip, &val->intval); + return smb_get_current_limit(chip, &val->intval); case POWER_SUPPLY_PROP_CURRENT_NOW: - return smb2_get_iio_chan(chip, chip->usb_in_i_chan, + return smb_get_iio_chan(chip, chip->usb_in_i_chan, &val->intval); case POWER_SUPPLY_PROP_VOLTAGE_NOW: - return smb2_get_iio_chan(chip, chip->usb_in_v_chan, + return smb_get_iio_chan(chip, chip->usb_in_v_chan, &val->intval); case POWER_SUPPLY_PROP_ONLINE: - return smb2_get_prop_usb_online(chip, &val->intval); + return smb_get_prop_usb_online(chip, &val->intval); case POWER_SUPPLY_PROP_STATUS: - return smb2_get_prop_status(chip, &val->intval); + return smb_get_prop_status(chip, &val->intval); case POWER_SUPPLY_PROP_HEALTH: - return smb2_get_prop_health(chip, &val->intval); + return smb_get_prop_health(chip, &val->intval); case POWER_SUPPLY_PROP_USB_TYPE: - return smb2_apsd_get_charger_type(chip, &val->intval); + return smb_apsd_get_charger_type(chip, &val->intval); default: dev_err(chip->dev, "invalid property: %d\n", psp); return -EINVAL; } } -static int smb2_set_property(struct power_supply *psy, +static int smb_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { - struct smb2_chip *chip = power_supply_get_drvdata(psy); + struct smb_chip *chip = power_supply_get_drvdata(psy); switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return regmap_update_bits(chip->regmap, chip->base + USBIN_CMD_IL, + USBIN_SUSPEND_BIT, !val->intval); case POWER_SUPPLY_PROP_CURRENT_MAX: - return smb2_set_current_limit(chip, val->intval); + return smb_set_current_limit(chip, val->intval); default: dev_err(chip->dev, "No setter for property: %d\n", psp); return -EINVAL; } } -static int smb2_property_is_writable(struct power_supply *psy, +static int smb_property_is_writable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { + case POWER_SUPPLY_PROP_STATUS: case POWER_SUPPLY_PROP_CURRENT_MAX: return 1; default: @@ -712,9 +719,9 @@ static int smb2_property_is_writable(struct power_supply *psy, } } -static irqreturn_t smb2_handle_batt_overvoltage(int irq, void *data) +static irqreturn_t smb_handle_batt_overvoltage(int irq, void *data) { - struct smb2_chip *chip = data; + struct smb_chip *chip = data; unsigned int status; regmap_read(chip->regmap, chip->base + BATTERY_CHARGER_STATUS_2, @@ -729,9 +736,9 @@ static irqreturn_t smb2_handle_batt_overvoltage(int irq, void *data) return IRQ_HANDLED; } -static irqreturn_t smb2_handle_usb_plugin(int irq, void *data) +static irqreturn_t smb_handle_usb_plugin(int irq, void *data) { - struct smb2_chip *chip = data; + struct smb_chip *chip = data; power_supply_changed(chip->chg_psy); @@ -741,18 +748,18 @@ static irqreturn_t smb2_handle_usb_plugin(int irq, void *data) return IRQ_HANDLED; } -static irqreturn_t smb2_handle_usb_icl_change(int irq, void *data) +static irqreturn_t smb_handle_usb_icl_change(int irq, void *data) { - struct smb2_chip *chip = data; + struct smb_chip *chip = data; power_supply_changed(chip->chg_psy); return IRQ_HANDLED; } -static irqreturn_t smb2_handle_wdog_bark(int irq, void *data) +static irqreturn_t smb_handle_wdog_bark(int irq, void *data) { - struct smb2_chip *chip = data; + struct smb_chip *chip = data; int rc; power_supply_changed(chip->chg_psy); @@ -765,22 +772,22 @@ static irqreturn_t smb2_handle_wdog_bark(int irq, void *data) return IRQ_HANDLED; } -static const struct power_supply_desc smb2_psy_desc = { +static const struct power_supply_desc smb_psy_desc = { .name = "pmi8998_charger", .type = POWER_SUPPLY_TYPE_USB, .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | BIT(POWER_SUPPLY_USB_TYPE_CDP) | BIT(POWER_SUPPLY_USB_TYPE_DCP) | BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), - .properties = smb2_properties, - .num_properties = ARRAY_SIZE(smb2_properties), - .get_property = smb2_get_property, - .set_property = smb2_set_property, - .property_is_writeable = smb2_property_is_writable, + .properties = smb_properties, + .num_properties = ARRAY_SIZE(smb_properties), + .get_property = smb_get_property, + .set_property = smb_set_property, + .property_is_writeable = smb_property_is_writable, }; /* Init sequence derived from vendor downstream driver */ -static const struct smb2_register smb2_init_seq[] = { +static const struct smb_init_register smb_init_seq[] = { { .addr = AICL_RERUN_TIME_CFG, .mask = AICL_RERUN_TIME_MASK, .val = 0 }, /* * By default configure us as an upstream facing port @@ -882,17 +889,17 @@ static const struct smb2_register smb2_init_seq[] = { .val = 1000000 / CURRENT_SCALE_FACTOR }, }; -static int smb2_init_hw(struct smb2_chip *chip) +static int smb_init_hw(struct smb_chip *chip) { int rc, i; - for (i = 0; i < ARRAY_SIZE(smb2_init_seq); i++) { + for (i = 0; i < ARRAY_SIZE(smb_init_seq); i++) { dev_dbg(chip->dev, "%d: Writing 0x%02x to 0x%02x\n", i, - smb2_init_seq[i].val, smb2_init_seq[i].addr); + smb_init_seq[i].val, smb_init_seq[i].addr); rc = regmap_update_bits(chip->regmap, - chip->base + smb2_init_seq[i].addr, - smb2_init_seq[i].mask, - smb2_init_seq[i].val); + chip->base + smb_init_seq[i].addr, + smb_init_seq[i].mask, + smb_init_seq[i].val); if (rc < 0) return dev_err_probe(chip->dev, rc, "%s: init command %d failed\n", @@ -902,7 +909,7 @@ static int smb2_init_hw(struct smb2_chip *chip) return 0; } -static int smb2_init_irq(struct smb2_chip *chip, int *irq, const char *name, +static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name, irqreturn_t (*handler)(int irq, void *data)) { int irqnum; @@ -924,11 +931,11 @@ static int smb2_init_irq(struct smb2_chip *chip, int *irq, const char *name, return 0; } -static int smb2_probe(struct platform_device *pdev) +static int smb_probe(struct platform_device *pdev) { struct power_supply_config supply_config = {}; struct power_supply_desc *desc; - struct smb2_chip *chip; + struct smb_chip *chip; int rc, irq; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); @@ -959,17 +966,17 @@ static int smb2_probe(struct platform_device *pdev) "Couldn't get usbin_i IIO channel\n"); } - rc = smb2_init_hw(chip); + rc = smb_init_hw(chip); if (rc < 0) return rc; supply_config.drv_data = chip; - supply_config.of_node = pdev->dev.of_node; + supply_config.fwnode = dev_fwnode(&pdev->dev); - desc = devm_kzalloc(chip->dev, sizeof(smb2_psy_desc), GFP_KERNEL); + desc = devm_kzalloc(chip->dev, sizeof(smb_psy_desc), GFP_KERNEL); if (!desc) return -ENOMEM; - memcpy(desc, &smb2_psy_desc, sizeof(smb2_psy_desc)); + memcpy(desc, &smb_psy_desc, sizeof(smb_psy_desc)); desc->name = devm_kasprintf(chip->dev, GFP_KERNEL, "%s-charger", (const char *)device_get_match_data(chip->dev)); @@ -988,7 +995,7 @@ static int smb2_probe(struct platform_device *pdev) "Failed to get battery info\n"); rc = devm_delayed_work_autocancel(chip->dev, &chip->status_change_work, - smb2_status_change_work); + smb_status_change_work); if (rc) return dev_err_probe(chip->dev, rc, "Failed to init status change work\n"); @@ -999,24 +1006,26 @@ static int smb2_probe(struct platform_device *pdev) if (rc < 0) return dev_err_probe(chip->dev, rc, "Couldn't set vbat max\n"); - rc = smb2_init_irq(chip, &irq, "bat-ov", smb2_handle_batt_overvoltage); + rc = smb_init_irq(chip, &irq, "bat-ov", smb_handle_batt_overvoltage); if (rc < 0) return rc; - rc = smb2_init_irq(chip, &chip->cable_irq, "usb-plugin", - smb2_handle_usb_plugin); + rc = smb_init_irq(chip, &chip->cable_irq, "usb-plugin", + smb_handle_usb_plugin); if (rc < 0) return rc; - rc = smb2_init_irq(chip, &irq, "usbin-icl-change", - smb2_handle_usb_icl_change); + rc = smb_init_irq(chip, &irq, "usbin-icl-change", + smb_handle_usb_icl_change); if (rc < 0) return rc; - rc = smb2_init_irq(chip, &irq, "wdog-bark", smb2_handle_wdog_bark); + rc = smb_init_irq(chip, &irq, "wdog-bark", smb_handle_wdog_bark); if (rc < 0) return rc; - rc = dev_pm_set_wake_irq(chip->dev, chip->cable_irq); + devm_device_init_wakeup(chip->dev); + + rc = devm_pm_set_wake_irq(chip->dev, chip->cable_irq); if (rc < 0) return dev_err_probe(chip->dev, rc, "Couldn't set wake irq\n"); @@ -1028,23 +1037,23 @@ static int smb2_probe(struct platform_device *pdev) return 0; } -static const struct of_device_id smb2_match_id_table[] = { +static const struct of_device_id smb_match_id_table[] = { { .compatible = "qcom,pmi8998-charger", .data = "pmi8998" }, { .compatible = "qcom,pm660-charger", .data = "pm660" }, { /* sentinal */ } }; -MODULE_DEVICE_TABLE(of, smb2_match_id_table); +MODULE_DEVICE_TABLE(of, smb_match_id_table); -static struct platform_driver qcom_spmi_smb2 = { - .probe = smb2_probe, +static struct platform_driver qcom_spmi_smb = { + .probe = smb_probe, .driver = { - .name = "qcom-pmi8998/pm660-charger", - .of_match_table = smb2_match_id_table, + .name = "qcom-smbx-charger", + .of_match_table = smb_match_id_table, }, }; -module_platform_driver(qcom_spmi_smb2); +module_platform_driver(qcom_spmi_smb); -MODULE_AUTHOR("Caleb Connolly <caleb.connolly@linaro.org>"); +MODULE_AUTHOR("Casey Connolly <casey.connolly@linaro.org>"); MODULE_DESCRIPTION("Qualcomm SMB2 Charger Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/rk817_charger.c b/drivers/power/supply/rk817_charger.c index e5f35d083c23..9436c6bbf51f 100644 --- a/drivers/power/supply/rk817_charger.c +++ b/drivers/power/supply/rk817_charger.c @@ -1046,7 +1046,7 @@ static void rk817_charging_monitor(struct work_struct *work) rk817_read_props(charger); /* Run every 8 seconds like the BSP driver did. */ - queue_delayed_work(system_wq, &charger->work, msecs_to_jiffies(8000)); + queue_delayed_work(system_percpu_wq, &charger->work, msecs_to_jiffies(8000)); } static void rk817_cleanup_node(void *data) @@ -1088,7 +1088,7 @@ static int rk817_charger_probe(struct platform_device *pdev) rk817_bat_calib_vol(charger); pscfg.drv_data = charger; - pscfg.of_node = node; + pscfg.fwnode = &node->fwnode; /* * Get sample resistor value. Note only values of 10000 or 20000 @@ -1206,7 +1206,7 @@ static int rk817_charger_probe(struct platform_device *pdev) return ret; /* Force the first update immediately. */ - mod_delayed_work(system_wq, &charger->work, 0); + mod_delayed_work(system_percpu_wq, &charger->work, 0); return 0; } @@ -1226,7 +1226,7 @@ static int __maybe_unused rk817_resume(struct device *dev) struct rk817_charger *charger = dev_get_drvdata(dev); /* force an immediate update */ - mod_delayed_work(system_wq, &charger->work, 0); + mod_delayed_work(system_percpu_wq, &charger->work, 0); return 0; } diff --git a/drivers/power/supply/rt5033_battery.c b/drivers/power/supply/rt5033_battery.c index 7a27b262fb84..b2674adfa30b 100644 --- a/drivers/power/supply/rt5033_battery.c +++ b/drivers/power/supply/rt5033_battery.c @@ -160,7 +160,7 @@ static int rt5033_battery_probe(struct i2c_client *client) } i2c_set_clientdata(client, battery); - psy_cfg.of_node = client->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&client->dev); psy_cfg.drv_data = battery; battery->psy = devm_power_supply_register(&client->dev, diff --git a/drivers/power/supply/rt5033_charger.c b/drivers/power/supply/rt5033_charger.c index d19c7e80a92a..de724f23e453 100644 --- a/drivers/power/supply/rt5033_charger.c +++ b/drivers/power/supply/rt5033_charger.c @@ -16,6 +16,7 @@ #include <linux/power_supply.h> #include <linux/regmap.h> #include <linux/mfd/rt5033-private.h> +#include <linux/property.h> struct rt5033_charger_data { unsigned int pre_uamp; @@ -675,7 +676,7 @@ static int rt5033_charger_probe(struct platform_device *pdev) charger->regmap = dev_get_regmap(pdev->dev.parent, NULL); mutex_init(&charger->lock); - psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); psy_cfg.drv_data = charger; charger->psy = devm_power_supply_register(charger->dev, @@ -700,6 +701,8 @@ static int rt5033_charger_probe(struct platform_device *pdev) np_conn = of_parse_phandle(pdev->dev.of_node, "richtek,usb-connector", 0); np_edev = of_get_parent(np_conn); charger->edev = extcon_find_edev_by_node(np_edev); + of_node_put(np_edev); + of_node_put(np_conn); if (IS_ERR(charger->edev)) { dev_warn(charger->dev, "no extcon device found in device-tree\n"); goto out; diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c index 64a23e3d7bb0..5130d2395e88 100644 --- a/drivers/power/supply/rt9455_charger.c +++ b/drivers/power/supply/rt9455_charger.c @@ -1579,7 +1579,7 @@ static const struct regmap_config rt9455_regmap_config = { .writeable_reg = rt9455_is_writeable_reg, .volatile_reg = rt9455_is_volatile_reg, .max_register = RT9455_REG_MASK3, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, }; static int rt9455_probe(struct i2c_client *client) @@ -1658,11 +1658,20 @@ static int rt9455_probe(struct i2c_client *client) INIT_DEFERRABLE_WORK(&info->batt_presence_work, rt9455_batt_presence_work_callback); - rt9455_charger_config.of_node = dev->of_node; + rt9455_charger_config.fwnode = dev_fwnode(dev); rt9455_charger_config.drv_data = info; rt9455_charger_config.supplied_to = rt9455_charger_supplied_to; rt9455_charger_config.num_supplicants = ARRAY_SIZE(rt9455_charger_supplied_to); + + info->charger = devm_power_supply_register(dev, &rt9455_charger_desc, + &rt9455_charger_config); + if (IS_ERR(info->charger)) { + dev_err(dev, "Failed to register charger\n"); + ret = PTR_ERR(info->charger); + goto put_usb_notifier; + } + ret = devm_request_threaded_irq(dev, client->irq, NULL, rt9455_irq_handler_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, @@ -1678,14 +1687,6 @@ static int rt9455_probe(struct i2c_client *client) goto put_usb_notifier; } - info->charger = devm_power_supply_register(dev, &rt9455_charger_desc, - &rt9455_charger_config); - if (IS_ERR(info->charger)) { - dev_err(dev, "Failed to register charger\n"); - ret = PTR_ERR(info->charger); - goto put_usb_notifier; - } - return 0; put_usb_notifier: diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c index 235169c85c5d..44c26fb37a77 100644 --- a/drivers/power/supply/rt9467-charger.c +++ b/drivers/power/supply/rt9467-charger.c @@ -376,7 +376,7 @@ static int rt9467_set_value_from_ranges(struct rt9467_chg_data *data, if (rsel == RT9467_RANGE_VMIVR) { ret = linear_range_get_selector_high(range, value, &sel, &found); if (ret) - value = range->max_sel; + sel = range->max_sel; } else { linear_range_get_selector_within(range, value, &sel); } @@ -588,6 +588,10 @@ static int rt9467_run_aicl(struct rt9467_chg_data *data) aicl_vth = mivr_vth + RT9467_AICLVTH_GAP_uV; ret = rt9467_set_value_from_ranges(data, F_AICL_VTH, RT9467_RANGE_AICL_VTH, aicl_vth); + if (ret) { + dev_err(data->dev, "Failed to set AICL VTH\n"); + return ret; + } /* Trigger AICL function */ ret = regmap_field_write(data->rm_field[F_AICL_MEAS], 1); @@ -633,7 +637,9 @@ out: static const enum power_supply_property rt9467_chg_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, @@ -656,6 +662,8 @@ static int rt9467_psy_get_property(struct power_supply *psy, return rt9467_psy_get_status(data, &val->intval); case POWER_SUPPLY_PROP_ONLINE: return regmap_field_read(data->rm_field[F_PWR_RDY], &val->intval); + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return rt9467_get_adc(data, RT9467_ADC_VBUS_DIV5, &val->intval); case POWER_SUPPLY_PROP_CURRENT_MAX: mutex_lock(&data->attach_lock); if (data->psy_usb_type == POWER_SUPPLY_USB_TYPE_UNKNOWN || @@ -665,6 +673,8 @@ static int rt9467_psy_get_property(struct power_supply *psy, val->intval = 1500000; mutex_unlock(&data->attach_lock); return 0; + case POWER_SUPPLY_PROP_CURRENT_NOW: + return rt9467_get_adc(data, RT9467_ADC_IBUS, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: mutex_lock(&data->ichg_ieoc_lock); val->intval = data->ichg_ua; @@ -826,7 +836,7 @@ static int rt9467_register_psy(struct rt9467_chg_data *data) { struct power_supply_config cfg = { .drv_data = data, - .of_node = dev_of_node(data->dev), + .fwnode = dev_fwnode(data->dev), .attr_grp = rt9467_sysfs_groups, }; @@ -1141,27 +1151,6 @@ static int rt9467_reset_chip(struct rt9467_chg_data *data) return regmap_field_write(data->rm_field[F_RST], 1); } -static void rt9467_chg_destroy_adc_lock(void *data) -{ - struct mutex *adc_lock = data; - - mutex_destroy(adc_lock); -} - -static void rt9467_chg_destroy_attach_lock(void *data) -{ - struct mutex *attach_lock = data; - - mutex_destroy(attach_lock); -} - -static void rt9467_chg_destroy_ichg_ieoc_lock(void *data) -{ - struct mutex *ichg_ieoc_lock = data; - - mutex_destroy(ichg_ieoc_lock); -} - static void rt9467_chg_complete_aicl_done(void *data) { struct completion *aicl_done = data; @@ -1214,29 +1203,23 @@ static int rt9467_charger_probe(struct i2c_client *i2c) if (ret) return dev_err_probe(dev, ret, "Failed to add irq chip\n"); - mutex_init(&data->adc_lock); - ret = devm_add_action_or_reset(dev, rt9467_chg_destroy_adc_lock, - &data->adc_lock); + ret = devm_mutex_init(dev, &data->adc_lock); if (ret) - return dev_err_probe(dev, ret, "Failed to init ADC lock\n"); + return ret; - mutex_init(&data->attach_lock); - ret = devm_add_action_or_reset(dev, rt9467_chg_destroy_attach_lock, - &data->attach_lock); + ret = devm_mutex_init(dev, &data->attach_lock); if (ret) - return dev_err_probe(dev, ret, "Failed to init attach lock\n"); + return ret; - mutex_init(&data->ichg_ieoc_lock); - ret = devm_add_action_or_reset(dev, rt9467_chg_destroy_ichg_ieoc_lock, - &data->ichg_ieoc_lock); + ret = devm_mutex_init(dev, &data->ichg_ieoc_lock); if (ret) - return dev_err_probe(dev, ret, "Failed to init ICHG/IEOC lock\n"); + return ret; init_completion(&data->aicl_done); ret = devm_add_action_or_reset(dev, rt9467_chg_complete_aicl_done, &data->aicl_done); if (ret) - return dev_err_probe(dev, ret, "Failed to init AICL done completion\n"); + return ret; ret = rt9467_do_charger_init(data); if (ret) diff --git a/drivers/power/supply/rt9471.c b/drivers/power/supply/rt9471.c index 67b86ac91a21..e7f843f12c98 100644 --- a/drivers/power/supply/rt9471.c +++ b/drivers/power/supply/rt9471.c @@ -192,12 +192,12 @@ static const struct reg_field rt9471_reg_fields[F_MAX_FIELDS] = { }; static const struct linear_range rt9471_chg_ranges[RT9471_MAX_RANGES] = { - [RT9471_RANGE_AICR] = { .min = 50000, .min_sel = 1, .max_sel = 63, .step = 50000 }, - [RT9471_RANGE_MIVR] = { .min = 3900000, .min_sel = 0, .max_sel = 15, .step = 100000 }, - [RT9471_RANGE_IPRE] = { .min = 50000, .min_sel = 0, .max_sel = 15, .step = 50000 }, - [RT9471_RANGE_VCHG] = { .min = 3900000, .min_sel = 0, .max_sel = 80, .step = 10000 }, - [RT9471_RANGE_ICHG] = { .min = 0, .min_sel = 0, .max_sel = 63, .step = 50000 }, - [RT9471_RANGE_IEOC] = { .min = 50000, .min_sel = 0, .max_sel = 15, .step = 50000 }, + [RT9471_RANGE_AICR] = LINEAR_RANGE(50000, 1, 63, 50000), + [RT9471_RANGE_MIVR] = LINEAR_RANGE(3900000, 0, 15, 100000), + [RT9471_RANGE_IPRE] = LINEAR_RANGE(50000, 0, 15, 50000), + [RT9471_RANGE_VCHG] = LINEAR_RANGE(3900000, 0, 80, 10000), + [RT9471_RANGE_ICHG] = LINEAR_RANGE(0, 0, 63, 50000), + [RT9471_RANGE_IEOC] = LINEAR_RANGE(50000, 0, 15, 50000), }; static int rt9471_set_value_by_field_range(struct rt9471_chip *chip, @@ -723,7 +723,7 @@ static int rt9471_register_psy(struct rt9471_chip *chip) char *psy_name; cfg.drv_data = chip; - cfg.of_node = dev->of_node; + cfg.fwnode = dev_fwnode(dev); cfg.attr_grp = rt9471_sysfs_groups; psy_name = devm_kasprintf(dev, GFP_KERNEL, "rt9471-%s", dev_name(dev)); diff --git a/drivers/power/supply/rt9756.c b/drivers/power/supply/rt9756.c new file mode 100644 index 000000000000..f254527be653 --- /dev/null +++ b/drivers/power/supply/rt9756.c @@ -0,0 +1,955 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright (C) 2025 Richtek Technology Corp. +// +// Authors: ChiYuan Huang <cy_huang@richtek.com> + +#include <linux/atomic.h> +#include <linux/cleanup.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/linear_range.h> +#include <linux/interrupt.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/power_supply.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/sysfs.h> +#include <linux/util_macros.h> + +#define RT9756_REG_INTFLAG1 0x0B +#define RT9756_REG_INTFLAG2 0x0D +#define RT9756_REG_INTFLAG3 0x0F +#define RT9756_REG_ADCCTL 0x11 +#define RT9756_REG_VBUSADC 0x12 +#define RT9756_REG_BC12FLAG 0x45 +#define RT9756_REG_INTFLAG4 0x49 + +/* Flag1 */ +#define RT9756_EVT_BUSOVP BIT(3) +#define RT9756_EVT_BUSOCP BIT(2) +#define RT9756_EVT_BUSUCP BIT(0) +/* Flag2 */ +#define RT9756_EVT_BATOVP BIT(7) +#define RT9756_EVT_BATOCP BIT(6) +#define RT9756_EVT_TDIEOTP BIT(3) +#define RT9756_EVT_VBUSLOW_ERR BIT(2) +#define RT9756_EVT_VAC_INSERT BIT(0) +/* Flag3 */ +#define RT9756_EVT_WDT BIT(5) +#define RT9756_EVT_VAC_UVLO BIT(4) +/* ADCCTL */ +#define RT9756_ADCEN_MASK BIT(7) +#define RT9756_ADCONCE_MASK BIT(6) +/* Bc12_flag */ +#define RT9756_EVT_BC12_DONE BIT(3) +/* Flag4 */ +#define RT9756_EVT_OUTOVP BIT(0) + +#define RICHTEK_DEVID 7 +#define RT9756_REVID 0 +#define RT9756A_REVID 1 +#define RT9757_REVID 2 +#define RT9757A_REVID 3 +#define RT9756_ADC_CONVTIME 1200 +#define RT9756_ADC_MAXWAIT 16000 + +enum rt9756_model { + MODEL_RT9756 = 0, + MODEL_RT9757, + MODEL_RT9770, + MODEL_MAX +}; + +enum rt9756_adc_chan { + ADC_VBUS = 0, + ADC_IBUS, + ADC_VBAT, + ADC_IBAT, + ADC_TDIE, + ADC_MAX_CHANNEL +}; + +enum rt9756_usb_type { + USB_NO_VBUS = 0, + USB_SDP = 2, + USB_NSTD, + USB_DCP, + USB_CDP, + MAX_USB_TYPE +}; + +enum rt9756_fields { + F_VBATOVP = 0, + F_VBATOVP_EN, + F_IBATOCP, + F_IBATOCP_EN, + F_VBUSOVP, + F_VBUSOVP_EN, + F_IBUSOCP, + F_IBUSOCP_EN, + F_SWITCHING, + F_REG_RST, + F_CHG_EN, + F_OP_MODE, + F_WDT_DIS, + F_WDT_TMR, + F_DEV_ID, + F_BC12_EN, + F_USB_STATE, + F_VBUS_STATE, + F_IBAT_RSEN, + F_REVISION, + F_MAX_FIELD +}; + +enum rt9756_ranges { + R_VBATOVP = 0, + R_IBATOCP, + R_VBUSOVP, + R_IBUSOCP, + R_MAX_RANGE +}; + +static const struct reg_field rt9756_chg_fields[F_MAX_FIELD] = { + [F_VBATOVP] = REG_FIELD(0x08, 0, 4), + [F_VBATOVP_EN] = REG_FIELD(0x08, 7, 7), + [F_IBATOCP] = REG_FIELD(0x09, 0, 5), + [F_IBATOCP_EN] = REG_FIELD(0x09, 7, 7), + [F_VBUSOVP] = REG_FIELD(0x06, 0, 5), + [F_VBUSOVP_EN] = REG_FIELD(0x06, 7, 7), + [F_IBUSOCP] = REG_FIELD(0x07, 0, 4), + [F_IBUSOCP_EN] = REG_FIELD(0x07, 5, 5), + [F_SWITCHING] = REG_FIELD(0x5c, 7, 7), + [F_REG_RST] = REG_FIELD(0x00, 7, 7), + [F_CHG_EN] = REG_FIELD(0x00, 6, 6), + [F_OP_MODE] = REG_FIELD(0x00, 5, 5), + [F_WDT_DIS] = REG_FIELD(0x00, 3, 3), + [F_WDT_TMR] = REG_FIELD(0x00, 0, 2), + [F_DEV_ID] = REG_FIELD(0x03, 0, 3), + [F_BC12_EN] = REG_FIELD(0x44, 7, 7), + [F_USB_STATE] = REG_FIELD(0x46, 5, 7), + [F_VBUS_STATE] = REG_FIELD(0x4c, 0, 0), + [F_IBAT_RSEN] = REG_FIELD(0x5e, 0, 1), + [F_REVISION] = REG_FIELD(0x62, 0, 1), +}; + +static const struct reg_field rt9770_chg_fields[F_MAX_FIELD] = { + [F_VBATOVP] = REG_FIELD(0x08, 0, 4), + [F_VBATOVP_EN] = REG_FIELD(0x08, 7, 7), + [F_IBATOCP] = REG_FIELD(0x09, 0, 5), + [F_IBATOCP_EN] = REG_FIELD(0x09, 7, 7), + [F_VBUSOVP] = REG_FIELD(0x06, 0, 5), + [F_VBUSOVP_EN] = REG_FIELD(0x06, 7, 7), + [F_IBUSOCP] = REG_FIELD(0x07, 0, 4), + [F_IBUSOCP_EN] = REG_FIELD(0x07, 5, 5), + [F_SWITCHING] = REG_FIELD(0x5c, 7, 7), + [F_REG_RST] = REG_FIELD(0x00, 7, 7), + [F_CHG_EN] = REG_FIELD(0x00, 6, 6), + [F_OP_MODE] = REG_FIELD(0x00, 5, 5), + [F_WDT_DIS] = REG_FIELD(0x00, 3, 3), + [F_WDT_TMR] = REG_FIELD(0x00, 0, 2), + [F_DEV_ID] = REG_FIELD(0x60, 0, 3), + [F_BC12_EN] = REG_FIELD(0x03, 7, 7), + [F_USB_STATE] = REG_FIELD(0x02, 5, 7), + [F_VBUS_STATE] = REG_FIELD(0x4c, 0, 0), + [F_IBAT_RSEN] = REG_FIELD(0x5e, 0, 1), + [F_REVISION] = REG_FIELD(0x62, 3, 7), +}; + +/* All converted to microvolt or microamp */ +static const struct linear_range rt9756_chg_ranges[R_MAX_RANGE] = { + LINEAR_RANGE_IDX(R_VBATOVP, 4200000, 0, 31, 25000), + LINEAR_RANGE_IDX(R_IBATOCP, 2000000, 0, 63, 100000), + LINEAR_RANGE_IDX(R_VBUSOVP, 3000000, 0, 63, 50000), + LINEAR_RANGE_IDX(R_IBUSOCP, 1000000, 0, 31, 250000), +}; + +struct charger_event { + unsigned int flag1; + unsigned int flag2; + unsigned int flag3; + unsigned int flag4; +}; + +struct rt9756_data { + struct device *dev; + struct regmap *regmap; + struct regmap_field *rm_fields[F_MAX_FIELD]; + struct power_supply *psy; + struct power_supply *bat_psy; + struct mutex adc_lock; + struct power_supply_desc psy_desc; + struct power_supply_desc bat_psy_desc; + struct charger_event chg_evt; + unsigned int rg_resistor; + unsigned int real_resistor; + enum rt9756_model model; + atomic_t usb_type; +}; + +struct rt975x_dev_data { + const struct regmap_config *regmap_config; + const struct reg_field *reg_fields; + const struct reg_sequence *init_regs; + size_t num_init_regs; + int (*check_device_model)(struct rt9756_data *data); +}; + +static int rt9756_get_value_field_range(struct rt9756_data *data, enum rt9756_fields en_field, + enum rt9756_fields field, enum rt9756_ranges rsel, int *val) +{ + const struct linear_range *range = rt9756_chg_ranges + rsel; + unsigned int enable, selector, value; + int ret; + + ret = regmap_field_read(data->rm_fields[en_field], &enable); + if (ret) + return ret; + + if (!enable) { + *val = 0; + return 0; + } + + ret = regmap_field_read(data->rm_fields[field], &selector); + if (ret) + return ret; + + ret = linear_range_get_value(range, selector, &value); + if (ret) + return ret; + + *val = (int)value; + + return 0; +} + +static int rt9756_set_value_field_range(struct rt9756_data *data, enum rt9756_fields en_field, + enum rt9756_fields field, enum rt9756_ranges rsel, int val) +{ + const struct linear_range *range = rt9756_chg_ranges + rsel; + unsigned int selector, value; + int ret; + + if (!val) + return regmap_field_write(data->rm_fields[en_field], 0); + + value = (unsigned int)val; + linear_range_get_selector_within(range, value, &selector); + ret = regmap_field_write(data->rm_fields[field], selector); + if (ret) + return ret; + + return regmap_field_write(data->rm_fields[en_field], 1); +} + +static int rt9756_get_adc(struct rt9756_data *data, enum rt9756_adc_chan chan, + int *val) +{ + struct regmap *regmap = data->regmap; + unsigned int reg_addr = RT9756_REG_VBUSADC + chan * 2; + unsigned int mask = RT9756_ADCEN_MASK | RT9756_ADCONCE_MASK; + unsigned int shift = 0, adc_cntl; + __be16 raws; + int scale, offset = 0, ret; + + guard(mutex)(&data->adc_lock); + + ret = regmap_update_bits(regmap, RT9756_REG_ADCCTL, mask, mask); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(regmap, RT9756_REG_ADCCTL, adc_cntl, + !(adc_cntl & RT9756_ADCEN_MASK), + RT9756_ADC_CONVTIME, RT9756_ADC_MAXWAIT); + if (ret && ret != -ETIMEDOUT) + return ret; + + ret = regmap_raw_read(regmap, reg_addr, &raws, sizeof(raws)); + if (ret) + return ret; + + /* + * TDIE LSB 1'c, others LSB 1000uV or 1000uA. + * Rsense ratio is needed for IBAT channel + */ + if (chan == ADC_TDIE) { + scale = 10; + shift = 8; + offset = -40; + } else if (chan == ADC_IBAT) + scale = 1000 * data->rg_resistor / data->real_resistor; + else + scale = 1000; + + *val = ((be16_to_cpu(raws) >> shift) + offset) * scale; + + return regmap_update_bits(regmap, RT9756_REG_ADCCTL, mask, 0); +} + +static int rt9756_get_switching_state(struct rt9756_data *data, int *status) +{ + unsigned int switching_state; + int ret; + + ret = regmap_field_read(data->rm_fields[F_SWITCHING], &switching_state); + if (ret) + return ret; + + if (switching_state) + *status = POWER_SUPPLY_STATUS_CHARGING; + else + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + + return 0; +} + +static int rt9756_get_charger_health(struct rt9756_data *data) +{ + struct charger_event *evt = &data->chg_evt; + + if (evt->flag2 & RT9756_EVT_VBUSLOW_ERR) + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + + if (evt->flag1 & RT9756_EVT_BUSOVP || evt->flag2 & RT9756_EVT_BATOVP || + evt->flag4 & RT9756_EVT_OUTOVP) + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; + + if (evt->flag1 & RT9756_EVT_BUSOCP || evt->flag2 & RT9756_EVT_BATOCP) + return POWER_SUPPLY_HEALTH_OVERCURRENT; + + if (evt->flag1 & RT9756_EVT_BUSUCP) + return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + + if (evt->flag2 & RT9756_EVT_TDIEOTP) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (evt->flag3 & RT9756_EVT_WDT) + return POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE; + + return POWER_SUPPLY_HEALTH_GOOD; +} + +static int rt9756_get_charger_online(struct rt9756_data *data, int *val) +{ + unsigned int online; + int ret; + + ret = regmap_field_read(data->rm_fields[F_VBUS_STATE], &online); + if (ret) + return ret; + + *val = !!online; + return 0; +} + +static int rt9756_get_vbus_ovp(struct rt9756_data *data, int *val) +{ + unsigned int opmode; + int ovpval, ret; + + /* operating mode -> 0 bypass, 1 div2 */ + ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode); + if (ret) + return ret; + + ret = rt9756_get_value_field_range(data, F_VBUSOVP_EN, F_VBUSOVP, R_VBUSOVP, &ovpval); + if (ret) + return ret; + + *val = opmode ? ovpval * 2 : ovpval; + return 0; +} + +static int rt9756_set_vbus_ovp(struct rt9756_data *data, int val) +{ + unsigned int opmode; + int ret; + + /* operating mode -> 0 bypass, 1 div2 */ + ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode); + if (ret) + return ret; + + return rt9756_set_value_field_range(data, F_VBUSOVP_EN, F_VBUSOVP, R_VBUSOVP, + opmode ? val / 2 : val); +} + +static const char * const rt9756_manufacturer = "Richtek Technology Corp."; +static const char * const rt9756_model[MODEL_MAX] = { "RT9756", "RT9757", "RT9770" }; + +static int rt9756_psy_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt9756_data *data = power_supply_get_drvdata(psy); + int *pval = &val->intval; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return rt9756_get_switching_state(data, pval); + case POWER_SUPPLY_PROP_HEALTH: + *pval = rt9756_get_charger_health(data); + return 0; + case POWER_SUPPLY_PROP_ONLINE: + return rt9756_get_charger_online(data, pval); + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return rt9756_get_vbus_ovp(data, pval); + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return rt9756_get_adc(data, ADC_VBUS, pval); + case POWER_SUPPLY_PROP_CURRENT_MAX: + return rt9756_get_value_field_range(data, F_IBUSOCP_EN, F_IBUSOCP, R_IBUSOCP, pval); + case POWER_SUPPLY_PROP_CURRENT_NOW: + return rt9756_get_adc(data, ADC_IBUS, pval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return rt9756_get_value_field_range(data, F_VBATOVP_EN, F_VBATOVP, R_VBATOVP, pval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return rt9756_get_value_field_range(data, F_IBATOCP_EN, F_IBATOCP, R_IBATOCP, pval); + case POWER_SUPPLY_PROP_TEMP: + return rt9756_get_adc(data, ADC_TDIE, pval); + case POWER_SUPPLY_PROP_USB_TYPE: + *pval = atomic_read(&data->usb_type); + return 0; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = rt9756_model[data->model]; + return 0; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = rt9756_manufacturer; + return 0; + default: + return -ENODATA; + } +} + +static int rt9756_psy_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct rt9756_data *data = power_supply_get_drvdata(psy); + int intval = val->intval; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + memset(&data->chg_evt, 0, sizeof(data->chg_evt)); + return regmap_field_write(data->rm_fields[F_CHG_EN], !!intval); + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return rt9756_set_vbus_ovp(data, intval); + case POWER_SUPPLY_PROP_CURRENT_MAX: + return rt9756_set_value_field_range(data, F_IBUSOCP_EN, F_IBUSOCP, R_IBUSOCP, + intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return rt9756_set_value_field_range(data, F_VBATOVP_EN, F_VBATOVP, R_VBATOVP, + intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return rt9756_set_value_field_range(data, F_IBATOCP_EN, F_IBATOCP, R_IBATOCP, + intval); + case POWER_SUPPLY_PROP_USB_TYPE: + return regmap_field_write(data->rm_fields[F_BC12_EN], !!intval); + default: + return -EINVAL; + } +} + +static const enum power_supply_property rt9756_psy_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static int rt9756_bat_psy_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt9756_data *data = power_supply_get_drvdata(psy); + int *pval = &val->intval; + + switch (psp) { + case POWER_SUPPLY_PROP_TECHNOLOGY: + *pval = POWER_SUPPLY_TECHNOLOGY_LION; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return rt9756_get_adc(data, ADC_VBAT, pval); + case POWER_SUPPLY_PROP_CURRENT_NOW: + return rt9756_get_adc(data, ADC_IBAT, pval); + default: + return -ENODATA; + } +} + +static const enum power_supply_property rt9756_bat_psy_properties[] = { + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int rt9756_psy_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + case POWER_SUPPLY_PROP_USB_TYPE: + return 1; + default: + return 0; + } +} + +static const unsigned int rt9756_wdt_millisecond[] = { + 500, 1000, 5000, 30000, 40000, 80000, 128000, 255000 +}; + +static ssize_t watchdog_timer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = to_power_supply(dev); + struct rt9756_data *data = power_supply_get_drvdata(psy); + unsigned int wdt_tmr_now = 0, wdt_sel, wdt_dis; + int ret; + + ret = regmap_field_read(data->rm_fields[F_WDT_DIS], &wdt_dis); + if (ret) + return ret; + + if (!wdt_dis) { + ret = regmap_field_read(data->rm_fields[F_WDT_TMR], &wdt_sel); + if (ret) + return ret; + + wdt_tmr_now = rt9756_wdt_millisecond[wdt_sel]; + } + + return sysfs_emit(buf, "%d\n", wdt_tmr_now); +} + +static ssize_t watchdog_timer_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = to_power_supply(dev); + struct rt9756_data *data = power_supply_get_drvdata(psy); + unsigned int wdt_set, wdt_sel; + int ret; + + ret = kstrtouint(buf, 10, &wdt_set); + if (ret) + return ret; + + ret = regmap_field_write(data->rm_fields[F_WDT_DIS], 1); + if (ret) + return ret; + + wdt_sel = find_closest(wdt_set, rt9756_wdt_millisecond, + ARRAY_SIZE(rt9756_wdt_millisecond)); + + ret = regmap_field_write(data->rm_fields[F_WDT_TMR], wdt_sel); + if (ret) + return ret; + + if (wdt_set) { + ret = regmap_field_write(data->rm_fields[F_WDT_DIS], 0); + if (ret) + return ret; + } + + return count; +} + +static const char * const rt9756_opmode_str[] = { "bypass", "div2" }; + +static ssize_t operation_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = to_power_supply(dev); + struct rt9756_data *data = power_supply_get_drvdata(psy); + unsigned int opmode; + int ret; + + ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode); + if (ret) + return ret; + + return sysfs_emit(buf, "%s\n", rt9756_opmode_str[opmode]); +} + +static ssize_t operation_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = to_power_supply(dev); + struct rt9756_data *data = power_supply_get_drvdata(psy); + int index, ret; + + index = sysfs_match_string(rt9756_opmode_str, buf); + if (index < 0) + return index; + + ret = regmap_field_write(data->rm_fields[F_OP_MODE], index); + + return ret ?: count; +} + +static DEVICE_ATTR_RW(watchdog_timer); +static DEVICE_ATTR_RW(operation_mode); + +static struct attribute *rt9756_sysfs_attrs[] = { + &dev_attr_watchdog_timer.attr, + &dev_attr_operation_mode.attr, + NULL +}; +ATTRIBUTE_GROUPS(rt9756_sysfs); + +static int rt9756_register_psy(struct rt9756_data *data) +{ + struct power_supply_desc *desc = &data->psy_desc; + struct power_supply_desc *bat_desc = &data->bat_psy_desc; + struct power_supply_config cfg = {}, bat_cfg = {}; + struct device *dev = data->dev; + char *psy_name, *bat_psy_name, **supplied_to; + + bat_cfg.drv_data = data; + bat_cfg.fwnode = dev_fwnode(dev); + + bat_psy_name = devm_kasprintf(dev, GFP_KERNEL, "rt9756-%s-battery", dev_name(dev)); + if (!bat_psy_name) + return -ENOMEM; + + bat_desc->name = bat_psy_name; + bat_desc->type = POWER_SUPPLY_TYPE_BATTERY; + bat_desc->properties = rt9756_bat_psy_properties; + bat_desc->num_properties = ARRAY_SIZE(rt9756_bat_psy_properties); + bat_desc->get_property = rt9756_bat_psy_get_property; + + data->bat_psy = devm_power_supply_register(dev, bat_desc, &bat_cfg); + if (IS_ERR(data->bat_psy)) + return dev_err_probe(dev, PTR_ERR(data->bat_psy), "Failed to register battery\n"); + + supplied_to = devm_kzalloc(dev, sizeof(*supplied_to), GFP_KERNEL); + if (!supplied_to) + return -ENOMEM; + + /* Link charger psy to battery psy */ + supplied_to[0] = bat_psy_name; + + cfg.drv_data = data; + cfg.fwnode = dev_fwnode(dev); + cfg.attr_grp = rt9756_sysfs_groups; + cfg.supplied_to = supplied_to; + cfg.num_supplicants = 1; + + psy_name = devm_kasprintf(dev, GFP_KERNEL, "rt9756-%s", dev_name(dev)); + if (!psy_name) + return -ENOMEM; + + desc->name = psy_name; + desc->type = POWER_SUPPLY_TYPE_USB; + desc->usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | BIT(POWER_SUPPLY_USB_TYPE_CDP); + desc->properties = rt9756_psy_properties; + desc->num_properties = ARRAY_SIZE(rt9756_psy_properties); + desc->property_is_writeable = rt9756_psy_property_is_writeable; + desc->get_property = rt9756_psy_get_property; + desc->set_property = rt9756_psy_set_property; + + data->psy = devm_power_supply_register(dev, desc, &cfg); + + return PTR_ERR_OR_ZERO(data->psy); +} + +static int rt9756_get_usb_type(struct rt9756_data *data) +{ + unsigned int type; + int report_type, ret; + + ret = regmap_field_read(data->rm_fields[F_USB_STATE], &type); + if (ret) + return ret; + + switch (type) { + case USB_SDP: + case USB_NSTD: + report_type = POWER_SUPPLY_USB_TYPE_SDP; + break; + case USB_DCP: + report_type = POWER_SUPPLY_USB_TYPE_DCP; + break; + case USB_CDP: + report_type = POWER_SUPPLY_USB_TYPE_CDP; + break; + case USB_NO_VBUS: + default: + report_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + break; + } + + atomic_set(&data->usb_type, report_type); + return 0; +} + +static irqreturn_t rt9756_irq_handler(int irq, void *devid) +{ + struct rt9756_data *data = devid; + struct regmap *regmap = data->regmap; + struct charger_event *evt = &data->chg_evt; + unsigned int bc12_flag = 0; + int ret; + + ret = regmap_read(regmap, RT9756_REG_INTFLAG1, &evt->flag1); + if (ret) + return IRQ_NONE; + + ret = regmap_read(regmap, RT9756_REG_INTFLAG2, &evt->flag2); + if (ret) + return IRQ_NONE; + + ret = regmap_read(regmap, RT9756_REG_INTFLAG3, &evt->flag3); + if (ret) + return IRQ_NONE; + + if (data->model != MODEL_RT9770) { + ret = regmap_read(regmap, RT9756_REG_INTFLAG4, &evt->flag4); + if (ret) + return IRQ_NONE; + + ret = regmap_read(regmap, RT9756_REG_BC12FLAG, &bc12_flag); + if (ret) + return IRQ_NONE; + } + + dev_dbg(data->dev, "events: 0x%02x,%02x,%02x,%02x,%02x\n", evt->flag1, evt->flag2, + evt->flag3, evt->flag4, bc12_flag); + + if (evt->flag2 & RT9756_EVT_VAC_INSERT) { + ret = regmap_field_write(data->rm_fields[F_BC12_EN], 1); + if (ret) + return IRQ_NONE; + } + + if (evt->flag3 & RT9756_EVT_VAC_UVLO) + atomic_set(&data->usb_type, POWER_SUPPLY_USB_TYPE_UNKNOWN); + + if (bc12_flag & RT9756_EVT_BC12_DONE) { + ret = rt9756_get_usb_type(data); + if (ret) + return IRQ_NONE; + } + + power_supply_changed(data->psy); + + return IRQ_HANDLED; +} + +static int rt9756_config_batsense_resistor(struct rt9756_data *data) +{ + unsigned int shunt_resistor_uohms = 2000, rsense_sel; + + device_property_read_u32(data->dev, "shunt-resistor-micro-ohms", &shunt_resistor_uohms); + + if (!shunt_resistor_uohms || shunt_resistor_uohms > 5000) + return -EINVAL; + + data->real_resistor = shunt_resistor_uohms; + + /* Always choose the larger or equal one to prevent false ocp alarm */ + if (shunt_resistor_uohms <= 1000) { + rsense_sel = 0; + data->rg_resistor = 1000; + } else if (shunt_resistor_uohms <= 2000) { + rsense_sel = 1; + data->rg_resistor = 2000; + } else { + rsense_sel = 2; + data->rg_resistor = 5000; + } + + return regmap_field_write(data->rm_fields[F_IBAT_RSEN], rsense_sel); +} + +static const struct reg_sequence rt9756_init_regs[] = { + REG_SEQ(0x00, 0x80, 1000), /* REG_RESET */ + REG_SEQ0(0x04, 0x13), /* VACOVP/OVPGATE 12V */ + REG_SEQ0(0x00, 0x28), /* WDT_DIS = 1 */ + REG_SEQ0(0x0c, 0x02), /* MASK FLAG1 */ + REG_SEQ0(0x0e, 0x06), /* MASK FLAG2 */ + REG_SEQ0(0x10, 0xca), /* MASK FLAG3 */ + REG_SEQ0(0x44, 0xa0), /* BC12_EN */ + REG_SEQ0(0x47, 0x07), /* MASK BC12FLAG */ + REG_SEQ0(0x4a, 0xfe), /* MASK FLAG4 */ + REG_SEQ0(0x5c, 0x40), /* MASK CON_SWITCHING */ + REG_SEQ0(0x63, 0x01), /* MASK VDDA_UVLO */ +}; + +static const struct reg_sequence rt9770_init_regs[] = { + REG_SEQ(0x00, 0x80, 1000), /* REG_RESET */ + REG_SEQ0(0x04, 0x13), /* VACOVP/OVPGATE 12V */ + REG_SEQ0(0x00, 0x28), /* WDT_DIS = 1 */ + REG_SEQ0(0x0c, 0x02), /* MASK FLAG1 */ + REG_SEQ0(0x0e, 0x06), /* MASK FLAG2 */ + REG_SEQ0(0x10, 0xca), /* MASK FLAG3 */ + REG_SEQ0(0x5c, 0x40), /* MASK CON_SWITCHING */ + REG_SEQ0(0x63, 0x01), /* MASK VDDA_UVLO */ +}; + +static const struct regmap_config rt9756_regmap_config = { + .name = "rt9756", + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x1ff, +}; + +static const struct regmap_config rt9770_regmap_config = { + .name = "rt9770", + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, +}; + +static int rt9756_check_device_model(struct rt9756_data *data) +{ + struct device *dev = data->dev; + unsigned int revid; + int ret; + + ret = regmap_field_read(data->rm_fields[F_REVISION], &revid); + if (ret) + return dev_err_probe(dev, ret, "Failed to read revid\n"); + + if (revid == RT9757_REVID || revid == RT9757A_REVID) + data->model = MODEL_RT9757; + else if (revid == RT9756_REVID || revid == RT9756A_REVID) + data->model = MODEL_RT9756; + else + return dev_err_probe(dev, -EINVAL, "Unknown revision %d\n", revid); + + return 0; +} + +static int rt9770_check_device_model(struct rt9756_data *data) +{ + data->model = MODEL_RT9770; + return 0; +} + +static int rt9756_probe(struct i2c_client *i2c) +{ + const struct rt975x_dev_data *dev_data; + struct device *dev = &i2c->dev; + struct rt9756_data *data; + struct regmap *regmap; + unsigned int devid; + int ret; + + dev_data = device_get_match_data(dev); + if (!dev_data) + return dev_err_probe(dev, -EINVAL, "No device data found\n"); + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + mutex_init(&data->adc_lock); + atomic_set(&data->usb_type, POWER_SUPPLY_USB_TYPE_UNKNOWN); + i2c_set_clientdata(i2c, data); + + regmap = devm_regmap_init_i2c(i2c, dev_data->regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to init regmap\n"); + + data->regmap = regmap; + + ret = devm_regmap_field_bulk_alloc(dev, regmap, data->rm_fields, dev_data->reg_fields, + F_MAX_FIELD); + if (ret) + return dev_err_probe(dev, ret, "Failed to alloc regmap fields\n"); + + /* Richtek Device ID check */ + ret = regmap_field_read(data->rm_fields[F_DEV_ID], &devid); + if (ret) + return dev_err_probe(dev, ret, "Failed to read devid\n"); + + if (devid != RICHTEK_DEVID) + return dev_err_probe(dev, -ENODEV, "Incorrect VID 0x%02x\n", devid); + + /* Get specific model */ + ret = dev_data->check_device_model(data); + if (ret) + return ret; + + ret = regmap_register_patch(regmap, dev_data->init_regs, dev_data->num_init_regs); + if (ret) + return dev_err_probe(dev, ret, "Failed to init registers\n"); + + ret = rt9756_config_batsense_resistor(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to config batsense resistor\n"); + + ret = rt9756_register_psy(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to init power supply\n"); + + return devm_request_threaded_irq(dev, i2c->irq, NULL, rt9756_irq_handler, IRQF_ONESHOT, + dev_name(dev), data); +} + +static void rt9756_shutdown(struct i2c_client *i2c) +{ + struct rt9756_data *data = i2c_get_clientdata(i2c); + + regmap_field_write(data->rm_fields[F_REG_RST], 1); +} + +static const struct rt975x_dev_data rt9756_dev_data = { + .regmap_config = &rt9756_regmap_config, + .reg_fields = rt9756_chg_fields, + .init_regs = rt9756_init_regs, + .num_init_regs = ARRAY_SIZE(rt9756_init_regs), + .check_device_model = rt9756_check_device_model, +}; + +static const struct rt975x_dev_data rt9770_dev_data = { + .regmap_config = &rt9770_regmap_config, + .reg_fields = rt9770_chg_fields, + .init_regs = rt9770_init_regs, + .num_init_regs = ARRAY_SIZE(rt9770_init_regs), + .check_device_model = rt9770_check_device_model, +}; + +static const struct of_device_id rt9756_device_match_table[] = { + { .compatible = "richtek,rt9756", .data = &rt9756_dev_data }, + { .compatible = "richtek,rt9770", .data = &rt9770_dev_data }, + {} +}; +MODULE_DEVICE_TABLE(of, rt9756_device_match_table); + +static struct i2c_driver rt9756_charger_driver = { + .driver = { + .name = "rt9756", + .of_match_table = rt9756_device_match_table, + }, + .probe = rt9756_probe, + .shutdown = rt9756_shutdown, +}; +module_i2c_driver(rt9756_charger_driver); + +MODULE_DESCRIPTION("Richtek RT9756 charger driver"); +MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/rx51_battery.c b/drivers/power/supply/rx51_battery.c index 7cdcd415e868..b0220ec2d926 100644 --- a/drivers/power/supply/rx51_battery.c +++ b/drivers/power/supply/rx51_battery.c @@ -116,7 +116,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di) int mid = (max + min) / 2; if (rx51_temp_table2[mid] <= raw) min = mid; - else if (rx51_temp_table2[mid] > raw) + else max = mid; if (rx51_temp_table2[mid] == raw) break; diff --git a/drivers/power/supply/s2mu005-battery.c b/drivers/power/supply/s2mu005-battery.c new file mode 100644 index 000000000000..64c57a14ef7d --- /dev/null +++ b/drivers/power/supply/s2mu005-battery.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Battery Fuel Gauge Driver for Samsung S2MU005 PMIC. + * + * Copyright (C) 2015 Samsung Electronics + * Copyright (C) 2023 Yassine Oudjana <y.oudjana@protonmail.com> + * Copyright (C) 2025 Kaustabh Chakraborty <kauschluss@disroot.org> + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/power_supply.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/units.h> + +#define S2MU005_FG_REG_STATUS 0x00 +#define S2MU005_FG_REG_IRQ 0x02 +#define S2MU005_FG_REG_RVBAT 0x04 +#define S2MU005_FG_REG_RCURCC 0x06 +#define S2MU005_FG_REG_RSOC 0x08 +#define S2MU005_FG_REG_MONOUT 0x0a +#define S2MU005_FG_REG_MONOUTSEL 0x0c +#define S2MU005_FG_REG_RBATCAP 0x0e +#define S2MU005_FG_REG_RZADJ 0x12 +#define S2MU005_FG_REG_RBATZ0 0x16 +#define S2MU005_FG_REG_RBATZ1 0x18 +#define S2MU005_FG_REG_IRQLVL 0x1a +#define S2MU005_FG_REG_START 0x1e + +#define S2MU005_FG_MONOUTSEL_AVGCURRENT 0x26 +#define S2MU005_FG_MONOUTSEL_AVGVOLTAGE 0x27 + +struct s2mu005_fg { + struct device *dev; + struct regmap *regmap; + struct power_supply *psy; + struct mutex monout_mutex; +}; + +static const struct regmap_config s2mu005_fg_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static irqreturn_t s2mu005_handle_irq(int irq, void *data) +{ + struct s2mu005_fg *priv = data; + + msleep(100); + power_supply_changed(priv->psy); + + return IRQ_HANDLED; +} + +static int s2mu005_fg_get_voltage_now(struct s2mu005_fg *priv, int *value) +{ + struct regmap *regmap = priv->regmap; + u32 val; + int ret; + + ret = regmap_read(regmap, S2MU005_FG_REG_RVBAT, &val); + if (ret < 0) { + dev_err(priv->dev, "failed to read voltage register (%d)\n", ret); + return ret; + } + + *value = (val * MICRO) >> 13; + + return 0; +} + +static int s2mu005_fg_get_voltage_avg(struct s2mu005_fg *priv, int *value) +{ + struct regmap *regmap = priv->regmap; + u32 val; + int ret; + + mutex_lock(&priv->monout_mutex); + + ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL, + S2MU005_FG_MONOUTSEL_AVGVOLTAGE); + if (ret < 0) { + dev_err(priv->dev, "failed to enable average voltage monitoring (%d)\n", + ret); + goto unlock; + } + + ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val); + if (ret < 0) { + dev_err(priv->dev, "failed to read current register (%d)\n", ret); + goto unlock; + } + + *value = (val * MICRO) >> 12; + +unlock: + mutex_unlock(&priv->monout_mutex); + + return ret; +} +static int s2mu005_fg_get_current_now(struct s2mu005_fg *priv, int *value) +{ + struct regmap *regmap = priv->regmap; + u32 val; + int ret; + + ret = regmap_read(regmap, S2MU005_FG_REG_RCURCC, &val); + if (ret < 0) { + dev_err(priv->dev, "failed to read current register (%d)\n", ret); + return ret; + } + + *value = -((s16)val * MICRO) >> 12; + + return 0; +} + +static int s2mu005_fg_get_current_avg(struct s2mu005_fg *priv, int *value) +{ + struct regmap *regmap = priv->regmap; + u32 val; + int ret; + + mutex_lock(&priv->monout_mutex); + + ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL, + S2MU005_FG_MONOUTSEL_AVGCURRENT); + if (ret < 0) { + dev_err(priv->dev, "failed to enable average current monitoring (%d)\n", + ret); + goto unlock; + } + + ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val); + if (ret < 0) { + dev_err(priv->dev, "failed to read current register (%d)\n", ret); + goto unlock; + } + + *value = -((s16)val * MICRO) >> 12; + +unlock: + mutex_unlock(&priv->monout_mutex); + + return ret; +} + +static int s2mu005_fg_get_capacity(struct s2mu005_fg *priv, int *value) +{ + struct regmap *regmap = priv->regmap; + u32 val; + int ret; + + ret = regmap_read(regmap, S2MU005_FG_REG_RSOC, &val); + if (ret < 0) { + dev_err(priv->dev, "failed to read capacity register (%d)\n", ret); + return ret; + } + + *value = (val * CENTI) >> 14; + + return 0; +} + +static int s2mu005_fg_get_status(struct s2mu005_fg *priv, int *value) +{ + int current_now, current_avg, capacity; + int ret; + + ret = s2mu005_fg_get_current_now(priv, ¤t_now); + if (ret < 0) + return ret; + + ret = s2mu005_fg_get_current_avg(priv, ¤t_avg); + if (ret < 0) + return ret; + + /* + * Verify both current values reported to reduce inaccuracies due to + * internal hysteresis. + */ + if (current_now < 0 && current_avg < 0) { + *value = POWER_SUPPLY_STATUS_DISCHARGING; + } else if (current_now == 0) { + *value = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + *value = POWER_SUPPLY_STATUS_CHARGING; + + ret = s2mu005_fg_get_capacity(priv, &capacity); + if (!ret && capacity > 98) + *value = POWER_SUPPLY_STATUS_FULL; + return ret; + } + + return 0; +} + +static const enum power_supply_property s2mu005_fg_properties[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_STATUS, +}; + +static int s2mu005_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct s2mu005_fg *priv = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return s2mu005_fg_get_voltage_now(priv, &val->intval); + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + return s2mu005_fg_get_voltage_avg(priv, &val->intval); + case POWER_SUPPLY_PROP_CURRENT_NOW: + return s2mu005_fg_get_current_now(priv, &val->intval); + case POWER_SUPPLY_PROP_CURRENT_AVG: + return s2mu005_fg_get_current_avg(priv, &val->intval); + case POWER_SUPPLY_PROP_CAPACITY: + return s2mu005_fg_get_capacity(priv, &val->intval); + case POWER_SUPPLY_PROP_STATUS: + return s2mu005_fg_get_status(priv, &val->intval); + default: + return -EINVAL; + } +} + +static const struct power_supply_desc s2mu005_fg_desc = { + .name = "s2mu005-fuel-gauge", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = s2mu005_fg_properties, + .num_properties = ARRAY_SIZE(s2mu005_fg_properties), + .get_property = s2mu005_fg_get_property, +}; + +static int s2mu005_fg_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct s2mu005_fg *priv; + struct power_supply_config psy_cfg = {}; + const struct power_supply_desc *psy_desc; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->dev = dev; + + priv->regmap = devm_regmap_init_i2c(client, &s2mu005_fg_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "failed to initialize regmap\n"); + + ret = devm_mutex_init(dev, &priv->monout_mutex); + if (ret) + dev_err_probe(dev, ret, "failed to initialize MONOUT mutex\n"); + + psy_desc = device_get_match_data(dev); + + psy_cfg.drv_data = priv; + psy_cfg.fwnode = dev_fwnode(dev); + priv->psy = devm_power_supply_register(priv->dev, psy_desc, &psy_cfg); + if (IS_ERR(priv->psy)) + return dev_err_probe(dev, PTR_ERR(priv->psy), + "failed to register power supply subsystem\n"); + + ret = devm_request_threaded_irq(priv->dev, client->irq, NULL, + s2mu005_handle_irq, IRQF_ONESHOT, + psy_desc->name, priv); + if (ret) + dev_err_probe(dev, ret, "failed to request IRQ\n"); + + return 0; +} + +static const struct of_device_id s2mu005_fg_of_match_table[] = { + { + .compatible = "samsung,s2mu005-fuel-gauge", + .data = &s2mu005_fg_desc, + }, + { } +}; +MODULE_DEVICE_TABLE(of, s2mu005_fg_of_match_table); + +static struct i2c_driver s2mu005_fg_i2c_driver = { + .probe = s2mu005_fg_i2c_probe, + .driver = { + .name = "s2mu005-fuel-gauge", + .of_match_table = s2mu005_fg_of_match_table, + }, +}; +module_i2c_driver(s2mu005_fg_i2c_driver); + +MODULE_DESCRIPTION("Samsung S2MU005 PMIC Battery Fuel Gauge Driver"); +MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>"); +MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 6f3d0413b1c1..43c48196c167 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -1138,7 +1138,7 @@ static int sbs_probe(struct i2c_client *client) chip->flags = (uintptr_t)i2c_get_match_data(client); chip->client = client; - psy_cfg.of_node = client->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&client->dev); psy_cfg.drv_data = chip; chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; sbs_invalidate_cached_props(chip); @@ -1174,24 +1174,6 @@ static int sbs_probe(struct i2c_client *client) i2c_set_clientdata(client, chip); - if (!chip->gpio_detect) - goto skip_gpio; - - irq = gpiod_to_irq(chip->gpio_detect); - if (irq <= 0) { - dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); - goto skip_gpio; - } - - rc = devm_request_threaded_irq(&client->dev, irq, NULL, sbs_irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - dev_name(&client->dev), chip); - if (rc) { - dev_warn(&client->dev, "Failed to request irq: %d\n", rc); - goto skip_gpio; - } - -skip_gpio: /* * Before we register, we might need to make sure we can actually talk * to the battery. @@ -1217,6 +1199,24 @@ skip_gpio: return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply), "Failed to register power supply\n"); + if (!chip->gpio_detect) + goto out; + + irq = gpiod_to_irq(chip->gpio_detect); + if (irq <= 0) { + dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); + goto out; + } + + rc = devm_request_threaded_irq(&client->dev, irq, NULL, sbs_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(&client->dev), chip); + if (rc) { + dev_warn(&client->dev, "Failed to request irq: %d\n", rc); + goto out; + } + +out: dev_info(&client->dev, "%s: battery gas gauge device registered\n", client->name); diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c index ab3f095d90ea..7d5e67620580 100644 --- a/drivers/power/supply/sbs-charger.c +++ b/drivers/power/supply/sbs-charger.c @@ -154,8 +154,7 @@ static const struct regmap_config sbs_regmap = { .val_format_endian = REGMAP_ENDIAN_LITTLE, /* since based on SMBus */ }; -static const struct power_supply_desc sbs_desc = { - .name = "sbs-charger", +static const struct power_supply_desc sbs_default_desc = { .type = POWER_SUPPLY_TYPE_MAINS, .properties = sbs_properties, .num_properties = ARRAY_SIZE(sbs_properties), @@ -165,15 +164,26 @@ static const struct power_supply_desc sbs_desc = { static int sbs_probe(struct i2c_client *client) { struct power_supply_config psy_cfg = {}; + struct power_supply_desc *sbs_desc; struct sbs_info *chip; int ret, val; + sbs_desc = devm_kmemdup(&client->dev, &sbs_default_desc, + sizeof(*sbs_desc), GFP_KERNEL); + if (!sbs_desc) + return -ENOMEM; + + sbs_desc->name = devm_kasprintf(&client->dev, GFP_KERNEL, "sbs-%s", + dev_name(&client->dev)); + if (!sbs_desc->name) + return -ENOMEM; + chip = devm_kzalloc(&client->dev, sizeof(struct sbs_info), GFP_KERNEL); if (!chip) return -ENOMEM; chip->client = client; - psy_cfg.of_node = client->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&client->dev); psy_cfg.drv_data = chip; i2c_set_clientdata(client, chip); @@ -191,7 +201,7 @@ static int sbs_probe(struct i2c_client *client) return dev_err_probe(&client->dev, ret, "Failed to get device status\n"); chip->last_state = val; - chip->power_supply = devm_power_supply_register(&client->dev, &sbs_desc, &psy_cfg); + chip->power_supply = devm_power_supply_register(&client->dev, sbs_desc, &psy_cfg); if (IS_ERR(chip->power_supply)) return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply), "Failed to register power supply\n"); diff --git a/drivers/power/supply/sbs-manager.c b/drivers/power/supply/sbs-manager.c index 7d2f39f19acb..343ad4ab4082 100644 --- a/drivers/power/supply/sbs-manager.c +++ b/drivers/power/supply/sbs-manager.c @@ -199,7 +199,7 @@ static int sbsm_gpio_get_value(struct gpio_chip *gc, unsigned int off) if (ret < 0) return ret; - return ret & BIT(off); + return !!(ret & BIT(off)); } /* @@ -348,7 +348,7 @@ static int sbsm_probe(struct i2c_client *client) data->muxc = i2c_mux_alloc(adapter, dev, SBSM_MAX_BATS, 0, I2C_MUX_LOCKED, &sbsm_select, NULL); if (!data->muxc) - return dev_err_probe(dev, -ENOMEM, "failed to alloc i2c mux\n"); + return -ENOMEM; data->muxc->priv = data; ret = devm_add_action_or_reset(dev, sbsm_del_mux_adapter, data); @@ -379,7 +379,7 @@ static int sbsm_probe(struct i2c_client *client) return ret; psy_cfg.drv_data = data; - psy_cfg.of_node = dev->of_node; + psy_cfg.fwnode = dev_fwnode(dev); data->psy = devm_power_supply_register(dev, psy_desc, &psy_cfg); if (IS_ERR(data->psy)) return dev_err_probe(dev, PTR_ERR(data->psy), diff --git a/drivers/power/supply/sc2731_charger.c b/drivers/power/supply/sc2731_charger.c index 50d5157af927..58b86fd78771 100644 --- a/drivers/power/supply/sc2731_charger.c +++ b/drivers/power/supply/sc2731_charger.c @@ -480,7 +480,7 @@ static int sc2731_charger_probe(struct platform_device *pdev) } charger_cfg.drv_data = info; - charger_cfg.of_node = np; + charger_cfg.fwnode = dev_fwnode(&pdev->dev); info->psy_usb = devm_power_supply_register(&pdev->dev, &sc2731_charger_desc, &charger_cfg); diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c index f36edc2ba708..a7ed9de8a289 100644 --- a/drivers/power/supply/sc27xx_fuel_gauge.c +++ b/drivers/power/supply/sc27xx_fuel_gauge.c @@ -1014,9 +1014,8 @@ static int sc27xx_fgu_hw_init(struct sc27xx_fgu_data *data) if (!table) return -EINVAL; - data->cap_table = devm_kmemdup(data->dev, table, - data->table_len * sizeof(*table), - GFP_KERNEL); + data->cap_table = devm_kmemdup_array(data->dev, table, data->table_len, + sizeof(*table), GFP_KERNEL); if (!data->cap_table) { power_supply_put_battery_info(data->battery, info); return -ENOMEM; @@ -1141,7 +1140,6 @@ disable_fgu: static int sc27xx_fgu_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; struct power_supply_config fgu_cfg = { }; struct sc27xx_fgu_data *data; int ret, irq; @@ -1205,7 +1203,7 @@ static int sc27xx_fgu_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); fgu_cfg.drv_data = data; - fgu_cfg.of_node = np; + fgu_cfg.fwnode = dev_fwnode(dev); data->battery = devm_power_supply_register(dev, &sc27xx_fgu_desc, &fgu_cfg); if (IS_ERR(data->battery)) { diff --git a/drivers/power/supply/smb347-charger.c b/drivers/power/supply/smb347-charger.c index c8392933ee28..8b95f7e8712f 100644 --- a/drivers/power/supply/smb347-charger.c +++ b/drivers/power/supply/smb347-charger.c @@ -1488,7 +1488,7 @@ static const struct regmap_config smb347_regmap = { .max_register = SMB347_MAX_REGISTER, .volatile_reg = smb347_volatile_reg, .readable_reg = smb347_readable_reg, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, }; static const struct regulator_ops smb347_usb_vbus_regulator_ops = { @@ -1553,7 +1553,7 @@ static int smb347_probe(struct i2c_client *client) return PTR_ERR(smb->regmap); mains_usb_cfg.drv_data = smb; - mains_usb_cfg.of_node = dev->of_node; + mains_usb_cfg.fwnode = dev_fwnode(dev); if (smb->use_mains) { smb->mains = devm_power_supply_register(dev, &smb347_mains_desc, &mains_usb_cfg); diff --git a/drivers/power/supply/test_power.c b/drivers/power/supply/test_power.c index 2a975a110f48..2c0e9ad820c0 100644 --- a/drivers/power/supply/test_power.c +++ b/drivers/power/supply/test_power.c @@ -37,6 +37,8 @@ static int battery_charge_counter = -1000; static int battery_current = -1600; static enum power_supply_charge_behaviour battery_charge_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; +static enum power_supply_charge_type battery_charge_types = + POWER_SUPPLY_CHARGE_TYPE_STANDARD; static bool battery_extension; static bool module_initialized; @@ -87,7 +89,7 @@ static int test_power_get_battery_property(struct power_supply *psy, val->intval = battery_status; break; case POWER_SUPPLY_PROP_CHARGE_TYPE: - val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + val->intval = battery_charge_types; break; case POWER_SUPPLY_PROP_HEALTH: val->intval = battery_health; @@ -129,6 +131,9 @@ static int test_power_get_battery_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: val->intval = battery_charge_behaviour; break; + case POWER_SUPPLY_PROP_CHARGE_TYPES: + val->intval = battery_charge_types; + break; default: pr_info("%s: some properties deliberately report errors.\n", __func__); @@ -140,7 +145,7 @@ static int test_power_get_battery_property(struct power_supply *psy, static int test_power_battery_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { - return psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR; + return psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR || psp == POWER_SUPPLY_PROP_CHARGE_TYPES; } static int test_power_set_battery_property(struct power_supply *psy, @@ -156,6 +161,14 @@ static int test_power_set_battery_property(struct power_supply *psy, } battery_charge_behaviour = val->intval; break; + case POWER_SUPPLY_PROP_CHARGE_TYPES: + if (val->intval < 0 || + val->intval >= BITS_PER_TYPE(typeof(psy->desc->charge_types)) || + !(BIT(val->intval) & psy->desc->charge_types)) { + return -EINVAL; + } + battery_charge_types = val->intval; + break; default: return -EINVAL; } @@ -188,6 +201,7 @@ static enum power_supply_property test_power_battery_props[] = { POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + POWER_SUPPLY_PROP_CHARGE_TYPES, }; static char *test_power_ac_supplied_to[] = { @@ -214,7 +228,10 @@ static const struct power_supply_desc test_power_desc[] = { .property_is_writeable = test_power_battery_property_is_writeable, .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) + | BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE) | BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE), + .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) + | BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE) }, [TEST_USB] = { .name = "test_usb", @@ -242,6 +259,7 @@ static const struct power_supply_config test_power_configs[] = { static int test_power_battery_extmanufacture_year = 1234; static int test_power_battery_exttemp_max = 1000; static const enum power_supply_property test_power_battery_extprops[] = { + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, POWER_SUPPLY_PROP_MANUFACTURE_YEAR, POWER_SUPPLY_PROP_TEMP_MAX, }; @@ -253,6 +271,9 @@ static int test_power_battery_extget_property(struct power_supply *psy, union power_supply_propval *val) { switch (psp) { + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + return power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + val); case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: val->intval = test_power_battery_extmanufacture_year; break; diff --git a/drivers/power/supply/tps65090-charger.c b/drivers/power/supply/tps65090-charger.c index d65193e410a6..d010f013af8c 100644 --- a/drivers/power/supply/tps65090-charger.c +++ b/drivers/power/supply/tps65090-charger.c @@ -259,7 +259,7 @@ static int tps65090_charger_probe(struct platform_device *pdev) psy_cfg.supplied_to = pdata->supplied_to; psy_cfg.num_supplicants = pdata->num_supplicants; - psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); psy_cfg.drv_data = cdata; cdata->ac = devm_power_supply_register(&pdev->dev, &tps65090_charger_desc, diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c index 6fff44e1ecac..6af17ce0b204 100644 --- a/drivers/power/supply/tps65217_charger.c +++ b/drivers/power/supply/tps65217_charger.c @@ -198,7 +198,7 @@ static int tps65217_charger_probe(struct platform_device *pdev) charger->tps = tps; charger->dev = &pdev->dev; - cfg.of_node = pdev->dev.of_node; + cfg.fwnode = dev_fwnode(&pdev->dev); cfg.drv_data = charger; charger->psy = devm_power_supply_register(&pdev->dev, diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index 9dcb5457bef4..04216b2bfb6c 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -512,7 +512,6 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) ret |= twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a, TWL4030_BCIMDKEY); if (bci->usb_enabled) { - pm_runtime_mark_last_busy(bci->transceiver->dev); pm_runtime_put_autosuspend(bci->transceiver->dev); bci->usb_enabled = 0; } diff --git a/drivers/power/supply/twl4030_madc_battery.c b/drivers/power/supply/twl4030_madc_battery.c index 3935162e350b..a99b3ff26929 100644 --- a/drivers/power/supply/twl4030_madc_battery.c +++ b/drivers/power/supply/twl4030_madc_battery.c @@ -11,9 +11,7 @@ */ #include <linux/module.h> -#include <linux/param.h> #include <linux/delay.h> -#include <linux/workqueue.h> #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/slab.h> diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c index 7382bec6a43c..3f44cc902f84 100644 --- a/drivers/power/supply/ucs1002_power.c +++ b/drivers/power/supply/ucs1002_power.c @@ -6,7 +6,6 @@ */ #include <linux/bits.h> #include <linux/freezer.h> -#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/kernel.h> @@ -493,7 +492,7 @@ static irqreturn_t ucs1002_alert_irq(int irq, void *data) { struct ucs1002_info *info = data; - mod_delayed_work(system_wq, &info->health_poll, 0); + mod_delayed_work(system_percpu_wq, &info->health_poll, 0); return IRQ_HANDLED; } @@ -560,7 +559,7 @@ static int ucs1002_probe(struct i2c_client *client) irq_a_det = of_irq_get_byname(dev->of_node, "a_det"); irq_alert = of_irq_get_byname(dev->of_node, "alert"); - charger_config.of_node = dev->of_node; + charger_config.fwnode = dev_fwnode(dev); charger_config.drv_data = info; ret = regmap_read(info->regmap, UCS1002_REG_PRODUCT_ID, ®val); diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c index 38e23bdd4603..210e0f9aa5e0 100644 --- a/drivers/power/supply/ug3105_battery.c +++ b/drivers/power/supply/ug3105_battery.c @@ -10,7 +10,22 @@ * is off or suspended, the coulomb counter is not used atm. * * Possible improvements: - * 1. Activate commented out total_coulomb_count code + * 1. Add coulumb counter reading, e.g. something like this: + * Read + reset coulomb counter every 10 polls (every 300 seconds) + * + * if ((chip->poll_count % 10) == 0) { + * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT); + * if (val < 0) + * goto out; + * + * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, + * UG3105_CTRL1_RESET_COULOMB_CNT); + * + * chip->total_coulomb_count += (s16)val; + * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n", + * (s16)val, chip->total_coulomb_count); + * } + * * 2. Reset total_coulomb_count val to 0 when the battery is as good as empty * and remember that we did this (and clear the flag for this on susp/resume) * 3. When the battery is full check if the flag that we set total_coulomb_count @@ -31,24 +46,16 @@ * has shown that an estimated 7404mWh increase of the battery's energy results * in a total_coulomb_count increase of 3277 units with a 5 milli-ohm sense R. * - * Copyright (C) 2021 Hans de Goede <hdegoede@redhat.com> + * Copyright (C) 2021 - 2025 Hans de Goede <hansg@kernel.org> */ -#include <linux/devm-helpers.h> #include <linux/module.h> -#include <linux/mutex.h> #include <linux/slab.h> #include <linux/i2c.h> #include <linux/mod_devicetable.h> #include <linux/power_supply.h> -#include <linux/workqueue.h> - -#define UG3105_MOV_AVG_WINDOW 8 -#define UG3105_INIT_POLL_TIME (5 * HZ) -#define UG3105_POLL_TIME (30 * HZ) -#define UG3105_SETTLE_TIME (1 * HZ) -#define UG3105_INIT_POLL_COUNT 30 +#include "adc-battery-helper.h" #define UG3105_REG_MODE 0x00 #define UG3105_REG_CTRL1 0x01 @@ -61,33 +68,13 @@ #define UG3105_CTRL1_RESET_COULOMB_CNT 0x03 -#define UG3105_CURR_HYST_UA 65000 - -#define UG3105_LOW_BAT_UV 3700000 -#define UG3105_FULL_BAT_HYST_UV 38000 - struct ug3105_chip { + /* Must be the first member see adc-battery-helper documentation */ + struct adc_battery_helper helper; struct i2c_client *client; struct power_supply *psy; - struct power_supply_battery_info *info; - struct delayed_work work; - struct mutex lock; - int ocv[UG3105_MOV_AVG_WINDOW]; /* micro-volt */ - int intern_res[UG3105_MOV_AVG_WINDOW]; /* milli-ohm */ - int poll_count; - int ocv_avg_index; - int ocv_avg; /* micro-volt */ - int intern_res_poll_count; - int intern_res_avg_index; - int intern_res_avg; /* milli-ohm */ - int volt; /* micro-volt */ - int curr; /* micro-ampere */ - int total_coulomb_count; int uv_per_unit; int ua_per_unit; - int status; - int capacity; - bool supplied; }; static int ug3105_read_word(struct i2c_client *client, u8 reg) @@ -101,280 +88,43 @@ static int ug3105_read_word(struct i2c_client *client, u8 reg) return val; } -static int ug3105_get_status(struct ug3105_chip *chip) -{ - int full = chip->info->constant_charge_voltage_max_uv - UG3105_FULL_BAT_HYST_UV; - - if (chip->curr > UG3105_CURR_HYST_UA) - return POWER_SUPPLY_STATUS_CHARGING; - - if (chip->curr < -UG3105_CURR_HYST_UA) - return POWER_SUPPLY_STATUS_DISCHARGING; - - if (chip->supplied && chip->ocv_avg > full) - return POWER_SUPPLY_STATUS_FULL; - - return POWER_SUPPLY_STATUS_NOT_CHARGING; -} - -static int ug3105_get_capacity(struct ug3105_chip *chip) -{ - /* - * OCV voltages in uV for 0-110% in 5% increments, the 100-110% is - * for LiPo HV (High-Voltage) bateries which can go up to 4.35V - * instead of the usual 4.2V. - */ - static const int ocv_capacity_tbl[23] = { - 3350000, - 3610000, - 3690000, - 3710000, - 3730000, - 3750000, - 3770000, - 3786667, - 3803333, - 3820000, - 3836667, - 3853333, - 3870000, - 3907500, - 3945000, - 3982500, - 4020000, - 4075000, - 4110000, - 4150000, - 4200000, - 4250000, - 4300000, - }; - int i, ocv_diff, ocv_step; - - if (chip->ocv_avg < ocv_capacity_tbl[0]) - return 0; - - if (chip->status == POWER_SUPPLY_STATUS_FULL) - return 100; - - for (i = 1; i < ARRAY_SIZE(ocv_capacity_tbl); i++) { - if (chip->ocv_avg > ocv_capacity_tbl[i]) - continue; - - ocv_diff = ocv_capacity_tbl[i] - chip->ocv_avg; - ocv_step = ocv_capacity_tbl[i] - ocv_capacity_tbl[i - 1]; - /* scale 0-110% down to 0-100% for LiPo HV */ - if (chip->info->constant_charge_voltage_max_uv >= 4300000) - return (i * 500 - ocv_diff * 500 / ocv_step) / 110; - else - return i * 5 - ocv_diff * 5 / ocv_step; - } - - return 100; -} - -static void ug3105_work(struct work_struct *work) -{ - struct ug3105_chip *chip = container_of(work, struct ug3105_chip, - work.work); - int i, val, curr_diff, volt_diff, res, win_size; - bool prev_supplied = chip->supplied; - int prev_status = chip->status; - int prev_volt = chip->volt; - int prev_curr = chip->curr; - struct power_supply *psy; - - mutex_lock(&chip->lock); - - psy = chip->psy; - if (!psy) - goto out; - - val = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); - if (val < 0) - goto out; - chip->volt = val * chip->uv_per_unit; - - val = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); - if (val < 0) - goto out; - chip->curr = (s16)val * chip->ua_per_unit; - - chip->ocv[chip->ocv_avg_index] = - chip->volt - chip->curr * chip->intern_res_avg / 1000; - chip->ocv_avg_index = (chip->ocv_avg_index + 1) % UG3105_MOV_AVG_WINDOW; - chip->poll_count++; - - /* - * See possible improvements comment above. - * - * Read + reset coulomb counter every 10 polls (every 300 seconds) - * if ((chip->poll_count % 10) == 0) { - * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT); - * if (val < 0) - * goto out; - * - * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, - * UG3105_CTRL1_RESET_COULOMB_CNT); - * - * chip->total_coulomb_count += (s16)val; - * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n", - * (s16)val, chip->total_coulomb_count); - * } - */ - - chip->ocv_avg = 0; - win_size = min(chip->poll_count, UG3105_MOV_AVG_WINDOW); - for (i = 0; i < win_size; i++) - chip->ocv_avg += chip->ocv[i]; - chip->ocv_avg /= win_size; - - chip->supplied = power_supply_am_i_supplied(psy); - chip->status = ug3105_get_status(chip); - chip->capacity = ug3105_get_capacity(chip); - - /* - * Skip internal resistance calc on charger [un]plug and - * when the battery is almost empty (voltage low). - */ - if (chip->supplied != prev_supplied || - chip->volt < UG3105_LOW_BAT_UV || - chip->poll_count < 2) - goto out; - - /* - * Assuming that the OCV voltage does not change significantly - * between 2 polls, then we can calculate the internal resistance - * on a significant current change by attributing all voltage - * change between the 2 readings to the internal resistance. - */ - curr_diff = abs(chip->curr - prev_curr); - if (curr_diff < UG3105_CURR_HYST_UA) - goto out; - - volt_diff = abs(chip->volt - prev_volt); - res = volt_diff * 1000 / curr_diff; - - if ((res < (chip->intern_res_avg * 2 / 3)) || - (res > (chip->intern_res_avg * 4 / 3))) { - dev_dbg(&chip->client->dev, "Ignoring outlier internal resistance %d mOhm\n", res); - goto out; - } - - dev_dbg(&chip->client->dev, "Internal resistance %d mOhm\n", res); - - chip->intern_res[chip->intern_res_avg_index] = res; - chip->intern_res_avg_index = (chip->intern_res_avg_index + 1) % UG3105_MOV_AVG_WINDOW; - chip->intern_res_poll_count++; - - chip->intern_res_avg = 0; - win_size = min(chip->intern_res_poll_count, UG3105_MOV_AVG_WINDOW); - for (i = 0; i < win_size; i++) - chip->intern_res_avg += chip->intern_res[i]; - chip->intern_res_avg /= win_size; - -out: - mutex_unlock(&chip->lock); - - queue_delayed_work(system_wq, &chip->work, - (chip->poll_count <= UG3105_INIT_POLL_COUNT) ? - UG3105_INIT_POLL_TIME : UG3105_POLL_TIME); - - if (chip->status != prev_status && psy) - power_supply_changed(psy); -} - -static enum power_supply_property ug3105_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_SCOPE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_OCV, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CAPACITY, -}; - -static int ug3105_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) +static int ug3105_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr) { struct ug3105_chip *chip = power_supply_get_drvdata(psy); - int ret = 0; - - mutex_lock(&chip->lock); + int ret; - if (!chip->psy) { - ret = -EAGAIN; - goto out; - } + ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); + if (ret < 0) + return ret; - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = chip->status; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = 1; - break; - case POWER_SUPPLY_PROP_SCOPE: - val->intval = POWER_SUPPLY_SCOPE_SYSTEM; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); - if (ret < 0) - break; - val->intval = ret * chip->uv_per_unit; - ret = 0; - break; - case POWER_SUPPLY_PROP_VOLTAGE_OCV: - val->intval = chip->ocv_avg; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); - if (ret < 0) - break; - val->intval = (s16)ret * chip->ua_per_unit; - ret = 0; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = chip->capacity; - break; - default: - ret = -EINVAL; - } + *volt = ret * chip->uv_per_unit; -out: - mutex_unlock(&chip->lock); - return ret; -} - -static void ug3105_external_power_changed(struct power_supply *psy) -{ - struct ug3105_chip *chip = power_supply_get_drvdata(psy); + ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); + if (ret < 0) + return ret; - dev_dbg(&chip->client->dev, "external power changed\n"); - mod_delayed_work(system_wq, &chip->work, UG3105_SETTLE_TIME); + *curr = (s16)ret * chip->ua_per_unit; + return 0; } static const struct power_supply_desc ug3105_psy_desc = { .name = "ug3105_battery", .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = ug3105_get_property, - .external_power_changed = ug3105_external_power_changed, - .properties = ug3105_battery_props, - .num_properties = ARRAY_SIZE(ug3105_battery_props), + .get_property = adc_battery_helper_get_property, + .external_power_changed = adc_battery_helper_external_power_changed, + .properties = adc_battery_helper_properties, + .num_properties = ADC_HELPER_NUM_PROPERTIES, }; -static void ug3105_init(struct ug3105_chip *chip) +static void ug3105_start(struct i2c_client *client) +{ + i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_RUN); + i2c_smbus_write_byte_data(client, UG3105_REG_CTRL1, UG3105_CTRL1_RESET_COULOMB_CNT); +} + +static void ug3105_stop(struct i2c_client *client) { - chip->poll_count = 0; - chip->ocv_avg_index = 0; - chip->total_coulomb_count = 0; - i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE, - UG3105_MODE_RUN); - i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, - UG3105_CTRL1_RESET_COULOMB_CNT); - queue_delayed_work(system_wq, &chip->work, 0); - flush_delayed_work(&chip->work); + i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_STANDBY); } static int ug3105_probe(struct i2c_client *client) @@ -382,7 +132,6 @@ static int ug3105_probe(struct i2c_client *client) struct power_supply_config psy_cfg = {}; struct device *dev = &client->dev; u32 curr_sense_res_uohm = 10000; - struct power_supply *psy; struct ug3105_chip *chip; int ret; @@ -391,25 +140,8 @@ static int ug3105_probe(struct i2c_client *client) return -ENOMEM; chip->client = client; - mutex_init(&chip->lock); - ret = devm_delayed_work_autocancel(dev, &chip->work, ug3105_work); - if (ret) - return ret; - - psy_cfg.drv_data = chip; - psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg); - if (IS_ERR(psy)) - return PTR_ERR(psy); - ret = power_supply_get_battery_info(psy, &chip->info); - if (ret) - return ret; - - if (chip->info->factory_internal_resistance_uohm == -EINVAL || - chip->info->constant_charge_voltage_max_uv == -EINVAL) { - dev_err(dev, "error required properties are missing\n"); - return -ENODEV; - } + ug3105_start(client); device_property_read_u32(dev, "upisemi,rsns-microohm", &curr_sense_res_uohm); @@ -417,35 +149,36 @@ static int ug3105_probe(struct i2c_client *client) * DAC maximum is 4.5V divided by 65536 steps + an unknown factor of 10 * coming from somewhere for some reason (verified with a volt-meter). */ - chip->uv_per_unit = 45000000/65536; + chip->uv_per_unit = 45000000 / 65536; /* Datasheet says 8.1 uV per unit for the current ADC */ chip->ua_per_unit = 8100000 / curr_sense_res_uohm; - /* Use provided internal resistance as start point (in milli-ohm) */ - chip->intern_res_avg = chip->info->factory_internal_resistance_uohm / 1000; - /* Also add it to the internal resistance moving average window */ - chip->intern_res[0] = chip->intern_res_avg; - chip->intern_res_avg_index = 1; - chip->intern_res_poll_count = 1; - - mutex_lock(&chip->lock); - chip->psy = psy; - mutex_unlock(&chip->lock); + psy_cfg.drv_data = chip; + chip->psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg); + if (IS_ERR(chip->psy)) { + ret = PTR_ERR(chip->psy); + goto stop; + } - ug3105_init(chip); + ret = adc_battery_helper_init(&chip->helper, chip->psy, + ug3105_get_voltage_and_current_now, NULL); + if (ret) + goto stop; i2c_set_clientdata(client, chip); return 0; + +stop: + ug3105_stop(client); + return ret; } static int __maybe_unused ug3105_suspend(struct device *dev) { struct ug3105_chip *chip = dev_get_drvdata(dev); - cancel_delayed_work_sync(&chip->work); - i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE, - UG3105_MODE_STANDBY); - + adc_battery_helper_suspend(dev); + ug3105_stop(chip->client); return 0; } @@ -453,8 +186,8 @@ static int __maybe_unused ug3105_resume(struct device *dev) { struct ug3105_chip *chip = dev_get_drvdata(dev); - ug3105_init(chip); - + ug3105_start(chip->client); + adc_battery_helper_resume(dev); return 0; } @@ -473,10 +206,12 @@ static struct i2c_driver ug3105_i2c_driver = { .pm = &ug3105_pm_ops, }, .probe = ug3105_probe, + .remove = ug3105_stop, + .shutdown = ug3105_stop, .id_table = ug3105_id, }; module_i2c_driver(ug3105_i2c_driver); -MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com"); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org"); MODULE_DESCRIPTION("uPI uG3105 battery monitor driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/wm831x_power.c b/drivers/power/supply/wm831x_power.c index 538055b29dec..78fa0573ef25 100644 --- a/drivers/power/supply/wm831x_power.c +++ b/drivers/power/supply/wm831x_power.c @@ -89,7 +89,7 @@ static int wm831x_wall_get_prop(struct power_supply *psy, return ret; } -static enum power_supply_property wm831x_wall_props[] = { +static const enum power_supply_property wm831x_wall_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_NOW, }; @@ -120,7 +120,7 @@ static int wm831x_usb_get_prop(struct power_supply *psy, return ret; } -static enum power_supply_property wm831x_usb_props[] = { +static const enum power_supply_property wm831x_usb_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_NOW, }; @@ -144,6 +144,7 @@ static int wm831x_usb_limit_change(struct notifier_block *nb, struct wm831x_power, usb_notify); unsigned int i, best; + int ret; /* Find the highest supported limit */ best = 0; @@ -156,8 +157,13 @@ static int wm831x_usb_limit_change(struct notifier_block *nb, dev_dbg(wm831x_power->wm831x->dev, "Limiting USB current to %umA", wm831x_usb_limits[best]); - wm831x_set_bits(wm831x_power->wm831x, WM831X_POWER_STATE, - WM831X_USB_ILIM_MASK, best); + ret = wm831x_set_bits(wm831x_power->wm831x, WM831X_POWER_STATE, + WM831X_USB_ILIM_MASK, best); + if (ret < 0) { + dev_err(wm831x_power->wm831x->dev, + "Failed to set USB current limit: %d\n", ret); + return ret; + } return 0; } @@ -171,21 +177,21 @@ struct chg_map { int reg_val; }; -static struct chg_map trickle_ilims[] = { +static const struct chg_map trickle_ilims[] = { { 50, 0 << WM831X_CHG_TRKL_ILIM_SHIFT }, { 100, 1 << WM831X_CHG_TRKL_ILIM_SHIFT }, { 150, 2 << WM831X_CHG_TRKL_ILIM_SHIFT }, { 200, 3 << WM831X_CHG_TRKL_ILIM_SHIFT }, }; -static struct chg_map vsels[] = { +static const struct chg_map vsels[] = { { 4050, 0 << WM831X_CHG_VSEL_SHIFT }, { 4100, 1 << WM831X_CHG_VSEL_SHIFT }, { 4150, 2 << WM831X_CHG_VSEL_SHIFT }, { 4200, 3 << WM831X_CHG_VSEL_SHIFT }, }; -static struct chg_map fast_ilims[] = { +static const struct chg_map fast_ilims[] = { { 0, 0 << WM831X_CHG_FAST_ILIM_SHIFT }, { 50, 1 << WM831X_CHG_FAST_ILIM_SHIFT }, { 100, 2 << WM831X_CHG_FAST_ILIM_SHIFT }, @@ -204,7 +210,7 @@ static struct chg_map fast_ilims[] = { { 1000, 15 << WM831X_CHG_FAST_ILIM_SHIFT }, }; -static struct chg_map eoc_iterms[] = { +static const struct chg_map eoc_iterms[] = { { 20, 0 << WM831X_CHG_ITERM_SHIFT }, { 30, 1 << WM831X_CHG_ITERM_SHIFT }, { 40, 2 << WM831X_CHG_ITERM_SHIFT }, @@ -215,7 +221,7 @@ static struct chg_map eoc_iterms[] = { { 90, 7 << WM831X_CHG_ITERM_SHIFT }, }; -static struct chg_map chg_times[] = { +static const struct chg_map chg_times[] = { { 60, 0 << WM831X_CHG_TIME_SHIFT }, { 90, 1 << WM831X_CHG_TIME_SHIFT }, { 120, 2 << WM831X_CHG_TIME_SHIFT }, @@ -235,7 +241,7 @@ static struct chg_map chg_times[] = { }; static void wm831x_battery_apply_config(struct wm831x *wm831x, - struct chg_map *map, int count, int val, + const struct chg_map *map, int count, int val, int *reg, const char *name, const char *units) { @@ -462,7 +468,7 @@ static int wm831x_bat_get_prop(struct power_supply *psy, return ret; } -static enum power_supply_property wm831x_bat_props[] = { +static const enum power_supply_property wm831x_bat_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_NOW, @@ -470,7 +476,7 @@ static enum power_supply_property wm831x_bat_props[] = { POWER_SUPPLY_PROP_CHARGE_TYPE, }; -static const char *wm831x_bat_irqs[] = { +static const char * const wm831x_bat_irqs[] = { "BATT HOT", "BATT COLD", "BATT FAIL", diff --git a/drivers/power/supply/wm97xx_battery.c b/drivers/power/supply/wm97xx_battery.c index b3b0c37a9dd2..a6c55b1e0286 100644 --- a/drivers/power/supply/wm97xx_battery.c +++ b/drivers/power/supply/wm97xx_battery.c @@ -178,12 +178,6 @@ static int wm97xx_bat_probe(struct platform_device *dev) "failed to get charge GPIO\n"); if (charge_gpiod) { gpiod_set_consumer_name(charge_gpiod, "BATT CHRG"); - ret = request_irq(gpiod_to_irq(charge_gpiod), - wm97xx_chrg_irq, 0, - "AC Detect", dev); - if (ret) - return dev_err_probe(&dev->dev, ret, - "failed to request GPIO irq\n"); props++; /* POWER_SUPPLY_PROP_STATUS */ } @@ -198,11 +192,9 @@ static int wm97xx_bat_probe(struct platform_device *dev) if (pdata->min_voltage >= 0) props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ - prop = kcalloc(props, sizeof(*prop), GFP_KERNEL); - if (!prop) { - ret = -ENOMEM; - goto err3; - } + prop = devm_kcalloc(&dev->dev, props, sizeof(*prop), GFP_KERNEL); + if (!prop) + return -ENOMEM; prop[i++] = POWER_SUPPLY_PROP_PRESENT; if (charge_gpiod) @@ -231,21 +223,21 @@ static int wm97xx_bat_probe(struct platform_device *dev) bat_psy_desc.properties = prop; bat_psy_desc.num_properties = props; - bat_psy = power_supply_register(&dev->dev, &bat_psy_desc, &cfg); - if (!IS_ERR(bat_psy)) { - schedule_work(&bat_work); - } else { - ret = PTR_ERR(bat_psy); - goto err4; + bat_psy = devm_power_supply_register(&dev->dev, &bat_psy_desc, &cfg); + if (IS_ERR(bat_psy)) + return PTR_ERR(bat_psy); + + schedule_work(&bat_work); + + if (charge_gpiod) { + ret = request_irq(gpiod_to_irq(charge_gpiod), wm97xx_chrg_irq, + 0, "AC Detect", dev); + if (ret) + return dev_err_probe(&dev->dev, ret, + "failed to request GPIO irq\n"); } return 0; -err4: - kfree(prop); -err3: - if (charge_gpiod) - free_irq(gpiod_to_irq(charge_gpiod), dev); - return ret; } static void wm97xx_bat_remove(struct platform_device *dev) @@ -253,8 +245,6 @@ static void wm97xx_bat_remove(struct platform_device *dev) if (charge_gpiod) free_irq(gpiod_to_irq(charge_gpiod), dev); cancel_work_sync(&bat_work); - power_supply_unregister(bat_psy); - kfree(prop); } static struct platform_driver wm97xx_bat_driver = { |
