diff options
Diffstat (limited to 'drivers/power')
181 files changed, 25107 insertions, 6427 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 696bf77a7042..9a8e44ca9ae4 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only source "drivers/power/reset/Kconfig" +source "drivers/power/sequencing/Kconfig" source "drivers/power/supply/Kconfig" diff --git a/drivers/power/Makefile b/drivers/power/Makefile index effbf0377f32..962a2cd30a51 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_POWER_RESET) += reset/ +obj-$(CONFIG_POWER_SEQUENCING) += sequencing/ obj-$(CONFIG_POWER_SUPPLY) += supply/ diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index a8c46ba5878f..f6c1bcbb57de 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -26,7 +26,7 @@ config POWER_RESET_AT91_POWEROFF config POWER_RESET_AT91_RESET tristate "Atmel AT91 reset driver" depends on ARCH_AT91 - default SOC_AT91SAM9 || SOC_SAM9X60 || SOC_SAMA5 + default SOC_AT91SAM9 || SOC_SAM9X60 || SOC_SAM9X7 || SOC_SAMA5 help This driver supports restart for Atmel AT91SAM9 and SAMA5 SoCs @@ -34,7 +34,7 @@ config POWER_RESET_AT91_RESET config POWER_RESET_AT91_SAMA5D2_SHDWC tristate "Atmel AT91 SAMA5D2-Compatible shutdown controller driver" depends on ARCH_AT91 - default SOC_SAM9X60 || SOC_SAMA5 + default SOC_SAM9X60 || SOC_SAM9X7 || SOC_SAMA5 help This driver supports the alternate shutdown controller for some Atmel SAMA5 SoCs. It is present for example on SAMA5D2 SoC. @@ -66,7 +66,7 @@ config POWER_RESET_BRCMKONA config POWER_RESET_BRCMSTB bool "Broadcom STB reset driver" - depends on ARM || ARM64 || MIPS || COMPILE_TEST + depends on ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST depends on MFD_SYSCON default ARCH_BRCMSTB || BMIPS_GENERIC help @@ -75,6 +75,17 @@ config POWER_RESET_BRCMSTB Say Y here if you have a Broadcom STB board and you wish to have restart support. +config POWER_RESET_EP93XX + bool "Cirrus EP93XX reset driver" if COMPILE_TEST + depends on MFD_SYSCON + default ARCH_EP93XX + select AUXILIARY_BUS + help + This driver provides restart support for Cirrus EP93XX SoC. + + Say Y here if you have a Cirrus EP93XX SoC and you wish + to have restart support. + config POWER_RESET_GEMINI_POWEROFF bool "Cortina Gemini power-off driver" depends on ARCH_GEMINI || COMPILE_TEST @@ -117,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 @@ -141,16 +161,17 @@ config POWER_RESET_OCELOT_RESET help This driver supports restart for Microsemi Ocelot SoC and similar. -config POWER_RESET_OXNAS - bool "OXNAS SoC restart driver" - depends on ARCH_OXNAS - default MACH_OX820 +config POWER_RESET_ODROID_GO_ULTRA_POWEROFF + bool "Odroid Go Ultra power-off driver" + depends on ARCH_MESON || COMPILE_TEST + depends on I2C=y && OF help - Restart support for OXNAS/PLXTECH OX820 SoC. + This driver supports Power off for Odroid Go Ultra device. config POWER_RESET_PIIX4_POWEROFF tristate "Intel PIIX4 power-off driver" depends on PCI + depends on HAS_IOPORT depends on MIPS || COMPILE_TEST help This driver supports powering off a system using the Intel PIIX4 @@ -204,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 @@ -241,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 @@ -299,7 +350,7 @@ config NVMEM_REBOOT_MODE config POWER_MLXBF tristate "Mellanox BlueField power handling driver" - depends on (GPIO_MLXBF2 && ACPI) + depends on (GPIO_MLXBF2 || GPIO_MLXBF3) && ACPI help This driver supports reset or low power mode handling for Mellanox BlueField. diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 0a39424fc558..0e4ae6f6b5c5 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -7,22 +7,27 @@ obj-$(CONFIG_POWER_RESET_ATC260X) += atc260x-poweroff.o obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o obj-$(CONFIG_POWER_RESET_BRCMKONA) += brcm-kona-reset.o obj-$(CONFIG_POWER_RESET_BRCMSTB) += brcmstb-reboot.o +obj-$(CONFIG_POWER_RESET_EP93XX) += ep93xx-restart.o obj-$(CONFIG_POWER_RESET_GEMINI_POWEROFF) += gemini-poweroff.o 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_OXNAS) += oxnas-restart.o obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o obj-$(CONFIG_POWER_RESET_OCELOT_RESET) += ocelot-reset.o +obj-$(CONFIG_POWER_RESET_ODROID_GO_ULTRA_POWEROFF) += odroid-go-ultra-poweroff.o obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o 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 diff --git a/drivers/power/reset/as3722-poweroff.c b/drivers/power/reset/as3722-poweroff.c index 661e1c67f82e..8075382cbc36 100644 --- a/drivers/power/reset/as3722-poweroff.c +++ b/drivers/power/reset/as3722-poweroff.c @@ -10,8 +10,8 @@ #include <linux/mfd/as3722.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_device.h> +#include <linux/reboot.h> #include <linux/slab.h> struct as3722_poweroff { @@ -19,22 +19,18 @@ struct as3722_poweroff { struct as3722 *as3722; }; -static struct as3722_poweroff *as3722_pm_poweroff; - -static void as3722_pm_power_off(void) +static int as3722_pm_power_off(struct sys_off_data *data) { + struct as3722_poweroff *as3722_pm_poweroff = data->cb_data; int ret; - if (!as3722_pm_poweroff) { - pr_err("AS3722 poweroff is not initialised\n"); - return; - } - ret = as3722_update_bits(as3722_pm_poweroff->as3722, AS3722_RESET_CONTROL_REG, AS3722_POWER_OFF, AS3722_POWER_OFF); if (ret < 0) dev_err(as3722_pm_poweroff->dev, "RESET_CONTROL_REG update failed, %d\n", ret); + + return NOTIFY_DONE; } static int as3722_poweroff_probe(struct platform_device *pdev) @@ -55,20 +51,12 @@ static int as3722_poweroff_probe(struct platform_device *pdev) as3722_poweroff->as3722 = dev_get_drvdata(pdev->dev.parent); as3722_poweroff->dev = &pdev->dev; - as3722_pm_poweroff = as3722_poweroff; - if (!pm_power_off) - pm_power_off = as3722_pm_power_off; - - return 0; -} - -static int as3722_poweroff_remove(struct platform_device *pdev) -{ - if (pm_power_off == as3722_pm_power_off) - pm_power_off = NULL; - as3722_pm_poweroff = NULL; - return 0; + return devm_register_sys_off_handler(as3722_poweroff->dev, + SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, + as3722_pm_power_off, + as3722_poweroff); } static struct platform_driver as3722_poweroff_driver = { @@ -76,7 +64,6 @@ static struct platform_driver as3722_poweroff_driver = { .name = "as3722-power-off", }, .probe = as3722_poweroff_probe, - .remove = as3722_poweroff_remove, }; module_platform_driver(as3722_poweroff_driver); @@ -84,4 +71,3 @@ module_platform_driver(as3722_poweroff_driver); MODULE_DESCRIPTION("Power off driver for ams AS3722 PMIC Device"); MODULE_ALIAS("platform:as3722-power-off"); MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/at91-poweroff.c b/drivers/power/reset/at91-poweroff.c index 9e74e131c675..7bc779055cf3 100644 --- a/drivers/power/reset/at91-poweroff.c +++ b/drivers/power/reset/at91-poweroff.c @@ -57,7 +57,7 @@ static struct shdwc { void __iomem *mpddrc_base; } at91_shdwc; -static void __init at91_wakeup_status(struct platform_device *pdev) +static void at91_wakeup_status(struct platform_device *pdev) { const char *reason; u32 reg = readl(at91_shdwc.shdwc_base + AT91_SHDW_SR); @@ -149,15 +149,13 @@ static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev) writel(wakeup_mode | mode, at91_shdwc.shdwc_base + AT91_SHDW_MR); } -static int __init at91_poweroff_probe(struct platform_device *pdev) +static int at91_poweroff_probe(struct platform_device *pdev) { - struct resource *res; struct device_node *np; u32 ddr_type; int ret; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - at91_shdwc.shdwc_base = devm_ioremap_resource(&pdev->dev, res); + at91_shdwc.shdwc_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(at91_shdwc.shdwc_base)) return PTR_ERR(at91_shdwc.shdwc_base); @@ -204,7 +202,7 @@ clk_disable: return ret; } -static int __exit at91_poweroff_remove(struct platform_device *pdev) +static void at91_poweroff_remove(struct platform_device *pdev) { if (pm_power_off == at91_poweroff) pm_power_off = NULL; @@ -213,8 +211,6 @@ static int __exit at91_poweroff_remove(struct platform_device *pdev) iounmap(at91_shdwc.mpddrc_base); clk_disable_unprepare(at91_shdwc.sclk); - - return 0; } static const struct of_device_id at91_poweroff_of_match[] = { @@ -226,13 +222,14 @@ static const struct of_device_id at91_poweroff_of_match[] = { MODULE_DEVICE_TABLE(of, at91_poweroff_of_match); static struct platform_driver at91_poweroff_driver = { - .remove = __exit_p(at91_poweroff_remove), + .probe = at91_poweroff_probe, + .remove = at91_poweroff_remove, .driver = { .name = "at91-poweroff", .of_match_table = at91_poweroff_of_match, }, }; -module_platform_driver_probe(at91_poweroff_driver, at91_poweroff_probe); +module_platform_driver(at91_poweroff_driver); MODULE_AUTHOR("Atmel Corporation"); MODULE_DESCRIPTION("Shutdown driver for Atmel SoCs"); diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c index 741e44a017c3..511f5a8f8961 100644 --- a/drivers/power/reset/at91-reset.c +++ b/drivers/power/reset/at91-reset.c @@ -18,6 +18,7 @@ #include <linux/platform_device.h> #include <linux/reboot.h> #include <linux/reset-controller.h> +#include <linux/power/power_on_reason.h> #include <soc/at91/at91sam9_ddrsdr.h> #include <soc/at91/at91sam9_sdramc.h> @@ -128,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" : @@ -144,50 +144,59 @@ 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; } -static void __init at91_reset_status(struct platform_device *pdev, - void __iomem *base) +static const char *at91_reset_reason(struct at91_reset *reset) { + u32 reg = readl(reset->rstc_base + AT91_RSTC_SR); const char *reason; - u32 reg = readl(base + AT91_RSTC_SR); switch ((reg & AT91_RSTC_RSTTYP) >> 8) { case RESET_TYPE_GENERAL: - reason = "general reset"; + reason = POWER_ON_REASON_REGULAR; break; case RESET_TYPE_WAKEUP: - reason = "wakeup"; + reason = POWER_ON_REASON_RTC; break; case RESET_TYPE_WATCHDOG: - reason = "watchdog reset"; + reason = POWER_ON_REASON_WATCHDOG; break; case RESET_TYPE_SOFTWARE: - reason = "software reset"; + reason = POWER_ON_REASON_SOFTWARE; break; case RESET_TYPE_USER: - reason = "user reset"; + reason = POWER_ON_REASON_RST_BTN; break; case RESET_TYPE_CPU_FAIL: - reason = "CPU clock failure detection"; + reason = POWER_ON_REASON_CPU_CLK_FAIL; break; case RESET_TYPE_XTAL_FAIL: - reason = "32.768 kHz crystal failure detection"; + reason = POWER_ON_REASON_XTAL_FAIL; break; case RESET_TYPE_ULP2: - reason = "ULP2 reset"; + reason = POWER_ON_REASON_BROWN_OUT; break; default: - reason = "unknown reset"; + reason = POWER_ON_REASON_UNKNOWN; break; } - dev_info(&pdev->dev, "Starting after %s\n", reason); + return reason; } +static ssize_t power_on_reason_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct at91_reset *reset = platform_get_drvdata(pdev); + + return sprintf(buf, "%s\n", at91_reset_reason(reset)); +} +static DEVICE_ATTR_RO(power_on_reason); + static const struct of_device_id at91_ramc_of_match[] = { { .compatible = "atmel,at91sam9260-sdramc", @@ -327,7 +336,7 @@ static int at91_rcdev_init(struct at91_reset *reset, return devm_reset_controller_register(&pdev->dev, &reset->rcdev); } -static int __init at91_reset_probe(struct platform_device *pdev) +static int at91_reset_probe(struct platform_device *pdev) { const struct of_device_id *match; struct at91_reset *reset; @@ -392,7 +401,13 @@ static int __init at91_reset_probe(struct platform_device *pdev) if (ret) goto disable_clk; - at91_reset_status(pdev, reset->rstc_base); + ret = device_create_file(&pdev->dev, &dev_attr_power_on_reason); + if (ret) { + dev_err(&pdev->dev, "Could not create sysfs entry\n"); + return ret; + } + + dev_info(&pdev->dev, "Starting after %s\n", at91_reset_reason(reset)); return 0; @@ -401,24 +416,23 @@ disable_clk: return ret; } -static int __exit at91_reset_remove(struct platform_device *pdev) +static void at91_reset_remove(struct platform_device *pdev) { struct at91_reset *reset = platform_get_drvdata(pdev); unregister_restart_handler(&reset->nb); clk_disable_unprepare(reset->sclk); - - return 0; } static struct platform_driver at91_reset_driver = { - .remove = __exit_p(at91_reset_remove), + .probe = at91_reset_probe, + .remove = at91_reset_remove, .driver = { .name = "at91-reset", .of_match_table = at91_reset_of_match, }, }; -module_platform_driver_probe(at91_reset_driver, at91_reset_probe); +module_platform_driver(at91_reset_driver); MODULE_AUTHOR("Atmel Corporation"); MODULE_DESCRIPTION("Reset driver for Atmel SoCs"); diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index d8ecffe72f16..ecf15694f925 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -107,7 +107,7 @@ static const unsigned long long sdwc_dbc_period[] = { 0, 3, 32, 512, 4096, 32768, }; -static void __init at91_wakeup_status(struct platform_device *pdev) +static void at91_wakeup_status(struct platform_device *pdev) { struct shdwc *shdw = platform_get_drvdata(pdev); const struct reg_config *rcfg = shdw->rcfg; @@ -129,7 +129,7 @@ static void __init 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) @@ -326,12 +326,13 @@ static const struct of_device_id at91_pmc_ids[] = { { .compatible = "atmel,sama5d2-pmc" }, { .compatible = "microchip,sam9x60-pmc" }, { .compatible = "microchip,sama7g5-pmc" }, + { .compatible = "microchip,sam9x7-pmc" }, + { .compatible = "microchip,sama7d65-pmc" }, { /* Sentinel. */ } }; -static int __init at91_shdwc_probe(struct platform_device *pdev) +static int at91_shdwc_probe(struct platform_device *pdev) { - struct resource *res; const struct of_device_id *match; struct device_node *np; u32 ddr_type; @@ -349,8 +350,7 @@ static int __init at91_shdwc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, at91_shdwc); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - at91_shdwc->shdwc_base = devm_ioremap_resource(&pdev->dev, res); + at91_shdwc->shdwc_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(at91_shdwc->shdwc_base)) return PTR_ERR(at91_shdwc->shdwc_base); @@ -423,7 +423,7 @@ clk_disable: return ret; } -static int __exit at91_shdwc_remove(struct platform_device *pdev) +static void at91_shdwc_remove(struct platform_device *pdev) { struct shdwc *shdw = platform_get_drvdata(pdev); @@ -439,18 +439,17 @@ static int __exit at91_shdwc_remove(struct platform_device *pdev) iounmap(shdw->pmc_base); clk_disable_unprepare(shdw->sclk); - - return 0; } static struct platform_driver at91_shdwc_driver = { - .remove = __exit_p(at91_shdwc_remove), + .probe = at91_shdwc_probe, + .remove = at91_shdwc_remove, .driver = { .name = "at91-shdwc", .of_match_table = at91_shdwc_of_match, }, }; -module_platform_driver_probe(at91_shdwc_driver, at91_shdwc_probe); +module_platform_driver(at91_shdwc_driver); MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>"); MODULE_DESCRIPTION("Atmel shutdown controller driver"); diff --git a/drivers/power/reset/atc260x-poweroff.c b/drivers/power/reset/atc260x-poweroff.c index 98f20251a6d1..e3e4621ccb1d 100644 --- a/drivers/power/reset/atc260x-poweroff.c +++ b/drivers/power/reset/atc260x-poweroff.c @@ -16,13 +16,9 @@ struct atc260x_pwrc { struct device *dev; struct regmap *regmap; - struct notifier_block restart_nb; int (*do_poweroff)(const struct atc260x_pwrc *pwrc, bool restart); }; -/* Global variable needed only for pm_power_off */ -static struct atc260x_pwrc *atc260x_pwrc_data; - static int atc2603c_do_poweroff(const struct atc260x_pwrc *pwrc, bool restart) { int ret, deep_sleep = 0; @@ -165,18 +161,20 @@ static int atc2609a_init(const struct atc260x_pwrc *pwrc) return ret; } -static void atc260x_pwrc_pm_handler(void) +static int atc260x_pwrc_pm_handler(struct sys_off_data *data) { - atc260x_pwrc_data->do_poweroff(atc260x_pwrc_data, false); + struct atc260x_pwrc *pwrc = data->cb_data; + + pwrc->do_poweroff(pwrc, false); WARN_ONCE(1, "Unable to power off system\n"); + + return NOTIFY_DONE; } -static int atc260x_pwrc_restart_handler(struct notifier_block *nb, - unsigned long mode, void *cmd) +static int atc260x_pwrc_restart_handler(struct sys_off_data *data) { - struct atc260x_pwrc *pwrc = container_of(nb, struct atc260x_pwrc, - restart_nb); + struct atc260x_pwrc *pwrc = data->cb_data; pwrc->do_poweroff(pwrc, true); return NOTIFY_DONE; @@ -194,8 +192,6 @@ static int atc260x_pwrc_probe(struct platform_device *pdev) priv->dev = &pdev->dev; priv->regmap = atc260x->regmap; - priv->restart_nb.notifier_call = atc260x_pwrc_restart_handler; - priv->restart_nb.priority = 192; switch (atc260x->ic_type) { case ATC2603C: @@ -216,16 +212,20 @@ static int atc260x_pwrc_probe(struct platform_device *pdev) if (ret) return ret; - platform_set_drvdata(pdev, priv); - - if (!pm_power_off) { - atc260x_pwrc_data = priv; - pm_power_off = atc260x_pwrc_pm_handler; - } else { - dev_warn(priv->dev, "Poweroff callback already assigned\n"); - } + ret = devm_register_sys_off_handler(priv->dev, + SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, + atc260x_pwrc_pm_handler, + priv); + if (ret) + dev_err(priv->dev, "failed to register power-off handler: %d\n", + ret); - ret = register_restart_handler(&priv->restart_nb); + ret = devm_register_sys_off_handler(priv->dev, + SYS_OFF_MODE_RESTART, + SYS_OFF_PRIO_HIGH, + atc260x_pwrc_restart_handler, + priv); if (ret) dev_err(priv->dev, "failed to register restart handler: %d\n", ret); @@ -233,23 +233,8 @@ static int atc260x_pwrc_probe(struct platform_device *pdev) return ret; } -static int atc260x_pwrc_remove(struct platform_device *pdev) -{ - struct atc260x_pwrc *priv = platform_get_drvdata(pdev); - - if (atc260x_pwrc_data == priv) { - pm_power_off = NULL; - atc260x_pwrc_data = NULL; - } - - unregister_restart_handler(&priv->restart_nb); - - return 0; -} - static struct platform_driver atc260x_pwrc_driver = { .probe = atc260x_pwrc_probe, - .remove = atc260x_pwrc_remove, .driver = { .name = "atc260x-pwrc", }, diff --git a/drivers/power/reset/axxia-reset.c b/drivers/power/reset/axxia-reset.c index f7b40be5d6b6..797bf6773860 100644 --- a/drivers/power/reset/axxia-reset.c +++ b/drivers/power/reset/axxia-reset.c @@ -26,11 +26,10 @@ #define SC_EFUSE_INT_STATUS 0x180c #define EFUSE_READ_DONE (1<<31) -static struct regmap *syscon; - -static int axxia_restart_handler(struct notifier_block *this, - unsigned long mode, void *cmd) +static int axxia_restart_handler(struct sys_off_data *data) { + struct regmap *syscon = data->cb_data; + /* Access Key (0xab) */ regmap_write(syscon, SC_CRIT_WRITE_KEY, 0xab); /* Select internal boot from 0xffff0000 */ @@ -44,14 +43,10 @@ static int axxia_restart_handler(struct notifier_block *this, return NOTIFY_DONE; } -static struct notifier_block axxia_restart_nb = { - .notifier_call = axxia_restart_handler, - .priority = 128, -}; - static int axxia_reset_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct regmap *syscon; int err; syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon"); @@ -60,7 +55,8 @@ static int axxia_reset_probe(struct platform_device *pdev) return PTR_ERR(syscon); } - err = register_restart_handler(&axxia_restart_nb); + err = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, + 128, axxia_restart_handler, syscon); if (err) dev_err(dev, "cannot register restart handler (err=%d)\n", err); @@ -80,9 +76,4 @@ static struct platform_driver axxia_reset_driver = { .of_match_table = of_match_ptr(of_axxia_reset_match), }, }; - -static int __init axxia_reset_init(void) -{ - return platform_driver_register(&axxia_reset_driver); -} -device_initcall(axxia_reset_init); +builtin_platform_driver(axxia_reset_driver); diff --git a/drivers/power/reset/brcm-kona-reset.c b/drivers/power/reset/brcm-kona-reset.c index 3de024e3ceb7..ee3f1bb97653 100644 --- a/drivers/power/reset/brcm-kona-reset.c +++ b/drivers/power/reset/brcm-kona-reset.c @@ -2,8 +2,8 @@ // Copyright (C) 2016 Broadcom #include <linux/io.h> -#include <linux/of_address.h> -#include <linux/of_platform.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> #include <linux/reboot.h> #define RSTMGR_REG_WR_ACCESS_OFFSET 0 @@ -15,8 +15,7 @@ static void __iomem *kona_reset_base; -static int kona_reset_handler(struct notifier_block *this, - unsigned long mode, void *cmd) +static int kona_reset_handler(struct sys_off_data *data) { /* * A soft reset is triggered by writing a 0 to bit 0 of the soft reset @@ -31,20 +30,14 @@ static int kona_reset_handler(struct notifier_block *this, return NOTIFY_DONE; } -static struct notifier_block kona_reset_nb = { - .notifier_call = kona_reset_handler, - .priority = 128, -}; - static int kona_reset_probe(struct platform_device *pdev) { - struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - kona_reset_base = devm_ioremap_resource(&pdev->dev, res); + kona_reset_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(kona_reset_base)) return PTR_ERR(kona_reset_base); - return register_restart_handler(&kona_reset_nb); + return devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, + 128, kona_reset_handler, NULL); } static const struct of_device_id of_match[] = { diff --git a/drivers/power/reset/brcmstb-reboot.c b/drivers/power/reset/brcmstb-reboot.c index 0f2944dc9355..b9c093f6064c 100644 --- a/drivers/power/reset/brcmstb-reboot.c +++ b/drivers/power/reset/brcmstb-reboot.c @@ -18,9 +18,6 @@ #include <linux/smp.h> #include <linux/mfd/syscon.h> -#define RESET_SOURCE_ENABLE_REG 1 -#define SW_MASTER_RESET_REG 2 - static struct regmap *regmap; static u32 rst_src_en; static u32 sw_mstr_rst; @@ -32,8 +29,7 @@ struct reset_reg_mask { static const struct reset_reg_mask *reset_masks; -static int brcmstb_restart_handler(struct notifier_block *this, - unsigned long mode, void *cmd) +static int brcmstb_restart_handler(struct sys_off_data *data) { int rc; u32 tmp; @@ -62,17 +58,9 @@ static int brcmstb_restart_handler(struct notifier_block *this, return NOTIFY_DONE; } - while (1) - ; - return NOTIFY_DONE; } -static struct notifier_block brcmstb_restart_nb = { - .notifier_call = brcmstb_restart_handler, - .priority = 128, -}; - static const struct reset_reg_mask reset_bits_40nm = { .rst_src_en_mask = BIT(0), .sw_mstr_rst_mask = BIT(0), @@ -83,46 +71,28 @@ static const struct reset_reg_mask reset_bits_65nm = { .sw_mstr_rst_mask = BIT(31), }; -static const struct of_device_id of_match[] = { - { .compatible = "brcm,brcmstb-reboot", .data = &reset_bits_40nm }, - { .compatible = "brcm,bcm7038-reboot", .data = &reset_bits_65nm }, - {}, -}; - static int brcmstb_reboot_probe(struct platform_device *pdev) { int rc; struct device_node *np = pdev->dev.of_node; - const struct of_device_id *of_id; + unsigned int args[2]; - of_id = of_match_node(of_match, np); - if (!of_id) { - pr_err("failed to look up compatible string\n"); + reset_masks = device_get_match_data(&pdev->dev); + if (!reset_masks) { + pr_err("failed to get match data\n"); return -EINVAL; } - reset_masks = of_id->data; - regmap = syscon_regmap_lookup_by_phandle(np, "syscon"); + regmap = syscon_regmap_lookup_by_phandle_args(np, "syscon", ARRAY_SIZE(args), args); if (IS_ERR(regmap)) { pr_err("failed to get syscon phandle\n"); return -EINVAL; } + rst_src_en = args[0]; + sw_mstr_rst = args[1]; - rc = of_property_read_u32_index(np, "syscon", RESET_SOURCE_ENABLE_REG, - &rst_src_en); - if (rc) { - pr_err("can't get rst_src_en offset (%d)\n", rc); - return -EINVAL; - } - - rc = of_property_read_u32_index(np, "syscon", SW_MASTER_RESET_REG, - &sw_mstr_rst); - if (rc) { - pr_err("can't get sw_mstr_rst offset (%d)\n", rc); - return -EINVAL; - } - - rc = register_restart_handler(&brcmstb_restart_nb); + rc = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, + 128, brcmstb_restart_handler, NULL); if (rc) dev_err(&pdev->dev, "cannot register restart handler (err=%d)\n", rc); @@ -130,6 +100,12 @@ static int brcmstb_reboot_probe(struct platform_device *pdev) return rc; } +static const struct of_device_id of_match[] = { + { .compatible = "brcm,brcmstb-reboot", .data = &reset_bits_40nm }, + { .compatible = "brcm,bcm7038-reboot", .data = &reset_bits_65nm }, + {}, +}; + static struct platform_driver brcmstb_reboot_driver = { .probe = brcmstb_reboot_probe, .driver = { @@ -140,7 +116,6 @@ static struct platform_driver brcmstb_reboot_driver = { static int __init brcmstb_reboot_init(void) { - return platform_driver_probe(&brcmstb_reboot_driver, - brcmstb_reboot_probe); + return platform_driver_register(&brcmstb_reboot_driver); } subsys_initcall(brcmstb_reboot_init); diff --git a/drivers/power/reset/ep93xx-restart.c b/drivers/power/reset/ep93xx-restart.c new file mode 100644 index 000000000000..57cfb8620faf --- /dev/null +++ b/drivers/power/reset/ep93xx-restart.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Cirrus EP93xx SoC reset driver + * + * Copyright (C) 2021 Nikita Shubin <nikita.shubin@maquefel.me> + */ + +#include <linux/bits.h> +#include <linux/container_of.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/slab.h> + +#include <linux/soc/cirrus/ep93xx.h> + +#define EP93XX_SYSCON_DEVCFG 0x80 +#define EP93XX_SYSCON_DEVCFG_SWRST BIT(31) + +struct ep93xx_restart { + struct ep93xx_regmap_adev *aux_dev; + struct notifier_block restart_handler; +}; + +static int ep93xx_restart_handle(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + struct ep93xx_restart *priv = + container_of(this, struct ep93xx_restart, restart_handler); + struct ep93xx_regmap_adev *aux = priv->aux_dev; + + /* Issue the reboot */ + aux->update_bits(aux->map, aux->lock, EP93XX_SYSCON_DEVCFG, + EP93XX_SYSCON_DEVCFG_SWRST, EP93XX_SYSCON_DEVCFG_SWRST); + aux->update_bits(aux->map, aux->lock, EP93XX_SYSCON_DEVCFG, + EP93XX_SYSCON_DEVCFG_SWRST, 0); + + return NOTIFY_DONE; +} + +static int ep93xx_reboot_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct ep93xx_regmap_adev *rdev = to_ep93xx_regmap_adev(adev); + struct device *dev = &adev->dev; + struct ep93xx_restart *priv; + int err; + + if (!rdev->update_bits) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->aux_dev = rdev; + + priv->restart_handler.notifier_call = ep93xx_restart_handle; + priv->restart_handler.priority = 128; + + err = register_restart_handler(&priv->restart_handler); + if (err) + return dev_err_probe(dev, err, "can't register restart notifier\n"); + + return 0; +} + +static const struct auxiliary_device_id ep93xx_reboot_ids[] = { + { + .name = "soc_ep93xx.reset-ep93xx", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(auxiliary, ep93xx_reboot_ids); + +static struct auxiliary_driver ep93xx_reboot_driver = { + .probe = ep93xx_reboot_probe, + .id_table = ep93xx_reboot_ids, +}; +module_auxiliary_driver(ep93xx_reboot_driver); diff --git a/drivers/power/reset/gemini-poweroff.c b/drivers/power/reset/gemini-poweroff.c index b7f7a8225f22..06d6992dec89 100644 --- a/drivers/power/reset/gemini-poweroff.c +++ b/drivers/power/reset/gemini-poweroff.c @@ -70,12 +70,9 @@ static irqreturn_t gemini_powerbutton_interrupt(int irq, void *data) return IRQ_HANDLED; } -/* This callback needs this static local as it has void as argument */ -static struct gemini_powercon *gpw_poweroff; - -static void gemini_poweroff(void) +static int gemini_poweroff(struct sys_off_data *data) { - struct gemini_powercon *gpw = gpw_poweroff; + struct gemini_powercon *gpw = data->cb_data; u32 val; dev_crit(gpw->dev, "Gemini power off\n"); @@ -86,12 +83,13 @@ static void gemini_poweroff(void) val &= ~GEMINI_CTRL_ENABLE; val |= GEMINI_CTRL_SHUTDOWN; writel(val, gpw->base + GEMINI_PWC_CTRLREG); + + return NOTIFY_DONE; } static int gemini_poweroff_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *res; struct gemini_powercon *gpw; u32 val; int irq; @@ -101,8 +99,7 @@ static int gemini_poweroff_probe(struct platform_device *pdev) if (!gpw) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - gpw->base = devm_ioremap_resource(dev, res); + gpw->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(gpw->base)) return PTR_ERR(gpw->base); @@ -150,8 +147,11 @@ static int gemini_poweroff_probe(struct platform_device *pdev) if (ret) return ret; - pm_power_off = gemini_poweroff; - gpw_poweroff = gpw; + ret = devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, + gemini_poweroff, gpw); + if (ret) + return ret; dev_info(dev, "Gemini poweroff driver registered\n"); diff --git a/drivers/power/reset/gpio-poweroff.c b/drivers/power/reset/gpio-poweroff.c index 1c5af2fef142..3eaae352ffb9 100644 --- a/drivers/power/reset/gpio-poweroff.c +++ b/drivers/power/reset/gpio-poweroff.c @@ -11,53 +11,61 @@ #include <linux/init.h> #include <linux/delay.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/gpio/consumer.h> -#include <linux/of_platform.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> +#include <linux/reboot.h> #define DEFAULT_TIMEOUT_MS 3000 -/* - * Hold configuration here, cannot be more than one instance of the driver - * since pm_power_off itself is global. - */ -static struct gpio_desc *reset_gpio; -static u32 timeout = DEFAULT_TIMEOUT_MS; -static u32 active_delay = 100; -static u32 inactive_delay = 100; -static void gpio_poweroff_do_poweroff(void) +struct gpio_poweroff { + struct gpio_desc *reset_gpio; + u32 timeout_ms; + u32 active_delay_ms; + u32 inactive_delay_ms; +}; + +static int gpio_poweroff_do_poweroff(struct sys_off_data *data) { - BUG_ON(!reset_gpio); + struct gpio_poweroff *gpio_poweroff = data->cb_data; /* drive it active, also inactive->active edge */ - gpiod_direction_output(reset_gpio, 1); - mdelay(active_delay); + gpiod_direction_output(gpio_poweroff->reset_gpio, 1); + mdelay(gpio_poweroff->active_delay_ms); /* drive inactive, also active->inactive edge */ - gpiod_set_value_cansleep(reset_gpio, 0); - mdelay(inactive_delay); + gpiod_set_value_cansleep(gpio_poweroff->reset_gpio, 0); + mdelay(gpio_poweroff->inactive_delay_ms); /* drive it active, also inactive->active edge */ - gpiod_set_value_cansleep(reset_gpio, 1); + gpiod_set_value_cansleep(gpio_poweroff->reset_gpio, 1); /* give it some time */ - mdelay(timeout); + mdelay(gpio_poweroff->timeout_ms); - WARN_ON(1); + /* + * If code reaches this point, it means that gpio-poweroff has failed + * to actually power off the system. + * Warn the user that the attempt to poweroff via gpio-poweroff + * has gone wrong. + */ + WARN(1, "Failed to poweroff via gpio-poweroff mechanism\n"); + + return NOTIFY_DONE; } static int gpio_poweroff_probe(struct platform_device *pdev) { + struct gpio_poweroff *gpio_poweroff; bool input = false; enum gpiod_flags flags; + int priority = SYS_OFF_PRIO_DEFAULT; + int ret; - /* If a pm_power_off function has already been added, leave it alone */ - if (pm_power_off != NULL) { - dev_err(&pdev->dev, - "%s: pm_power_off function already registered\n", - __func__); - return -EBUSY; - } + gpio_poweroff = devm_kzalloc(&pdev->dev, sizeof(*gpio_poweroff), GFP_KERNEL); + if (!gpio_poweroff) + return -ENOMEM; input = device_property_read_bool(&pdev->dev, "input"); if (input) @@ -65,23 +73,29 @@ static int gpio_poweroff_probe(struct platform_device *pdev) else flags = GPIOD_OUT_LOW; - device_property_read_u32(&pdev->dev, "active-delay-ms", &active_delay); - device_property_read_u32(&pdev->dev, "inactive-delay-ms", - &inactive_delay); - device_property_read_u32(&pdev->dev, "timeout-ms", &timeout); - reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags); - if (IS_ERR(reset_gpio)) - return PTR_ERR(reset_gpio); + gpio_poweroff->active_delay_ms = 100; + gpio_poweroff->inactive_delay_ms = 100; + gpio_poweroff->timeout_ms = DEFAULT_TIMEOUT_MS; - pm_power_off = &gpio_poweroff_do_poweroff; - return 0; -} + device_property_read_u32(&pdev->dev, "active-delay-ms", &gpio_poweroff->active_delay_ms); + device_property_read_u32(&pdev->dev, "inactive-delay-ms", + &gpio_poweroff->inactive_delay_ms); + device_property_read_u32(&pdev->dev, "timeout-ms", &gpio_poweroff->timeout_ms); + device_property_read_u32(&pdev->dev, "priority", &priority); + if (priority > 255) { + dev_err(&pdev->dev, "Invalid priority property: %u\n", priority); + return -EINVAL; + } -static int gpio_poweroff_remove(struct platform_device *pdev) -{ - if (pm_power_off == &gpio_poweroff_do_poweroff) - pm_power_off = NULL; + gpio_poweroff->reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags); + if (IS_ERR(gpio_poweroff->reset_gpio)) + return PTR_ERR(gpio_poweroff->reset_gpio); + + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, + priority, gpio_poweroff_do_poweroff, gpio_poweroff); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Cannot register poweroff handler\n"); return 0; } @@ -94,7 +108,6 @@ MODULE_DEVICE_TABLE(of, of_gpio_poweroff_match); static struct platform_driver gpio_poweroff_driver = { .probe = gpio_poweroff_probe, - .remove = gpio_poweroff_remove, .driver = { .name = "poweroff-gpio", .of_match_table = of_gpio_poweroff_match, @@ -105,5 +118,4 @@ module_platform_driver(gpio_poweroff_driver); MODULE_AUTHOR("Jamie Lentin <jm@lentin.co.uk>"); MODULE_DESCRIPTION("GPIO poweroff driver"); -MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:poweroff-gpio"); diff --git a/drivers/power/reset/gpio-restart.c b/drivers/power/reset/gpio-restart.c index 5466eeea261c..d1e177176fa1 100644 --- a/drivers/power/reset/gpio-restart.c +++ b/drivers/power/reset/gpio-restart.c @@ -12,22 +12,19 @@ #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/gpio/consumer.h> -#include <linux/of_platform.h> #include <linux/module.h> +#include <linux/of.h> struct gpio_restart { struct gpio_desc *reset_gpio; - struct notifier_block restart_handler; u32 active_delay_ms; u32 inactive_delay_ms; u32 wait_delay_ms; }; -static int gpio_restart_notify(struct notifier_block *this, - unsigned long mode, void *cmd) +static int gpio_restart_notify(struct sys_off_data *data) { - struct gpio_restart *gpio_restart = - container_of(this, struct gpio_restart, restart_handler); + struct gpio_restart *gpio_restart = data->cb_data; /* drive it active, also inactive->active edge */ gpiod_direction_output(gpio_restart->reset_gpio, 1); @@ -52,6 +49,7 @@ static int gpio_restart_probe(struct platform_device *pdev) { struct gpio_restart *gpio_restart; bool open_source = false; + int priority = 129; u32 property; int ret; @@ -71,8 +69,6 @@ static int gpio_restart_probe(struct platform_device *pdev) return ret; } - gpio_restart->restart_handler.notifier_call = gpio_restart_notify; - gpio_restart->restart_handler.priority = 129; gpio_restart->active_delay_ms = 100; gpio_restart->inactive_delay_ms = 100; gpio_restart->wait_delay_ms = 3000; @@ -83,7 +79,7 @@ static int gpio_restart_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Invalid priority property: %u\n", property); else - gpio_restart->restart_handler.priority = property; + priority = property; } of_property_read_u32(pdev->dev.of_node, "active-delay", @@ -93,9 +89,11 @@ static int gpio_restart_probe(struct platform_device *pdev) of_property_read_u32(pdev->dev.of_node, "wait-delay", &gpio_restart->wait_delay_ms); - platform_set_drvdata(pdev, gpio_restart); - - ret = register_restart_handler(&gpio_restart->restart_handler); + ret = devm_register_sys_off_handler(&pdev->dev, + SYS_OFF_MODE_RESTART, + priority, + gpio_restart_notify, + gpio_restart); if (ret) { dev_err(&pdev->dev, "%s: cannot register restart handler, %d\n", __func__, ret); @@ -105,22 +103,6 @@ static int gpio_restart_probe(struct platform_device *pdev) return 0; } -static int gpio_restart_remove(struct platform_device *pdev) -{ - struct gpio_restart *gpio_restart = platform_get_drvdata(pdev); - int ret; - - ret = unregister_restart_handler(&gpio_restart->restart_handler); - if (ret) { - dev_err(&pdev->dev, - "%s: cannot unregister restart handler, %d\n", - __func__, ret); - return -ENODEV; - } - - return 0; -} - static const struct of_device_id of_gpio_restart_match[] = { { .compatible = "gpio-restart", }, {}, @@ -128,7 +110,6 @@ static const struct of_device_id of_gpio_restart_match[] = { static struct platform_driver gpio_restart_driver = { .probe = gpio_restart_probe, - .remove = gpio_restart_remove, .driver = { .name = "restart-gpio", .of_match_table = of_gpio_restart_match, @@ -139,4 +120,3 @@ module_platform_driver(gpio_restart_driver); MODULE_AUTHOR("David Riley <davidriley@chromium.org>"); MODULE_DESCRIPTION("GPIO restart driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/reset/keystone-reset.c b/drivers/power/reset/keystone-reset.c index c720112db704..d9268d150e1f 100644 --- a/drivers/power/reset/keystone-reset.c +++ b/drivers/power/reset/keystone-reset.c @@ -10,12 +10,12 @@ #include <linux/io.h> #include <linux/module.h> #include <linux/notifier.h> +#include <linux/platform_device.h> #include <linux/reboot.h> #include <linux/regmap.h> #include <linux/mfd/syscon.h> -#include <linux/of_platform.h> +#include <linux/of.h> -#define RSTYPE_RG 0x0 #define RSCTRL_RG 0x4 #define RSCFG_RG 0x8 #define RSISO_RG 0xc @@ -27,7 +27,6 @@ #define RSMUX_OMODE_MASK 0xe #define RSMUX_OMODE_RESET_ON 0xa #define RSMUX_OMODE_RESET_OFF 0x0 -#define RSMUX_LOCK_MASK 0x1 #define RSMUX_LOCK_SET 0x1 #define RSCFG_RSTYPE_SOFT 0x300f @@ -88,26 +87,16 @@ static int rsctrl_probe(struct platform_device *pdev) return -ENODEV; /* get regmaps */ - pllctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-pll"); + pllctrl_regs = syscon_regmap_lookup_by_phandle_args(np, "ti,syscon-pll", + 1, &rspll_offset); if (IS_ERR(pllctrl_regs)) return PTR_ERR(pllctrl_regs); - devctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-dev"); + devctrl_regs = syscon_regmap_lookup_by_phandle_args(np, "ti,syscon-dev", + 1, &rsmux_offset); if (IS_ERR(devctrl_regs)) return PTR_ERR(devctrl_regs); - ret = of_property_read_u32_index(np, "ti,syscon-pll", 1, &rspll_offset); - if (ret) { - dev_err(dev, "couldn't read the reset pll offset!\n"); - return -EINVAL; - } - - ret = of_property_read_u32_index(np, "ti,syscon-dev", 1, &rsmux_offset); - if (ret) { - dev_err(dev, "couldn't read the rsmux offset!\n"); - return -EINVAL; - } - /* set soft/hard reset */ val = of_property_read_bool(np, "ti,soft-reset"); val = val ? RSCFG_RSTYPE_SOFT : RSCFG_RSTYPE_HARD; @@ -169,5 +158,4 @@ module_platform_driver(rsctrl_driver); MODULE_AUTHOR("Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>"); MODULE_DESCRIPTION("Texas Instruments keystone reset driver"); -MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:" KBUILD_MODNAME); diff --git a/drivers/power/reset/ltc2952-poweroff.c b/drivers/power/reset/ltc2952-poweroff.c index 65d9528cc989..90c664d344d0 100644 --- a/drivers/power/reset/ltc2952-poweroff.c +++ b/drivers/power/reset/ltc2952-poweroff.c @@ -162,11 +162,11 @@ static void ltc2952_poweroff_default(struct ltc2952_poweroff *data) data->wde_interval = 300L * NSEC_PER_MSEC; data->trigger_delay = ktime_set(2, 500L * NSEC_PER_MSEC); - hrtimer_init(&data->timer_trigger, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - data->timer_trigger.function = ltc2952_poweroff_timer_trigger; + hrtimer_setup(&data->timer_trigger, ltc2952_poweroff_timer_trigger, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); - hrtimer_init(&data->timer_wde, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - data->timer_wde.function = ltc2952_poweroff_timer_wde; + hrtimer_setup(&data->timer_wde, ltc2952_poweroff_timer_wde, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); } static int ltc2952_poweroff_init(struct platform_device *pdev) @@ -286,7 +286,7 @@ static int ltc2952_poweroff_probe(struct platform_device *pdev) return 0; } -static int ltc2952_poweroff_remove(struct platform_device *pdev) +static void ltc2952_poweroff_remove(struct platform_device *pdev) { struct ltc2952_poweroff *data = platform_get_drvdata(pdev); @@ -295,7 +295,6 @@ static int ltc2952_poweroff_remove(struct platform_device *pdev) hrtimer_cancel(&data->timer_wde); atomic_notifier_chain_unregister(&panic_notifier_list, &data->panic_notifier); - return 0; } static const struct of_device_id of_ltc2952_poweroff_match[] = { @@ -317,4 +316,3 @@ module_platform_driver(ltc2952_poweroff_driver); MODULE_AUTHOR("René Moll <rene.moll@xsens.com>"); MODULE_DESCRIPTION("LTC PowerPath power-off driver"); -MODULE_LICENSE("GPL v2"); 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/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c index 0c439f83bf65..c7eb6dc8e90a 100644 --- a/drivers/power/reset/msm-poweroff.c +++ b/drivers/power/reset/msm-poweroff.c @@ -14,8 +14,8 @@ #include <linux/pm.h> static void __iomem *msm_ps_hold; -static int deassert_pshold(struct notifier_block *nb, unsigned long action, - void *data) + +static int do_msm_poweroff(struct sys_off_data *data) { writel(0, msm_ps_hold); mdelay(10000); @@ -23,29 +23,18 @@ static int deassert_pshold(struct notifier_block *nb, unsigned long action, return NOTIFY_DONE; } -static struct notifier_block restart_nb = { - .notifier_call = deassert_pshold, - .priority = 128, -}; - -static void do_msm_poweroff(void) -{ - deassert_pshold(&restart_nb, 0, NULL); -} - static int msm_restart_probe(struct platform_device *pdev) { - struct device *dev = &pdev->dev; - struct resource *mem; - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - msm_ps_hold = devm_ioremap_resource(dev, mem); + msm_ps_hold = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(msm_ps_hold)) return PTR_ERR(msm_ps_hold); - register_restart_handler(&restart_nb); + devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, + 128, do_msm_poweroff, NULL); - pm_power_off = do_msm_poweroff; + devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, do_msm_poweroff, + NULL); return 0; } @@ -63,9 +52,4 @@ static struct platform_driver msm_restart_driver = { .of_match_table = of_match_ptr(of_msm_restart_match), }, }; - -static int __init msm_restart_init(void) -{ - return platform_driver_register(&msm_restart_driver); -} -device_initcall(msm_restart_init); +builtin_platform_driver(msm_restart_driver); diff --git a/drivers/power/reset/mt6323-poweroff.c b/drivers/power/reset/mt6323-poweroff.c index d90e76fcb938..c663347547f9 100644 --- a/drivers/power/reset/mt6323-poweroff.c +++ b/drivers/power/reset/mt6323-poweroff.c @@ -14,6 +14,7 @@ #include <linux/platform_device.h> #include <linux/mfd/mt6397/core.h> #include <linux/mfd/mt6397/rtc.h> +#include <linux/reboot.h> struct mt6323_pwrc { struct device *dev; @@ -21,11 +22,9 @@ struct mt6323_pwrc { u32 base; }; -static struct mt6323_pwrc *mt_pwrc; - -static void mt6323_do_pwroff(void) +static int mt6323_do_pwroff(struct sys_off_data *data) { - struct mt6323_pwrc *pwrc = mt_pwrc; + struct mt6323_pwrc *pwrc = data->cb_data; unsigned int val; int ret; @@ -44,6 +43,8 @@ static void mt6323_do_pwroff(void) mdelay(1000); WARN_ONCE(1, "Unable to power off system\n"); + + return NOTIFY_DONE; } static int mt6323_pwrc_probe(struct platform_device *pdev) @@ -51,6 +52,7 @@ static int mt6323_pwrc_probe(struct platform_device *pdev) struct mt6397_chip *mt6397_chip = dev_get_drvdata(pdev->dev.parent); struct mt6323_pwrc *pwrc; struct resource *res; + int ret; pwrc = devm_kzalloc(&pdev->dev, sizeof(*pwrc), GFP_KERNEL); if (!pwrc) @@ -63,17 +65,14 @@ static int mt6323_pwrc_probe(struct platform_device *pdev) pwrc->base = res->start; pwrc->regmap = mt6397_chip->regmap; pwrc->dev = &pdev->dev; - mt_pwrc = pwrc; - pm_power_off = &mt6323_do_pwroff; - - return 0; -} - -static int mt6323_pwrc_remove(struct platform_device *pdev) -{ - if (pm_power_off == &mt6323_do_pwroff) - pm_power_off = NULL; + ret = devm_register_sys_off_handler(pwrc->dev, + SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, + mt6323_do_pwroff, + pwrc); + if (ret) + return dev_err_probe(pwrc->dev, ret, "failed to register power-off handler\n"); return 0; } @@ -86,7 +85,6 @@ MODULE_DEVICE_TABLE(of, mt6323_pwrc_dt_match); static struct platform_driver mt6323_pwrc_driver = { .probe = mt6323_pwrc_probe, - .remove = mt6323_pwrc_remove, .driver = { .name = "mt6323-pwrc", .of_match_table = mt6323_pwrc_dt_match, @@ -97,4 +95,3 @@ module_platform_driver(mt6323_pwrc_driver); MODULE_DESCRIPTION("Poweroff driver for MT6323 PMIC"); MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/nvmem-reboot-mode.c b/drivers/power/reset/nvmem-reboot-mode.c index e229308d43e2..41530b70cfc4 100644 --- a/drivers/power/reset/nvmem-reboot-mode.c +++ b/drivers/power/reset/nvmem-reboot-mode.c @@ -45,8 +45,8 @@ static int nvmem_reboot_mode_probe(struct platform_device *pdev) nvmem_rbm->cell = devm_nvmem_cell_get(&pdev->dev, "reboot-mode"); if (IS_ERR(nvmem_rbm->cell)) { - dev_err(&pdev->dev, "failed to get the nvmem cell reboot-mode\n"); - return PTR_ERR(nvmem_rbm->cell); + return dev_err_probe(&pdev->dev, PTR_ERR(nvmem_rbm->cell), + "failed to get the nvmem cell reboot-mode\n"); } ret = devm_reboot_mode_register(&pdev->dev, &nvmem_rbm->reboot); diff --git a/drivers/power/reset/ocelot-reset.c b/drivers/power/reset/ocelot-reset.c index 8caa90cb58fc..56be64decf54 100644 --- a/drivers/power/reset/ocelot-reset.c +++ b/drivers/power/reset/ocelot-reset.c @@ -8,10 +8,10 @@ #include <linux/delay.h> #include <linux/io.h> #include <linux/notifier.h> +#include <linux/mod_devicetable.h> #include <linux/mfd/syscon.h> -#include <linux/of_address.h> -#include <linux/of_device.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/reboot.h> #include <linux/regmap.h> @@ -69,8 +69,6 @@ static int ocelot_restart_handle(struct notifier_block *this, static int ocelot_reset_probe(struct platform_device *pdev) { struct ocelot_reset_context *ctx; - struct resource *res; - struct device *dev = &pdev->dev; int err; @@ -78,8 +76,7 @@ static int ocelot_reset_probe(struct platform_device *pdev) if (!ctx) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - ctx->base = devm_ioremap_resource(dev, res); + ctx->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(ctx->base)) return PTR_ERR(ctx->base); diff --git a/drivers/power/reset/odroid-go-ultra-poweroff.c b/drivers/power/reset/odroid-go-ultra-poweroff.c new file mode 100644 index 000000000000..9cac7aef77f0 --- /dev/null +++ b/drivers/power/reset/odroid-go-ultra-poweroff.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2023 Neil Armstrong <neil.armstrong@linaro.org> + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/mfd/rk808.h> +#include <linux/regmap.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/i2c.h> + +/* + * The Odroid Go Ultra has 2 PMICs: + * - RK818 (manages the battery and USB-C power supply) + * - RK817 + * Both PMICs feeds power to the S922X SoC, so they must be powered-off in sequence. + * Vendor does power-off the RK817 first, then the RK818 so here we follow this sequence. + */ + +struct odroid_go_ultra_poweroff_data { + struct device *dev; + struct device *rk817; + struct device *rk818; +}; + +static int odroid_go_ultra_poweroff_prepare(struct sys_off_data *data) +{ + struct odroid_go_ultra_poweroff_data *poweroff_data = data->cb_data; + struct regmap *rk817, *rk818; + int ret; + + /* RK817 Regmap */ + rk817 = dev_get_regmap(poweroff_data->rk817, NULL); + if (!rk817) { + dev_err(poweroff_data->dev, "failed to get rk817 regmap\n"); + return notifier_from_errno(-EINVAL); + } + + /* RK818 Regmap */ + rk818 = dev_get_regmap(poweroff_data->rk818, NULL); + if (!rk818) { + dev_err(poweroff_data->dev, "failed to get rk818 regmap\n"); + return notifier_from_errno(-EINVAL); + } + + dev_info(poweroff_data->dev, "Setting PMICs for power off"); + + /* RK817 */ + ret = regmap_update_bits(rk817, RK817_SYS_CFG(3), DEV_OFF, DEV_OFF); + if (ret) { + dev_err(poweroff_data->dev, "failed to poweroff rk817\n"); + return notifier_from_errno(ret); + } + + /* RK818 */ + ret = regmap_update_bits(rk818, RK818_DEVCTRL_REG, DEV_OFF, DEV_OFF); + if (ret) { + dev_err(poweroff_data->dev, "failed to poweroff rk818\n"); + return notifier_from_errno(ret); + } + + return NOTIFY_OK; +} + +static void odroid_go_ultra_poweroff_put_pmic_device(void *data) +{ + struct device *dev = data; + + put_device(dev); +} + +static int odroid_go_ultra_poweroff_get_pmic_device(struct device *dev, const char *compatible, + struct device **pmic) +{ + struct device_node *pmic_node; + struct i2c_client *pmic_client; + + pmic_node = of_find_compatible_node(NULL, NULL, compatible); + if (!pmic_node) + return -ENODEV; + + pmic_client = of_find_i2c_device_by_node(pmic_node); + of_node_put(pmic_node); + if (!pmic_client) + return -EPROBE_DEFER; + + *pmic = &pmic_client->dev; + + return devm_add_action_or_reset(dev, odroid_go_ultra_poweroff_put_pmic_device, *pmic); +} + +static int odroid_go_ultra_poweroff_probe(struct platform_device *pdev) +{ + struct odroid_go_ultra_poweroff_data *poweroff_data; + int ret; + + poweroff_data = devm_kzalloc(&pdev->dev, sizeof(*poweroff_data), GFP_KERNEL); + if (!poweroff_data) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, poweroff_data); + + /* RK818 PMIC Device */ + ret = odroid_go_ultra_poweroff_get_pmic_device(&pdev->dev, "rockchip,rk818", + &poweroff_data->rk818); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to get rk818 mfd data\n"); + + /* RK817 PMIC Device */ + ret = odroid_go_ultra_poweroff_get_pmic_device(&pdev->dev, "rockchip,rk817", + &poweroff_data->rk817); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to get rk817 mfd data\n"); + + /* Register as SYS_OFF_MODE_POWER_OFF_PREPARE because regmap_update_bits may sleep */ + ret = devm_register_sys_off_handler(&pdev->dev, + SYS_OFF_MODE_POWER_OFF_PREPARE, + SYS_OFF_PRIO_DEFAULT, + odroid_go_ultra_poweroff_prepare, + poweroff_data); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to register sys-off handler\n"); + + dev_info(&pdev->dev, "Registered Power-Off handler\n"); + + return 0; +} +static struct platform_device *pdev; + +static struct platform_driver odroid_go_ultra_poweroff_driver = { + .driver = { + .name = "odroid-go-ultra-poweroff", + }, + .probe = odroid_go_ultra_poweroff_probe, +}; + +static int __init odroid_go_ultra_poweroff_init(void) +{ + int ret; + + /* Only create when running on the Odroid Go Ultra device */ + if (!of_device_is_compatible(of_root, "hardkernel,odroid-go-ultra")) + return -ENODEV; + + ret = platform_driver_register(&odroid_go_ultra_poweroff_driver); + if (ret) + return ret; + + pdev = platform_device_register_resndata(NULL, "odroid-go-ultra-poweroff", -1, + NULL, 0, NULL, 0); + + if (IS_ERR(pdev)) { + platform_driver_unregister(&odroid_go_ultra_poweroff_driver); + return PTR_ERR(pdev); + } + + return 0; +} + +static void __exit odroid_go_ultra_poweroff_exit(void) +{ + /* Only delete when running on the Odroid Go Ultra device */ + if (!of_device_is_compatible(of_root, "hardkernel,odroid-go-ultra")) + return; + + platform_device_unregister(pdev); + platform_driver_unregister(&odroid_go_ultra_poweroff_driver); +} + +module_init(odroid_go_ultra_poweroff_init); +module_exit(odroid_go_ultra_poweroff_exit); + +MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>"); +MODULE_DESCRIPTION("Odroid Go Ultra poweroff driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/reset/oxnas-restart.c b/drivers/power/reset/oxnas-restart.c deleted file mode 100644 index 13090bec058a..000000000000 --- a/drivers/power/reset/oxnas-restart.c +++ /dev/null @@ -1,233 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0) -/* - * oxnas SoC reset driver - * based on: - * Microsemi MIPS SoC reset driver - * and ox820_assert_system_reset() written by Ma Hajun <mahaijuns@gmail.com> - * - * Copyright (c) 2013 Ma Hajun <mahaijuns@gmail.com> - * Copyright (c) 2017 Microsemi Corporation - * Copyright (c) 2020 Daniel Golle <daniel@makrotopia.org> - */ -#include <linux/delay.h> -#include <linux/io.h> -#include <linux/notifier.h> -#include <linux/mfd/syscon.h> -#include <linux/of_address.h> -#include <linux/of_device.h> -#include <linux/platform_device.h> -#include <linux/reboot.h> -#include <linux/regmap.h> - -/* bit numbers of reset control register */ -#define OX820_SYS_CTRL_RST_SCU 0 -#define OX820_SYS_CTRL_RST_COPRO 1 -#define OX820_SYS_CTRL_RST_ARM0 2 -#define OX820_SYS_CTRL_RST_ARM1 3 -#define OX820_SYS_CTRL_RST_USBHS 4 -#define OX820_SYS_CTRL_RST_USBHSPHYA 5 -#define OX820_SYS_CTRL_RST_MACA 6 -#define OX820_SYS_CTRL_RST_MAC OX820_SYS_CTRL_RST_MACA -#define OX820_SYS_CTRL_RST_PCIEA 7 -#define OX820_SYS_CTRL_RST_SGDMA 8 -#define OX820_SYS_CTRL_RST_CIPHER 9 -#define OX820_SYS_CTRL_RST_DDR 10 -#define OX820_SYS_CTRL_RST_SATA 11 -#define OX820_SYS_CTRL_RST_SATA_LINK 12 -#define OX820_SYS_CTRL_RST_SATA_PHY 13 -#define OX820_SYS_CTRL_RST_PCIEPHY 14 -#define OX820_SYS_CTRL_RST_STATIC 15 -#define OX820_SYS_CTRL_RST_GPIO 16 -#define OX820_SYS_CTRL_RST_UART1 17 -#define OX820_SYS_CTRL_RST_UART2 18 -#define OX820_SYS_CTRL_RST_MISC 19 -#define OX820_SYS_CTRL_RST_I2S 20 -#define OX820_SYS_CTRL_RST_SD 21 -#define OX820_SYS_CTRL_RST_MACB 22 -#define OX820_SYS_CTRL_RST_PCIEB 23 -#define OX820_SYS_CTRL_RST_VIDEO 24 -#define OX820_SYS_CTRL_RST_DDR_PHY 25 -#define OX820_SYS_CTRL_RST_USBHSPHYB 26 -#define OX820_SYS_CTRL_RST_USBDEV 27 -#define OX820_SYS_CTRL_RST_ARMDBG 29 -#define OX820_SYS_CTRL_RST_PLLA 30 -#define OX820_SYS_CTRL_RST_PLLB 31 - -/* bit numbers of clock control register */ -#define OX820_SYS_CTRL_CLK_COPRO 0 -#define OX820_SYS_CTRL_CLK_DMA 1 -#define OX820_SYS_CTRL_CLK_CIPHER 2 -#define OX820_SYS_CTRL_CLK_SD 3 -#define OX820_SYS_CTRL_CLK_SATA 4 -#define OX820_SYS_CTRL_CLK_I2S 5 -#define OX820_SYS_CTRL_CLK_USBHS 6 -#define OX820_SYS_CTRL_CLK_MACA 7 -#define OX820_SYS_CTRL_CLK_MAC OX820_SYS_CTRL_CLK_MACA -#define OX820_SYS_CTRL_CLK_PCIEA 8 -#define OX820_SYS_CTRL_CLK_STATIC 9 -#define OX820_SYS_CTRL_CLK_MACB 10 -#define OX820_SYS_CTRL_CLK_PCIEB 11 -#define OX820_SYS_CTRL_CLK_REF600 12 -#define OX820_SYS_CTRL_CLK_USBDEV 13 -#define OX820_SYS_CTRL_CLK_DDR 14 -#define OX820_SYS_CTRL_CLK_DDRPHY 15 -#define OX820_SYS_CTRL_CLK_DDRCK 16 - -/* Regmap offsets */ -#define OX820_CLK_SET_REGOFFSET 0x2c -#define OX820_CLK_CLR_REGOFFSET 0x30 -#define OX820_RST_SET_REGOFFSET 0x34 -#define OX820_RST_CLR_REGOFFSET 0x38 -#define OX820_SECONDARY_SEL_REGOFFSET 0x14 -#define OX820_TERTIARY_SEL_REGOFFSET 0x8c -#define OX820_QUATERNARY_SEL_REGOFFSET 0x94 -#define OX820_DEBUG_SEL_REGOFFSET 0x9c -#define OX820_ALTERNATIVE_SEL_REGOFFSET 0xa4 -#define OX820_PULLUP_SEL_REGOFFSET 0xac -#define OX820_SEC_SECONDARY_SEL_REGOFFSET 0x100014 -#define OX820_SEC_TERTIARY_SEL_REGOFFSET 0x10008c -#define OX820_SEC_QUATERNARY_SEL_REGOFFSET 0x100094 -#define OX820_SEC_DEBUG_SEL_REGOFFSET 0x10009c -#define OX820_SEC_ALTERNATIVE_SEL_REGOFFSET 0x1000a4 -#define OX820_SEC_PULLUP_SEL_REGOFFSET 0x1000ac - -struct oxnas_restart_context { - struct regmap *sys_ctrl; - struct notifier_block restart_handler; -}; - -static int ox820_restart_handle(struct notifier_block *this, - unsigned long mode, void *cmd) -{ - struct oxnas_restart_context *ctx = container_of(this, struct - oxnas_restart_context, - restart_handler); - u32 value; - - /* - * Assert reset to cores as per power on defaults - * Don't touch the DDR interface as things will come to an impromptu - * stop NB Possibly should be asserting reset for PLLB, but there are - * timing concerns here according to the docs - */ - value = BIT(OX820_SYS_CTRL_RST_COPRO) | - BIT(OX820_SYS_CTRL_RST_USBHS) | - BIT(OX820_SYS_CTRL_RST_USBHSPHYA) | - BIT(OX820_SYS_CTRL_RST_MACA) | - BIT(OX820_SYS_CTRL_RST_PCIEA) | - BIT(OX820_SYS_CTRL_RST_SGDMA) | - BIT(OX820_SYS_CTRL_RST_CIPHER) | - BIT(OX820_SYS_CTRL_RST_SATA) | - BIT(OX820_SYS_CTRL_RST_SATA_LINK) | - BIT(OX820_SYS_CTRL_RST_SATA_PHY) | - BIT(OX820_SYS_CTRL_RST_PCIEPHY) | - BIT(OX820_SYS_CTRL_RST_STATIC) | - BIT(OX820_SYS_CTRL_RST_UART1) | - BIT(OX820_SYS_CTRL_RST_UART2) | - BIT(OX820_SYS_CTRL_RST_MISC) | - BIT(OX820_SYS_CTRL_RST_I2S) | - BIT(OX820_SYS_CTRL_RST_SD) | - BIT(OX820_SYS_CTRL_RST_MACB) | - BIT(OX820_SYS_CTRL_RST_PCIEB) | - BIT(OX820_SYS_CTRL_RST_VIDEO) | - BIT(OX820_SYS_CTRL_RST_USBHSPHYB) | - BIT(OX820_SYS_CTRL_RST_USBDEV); - - regmap_write(ctx->sys_ctrl, OX820_RST_SET_REGOFFSET, value); - - /* Release reset to cores as per power on defaults */ - regmap_write(ctx->sys_ctrl, OX820_RST_CLR_REGOFFSET, - BIT(OX820_SYS_CTRL_RST_GPIO)); - - /* - * Disable clocks to cores as per power-on defaults - must leave DDR - * related clocks enabled otherwise we'll stop rather abruptly. - */ - value = BIT(OX820_SYS_CTRL_CLK_COPRO) | - BIT(OX820_SYS_CTRL_CLK_DMA) | - BIT(OX820_SYS_CTRL_CLK_CIPHER) | - BIT(OX820_SYS_CTRL_CLK_SD) | - BIT(OX820_SYS_CTRL_CLK_SATA) | - BIT(OX820_SYS_CTRL_CLK_I2S) | - BIT(OX820_SYS_CTRL_CLK_USBHS) | - BIT(OX820_SYS_CTRL_CLK_MAC) | - BIT(OX820_SYS_CTRL_CLK_PCIEA) | - BIT(OX820_SYS_CTRL_CLK_STATIC) | - BIT(OX820_SYS_CTRL_CLK_MACB) | - BIT(OX820_SYS_CTRL_CLK_PCIEB) | - BIT(OX820_SYS_CTRL_CLK_REF600) | - BIT(OX820_SYS_CTRL_CLK_USBDEV); - - regmap_write(ctx->sys_ctrl, OX820_CLK_CLR_REGOFFSET, value); - - /* Enable clocks to cores as per power-on defaults */ - - /* Set sys-control pin mux'ing as per power-on defaults */ - regmap_write(ctx->sys_ctrl, OX820_SECONDARY_SEL_REGOFFSET, 0); - regmap_write(ctx->sys_ctrl, OX820_TERTIARY_SEL_REGOFFSET, 0); - regmap_write(ctx->sys_ctrl, OX820_QUATERNARY_SEL_REGOFFSET, 0); - regmap_write(ctx->sys_ctrl, OX820_DEBUG_SEL_REGOFFSET, 0); - regmap_write(ctx->sys_ctrl, OX820_ALTERNATIVE_SEL_REGOFFSET, 0); - regmap_write(ctx->sys_ctrl, OX820_PULLUP_SEL_REGOFFSET, 0); - - regmap_write(ctx->sys_ctrl, OX820_SEC_SECONDARY_SEL_REGOFFSET, 0); - regmap_write(ctx->sys_ctrl, OX820_SEC_TERTIARY_SEL_REGOFFSET, 0); - regmap_write(ctx->sys_ctrl, OX820_SEC_QUATERNARY_SEL_REGOFFSET, 0); - regmap_write(ctx->sys_ctrl, OX820_SEC_DEBUG_SEL_REGOFFSET, 0); - regmap_write(ctx->sys_ctrl, OX820_SEC_ALTERNATIVE_SEL_REGOFFSET, 0); - regmap_write(ctx->sys_ctrl, OX820_SEC_PULLUP_SEL_REGOFFSET, 0); - - /* - * No need to save any state, as the ROM loader can determine whether - * reset is due to power cycling or programatic action, just hit the - * (self-clearing) CPU reset bit of the block reset register - */ - value = - BIT(OX820_SYS_CTRL_RST_SCU) | - BIT(OX820_SYS_CTRL_RST_ARM0) | - BIT(OX820_SYS_CTRL_RST_ARM1); - - regmap_write(ctx->sys_ctrl, OX820_RST_SET_REGOFFSET, value); - - pr_emerg("Unable to restart system\n"); - return NOTIFY_DONE; -} - -static int ox820_restart_probe(struct platform_device *pdev) -{ - struct oxnas_restart_context *ctx; - struct regmap *sys_ctrl; - struct device *dev = &pdev->dev; - int err = 0; - - sys_ctrl = syscon_node_to_regmap(pdev->dev.of_node); - if (IS_ERR(sys_ctrl)) - return PTR_ERR(sys_ctrl); - - ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); - if (!ctx) - return -ENOMEM; - - ctx->sys_ctrl = sys_ctrl; - ctx->restart_handler.notifier_call = ox820_restart_handle; - ctx->restart_handler.priority = 192; - err = register_restart_handler(&ctx->restart_handler); - if (err) - dev_err(dev, "can't register restart notifier (err=%d)\n", err); - - return err; -} - -static const struct of_device_id ox820_restart_of_match[] = { - { .compatible = "oxsemi,ox820-sys-ctrl" }, - {} -}; - -static struct platform_driver ox820_restart_driver = { - .probe = ox820_restart_probe, - .driver = { - .name = "ox820-chip-reset", - .of_match_table = ox820_restart_of_match, - }, -}; -builtin_platform_driver(ox820_restart_driver); diff --git a/drivers/power/reset/piix4-poweroff.c b/drivers/power/reset/piix4-poweroff.c index 7f308292d7e3..e6822c021000 100644 --- a/drivers/power/reset/piix4-poweroff.c +++ b/drivers/power/reset/piix4-poweroff.c @@ -106,4 +106,5 @@ static struct pci_driver piix4_poweroff_driver = { module_pci_driver(piix4_poweroff_driver); MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>"); +MODULE_DESCRIPTION("Intel PIIX4 power-off driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/reset/pwr-mlxbf.c b/drivers/power/reset/pwr-mlxbf.c index 12dedf841a44..4f1cd1c0018c 100644 --- a/drivers/power/reset/pwr-mlxbf.c +++ b/drivers/power/reset/pwr-mlxbf.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only or BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause /* * Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. @@ -17,26 +17,26 @@ #include <linux/types.h> struct pwr_mlxbf { - struct work_struct send_work; + struct work_struct reboot_work; const char *hid; }; -static void pwr_mlxbf_send_work(struct work_struct *work) +static void pwr_mlxbf_reboot_work(struct work_struct *work) { - acpi_bus_generate_netlink_event("button/power.*", "Power Button", 0x80, 1); + acpi_bus_generate_netlink_event("button/reboot.*", "Reboot Button", 0x80, 1); } static irqreturn_t pwr_mlxbf_irq(int irq, void *ptr) { const char *rst_pwr_hid = "MLNXBF24"; - const char *low_pwr_hid = "MLNXBF29"; + const char *shutdown_hid = "MLNXBF29"; struct pwr_mlxbf *priv = ptr; if (!strncmp(priv->hid, rst_pwr_hid, 8)) - emergency_restart(); + schedule_work(&priv->reboot_work); - if (!strncmp(priv->hid, low_pwr_hid, 8)) - schedule_work(&priv->send_work); + if (!strncmp(priv->hid, shutdown_hid, 8)) + orderly_poweroff(true); return IRQ_HANDLED; } @@ -64,7 +64,7 @@ static int pwr_mlxbf_probe(struct platform_device *pdev) if (irq < 0) return dev_err_probe(dev, irq, "Error getting %s irq.\n", priv->hid); - err = devm_work_autocancel(dev, &priv->send_work, pwr_mlxbf_send_work); + err = devm_work_autocancel(dev, &priv->reboot_work, pwr_mlxbf_reboot_work); if (err) return err; diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c index 16bc01738be9..7e108982a582 100644 --- a/drivers/power/reset/qcom-pon.c +++ b/drivers/power/reset/qcom-pon.c @@ -17,7 +17,9 @@ #define GEN1_REASON_SHIFT 2 #define GEN2_REASON_SHIFT 1 -struct pm8916_pon { +#define NO_REASON_SHIFT 0 + +struct qcom_pon { struct device *dev; struct regmap *regmap; u32 baseaddr; @@ -25,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, @@ -42,9 +44,10 @@ 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; pon = devm_kzalloc(&pdev->dev, sizeof(*pon), GFP_KERNEL); @@ -64,13 +67,17 @@ static int pm8916_pon_probe(struct platform_device *pdev) if (error) return error; - pon->reboot_mode.dev = &pdev->dev; - pon->reason_shift = (long)of_device_get_match_data(&pdev->dev); - pon->reboot_mode.write = pm8916_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"); - return error; + reason_shift = (long)of_device_get_match_data(&pdev->dev); + + if (reason_shift != NO_REASON_SHIFT) { + pon->reboot_mode.dev = &pdev->dev; + pon->reason_shift = reason_shift; + 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"); + return error; + } } platform_set_drvdata(pdev, pon); @@ -78,23 +85,24 @@ 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 }, { .compatible = "qcom,pm8998-pon", .data = (void *)GEN2_REASON_SHIFT }, { .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 = of_match_ptr(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/qnap-poweroff.c b/drivers/power/reset/qnap-poweroff.c index 0ddf7f25f7b8..f7d8fc935d51 100644 --- a/drivers/power/reset/qnap-poweroff.c +++ b/drivers/power/reset/qnap-poweroff.c @@ -111,10 +111,9 @@ static int qnap_power_off_probe(struct platform_device *pdev) return 0; } -static int qnap_power_off_remove(struct platform_device *pdev) +static void qnap_power_off_remove(struct platform_device *pdev) { pm_power_off = NULL; - return 0; } static struct platform_driver qnap_power_off_driver = { diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index b4076b10b893..fba53f638da0 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -23,20 +23,29 @@ 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, diff --git a/drivers/power/reset/regulator-poweroff.c b/drivers/power/reset/regulator-poweroff.c index 20701203935f..fed4978e3858 100644 --- a/drivers/power/reset/regulator-poweroff.c +++ b/drivers/power/reset/regulator-poweroff.c @@ -13,18 +13,15 @@ #include <linux/of.h> #include <linux/platform_device.h> #include <linux/pm.h> +#include <linux/reboot.h> #include <linux/regulator/consumer.h> #define TIMEOUT_MS 3000 -/* - * Hold configuration here, cannot be more than one instance of the driver - * since pm_power_off itself is global. - */ -static struct regulator *cpu_regulator; - -static void regulator_poweroff_do_poweroff(void) +static int regulator_poweroff_do_poweroff(struct sys_off_data *data) { + struct regulator *cpu_regulator = data->cb_data; + if (cpu_regulator && regulator_is_enabled(cpu_regulator)) regulator_force_disable(cpu_regulator); @@ -32,32 +29,24 @@ static void regulator_poweroff_do_poweroff(void) mdelay(TIMEOUT_MS); WARN_ON(1); + + return NOTIFY_DONE; } static int regulator_poweroff_probe(struct platform_device *pdev) { - /* If a pm_power_off function has already been added, leave it alone */ - if (pm_power_off != NULL) { - dev_err(&pdev->dev, - "%s: pm_power_off function already registered\n", - __func__); - return -EBUSY; - } + struct regulator *cpu_regulator; cpu_regulator = devm_regulator_get(&pdev->dev, "cpu"); if (IS_ERR(cpu_regulator)) return PTR_ERR(cpu_regulator); - pm_power_off = ®ulator_poweroff_do_poweroff; - return 0; -} - -static int regulator_poweroff_remove(__maybe_unused struct platform_device *pdev) -{ - if (pm_power_off == ®ulator_poweroff_do_poweroff) - pm_power_off = NULL; - - return 0; + /* Set this handler to low priority to not override an existing handler */ + return devm_register_sys_off_handler(&pdev->dev, + SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_LOW, + regulator_poweroff_do_poweroff, + cpu_regulator); } static const struct of_device_id of_regulator_poweroff_match[] = { @@ -68,7 +57,6 @@ MODULE_DEVICE_TABLE(of, of_regulator_poweroff_match); static struct platform_driver regulator_poweroff_driver = { .probe = regulator_poweroff_probe, - .remove = regulator_poweroff_remove, .driver = { .name = "poweroff-regulator", .of_match_table = of_regulator_poweroff_match, @@ -79,5 +67,4 @@ module_platform_driver(regulator_poweroff_driver); MODULE_AUTHOR("Michael Klein <michael@fossekall.de>"); MODULE_DESCRIPTION("Regulator poweroff driver"); -MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:poweroff-regulator"); diff --git a/drivers/power/reset/restart-poweroff.c b/drivers/power/reset/restart-poweroff.c index 04d4228119b2..fcd588f9ae9d 100644 --- a/drivers/power/reset/restart-poweroff.c +++ b/drivers/power/reset/restart-poweroff.c @@ -14,31 +14,21 @@ #include <linux/module.h> #include <linux/reboot.h> -static void restart_poweroff_do_poweroff(void) +static int restart_poweroff_do_poweroff(struct sys_off_data *data) { reboot_mode = REBOOT_HARD; machine_restart(NULL); + return NOTIFY_DONE; } static int restart_poweroff_probe(struct platform_device *pdev) { - /* If a pm_power_off function has already been added, leave it alone */ - if (pm_power_off != NULL) { - dev_err(&pdev->dev, - "pm_power_off function already registered"); - return -EBUSY; - } - - pm_power_off = &restart_poweroff_do_poweroff; - return 0; -} - -static int restart_poweroff_remove(struct platform_device *pdev) -{ - if (pm_power_off == &restart_poweroff_do_poweroff) - pm_power_off = NULL; - - return 0; + /* Set this handler to low priority to not override an existing handler */ + return devm_register_sys_off_handler(&pdev->dev, + SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_LOW, + restart_poweroff_do_poweroff, + NULL); } static const struct of_device_id of_restart_poweroff_match[] = { @@ -49,7 +39,6 @@ MODULE_DEVICE_TABLE(of, of_restart_poweroff_match); static struct platform_driver restart_poweroff_driver = { .probe = restart_poweroff_probe, - .remove = restart_poweroff_remove, .driver = { .name = "poweroff-restart", .of_match_table = of_restart_poweroff_match, @@ -59,5 +48,4 @@ module_platform_driver(restart_poweroff_driver); MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch"); MODULE_DESCRIPTION("restart poweroff driver"); -MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:poweroff-restart"); diff --git a/drivers/power/reset/rmobile-reset.c b/drivers/power/reset/rmobile-reset.c index bd3b396558e0..7dbc51c32b0e 100644 --- a/drivers/power/reset/rmobile-reset.c +++ b/drivers/power/reset/rmobile-reset.c @@ -19,12 +19,9 @@ /* Reset Control Register 2 */ #define RESCNT2_PRES 0x80000000 /* Soft power-on reset */ -static void __iomem *sysc_base2; - -static int rmobile_reset_handler(struct notifier_block *this, - unsigned long mode, void *cmd) +static int rmobile_reset_handler(struct sys_off_data *data) { - pr_debug("%s %lu\n", __func__, mode); + void __iomem *sysc_base2 = (void __iomem *)data->cb_data; /* Let's assume we have acquired the HPB semaphore */ writel(RESCNT2_PRES, sysc_base2 + RESCNT2); @@ -32,38 +29,27 @@ static int rmobile_reset_handler(struct notifier_block *this, return NOTIFY_DONE; } -static struct notifier_block rmobile_reset_nb = { - .notifier_call = rmobile_reset_handler, - .priority = 192, -}; - static int rmobile_reset_probe(struct platform_device *pdev) { + void __iomem *sysc_base2; int error; - sysc_base2 = of_iomap(pdev->dev.of_node, 1); - if (!sysc_base2) - return -ENODEV; + sysc_base2 = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(sysc_base2)) + return PTR_ERR(sysc_base2); - error = register_restart_handler(&rmobile_reset_nb); + error = devm_register_sys_off_handler(&pdev->dev, + SYS_OFF_MODE_RESTART, + SYS_OFF_PRIO_HIGH, + rmobile_reset_handler, + (__force void *)sysc_base2); if (error) { dev_err(&pdev->dev, "cannot register restart handler (err=%d)\n", error); - goto fail_unmap; + return error; } return 0; - -fail_unmap: - iounmap(sysc_base2); - return error; -} - -static int rmobile_reset_remove(struct platform_device *pdev) -{ - unregister_restart_handler(&rmobile_reset_nb); - iounmap(sysc_base2); - return 0; } static const struct of_device_id rmobile_reset_of_match[] = { @@ -74,7 +60,6 @@ MODULE_DEVICE_TABLE(of, rmobile_reset_of_match); static struct platform_driver rmobile_reset_driver = { .probe = rmobile_reset_probe, - .remove = rmobile_reset_remove, .driver = { .name = "rmobile_reset", .of_match_table = rmobile_reset_of_match, 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/st-poweroff.c b/drivers/power/reset/st-poweroff.c index 5ccaacffab54..85175066beea 100644 --- a/drivers/power/reset/st-poweroff.c +++ b/drivers/power/reset/st-poweroff.c @@ -9,7 +9,7 @@ #include <linux/module.h> #include <linux/of.h> -#include <linux/of_platform.h> +#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/mfd/syscon.h> #include <linux/reboot.h> @@ -100,12 +100,7 @@ static struct platform_driver st_reset_driver = { }, }; -static int __init st_reset_init(void) -{ - return platform_driver_register(&st_reset_driver); -} - -device_initcall(st_reset_init); +builtin_platform_driver(st_reset_driver); MODULE_AUTHOR("Christophe Kerello <christophe.kerello@st.com>"); MODULE_DESCRIPTION("STMicroelectronics Power off Restart driver"); diff --git a/drivers/power/reset/syscon-poweroff.c b/drivers/power/reset/syscon-poweroff.c index ed58bdf41e27..203936f4c544 100644 --- a/drivers/power/reset/syscon-poweroff.c +++ b/drivers/power/reset/syscon-poweroff.c @@ -10,75 +10,77 @@ #include <linux/io.h> #include <linux/notifier.h> #include <linux/mfd/syscon.h> -#include <linux/of_address.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/pm.h> +#include <linux/reboot.h> #include <linux/regmap.h> -static struct regmap *map; -static u32 offset; -static u32 value; -static u32 mask; +struct syscon_poweroff_data { + struct regmap *map; + u32 offset; + u32 value; + u32 mask; +}; -static void syscon_poweroff(void) +static int syscon_poweroff(struct sys_off_data *off_data) { + struct syscon_poweroff_data *data = off_data->cb_data; + /* Issue the poweroff */ - regmap_update_bits(map, offset, mask, value); + regmap_update_bits(data->map, data->offset, data->mask, data->value); mdelay(1000); pr_emerg("Unable to poweroff system\n"); + + return NOTIFY_DONE; } static int syscon_poweroff_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; + struct syscon_poweroff_data *data; int mask_err, value_err; - map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap"); - if (IS_ERR(map)) { - dev_err(&pdev->dev, "unable to get syscon"); - return PTR_ERR(map); + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->map = syscon_regmap_lookup_by_phandle(dev->of_node, "regmap"); + if (IS_ERR(data->map)) { + data->map = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(data->map)) { + dev_err(dev, "unable to get syscon"); + return PTR_ERR(data->map); + } } - if (of_property_read_u32(pdev->dev.of_node, "offset", &offset)) { - dev_err(&pdev->dev, "unable to read 'offset'"); + if (of_property_read_u32(dev->of_node, "offset", &data->offset)) { + dev_err(dev, "unable to read 'offset'"); return -EINVAL; } - value_err = of_property_read_u32(pdev->dev.of_node, "value", &value); - mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &mask); + value_err = of_property_read_u32(dev->of_node, "value", &data->value); + mask_err = of_property_read_u32(dev->of_node, "mask", &data->mask); if (value_err && mask_err) { - dev_err(&pdev->dev, "unable to read 'value' and 'mask'"); + dev_err(dev, "unable to read 'value' and 'mask'"); return -EINVAL; } if (value_err) { /* support old binding */ - value = mask; - mask = 0xFFFFFFFF; + data->value = data->mask; + data->mask = 0xFFFFFFFF; } else if (mask_err) { /* support value without mask*/ - mask = 0xFFFFFFFF; + data->mask = 0xFFFFFFFF; } - if (pm_power_off) { - dev_err(&pdev->dev, "pm_power_off already claimed for %ps", - pm_power_off); - return -EBUSY; - } - - pm_power_off = syscon_poweroff; - - return 0; -} - -static int syscon_poweroff_remove(struct platform_device *pdev) -{ - if (pm_power_off == syscon_poweroff) - pm_power_off = NULL; - - return 0; + return devm_register_sys_off_handler(&pdev->dev, + SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, + syscon_poweroff, data); } static const struct of_device_id syscon_poweroff_of_match[] = { @@ -88,15 +90,9 @@ static const struct of_device_id syscon_poweroff_of_match[] = { static struct platform_driver syscon_poweroff_driver = { .probe = syscon_poweroff_probe, - .remove = syscon_poweroff_remove, .driver = { .name = "syscon-poweroff", .of_match_table = syscon_poweroff_of_match, }, }; - -static int __init syscon_poweroff_register(void) -{ - return platform_driver_register(&syscon_poweroff_driver); -} -device_initcall(syscon_poweroff_register); +builtin_platform_driver(syscon_poweroff_driver); diff --git a/drivers/power/reset/syscon-reboot.c b/drivers/power/reset/syscon-reboot.c index 510e363381ca..2e2cf5f62d73 100644 --- a/drivers/power/reset/syscon-reboot.c +++ b/drivers/power/reset/syscon-reboot.c @@ -9,17 +9,29 @@ #include <linux/io.h> #include <linux/notifier.h> #include <linux/mfd/syscon.h> -#include <linux/of_address.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/platform_device.h> #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; }; @@ -29,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); @@ -43,7 +67,7 @@ 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; ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); @@ -57,27 +81,40 @@ static int syscon_reboot_probe(struct platform_device *pdev) return PTR_ERR(ctx->map); } - if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset)) - return -EINVAL; + if (of_property_read_s32(pdev->dev.of_node, "priority", &priority)) + priority = 192; - 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; - } + ctx->rd = of_device_get_match_data(dev); + if (!ctx->rd) { + int mask_err, value_err; + + 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; - ctx->restart_handler.priority = 192; + ctx->restart_handler.priority = priority; err = register_restart_handler(&ctx->restart_handler); if (err) dev_err(dev, "can't register restart notifier (err=%d)\n", err); @@ -85,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..3302a127fce5 --- /dev/null +++ b/drivers/power/reset/tdx-ec-poweroff.c @@ -0,0 +1,150 @@ +// 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/device.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 + +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); + + 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); + + 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/reset/tps65086-restart.c b/drivers/power/reset/tps65086-restart.c index 78b89f745a3d..6976dbcac74f 100644 --- a/drivers/power/reset/tps65086-restart.c +++ b/drivers/power/reset/tps65086-restart.c @@ -9,22 +9,14 @@ #include <linux/platform_device.h> #include <linux/reboot.h> -struct tps65086_restart { - struct notifier_block handler; - struct device *dev; -}; - -static int tps65086_restart_notify(struct notifier_block *this, - unsigned long mode, void *cmd) +static int tps65086_restart_notify(struct sys_off_data *data) { - struct tps65086_restart *tps65086_restart = - container_of(this, struct tps65086_restart, handler); - struct tps65086 *tps65086 = dev_get_drvdata(tps65086_restart->dev->parent); + struct tps65086 *tps65086 = data->cb_data; int ret; ret = regmap_write(tps65086->regmap, TPS65086_FORCESHUTDN, 1); if (ret) { - dev_err(tps65086_restart->dev, "%s: error writing to tps65086 pmic: %d\n", + dev_err(tps65086->dev, "%s: error writing to tps65086 pmic: %d\n", __func__, ret); return NOTIFY_DONE; } @@ -39,42 +31,13 @@ static int tps65086_restart_notify(struct notifier_block *this, static int tps65086_restart_probe(struct platform_device *pdev) { - struct tps65086_restart *tps65086_restart; - int ret; - - tps65086_restart = devm_kzalloc(&pdev->dev, sizeof(*tps65086_restart), GFP_KERNEL); - if (!tps65086_restart) - return -ENOMEM; - - platform_set_drvdata(pdev, tps65086_restart); - - tps65086_restart->handler.notifier_call = tps65086_restart_notify; - tps65086_restart->handler.priority = 192; - tps65086_restart->dev = &pdev->dev; - - ret = register_restart_handler(&tps65086_restart->handler); - if (ret) { - dev_err(&pdev->dev, "%s: cannot register restart handler: %d\n", - __func__, ret); - return -ENODEV; - } - - return 0; -} - -static int tps65086_restart_remove(struct platform_device *pdev) -{ - struct tps65086_restart *tps65086_restart = platform_get_drvdata(pdev); - int ret; - - ret = unregister_restart_handler(&tps65086_restart->handler); - if (ret) { - dev_err(&pdev->dev, "%s: cannot unregister restart handler: %d\n", - __func__, ret); - return -ENODEV; - } + struct tps65086 *tps65086 = dev_get_drvdata(pdev->dev.parent); - return 0; + return devm_register_sys_off_handler(&pdev->dev, + SYS_OFF_MODE_RESTART, + SYS_OFF_PRIO_HIGH, + tps65086_restart_notify, + tps65086); } static const struct platform_device_id tps65086_restart_id_table[] = { @@ -88,11 +51,9 @@ static struct platform_driver tps65086_restart_driver = { .name = "tps65086-restart", }, .probe = tps65086_restart_probe, - .remove = tps65086_restart_remove, .id_table = tps65086_restart_id_table, }; module_platform_driver(tps65086_restart_driver); MODULE_AUTHOR("Emil Renner Berthing <kernel@esmil.dk>"); MODULE_DESCRIPTION("TPS65086 restart driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/vexpress-poweroff.c b/drivers/power/reset/vexpress-poweroff.c index 447ffdacddf9..bb22b2db5907 100644 --- a/drivers/power/reset/vexpress-poweroff.c +++ b/drivers/power/reset/vexpress-poweroff.c @@ -7,8 +7,8 @@ #include <linux/delay.h> #include <linux/notifier.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/reboot.h> #include <linux/stat.h> #include <linux/vexpress.h> @@ -108,20 +108,17 @@ static int _vexpress_register_restart_handler(struct device *dev) static int vexpress_reset_probe(struct platform_device *pdev) { - const struct of_device_id *match = - of_match_device(vexpress_reset_of_match, &pdev->dev); + enum vexpress_reset_func func; struct regmap *regmap; int ret = 0; - if (!match) - return -EINVAL; - regmap = devm_regmap_init_vexpress_config(&pdev->dev); if (IS_ERR(regmap)) return PTR_ERR(regmap); dev_set_drvdata(&pdev->dev, regmap); - switch ((enum vexpress_reset_func)match->data) { + func = (uintptr_t)device_get_match_data(&pdev->dev); + switch (func) { case FUNC_SHUTDOWN: vexpress_power_off_device = &pdev->dev; pm_power_off = vexpress_power_off; diff --git a/drivers/power/reset/xgene-reboot.c b/drivers/power/reset/xgene-reboot.c index 0b0d2fd2bd0c..b5eee19bac42 100644 --- a/drivers/power/reset/xgene-reboot.c +++ b/drivers/power/reset/xgene-reboot.c @@ -13,7 +13,7 @@ #include <linux/delay.h> #include <linux/io.h> #include <linux/notifier.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/of_address.h> #include <linux/platform_device.h> #include <linux/reboot.h> @@ -22,17 +22,13 @@ struct xgene_reboot_context { struct device *dev; - void *csr; + void __iomem *csr; u32 mask; - struct notifier_block restart_handler; }; -static int xgene_restart_handler(struct notifier_block *this, - unsigned long mode, void *cmd) +static int xgene_restart_handler(struct sys_off_data *data) { - struct xgene_reboot_context *ctx = - container_of(this, struct xgene_reboot_context, - restart_handler); + struct xgene_reboot_context *ctx = data->cb_data; /* Issue the reboot */ writel(ctx->mask, ctx->csr); @@ -54,23 +50,20 @@ static int xgene_reboot_probe(struct platform_device *pdev) if (!ctx) return -ENOMEM; - ctx->csr = of_iomap(dev->of_node, 0); - if (!ctx->csr) { + ctx->csr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctx->csr)) { dev_err(dev, "can not map resource\n"); - return -ENODEV; + return PTR_ERR(ctx->csr); } if (of_property_read_u32(dev->of_node, "mask", &ctx->mask)) ctx->mask = 0xFFFFFFFF; ctx->dev = dev; - ctx->restart_handler.notifier_call = xgene_restart_handler; - ctx->restart_handler.priority = 128; - err = register_restart_handler(&ctx->restart_handler); - if (err) { - iounmap(ctx->csr); + err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART, 128, + xgene_restart_handler, ctx); + if (err) dev_err(dev, "cannot register restart handler (err=%d)\n", err); - } return err; } @@ -87,9 +80,4 @@ static struct platform_driver xgene_reboot_driver = { .of_match_table = xgene_reboot_of_match, }, }; - -static int __init xgene_reboot_init(void) -{ - return platform_driver_register(&xgene_reboot_driver); -} -device_initcall(xgene_reboot_init); +builtin_platform_driver(xgene_reboot_driver); diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig new file mode 100644 index 000000000000..280f92beb5d0 --- /dev/null +++ b/drivers/power/sequencing/Kconfig @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menuconfig POWER_SEQUENCING + tristate "Power Sequencing support" + help + Say Y here to enable the Power Sequencing subsystem. + + This subsystem is designed to control power to devices that share + complex resources and/or require specific power sequences to be run + during power-up. + + If unsure, say no. + +if POWER_SEQUENCING + +config POWER_SEQUENCING_QCOM_WCN + tristate "Qualcomm WCN family PMU driver" + default m if ARCH_QCOM + depends on OF || COMPILE_TEST + help + Say Y here to enable the power sequencing driver for Qualcomm + WCN Bluetooth/WLAN chipsets. + + Typically, a package from the Qualcomm WCN family contains the BT + and WLAN modules whose power is controlled by the PMU module. As the + former two share the power-up sequence which is executed by the PMU, + 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. + +endif diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/Makefile new file mode 100644 index 000000000000..96c1cf0a98ac --- /dev/null +++ b/drivers/power/sequencing/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +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 diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c new file mode 100644 index 000000000000..190564e55988 --- /dev/null +++ b/drivers/power/sequencing/core.c @@ -0,0 +1,1106 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Linaro Ltd. + */ + +#include <linux/bug.h> +#include <linux/cleanup.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/idr.h> +#include <linux/kernel.h> +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/lockdep.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/property.h> +#include <linux/pwrseq/consumer.h> +#include <linux/pwrseq/provider.h> +#include <linux/radix-tree.h> +#include <linux/rwsem.h> +#include <linux/slab.h> + +/* + * Power-sequencing framework for linux. + * + * This subsystem allows power sequence providers to register a set of targets + * that consumers may request and power-up/down. + * + * Glossary: + * + * Unit - a unit is a discreet chunk of a power sequence. For instance one unit + * may enable a set of regulators, another may enable a specific GPIO. Units + * can define dependencies in the form of other units that must be enabled + * before it itself can be. + * + * Target - a target is a set of units (composed of the "final" unit and its + * dependencies) that a consumer selects by its name when requesting a handle + * to the power sequencer. Via the dependency system, multiple targets may + * share the same parts of a power sequence but ignore parts that are + * irrelevant. + * + * Descriptor - a handle passed by the pwrseq core to every consumer that + * serves as the entry point to the provider layer. It ensures coherence + * between different users and keeps reference counting consistent. + * + * Each provider must define a .match() callback whose role is to determine + * whether a potential consumer is in fact associated with this sequencer. + * This allows creating abstraction layers on top of regular device-tree + * resources like regulators, clocks and other nodes connected to the consumer + * via phandle. + */ + +static DEFINE_IDA(pwrseq_ida); + +/* + * Protects the device list on the pwrseq bus from concurrent modifications + * but allows simultaneous read-only access. + */ +static DECLARE_RWSEM(pwrseq_sem); + +/** + * struct pwrseq_unit - Private power-sequence unit data. + * @ref: Reference count for this object. When it goes to 0, the object is + * destroyed. + * @name: Name of this target. + * @list: Link to siblings on the list of all units of a single sequencer. + * @deps: List of units on which this unit depends. + * @enable: Callback running the part of the power-on sequence provided by + * this unit. + * @disable: Callback running the part of the power-off sequence provided + * by this unit. + * @enable_count: Current number of users that enabled this unit. May be the + * consumer of the power sequencer or other units that depend + * on this one. + */ +struct pwrseq_unit { + struct kref ref; + const char *name; + struct list_head list; + struct list_head deps; + pwrseq_power_state_func enable; + pwrseq_power_state_func disable; + unsigned int enable_count; +}; + +static struct pwrseq_unit *pwrseq_unit_new(const struct pwrseq_unit_data *data) +{ + struct pwrseq_unit *unit; + + unit = kzalloc(sizeof(*unit), GFP_KERNEL); + if (!unit) + return NULL; + + unit->name = kstrdup_const(data->name, GFP_KERNEL); + if (!unit->name) { + kfree(unit); + return NULL; + } + + kref_init(&unit->ref); + INIT_LIST_HEAD(&unit->deps); + unit->enable = data->enable; + unit->disable = data->disable; + + return unit; +} + +static struct pwrseq_unit *pwrseq_unit_get(struct pwrseq_unit *unit) +{ + kref_get(&unit->ref); + + return unit; +} + +static void pwrseq_unit_release(struct kref *ref); + +static void pwrseq_unit_put(struct pwrseq_unit *unit) +{ + kref_put(&unit->ref, pwrseq_unit_release); +} + +/** + * struct pwrseq_unit_dep - Wrapper around a reference to the unit structure + * allowing to keep it on multiple dependency lists + * in different units. + * @list: Siblings on the list. + * @unit: Address of the referenced unit. + */ +struct pwrseq_unit_dep { + struct list_head list; + struct pwrseq_unit *unit; +}; + +static struct pwrseq_unit_dep *pwrseq_unit_dep_new(struct pwrseq_unit *unit) +{ + struct pwrseq_unit_dep *dep; + + dep = kzalloc(sizeof(*dep), GFP_KERNEL); + if (!dep) + return NULL; + + dep->unit = unit; + + return dep; +} + +static void pwrseq_unit_dep_free(struct pwrseq_unit_dep *ref) +{ + pwrseq_unit_put(ref->unit); + kfree(ref); +} + +static void pwrseq_unit_free_deps(struct list_head *list) +{ + struct pwrseq_unit_dep *dep, *next; + + list_for_each_entry_safe(dep, next, list, list) { + list_del(&dep->list); + pwrseq_unit_dep_free(dep); + } +} + +static void pwrseq_unit_release(struct kref *ref) +{ + struct pwrseq_unit *unit = container_of(ref, struct pwrseq_unit, ref); + + pwrseq_unit_free_deps(&unit->deps); + list_del(&unit->list); + kfree_const(unit->name); + kfree(unit); +} + +/** + * struct pwrseq_target - Private power-sequence target data. + * @list: Siblings on the list of all targets exposed by a power sequencer. + * @name: Name of the target. + * @unit: Final unit for this target. + * @post_enable: Callback run after the target unit has been enabled, *after* + * the state lock has been released. It's useful for implementing + * boot-up delays without blocking other users from powering up + * using the same power sequencer. + */ +struct pwrseq_target { + struct list_head list; + const char *name; + struct pwrseq_unit *unit; + pwrseq_power_state_func post_enable; +}; + +static struct pwrseq_target * +pwrseq_target_new(const struct pwrseq_target_data *data) +{ + struct pwrseq_target *target; + + target = kzalloc(sizeof(*target), GFP_KERNEL); + if (!target) + return NULL; + + target->name = kstrdup_const(data->name, GFP_KERNEL); + if (!target->name) { + kfree(target); + return NULL; + } + + target->post_enable = data->post_enable; + + return target; +} + +static void pwrseq_target_free(struct pwrseq_target *target) +{ + if (!IS_ERR_OR_NULL(target->unit)) + pwrseq_unit_put(target->unit); + kfree_const(target->name); + kfree(target); +} + +/** + * struct pwrseq_device - Private power sequencing data. + * @dev: Device struct associated with this sequencer. + * @id: Device ID. + * @owner: Prevents removal of active power sequencing providers. + * @rw_lock: Protects the device from being unregistered while in use. + * @state_lock: Prevents multiple users running the power sequence at the same + * time. + * @match: Power sequencer matching callback. + * @targets: List of targets exposed by this sequencer. + * @units: List of all units supported by this sequencer. + */ +struct pwrseq_device { + struct device dev; + int id; + struct module *owner; + struct rw_semaphore rw_lock; + struct mutex state_lock; + pwrseq_match_func match; + struct list_head targets; + struct list_head units; +}; + +static struct pwrseq_device *to_pwrseq_device(struct device *dev) +{ + return container_of(dev, struct pwrseq_device, dev); +} + +static struct pwrseq_device *pwrseq_device_get(struct pwrseq_device *pwrseq) +{ + get_device(&pwrseq->dev); + + return pwrseq; +} + +static void pwrseq_device_put(struct pwrseq_device *pwrseq) +{ + put_device(&pwrseq->dev); +} + +/** + * struct pwrseq_desc - Wraps access to the pwrseq_device and ensures that one + * user cannot break the reference counting for others. + * @pwrseq: Reference to the power sequencing device. + * @target: Reference to the target this descriptor allows to control. + * @powered_on: Power state set by the holder of the descriptor (not necessarily + * corresponding to the actual power state of the device). + */ +struct pwrseq_desc { + struct pwrseq_device *pwrseq; + struct pwrseq_target *target; + bool powered_on; +}; + +static const struct bus_type pwrseq_bus = { + .name = "pwrseq", +}; + +static void pwrseq_release(struct device *dev) +{ + struct pwrseq_device *pwrseq = to_pwrseq_device(dev); + struct pwrseq_target *target, *pos; + + list_for_each_entry_safe(target, pos, &pwrseq->targets, list) { + list_del(&target->list); + pwrseq_target_free(target); + } + + mutex_destroy(&pwrseq->state_lock); + ida_free(&pwrseq_ida, pwrseq->id); + kfree(pwrseq); +} + +static const struct device_type pwrseq_device_type = { + .name = "power_sequencer", + .release = pwrseq_release, +}; + +static int pwrseq_check_unit_deps(const struct pwrseq_unit_data *data, + struct radix_tree_root *visited_units) +{ + const struct pwrseq_unit_data *tmp, **cur; + int ret; + + ret = radix_tree_insert(visited_units, (unsigned long)data, + (void *)data); + if (ret) + return ret; + + for (cur = data->deps; cur && *cur; cur++) { + tmp = radix_tree_lookup(visited_units, (unsigned long)*cur); + if (tmp) { + WARN(1, "Circular dependency in power sequencing flow detected!\n"); + return -EINVAL; + } + + ret = pwrseq_check_unit_deps(*cur, visited_units); + if (ret) + return ret; + } + + return 0; +} + +static int pwrseq_check_target_deps(const struct pwrseq_target_data *data) +{ + struct radix_tree_root visited_units; + struct radix_tree_iter iter; + void __rcu **slot; + int ret; + + if (!data->unit) + return -EINVAL; + + INIT_RADIX_TREE(&visited_units, GFP_KERNEL); + ret = pwrseq_check_unit_deps(data->unit, &visited_units); + radix_tree_for_each_slot(slot, &visited_units, &iter, 0) + radix_tree_delete(&visited_units, iter.index); + + return ret; +} + +static int pwrseq_unit_setup_deps(const struct pwrseq_unit_data **data, + struct list_head *dep_list, + struct list_head *unit_list, + struct radix_tree_root *processed_units); + +static struct pwrseq_unit * +pwrseq_unit_setup(const struct pwrseq_unit_data *data, + struct list_head *unit_list, + struct radix_tree_root *processed_units) +{ + struct pwrseq_unit *unit; + int ret; + + unit = radix_tree_lookup(processed_units, (unsigned long)data); + if (unit) + return pwrseq_unit_get(unit); + + unit = pwrseq_unit_new(data); + if (!unit) + return ERR_PTR(-ENOMEM); + + if (data->deps) { + ret = pwrseq_unit_setup_deps(data->deps, &unit->deps, + unit_list, processed_units); + if (ret) { + pwrseq_unit_put(unit); + return ERR_PTR(ret); + } + } + + ret = radix_tree_insert(processed_units, (unsigned long)data, unit); + if (ret) { + pwrseq_unit_put(unit); + return ERR_PTR(ret); + } + + list_add_tail(&unit->list, unit_list); + + return unit; +} + +static int pwrseq_unit_setup_deps(const struct pwrseq_unit_data **data, + struct list_head *dep_list, + struct list_head *unit_list, + struct radix_tree_root *processed_units) +{ + const struct pwrseq_unit_data *pos; + struct pwrseq_unit_dep *dep; + struct pwrseq_unit *unit; + int i; + + for (i = 0; data[i]; i++) { + pos = data[i]; + + unit = pwrseq_unit_setup(pos, unit_list, processed_units); + if (IS_ERR(unit)) + return PTR_ERR(unit); + + dep = pwrseq_unit_dep_new(unit); + if (!dep) { + pwrseq_unit_put(unit); + return -ENOMEM; + } + + list_add_tail(&dep->list, dep_list); + } + + return 0; +} + +static int pwrseq_do_setup_targets(const struct pwrseq_target_data **data, + struct pwrseq_device *pwrseq, + struct radix_tree_root *processed_units) +{ + const struct pwrseq_target_data *pos; + struct pwrseq_target *target; + int ret, i; + + for (i = 0; data[i]; i++) { + pos = data[i]; + + ret = pwrseq_check_target_deps(pos); + if (ret) + return ret; + + target = pwrseq_target_new(pos); + if (!target) + return -ENOMEM; + + target->unit = pwrseq_unit_setup(pos->unit, &pwrseq->units, + processed_units); + if (IS_ERR(target->unit)) { + ret = PTR_ERR(target->unit); + pwrseq_target_free(target); + return ret; + } + + list_add_tail(&target->list, &pwrseq->targets); + } + + return 0; +} + +static int pwrseq_setup_targets(const struct pwrseq_target_data **targets, + struct pwrseq_device *pwrseq) +{ + struct radix_tree_root processed_units; + struct radix_tree_iter iter; + void __rcu **slot; + int ret; + + INIT_RADIX_TREE(&processed_units, GFP_KERNEL); + ret = pwrseq_do_setup_targets(targets, pwrseq, &processed_units); + radix_tree_for_each_slot(slot, &processed_units, &iter, 0) + radix_tree_delete(&processed_units, iter.index); + + return ret; +} + +/** + * pwrseq_device_register() - Register a new power sequencer. + * @config: Configuration of the new power sequencing device. + * + * The config structure is only used during the call and can be freed after + * the function returns. The config structure *must* have the parent device + * as well as the match() callback and at least one target set. + * + * Returns: + * Returns the address of the new pwrseq device or ERR_PTR() on failure. + */ +struct pwrseq_device * +pwrseq_device_register(const struct pwrseq_config *config) +{ + struct pwrseq_device *pwrseq; + int ret, id; + + if (!config->parent || !config->match || !config->targets || + !config->targets[0]) + return ERR_PTR(-EINVAL); + + pwrseq = kzalloc(sizeof(*pwrseq), GFP_KERNEL); + if (!pwrseq) + return ERR_PTR(-ENOMEM); + + pwrseq->dev.type = &pwrseq_device_type; + pwrseq->dev.bus = &pwrseq_bus; + pwrseq->dev.parent = config->parent; + device_set_node(&pwrseq->dev, dev_fwnode(config->parent)); + dev_set_drvdata(&pwrseq->dev, config->drvdata); + + id = ida_alloc(&pwrseq_ida, GFP_KERNEL); + if (id < 0) { + kfree(pwrseq); + return ERR_PTR(id); + } + + pwrseq->id = id; + + /* + * From this point onwards the device's release() callback is + * responsible for freeing resources. + */ + device_initialize(&pwrseq->dev); + + ret = dev_set_name(&pwrseq->dev, "pwrseq.%d", pwrseq->id); + if (ret) + goto err_put_pwrseq; + + pwrseq->owner = config->owner ?: THIS_MODULE; + pwrseq->match = config->match; + + init_rwsem(&pwrseq->rw_lock); + mutex_init(&pwrseq->state_lock); + INIT_LIST_HEAD(&pwrseq->targets); + INIT_LIST_HEAD(&pwrseq->units); + + ret = pwrseq_setup_targets(config->targets, pwrseq); + if (ret) + goto err_put_pwrseq; + + scoped_guard(rwsem_write, &pwrseq_sem) { + ret = device_add(&pwrseq->dev); + if (ret) + goto err_put_pwrseq; + } + + return pwrseq; + +err_put_pwrseq: + pwrseq_device_put(pwrseq); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(pwrseq_device_register); + +/** + * pwrseq_device_unregister() - Unregister the power sequencer. + * @pwrseq: Power sequencer to unregister. + */ +void pwrseq_device_unregister(struct pwrseq_device *pwrseq) +{ + struct device *dev = &pwrseq->dev; + struct pwrseq_target *target; + + scoped_guard(mutex, &pwrseq->state_lock) { + guard(rwsem_write)(&pwrseq->rw_lock); + + list_for_each_entry(target, &pwrseq->targets, list) + WARN(target->unit->enable_count, + "REMOVING POWER SEQUENCER WITH ACTIVE USERS\n"); + + guard(rwsem_write)(&pwrseq_sem); + + device_del(dev); + } + + pwrseq_device_put(pwrseq); +} +EXPORT_SYMBOL_GPL(pwrseq_device_unregister); + +static void devm_pwrseq_device_unregister(void *data) +{ + struct pwrseq_device *pwrseq = data; + + pwrseq_device_unregister(pwrseq); +} + +/** + * devm_pwrseq_device_register() - Managed variant of pwrseq_device_register(). + * @dev: Managing device. + * @config: Configuration of the new power sequencing device. + * + * Returns: + * Returns the address of the new pwrseq device or ERR_PTR() on failure. + */ +struct pwrseq_device * +devm_pwrseq_device_register(struct device *dev, + const struct pwrseq_config *config) +{ + struct pwrseq_device *pwrseq; + int ret; + + pwrseq = pwrseq_device_register(config); + if (IS_ERR(pwrseq)) + return pwrseq; + + ret = devm_add_action_or_reset(dev, devm_pwrseq_device_unregister, + pwrseq); + if (ret) + return ERR_PTR(ret); + + return pwrseq; +} +EXPORT_SYMBOL_GPL(devm_pwrseq_device_register); + +/** + * pwrseq_device_get_drvdata() - Get the driver private data associated with + * this sequencer. + * @pwrseq: Power sequencer object. + * + * Returns: + * Address of the private driver data. + */ +void *pwrseq_device_get_drvdata(struct pwrseq_device *pwrseq) +{ + return dev_get_drvdata(&pwrseq->dev); +} +EXPORT_SYMBOL_GPL(pwrseq_device_get_drvdata); + +struct pwrseq_match_data { + struct pwrseq_desc *desc; + struct device *dev; + const char *target; +}; + +static int pwrseq_match_device(struct device *pwrseq_dev, void *data) +{ + struct pwrseq_device *pwrseq = to_pwrseq_device(pwrseq_dev); + struct pwrseq_match_data *match_data = data; + struct pwrseq_target *target; + int ret; + + lockdep_assert_held_read(&pwrseq_sem); + + guard(rwsem_read)(&pwrseq->rw_lock); + if (!device_is_registered(&pwrseq->dev)) + return 0; + + ret = pwrseq->match(pwrseq, match_data->dev); + if (ret == PWRSEQ_NO_MATCH || ret < 0) + return ret; + + /* We got the matching device, let's find the right target. */ + list_for_each_entry(target, &pwrseq->targets, list) { + if (strcmp(target->name, match_data->target)) + continue; + + match_data->desc->target = target; + } + + /* + * This device does not have this target. No point in deferring as it + * will not get a new target dynamically later. + */ + if (!match_data->desc->target) + return -ENOENT; + + if (!try_module_get(pwrseq->owner)) + return -EPROBE_DEFER; + + match_data->desc->pwrseq = pwrseq_device_get(pwrseq); + + return PWRSEQ_MATCH_OK; +} + +/** + * pwrseq_get() - Get the power sequencer associated with this device. + * @dev: Device for which to get the sequencer. + * @target: Name of the target exposed by the sequencer this device wants to + * reach. + * + * Returns: + * New power sequencer descriptor for use by the consumer driver or ERR_PTR() + * on failure. + */ +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); + if (!desc) + return ERR_PTR(-ENOMEM); + + match_data.desc = desc; + match_data.dev = dev; + match_data.target = target; + + guard(rwsem_read)(&pwrseq_sem); + + ret = bus_for_each_dev(&pwrseq_bus, NULL, &match_data, + pwrseq_match_device); + if (ret < 0) + return ERR_PTR(ret); + if (ret == PWRSEQ_NO_MATCH) + /* No device matched. */ + return ERR_PTR(-EPROBE_DEFER); + + return_ptr(desc); +} +EXPORT_SYMBOL_GPL(pwrseq_get); + +/** + * pwrseq_put() - Release the power sequencer descriptor. + * @desc: Descriptor to release. + */ +void pwrseq_put(struct pwrseq_desc *desc) +{ + struct pwrseq_device *pwrseq; + + if (!desc) + return; + + pwrseq = desc->pwrseq; + + if (desc->powered_on) + pwrseq_power_off(desc); + + kfree(desc); + module_put(pwrseq->owner); + pwrseq_device_put(pwrseq); +} +EXPORT_SYMBOL_GPL(pwrseq_put); + +static void devm_pwrseq_put(void *data) +{ + struct pwrseq_desc *desc = data; + + pwrseq_put(desc); +} + +/** + * devm_pwrseq_get() - Managed variant of pwrseq_get(). + * @dev: Device for which to get the sequencer and which also manages its + * lifetime. + * @target: Name of the target exposed by the sequencer this device wants to + * reach. + * + * Returns: + * New power sequencer descriptor for use by the consumer driver or ERR_PTR() + * on failure. + */ +struct pwrseq_desc *devm_pwrseq_get(struct device *dev, const char *target) +{ + struct pwrseq_desc *desc; + int ret; + + desc = pwrseq_get(dev, target); + if (IS_ERR(desc)) + return desc; + + ret = devm_add_action_or_reset(dev, devm_pwrseq_put, desc); + if (ret) + return ERR_PTR(ret); + + return desc; +} +EXPORT_SYMBOL_GPL(devm_pwrseq_get); + +static int pwrseq_unit_enable(struct pwrseq_device *pwrseq, + struct pwrseq_unit *target); +static int pwrseq_unit_disable(struct pwrseq_device *pwrseq, + struct pwrseq_unit *target); + +static int pwrseq_unit_enable_deps(struct pwrseq_device *pwrseq, + struct list_head *list) +{ + struct pwrseq_unit_dep *pos; + int ret = 0; + + list_for_each_entry(pos, list, list) { + ret = pwrseq_unit_enable(pwrseq, pos->unit); + if (ret) { + list_for_each_entry_continue_reverse(pos, list, list) + pwrseq_unit_disable(pwrseq, pos->unit); + break; + } + } + + return ret; +} + +static int pwrseq_unit_disable_deps(struct pwrseq_device *pwrseq, + struct list_head *list) +{ + struct pwrseq_unit_dep *pos; + int ret = 0; + + list_for_each_entry_reverse(pos, list, list) { + ret = pwrseq_unit_disable(pwrseq, pos->unit); + if (ret) { + list_for_each_entry_continue(pos, list, list) + pwrseq_unit_enable(pwrseq, pos->unit); + break; + } + } + + return ret; +} + +static int pwrseq_unit_enable(struct pwrseq_device *pwrseq, + struct pwrseq_unit *unit) +{ + int ret; + + lockdep_assert_held_read(&pwrseq->rw_lock); + lockdep_assert_held(&pwrseq->state_lock); + + if (unit->enable_count != 0) { + unit->enable_count++; + return 0; + } + + ret = pwrseq_unit_enable_deps(pwrseq, &unit->deps); + if (ret) { + dev_err(&pwrseq->dev, + "Failed to enable dependencies before power-on for target '%s': %d\n", + unit->name, ret); + return ret; + } + + if (unit->enable) { + ret = unit->enable(pwrseq); + if (ret) { + dev_err(&pwrseq->dev, + "Failed to enable target '%s': %d\n", + unit->name, ret); + pwrseq_unit_disable_deps(pwrseq, &unit->deps); + return ret; + } + } + + unit->enable_count++; + + return 0; +} + +static int pwrseq_unit_disable(struct pwrseq_device *pwrseq, + struct pwrseq_unit *unit) +{ + int ret; + + lockdep_assert_held_read(&pwrseq->rw_lock); + lockdep_assert_held(&pwrseq->state_lock); + + if (unit->enable_count == 0) { + WARN(1, "Unmatched power-off for target '%s'\n", + unit->name); + return -EBUSY; + } + + if (unit->enable_count != 1) { + unit->enable_count--; + return 0; + } + + if (unit->disable) { + ret = unit->disable(pwrseq); + if (ret) { + dev_err(&pwrseq->dev, + "Failed to disable target '%s': %d\n", + unit->name, ret); + return ret; + } + } + + ret = pwrseq_unit_disable_deps(pwrseq, &unit->deps); + if (ret) { + dev_err(&pwrseq->dev, + "Failed to disable dependencies after power-off for target '%s': %d\n", + unit->name, ret); + if (unit->enable) + unit->enable(pwrseq); + return ret; + } + + unit->enable_count--; + + return 0; +} + +/** + * pwrseq_power_on() - Issue a power-on request on behalf of the consumer + * device. + * @desc: Descriptor referencing the power sequencer. + * + * This function tells the power sequencer that the consumer wants to be + * powered-up. The sequencer may already have powered-up the device in which + * case the function returns 0. If the power-up sequence is already in + * progress, the function will block until it's done and return 0. If this is + * the first request, the device will be powered up. + * + * Returns: + * 0 on success, negative error number on failure. + */ +int pwrseq_power_on(struct pwrseq_desc *desc) +{ + struct pwrseq_device *pwrseq; + struct pwrseq_target *target; + struct pwrseq_unit *unit; + int ret; + + might_sleep(); + + if (!desc || desc->powered_on) + return 0; + + pwrseq = desc->pwrseq; + target = desc->target; + unit = target->unit; + + guard(rwsem_read)(&pwrseq->rw_lock); + if (!device_is_registered(&pwrseq->dev)) + return -ENODEV; + + scoped_guard(mutex, &pwrseq->state_lock) { + ret = pwrseq_unit_enable(pwrseq, unit); + if (!ret) + desc->powered_on = true; + } + + if (target->post_enable) { + ret = target->post_enable(pwrseq); + if (ret) { + pwrseq_unit_disable(pwrseq, unit); + desc->powered_on = false; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(pwrseq_power_on); + +/** + * pwrseq_power_off() - Issue a power-off request on behalf of the consumer + * device. + * @desc: Descriptor referencing the power sequencer. + * + * This undoes the effects of pwrseq_power_on(). It issues a power-off request + * on behalf of the consumer and when the last remaining user does so, the + * power-down sequence will be started. If one is in progress, the function + * will block until it's complete and then return. + * + * Returns: + * 0 on success, negative error number on failure. + */ +int pwrseq_power_off(struct pwrseq_desc *desc) +{ + struct pwrseq_device *pwrseq; + struct pwrseq_unit *unit; + int ret; + + might_sleep(); + + if (!desc || !desc->powered_on) + return 0; + + pwrseq = desc->pwrseq; + unit = desc->target->unit; + + guard(rwsem_read)(&pwrseq->rw_lock); + if (!device_is_registered(&pwrseq->dev)) + return -ENODEV; + + guard(mutex)(&pwrseq->state_lock); + + ret = pwrseq_unit_disable(pwrseq, unit); + if (!ret) + desc->powered_on = false; + + return ret; +} +EXPORT_SYMBOL_GPL(pwrseq_power_off); + +#if IS_ENABLED(CONFIG_DEBUG_FS) + +struct pwrseq_debugfs_count_ctx { + struct device *dev; + loff_t index; +}; + +static int pwrseq_debugfs_seq_count(struct device *dev, void *data) +{ + struct pwrseq_debugfs_count_ctx *ctx = data; + + ctx->dev = dev; + + return ctx->index-- ? 0 : 1; +} + +static void *pwrseq_debugfs_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct pwrseq_debugfs_count_ctx ctx; + + ctx.dev = NULL; + ctx.index = *pos; + + /* + * We're holding the lock for the entire printout so no need to fiddle + * with device reference count. + */ + down_read(&pwrseq_sem); + + bus_for_each_dev(&pwrseq_bus, NULL, &ctx, pwrseq_debugfs_seq_count); + if (!ctx.index) + return NULL; + + return ctx.dev; +} + +static void *pwrseq_debugfs_seq_next(struct seq_file *seq, void *data, + loff_t *pos) +{ + struct device *curr = data; + + ++*pos; + + struct device *next __free(put_device) = + bus_find_next_device(&pwrseq_bus, curr); + return next; +} + +static void pwrseq_debugfs_seq_show_target(struct seq_file *seq, + struct pwrseq_target *target) +{ + seq_printf(seq, " target: [%s] (target unit: [%s])\n", + target->name, target->unit->name); +} + +static void pwrseq_debugfs_seq_show_unit(struct seq_file *seq, + struct pwrseq_unit *unit) +{ + struct pwrseq_unit_dep *ref; + + seq_printf(seq, " unit: [%s] - enable count: %u\n", + unit->name, unit->enable_count); + + if (list_empty(&unit->deps)) + return; + + seq_puts(seq, " dependencies:\n"); + list_for_each_entry(ref, &unit->deps, list) + seq_printf(seq, " [%s]\n", ref->unit->name); +} + +static int pwrseq_debugfs_seq_show(struct seq_file *seq, void *data) +{ + struct device *dev = data; + struct pwrseq_device *pwrseq = to_pwrseq_device(dev); + struct pwrseq_target *target; + struct pwrseq_unit *unit; + + seq_printf(seq, "%s:\n", dev_name(dev)); + + seq_puts(seq, " targets:\n"); + list_for_each_entry(target, &pwrseq->targets, list) + pwrseq_debugfs_seq_show_target(seq, target); + + seq_puts(seq, " units:\n"); + list_for_each_entry(unit, &pwrseq->units, list) + pwrseq_debugfs_seq_show_unit(seq, unit); + + return 0; +} + +static void pwrseq_debugfs_seq_stop(struct seq_file *seq, void *data) +{ + up_read(&pwrseq_sem); +} + +static const struct seq_operations pwrseq_debugfs_sops = { + .start = pwrseq_debugfs_seq_start, + .next = pwrseq_debugfs_seq_next, + .show = pwrseq_debugfs_seq_show, + .stop = pwrseq_debugfs_seq_stop, +}; +DEFINE_SEQ_ATTRIBUTE(pwrseq_debugfs); + +static struct dentry *pwrseq_debugfs_dentry; + +#endif /* CONFIG_DEBUG_FS */ + +static int __init pwrseq_init(void) +{ + int ret; + + ret = bus_register(&pwrseq_bus); + if (ret) { + pr_err("Failed to register the power sequencer bus\n"); + return ret; + } + +#if IS_ENABLED(CONFIG_DEBUG_FS) + pwrseq_debugfs_dentry = debugfs_create_file("pwrseq", 0444, NULL, NULL, + &pwrseq_debugfs_fops); +#endif /* CONFIG_DEBUG_FS */ + + return 0; +} +subsys_initcall(pwrseq_init); + +static void __exit pwrseq_exit(void) +{ +#if IS_ENABLED(CONFIG_DEBUG_FS) + debugfs_remove_recursive(pwrseq_debugfs_dentry); +#endif /* CONFIG_DEBUG_FS */ + + bus_unregister(&pwrseq_bus); +} +module_exit(pwrseq_exit); + +MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>"); +MODULE_DESCRIPTION("Power Sequencing subsystem core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c new file mode 100644 index 000000000000..663d9a537065 --- /dev/null +++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Linaro Ltd. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/jiffies.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/pwrseq/provider.h> +#include <linux/string.h> +#include <linux/types.h> + +struct pwrseq_qcom_wcn_pdata { + const char *const *vregs; + size_t num_vregs; + unsigned int pwup_delay_ms; + unsigned int gpio_enable_delay_ms; + const struct pwrseq_target_data **targets; +}; + +struct pwrseq_qcom_wcn_ctx { + struct pwrseq_device *pwrseq; + struct device_node *of_node; + const struct pwrseq_qcom_wcn_pdata *pdata; + struct regulator_bulk_data *regs; + struct gpio_desc *bt_gpio; + struct gpio_desc *wlan_gpio; + struct gpio_desc *xo_clk_gpio; + struct clk *clk; + unsigned long last_gpio_enable_jf; +}; + +static void pwrseq_qcom_wcn_ensure_gpio_delay(struct pwrseq_qcom_wcn_ctx *ctx) +{ + unsigned long diff_jiffies; + unsigned int diff_msecs; + + if (!ctx->pdata->gpio_enable_delay_ms) + return; + + diff_jiffies = jiffies - ctx->last_gpio_enable_jf; + diff_msecs = jiffies_to_msecs(diff_jiffies); + + if (diff_msecs < ctx->pdata->gpio_enable_delay_ms) + msleep(ctx->pdata->gpio_enable_delay_ms - diff_msecs); +} + +static int pwrseq_qcom_wcn_vregs_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_bulk_enable(ctx->pdata->num_vregs, ctx->regs); +} + +static int pwrseq_qcom_wcn_vregs_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_bulk_disable(ctx->pdata->num_vregs, ctx->regs); +} + +static const struct pwrseq_unit_data pwrseq_qcom_wcn_vregs_unit_data = { + .name = "regulators-enable", + .enable = pwrseq_qcom_wcn_vregs_enable, + .disable = pwrseq_qcom_wcn_vregs_disable, +}; + +static int pwrseq_qcom_wcn_clk_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return clk_prepare_enable(ctx->clk); +} + +static int pwrseq_qcom_wcn_clk_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + clk_disable_unprepare(ctx->clk); + + return 0; +} + +static const struct pwrseq_unit_data pwrseq_qcom_wcn_clk_unit_data = { + .name = "clock-enable", + .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, + NULL +}; + +static int pwrseq_qcom_wcn6855_clk_assert(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + if (!ctx->xo_clk_gpio) + return 0; + + msleep(1); + + gpiod_set_value_cansleep(ctx->xo_clk_gpio, 1); + usleep_range(100, 200); + + return 0; +} + +static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_xo_clk_assert = { + .name = "xo-clk-assert", + .enable = pwrseq_qcom_wcn6855_clk_assert, +}; + +static const struct pwrseq_unit_data *pwrseq_qcom_wcn6855_unit_deps[] = { + &pwrseq_qcom_wcn_vregs_unit_data, + &pwrseq_qcom_wcn_clk_unit_data, + &pwrseq_qcom_wcn6855_xo_clk_assert, + NULL +}; + +static int pwrseq_qcom_wcn_bt_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + pwrseq_qcom_wcn_ensure_gpio_delay(ctx); + gpiod_set_value_cansleep(ctx->bt_gpio, 1); + ctx->last_gpio_enable_jf = jiffies; + + return 0; +} + +static int pwrseq_qcom_wcn_bt_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + gpiod_set_value_cansleep(ctx->bt_gpio, 0); + + return 0; +} + +static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = { + .name = "bluetooth-enable", + .deps = pwrseq_qcom_wcn_unit_deps, + .enable = pwrseq_qcom_wcn_bt_enable, + .disable = pwrseq_qcom_wcn_bt_disable, +}; + +static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_bt_unit_data = { + .name = "bluetooth-enable", + .deps = pwrseq_qcom_wcn6855_unit_deps, + .enable = pwrseq_qcom_wcn_bt_enable, + .disable = pwrseq_qcom_wcn_bt_disable, +}; + +static int pwrseq_qcom_wcn_wlan_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + pwrseq_qcom_wcn_ensure_gpio_delay(ctx); + gpiod_set_value_cansleep(ctx->wlan_gpio, 1); + ctx->last_gpio_enable_jf = jiffies; + + return 0; +} + +static int pwrseq_qcom_wcn_wlan_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + gpiod_set_value_cansleep(ctx->wlan_gpio, 0); + + return 0; +} + +static const struct pwrseq_unit_data pwrseq_qcom_wcn_wlan_unit_data = { + .name = "wlan-enable", + .deps = pwrseq_qcom_wcn_unit_deps, + .enable = pwrseq_qcom_wcn_wlan_enable, + .disable = pwrseq_qcom_wcn_wlan_disable, +}; + +static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_wlan_unit_data = { + .name = "wlan-enable", + .deps = pwrseq_qcom_wcn6855_unit_deps, + .enable = pwrseq_qcom_wcn_wlan_enable, + .disable = pwrseq_qcom_wcn_wlan_disable, +}; + +static int pwrseq_qcom_wcn_pwup_delay(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + if (ctx->pdata->pwup_delay_ms) + msleep(ctx->pdata->pwup_delay_ms); + + return 0; +} + +static int pwrseq_qcom_wcn6855_xo_clk_deassert(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + if (ctx->xo_clk_gpio) { + usleep_range(2000, 5000); + gpiod_set_value_cansleep(ctx->xo_clk_gpio, 0); + } + + return pwrseq_qcom_wcn_pwup_delay(pwrseq); +} + +static const struct pwrseq_target_data pwrseq_qcom_wcn_bt_target_data = { + .name = "bluetooth", + .unit = &pwrseq_qcom_wcn_bt_unit_data, + .post_enable = pwrseq_qcom_wcn_pwup_delay, +}; + +static const struct pwrseq_target_data pwrseq_qcom_wcn_wlan_target_data = { + .name = "wlan", + .unit = &pwrseq_qcom_wcn_wlan_unit_data, + .post_enable = pwrseq_qcom_wcn_pwup_delay, +}; + +static const struct pwrseq_target_data pwrseq_qcom_wcn6855_bt_target_data = { + .name = "bluetooth", + .unit = &pwrseq_qcom_wcn6855_bt_unit_data, + .post_enable = pwrseq_qcom_wcn6855_xo_clk_deassert, +}; + +static const struct pwrseq_target_data pwrseq_qcom_wcn6855_wlan_target_data = { + .name = "wlan", + .unit = &pwrseq_qcom_wcn6855_wlan_unit_data, + .post_enable = pwrseq_qcom_wcn6855_xo_clk_deassert, +}; + +static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = { + &pwrseq_qcom_wcn_bt_target_data, + &pwrseq_qcom_wcn_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, + NULL +}; + +static const char *const pwrseq_qca6390_vregs[] = { + "vddio", + "vddaon", + "vddpmu", + "vddrfa0p95", + "vddrfa1p3", + "vddrfa1p9", + "vddpcie1p3", + "vddpcie1p9", +}; + +static const struct pwrseq_qcom_wcn_pdata pwrseq_qca6390_of_data = { + .vregs = pwrseq_qca6390_vregs, + .num_vregs = ARRAY_SIZE(pwrseq_qca6390_vregs), + .pwup_delay_ms = 60, + .gpio_enable_delay_ms = 100, + .targets = pwrseq_qcom_wcn_targets, +}; + +static const char *const pwrseq_wcn6750_vregs[] = { + "vddaon", + "vddasd", + "vddpmu", + "vddrfa0p8", + "vddrfa1p2", + "vddrfa1p7", + "vddrfa2p2", +}; + +static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn6750_of_data = { + .vregs = pwrseq_wcn6750_vregs, + .num_vregs = ARRAY_SIZE(pwrseq_wcn6750_vregs), + .pwup_delay_ms = 50, + .gpio_enable_delay_ms = 5, + .targets = pwrseq_qcom_wcn_targets, +}; + +static const char *const pwrseq_wcn6855_vregs[] = { + "vddio", + "vddaon", + "vddpmu", + "vddpmumx", + "vddpmucx", + "vddrfa0p95", + "vddrfa1p3", + "vddrfa1p9", + "vddpcie1p3", + "vddpcie1p9", +}; + +static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn6855_of_data = { + .vregs = pwrseq_wcn6855_vregs, + .num_vregs = ARRAY_SIZE(pwrseq_wcn6855_vregs), + .pwup_delay_ms = 50, + .gpio_enable_delay_ms = 5, + .targets = pwrseq_qcom_wcn6855_targets, +}; + +static const char *const pwrseq_wcn7850_vregs[] = { + "vdd", + "vddio", + "vddio1p2", + "vddaon", + "vdddig", + "vddrfa1p2", + "vddrfa1p8", +}; + +static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn7850_of_data = { + .vregs = pwrseq_wcn7850_vregs, + .num_vregs = ARRAY_SIZE(pwrseq_wcn7850_vregs), + .pwup_delay_ms = 50, + .targets = pwrseq_qcom_wcn_targets, +}; + +static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq, + struct device *dev) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + struct device_node *dev_node = dev->of_node; + + /* + * The PMU supplies power to the Bluetooth and WLAN modules. both + * consume the PMU AON output so check the presence of the + * 'vddaon-supply' property and whether it leads us to the right + * device. + */ + if (!of_property_present(dev_node, "vddaon-supply")) + return PWRSEQ_NO_MATCH; + + struct device_node *reg_node __free(device_node) = + of_parse_phandle(dev_node, "vddaon-supply", 0); + if (!reg_node) + return PWRSEQ_NO_MATCH; + + /* + * `reg_node` is the PMU AON regulator, its parent is the `regulators` + * node and finally its grandparent is the PMU device node that we're + * looking for. + */ + if (!reg_node->parent || !reg_node->parent->parent || + reg_node->parent->parent != ctx->of_node) + return PWRSEQ_NO_MATCH; + + return PWRSEQ_MATCH_OK; +} + +static int pwrseq_qcom_wcn_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwrseq_qcom_wcn_ctx *ctx; + struct pwrseq_config config; + int i, ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->of_node = dev->of_node; + + ctx->pdata = of_device_get_match_data(dev); + if (!ctx->pdata) + return dev_err_probe(dev, -ENODEV, + "Failed to obtain platform data\n"); + + ctx->regs = devm_kcalloc(dev, ctx->pdata->num_vregs, + sizeof(*ctx->regs), GFP_KERNEL); + if (!ctx->regs) + return -ENOMEM; + + for (i = 0; i < ctx->pdata->num_vregs; i++) + ctx->regs[i].supply = ctx->pdata->vregs[i]; + + ret = devm_regulator_bulk_get(dev, ctx->pdata->num_vregs, ctx->regs); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get all regulators\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), + "Failed to get the Bluetooth enable GPIO\n"); + + /* + * FIXME: This should actually be GPIOD_OUT_LOW, but doing so would + * cause the WLAN power to be toggled, resulting in PCIe link down. + * Since the PCIe controller driver is not handling link down currently, + * the device becomes unusable. So we need to keep this workaround until + * the link down handling is implemented in the controller driver. + */ + ctx->wlan_gpio = devm_gpiod_get_optional(dev, "wlan-enable", + GPIOD_ASIS); + if (IS_ERR(ctx->wlan_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->wlan_gpio), + "Failed to get the WLAN enable GPIO\n"); + + ctx->xo_clk_gpio = devm_gpiod_get_optional(dev, "xo-clk", + GPIOD_OUT_LOW); + if (IS_ERR(ctx->xo_clk_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->xo_clk_gpio), + "Failed to get the XO_CLK GPIO\n"); + + /* + * Set direction to output but keep the current value in order to not + * disable the WLAN module accidentally if it's already powered on. + */ + gpiod_direction_output(ctx->wlan_gpio, + gpiod_get_value_cansleep(ctx->wlan_gpio)); + + ctx->clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(ctx->clk)) + return dev_err_probe(dev, PTR_ERR(ctx->clk), + "Failed to get the reference clock\n"); + + memset(&config, 0, sizeof(config)); + + config.parent = dev; + config.owner = THIS_MODULE; + config.drvdata = ctx; + config.match = pwrseq_qcom_wcn_match; + config.targets = ctx->pdata->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 the power sequencer\n"); + + return 0; +} + +static const struct of_device_id pwrseq_qcom_wcn_of_match[] = { + { + .compatible = "qcom,qca6390-pmu", + .data = &pwrseq_qca6390_of_data, + }, + { + .compatible = "qcom,wcn6855-pmu", + .data = &pwrseq_wcn6855_of_data, + }, + { + .compatible = "qcom,wcn7850-pmu", + .data = &pwrseq_wcn7850_of_data, + }, + { + .compatible = "qcom,wcn6750-pmu", + .data = &pwrseq_wcn6750_of_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, pwrseq_qcom_wcn_of_match); + +static struct platform_driver pwrseq_qcom_wcn_driver = { + .driver = { + .name = "pwrseq-qcom_wcn", + .of_match_table = pwrseq_qcom_wcn_of_match, + }, + .probe = pwrseq_qcom_wcn_probe, +}; +module_platform_driver(pwrseq_qcom_wcn_driver); + +MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>"); +MODULE_DESCRIPTION("Qualcomm WCN PMU power sequencing driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/sequencing/pwrseq-thead-gpu.c b/drivers/power/sequencing/pwrseq-thead-gpu.c new file mode 100644 index 000000000000..7c82a10ca9f6 --- /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 = kcalloc(ctx->num_clks, sizeof(*ctx->clks), GFP_KERNEL); + 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_battery.c b/drivers/power/supply/88pm860x_battery.c index f3f3f8cd1a7f..edae1e843c51 100644 --- a/drivers/power/supply/88pm860x_battery.c +++ b/drivers/power/supply/88pm860x_battery.c @@ -14,6 +14,7 @@ #include <linux/mutex.h> #include <linux/string.h> #include <linux/power_supply.h> +#include <linux/string_choices.h> #include <linux/mfd/88pm860x.h> #include <linux/delay.h> @@ -422,7 +423,7 @@ static irqreturn_t pm860x_batt_handler(int irq, void *data) info->temp_type = PM860X_TEMP_TINT; } mutex_unlock(&info->lock); - /* clear ccnt since battery is attached or dettached */ + /* clear ccnt since battery is attached or detached */ clear_ccnt(info, &ccnt_data); return IRQ_HANDLED; } @@ -503,8 +504,7 @@ static void pm860x_init_battery(struct pm860x_battery_info *info) data = pm860x_reg_read(info->i2c, PM8607_POWER_UP_LOG); bat_remove = data & BAT_WU_LOG; - dev_dbg(info->dev, "battery wake up? %s\n", - bat_remove != 0 ? "yes" : "no"); + dev_dbg(info->dev, "battery wake up? %s\n", str_yes_no(bat_remove)); /* restore SOC from RTC domain register */ if (bat_remove == 0) { @@ -566,7 +566,7 @@ static int measure_temp(struct pm860x_battery_info *info, int *data) ret = measure_12bit_voltage(info, PM8607_GPADC1_MEAS1, data); if (ret) return ret; - /* meausered Vtbat(mV) / Ibias_current(11uA)*/ + /* measured Vtbat(mV) / Ibias_current(11uA)*/ *data = (*data * 1000) / GPBIAS2_GPADC1_UA; if (*data > TBAT_NEG_25D) { @@ -921,12 +921,12 @@ static int pm860x_battery_probe(struct platform_device *pdev) return -ENOMEM; info->irq_cc = platform_get_irq(pdev, 0); - if (info->irq_cc <= 0) - return -EINVAL; + if (info->irq_cc < 0) + return info->irq_cc; info->irq_batt = platform_get_irq(pdev, 1); - if (info->irq_batt <= 0) - return -EINVAL; + if (info->irq_batt < 0) + return info->irq_batt; info->chip = chip; info->i2c = 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 0bbfe6a7ce4d..92f9f7aae92f 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -28,15 +28,6 @@ config POWER_SUPPLY_HWMON Say 'Y' here if you want power supplies to have hwmon sysfs interface too. - -config PDA_POWER - tristate "Generic PDA/phone power driver" - depends on !S390 - help - Say Y here to enable generic power driver for PDAs and phones with - one or two external power supplies (AC/USB) connected to main and - backup batteries, and optional builtin charger. - config APM_POWER tristate "APM emulation for class batteries" depends on APM_EMULATION @@ -44,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 @@ -116,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 @@ -170,12 +176,31 @@ 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) help Say Y here to enable support for the LEGO MINDSTORMS EV3 battery. +config BATTERY_LENOVO_YOGA_C630 + tristate "Lenovo Yoga C630 battery" + depends on EC_LENOVO_YOGA_C630 + help + This driver enables battery support on the Lenovo Yoga C630 laptop. + + To compile the driver as a module, choose M here: the module will be + called lenovo_yoga_c630_battery. + config BATTERY_PMU tristate "Apple PMU battery" depends on PPC32 && ADB_PMU @@ -183,6 +208,15 @@ config BATTERY_PMU Say Y here to expose battery information on Apple machines through the generic battery class. +config BATTERY_QCOM_BATTMGR + tristate "Qualcomm PMIC GLINK battery manager support" + depends on QCOM_PMIC_GLINK + select AUXILIARY_BUS + help + Say Y here to enable the Qualcomm PMIC GLINK power supply driver, + which is used on modern Qualcomm platforms to provide battery and + power supply information. + config BATTERY_OLPC tristate "One Laptop Per Child battery" depends on OLPC_EC @@ -195,13 +229,6 @@ config BATTERY_SAMSUNG_SDI Say Y to enable support for Samsung SDI battery data. These batteries are used in Samsung mobile phones. -config BATTERY_TOSA - tristate "Sharp SL-6000 (tosa) battery" - depends on MACH_TOSA && MFD_TC6393XB && TOUCHSCREEN_WM97XX - help - Say Y to enable support for the battery on the Sharp Zaurus - SL-6000 (tosa) models. - config BATTERY_COLLIE tristate "Sharp SL-5500 (collie) battery" depends on SA1100_COLLIE && MCP_UCB1200 @@ -220,6 +247,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 @@ -381,7 +420,7 @@ config AXP288_FUEL_GAUGE config BATTERY_MAX17040 tristate "Maxim MAX17040/17041/17043 family Fuel Gauge" - depends on I2C + depends on I2C && IIO select REGMAP_I2C help Driver supports Maxim fuel-gauge systems for lithium-ion (Li+) @@ -409,6 +448,18 @@ config BATTERY_MAX17042 Driver can be build as a module (max17042_battery). +config BATTERY_MAX1720X + tristate "Maxim MAX17201/MAX17205 Fuel Gauge" + depends on I2C + select REGMAP_I2C + help + MAX1720x is a family of fuel-gauge systems for lithium-ion (Li+) + batteries in handheld and portable equipment. MAX17201 are + configured to operate with a single lithium cell, the MAX17205 + can operate with multiple cells. + + Say Y to include support for the MAX17201/MAX17205 Fuel Gauges. + config BATTERY_MAX1721X tristate "MAX17211/MAX17215 standalone gas-gauge" depends on W1 @@ -422,18 +473,6 @@ config BATTERY_MAX1721X Say Y here to enable support for the MAX17211/MAX17215 standalone battery gas-gauge. -config BATTERY_Z2 - tristate "Z2 battery driver" - depends on I2C && MACH_ZIPIT2 - help - Say Y to include support for the battery on the Zipit Z2. - -config BATTERY_S3C_ADC - tristate "Battery driver for Samsung ADC based monitoring" - depends on S3C_ADC - help - Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery - config BATTERY_TWL4030_MADC tristate "TWL4030 MADC battery driver" depends on TWL4030_MADC @@ -447,11 +486,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" @@ -491,6 +535,16 @@ config CHARGER_TWL4030 help Say Y here to enable support for TWL4030 Battery Charge Interface. +config CHARGER_TWL6030 + tristate "OMAP TWL6030 BCI charger driver" + depends on IIO && TWL4030_CORE + help + Say Y here to enable support for TWL6030/6032 Battery Charge + Interface. + + This driver can be build as a module. If so, the module will be + called twl6030_charger. + config CHARGER_LP8727 tristate "TI/National Semiconductor LP8727 charger driver" depends on I2C @@ -571,6 +625,12 @@ 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_MAX77976 tristate "Maxim MAX77976 battery charger driver" depends on I2C @@ -583,6 +643,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 @@ -648,6 +723,29 @@ config CHARGER_QCOM_SMBB documentation for more detail. The base name for this driver is 'pm8941_charger'. +config BATTERY_PM8916_BMS_VM + tristate "Qualcomm PM8916 BMS-VM support" + depends on MFD_SPMI_PMIC || COMPILE_TEST + help + Say Y to add support for Voltage Mode BMS block found in some + Qualcomm PMICs such as PM8916. This hardware block provides + battery voltage monitoring for the system. + + To compile this driver as module, choose M here: the + module will be called pm8916_bms_vm. + +config CHARGER_PM8916_LBC + tristate "Qualcomm PM8916 Linear Battery Charger support" + depends on MFD_SPMI_PMIC || COMPILE_TEST + depends on EXTCON || !EXTCON + help + Say Y here to add support for Linear Battery Charger block + found in some Qualcomm PMICs such as PM8916. This hardware + blokc provides simple CC/CV battery charger. + + To compile this driver as module, choose M here: the + module will be called pm8916_lbc. + config CHARGER_BQ2415X tristate "TI BQ2415x battery charger driver" depends on I2C @@ -695,6 +793,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 @@ -725,7 +830,7 @@ config CHARGER_BQ256XX config CHARGER_RK817 tristate "Rockchip RK817 PMIC Battery Charger" - depends on MFD_RK808 + depends on MFD_RK8XX help Say Y to include support for Rockchip RK817 Battery Charger. @@ -785,6 +890,15 @@ config BATTERY_RT5033 The fuelgauge calculates and determines the battery state of charge according to battery open circuit voltage. +config CHARGER_RT5033 + tristate "RT5033 battery charger support" + depends on MFD_RT5033 + depends on EXTCON || !EXTCON + help + This adds support for battery charger in Richtek RT5033 PMIC. + The device supports pre-charge mode, fast charge mode and + constant voltage mode. + config CHARGER_RT9455 tristate "Richtek RT9455 battery charger driver" depends on I2C @@ -793,6 +907,56 @@ config CHARGER_RT9455 help Say Y to enable support for Richtek RT9455 battery charger. +config CHARGER_RT9467 + tristate "Richtek RT9467 Battery Charger Driver" + depends on I2C && GPIOLIB && REGULATOR + select REGMAP_I2C + select REGMAP_IRQ + select LINEAR_RANGES + help + Say Y here to enable RT9467 Battery Charger. + RT9467 is a switch-mode single cell Li-Ion/Li-Polymer battery charger + for portable applications. It integrates a synchronous PWM controller, + power MOSFETs, input current sensing and regulation, high-accuracy + voltage regulation, and charge termination. The charge current is + regulated through integrated sensing resistors. It also features + USB On-The-Go (OTG) support and integrates D+/D- pin for USB + host/charging port detection. + + This driver can also be built as a module. If so, the module + will be called "rt9467-charger". + +config CHARGER_RT9471 + tristate "Richtek RT9471 battery charger driver" + depends on I2C && GPIOLIB && REGULATOR + select REGMAP_I2C + select REGMAP_IRQ + select LINEAR_RANGES + help + This adds support for Richtek RT9471 battery charger. RT9471 is + highly-integrated switch mode battery charger which is system power + patch manageable device for single cell Li-Ion and Li-polymer battery. + It can support BC12 detection on DPDM, and current and voltage + regulation on both charging and boost mode. + + 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 @@ -812,6 +976,18 @@ config CHARGER_CROS_PCHG the peripheral charge ports from the EC and converts that into power_supply properties. +config CHARGER_CROS_CONTROL + tristate "ChromeOS EC based charge control" + depends on MFD_CROS_EC_DEV + depends on ACPI_BATTERY + default MFD_CROS_EC_DEV + help + Say Y here to enable ChromeOS EC based battery charge control. + This driver can manage charge thresholds and behaviour. + + This driver can also be built as a module. If so, the module will be + called cros_charge-control. + config CHARGER_SC2731 tristate "Spreadtrum SC2731 charger driver" depends on MFD_SC27XX_PMIC || COMPILE_TEST @@ -827,6 +1003,15 @@ config FUEL_GAUGE_SC27XX Say Y here to enable support for fuel gauge with SC27XX PMIC chips. +config FUEL_GAUGE_STC3117 + tristate "STMicroelectronics STC3117 fuel gauge driver" + depends on CRC8 + depends on I2C + select REGMAP_I2C + help + Say Y here to enable support for fuel gauge with STC3117 + chip. + config CHARGER_UCS1002 tristate "Microchip UCS1002 USB Port Power Controller" depends on I2C @@ -837,6 +1022,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 @@ -906,6 +1100,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. @@ -918,4 +1113,23 @@ config BATTERY_UG3105 device is off or suspended, the functionality of this driver is limited to reporting capacity only. +config CHARGER_QCOM_SMB2 + tristate "Qualcomm PMI8998 PMIC charger driver" + depends on MFD_SPMI_PMIC + depends on IIO + help + Say Y here to enable the Qualcomm PMIC Charger driver. This + adds support for the SMB2 switch mode battery charger found + in PMI8998 and related PMICs. + +config FUEL_GAUGE_MM8013 + tristate "Mitsumi MM8013 fuel gauge driver" + depends on I2C + select REGMAP_I2C + help + Say Y here to enable the Mitsumi MM8013 fuel gauge driver. + It enables the monitoring of many battery parameters, including + the state of charge, temperature, cycle count, actual and design + capacity, etc. + endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 0ee8653e882e..4b79d5abc49a 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -7,9 +7,9 @@ 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_PDA_POWER) += pda_power.o obj-$(CONFIG_APM_POWER) += apm_power.o obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o obj-$(CONFIG_IP5XXX_POWER) += ip5xxx_power.o @@ -24,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 @@ -32,13 +33,16 @@ 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_TOSA) += tosa_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 @@ -53,20 +57,24 @@ obj-$(CONFIG_CHARGER_DA9150) += da9150-charger.o obj-$(CONFIG_BATTERY_DA9150) += da9150-fg.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o +obj-$(CONFIG_BATTERY_MAX1720X) += max1720x_battery.o obj-$(CONFIG_BATTERY_MAX1721X) += max1721x_battery.o -obj-$(CONFIG_BATTERY_Z2) += z2_battery.o obj-$(CONFIG_BATTERY_RT5033) += rt5033_battery.o +obj-$(CONFIG_CHARGER_RT5033) += rt5033_charger.o obj-$(CONFIG_CHARGER_RT9455) += rt9455_charger.o -obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.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 obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o +obj-$(CONFIG_CHARGER_TWL6030) += twl6030_charger.o obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o @@ -77,18 +85,23 @@ 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 obj-$(CONFIG_CHARGER_MT6360) += mt6360_charger.o obj-$(CONFIG_CHARGER_MT6370) += mt6370-charger.o obj-$(CONFIG_CHARGER_QCOM_SMBB) += qcom_smbb.o +obj-$(CONFIG_BATTERY_PM8916_BMS_VM) += pm8916_bms_vm.o +obj-$(CONFIG_CHARGER_PM8916_LBC) += pm8916_lbc.o obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o 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 @@ -98,11 +111,14 @@ obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o +obj-$(CONFIG_CHARGER_CROS_CONTROL) += cros_charge-control.o obj-$(CONFIG_CHARGER_CROS_USBPD) += cros_usbpd-charger.o obj-$(CONFIG_CHARGER_CROS_PCHG) += cros_peripheral_charger.o 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 @@ -110,3 +126,5 @@ 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_smbx.o +obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index 3e6ea22372b2..19ed52852804 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -16,7 +16,7 @@ /* Default: temperature hysteresis */ #define AB8500_TEMP_HYSTERESIS 3 -static struct power_supply_battery_ocv_table ocv_cap_tbl[] = { +static const struct power_supply_battery_ocv_table ocv_cap_tbl[] = { { .ocv = 4186000, .capacity = 100}, { .ocv = 4163000, .capacity = 99}, { .ocv = 4114000, .capacity = 95}, @@ -48,7 +48,7 @@ static struct power_supply_battery_ocv_table ocv_cap_tbl[] = { * temperature values to work. Factory resistance is 300 mOhm and the * resistance values to the right are percentages of 300 mOhm. */ -static struct power_supply_resistance_temp_table temp_to_batres_tbl_thermistor[] = { +static const struct power_supply_resistance_temp_table temp_to_batres_tbl_thermistor[] = { { .temp = 40, .resistance = 40 /* 120 mOhm */ }, { .temp = 30, .resistance = 45 /* 135 mOhm */ }, { .temp = 20, .resistance = 55 /* 165 mOhm */ }, diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index 307ee6f71042..e5202a7b6209 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -115,7 +115,6 @@ struct ab8500_btemp { static enum power_supply_property ab8500_btemp_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_TEMP, }; @@ -284,7 +283,7 @@ static void ab8500_btemp_periodic_work(struct work_struct *work) dev_warn(di->dev, "failed to identify the battery\n"); } - /* Failover if a reading is erroneous, use last meausurement */ + /* Failover if a reading is erroneous, use last measurement */ ret = thermal_zone_get_temp(di->tz, &bat_temp); if (ret) { dev_err(di->dev, "error reading temperature\n"); @@ -532,12 +531,6 @@ static int ab8500_btemp_get_property(struct power_supply *psy, else val->intval = 1; break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - if (di->bm->bi) - val->intval = di->bm->bi->technology; - else - val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; - break; case POWER_SUPPLY_PROP_TEMP: val->intval = ab8500_btemp_get_temp(di); break; @@ -547,10 +540,9 @@ static int ab8500_btemp_get_property(struct power_supply *psy, return 0; } -static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data) +static int ab8500_btemp_get_ext_psy_data(struct power_supply *ext, void *data) { struct power_supply *psy; - struct power_supply *ext = dev_get_drvdata(dev); const char **supplicants = (const char **)ext->supplied_to; struct ab8500_btemp *di; union power_supply_propval ret; @@ -624,10 +616,7 @@ static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data) */ static void ab8500_btemp_external_power_changed(struct power_supply *psy) { - struct ab8500_btemp *di = power_supply_get_drvdata(psy); - - class_for_each_device(power_supply_class, NULL, - di->btemp_psy, ab8500_btemp_get_ext_psy_data); + power_supply_for_each_psy(psy, ab8500_btemp_get_ext_psy_data); } /* ab8500 btemp driver interrupts and their respective isr */ @@ -664,7 +653,7 @@ static char *supply_interface[] = { static const struct power_supply_desc ab8500_btemp_desc = { .name = "ab8500_btemp", - .type = POWER_SUPPLY_TYPE_BATTERY, + .type = POWER_SUPPLY_TYPE_UNKNOWN, .properties = ab8500_btemp_props, .num_properties = ARRAY_SIZE(ab8500_btemp_props), .get_property = ab8500_btemp_get_property, @@ -678,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; @@ -813,11 +803,9 @@ static int ab8500_btemp_probe(struct platform_device *pdev) return component_add(dev, &ab8500_btemp_component_ops); } -static int ab8500_btemp_remove(struct platform_device *pdev) +static void ab8500_btemp_remove(struct platform_device *pdev) { component_del(&pdev->dev, &ab8500_btemp_component_ops); - - return 0; } static SIMPLE_DEV_PM_OPS(ab8500_btemp_pm_ops, ab8500_btemp_suspend, ab8500_btemp_resume); diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index ea4ad61d4c7e..dc6c8b0dd1cf 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -844,10 +844,9 @@ static void handle_maxim_chg_curr(struct ab8500_chargalg *di) } } -static int ab8500_chargalg_get_ext_psy_data(struct device *dev, void *data) +static int ab8500_chargalg_get_ext_psy_data(struct power_supply *ext, void *data) { struct power_supply *psy; - struct power_supply *ext = dev_get_drvdata(dev); const char **supplicants = (const char **)ext->supplied_to; struct ab8500_chargalg *di; union power_supply_propval ret; @@ -1225,14 +1224,13 @@ static bool ab8500_chargalg_time_to_restart(struct ab8500_chargalg *di) */ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) { + const struct power_supply_maintenance_charge_table *mt; struct power_supply_battery_info *bi = di->bm->bi; - struct power_supply_maintenance_charge_table *mt; int charger_status; int ret; /* Collect data from all power_supply class devices */ - class_for_each_device(power_supply_class, NULL, - di->chargalg_psy, ab8500_chargalg_get_ext_psy_data); + power_supply_for_each_psy(di->chargalg_psy, ab8500_chargalg_get_ext_psy_data); ab8500_chargalg_end_of_charge(di); ab8500_chargalg_check_temp(di); @@ -1720,7 +1718,7 @@ static char *supply_interface[] = { static const struct power_supply_desc ab8500_chargalg_desc = { .name = "ab8500_chargalg", - .type = POWER_SUPPLY_TYPE_BATTERY, + .type = POWER_SUPPLY_TYPE_UNKNOWN, .properties = ab8500_chargalg_props, .num_properties = ARRAY_SIZE(ab8500_chargalg_props), .get_property = ab8500_chargalg_get_property, @@ -1789,13 +1787,12 @@ static int ab8500_chargalg_probe(struct platform_device *pdev) psy_cfg.drv_data = di; /* Initilialize safety timer */ - hrtimer_init(&di->safety_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - di->safety_timer.function = ab8500_chargalg_safety_timer_expired; + hrtimer_setup(&di->safety_timer, ab8500_chargalg_safety_timer_expired, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); /* Initilialize maintenance timer */ - hrtimer_init(&di->maintenance_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - di->maintenance_timer.function = - ab8500_chargalg_maintenance_timer_expired; + hrtimer_setup(&di->maintenance_timer, ab8500_chargalg_maintenance_timer_expired, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); /* Init work for chargalg */ INIT_DEFERRABLE_WORK(&di->chargalg_periodic_work, @@ -1824,11 +1821,9 @@ static int ab8500_chargalg_probe(struct platform_device *pdev) return component_add(dev, &ab8500_chargalg_component_ops); } -static int ab8500_chargalg_remove(struct platform_device *pdev) +static void ab8500_chargalg_remove(struct platform_device *pdev) { component_del(&pdev->dev, &ab8500_chargalg_component_ops); - - return 0; } static SIMPLE_DEV_PM_OPS(ab8500_chargalg_pm_ops, ab8500_chargalg_suspend, ab8500_chargalg_resume); diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 308e68545d44..5f4537766e5b 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -487,14 +487,17 @@ static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) /* Only measure voltage if the charger is connected */ if (di->ac.charger_connected) { - ret = iio_read_channel_processed(di->adc_main_charger_v, &vch); - if (ret < 0) - dev_err(di->dev, "%s ADC conv failed,\n", __func__); + /* Convert to microvolt, IIO returns millivolt */ + ret = iio_read_channel_processed_scale(di->adc_main_charger_v, + &vch, 1000); + if (ret < 0) { + dev_err(di->dev, "%s ADC conv failed\n", __func__); + return ret; + } } else { vch = 0; } - /* Convert to microvolt, IIO returns millivolt */ - return vch * 1000; + return vch; } /** @@ -539,14 +542,17 @@ static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) /* Only measure voltage if the charger is connected */ if (di->usb.charger_connected) { - ret = iio_read_channel_processed(di->adc_vbus_v, &vch); - if (ret < 0) - dev_err(di->dev, "%s ADC conv failed,\n", __func__); + /* Convert to microvolt, IIO returns millivolt */ + ret = iio_read_channel_processed_scale(di->adc_vbus_v, + &vch, 1000); + if (ret < 0) { + dev_err(di->dev, "%s ADC conv failed\n", __func__); + return ret; + } } else { vch = 0; } - /* Convert to microvolt, IIO returns millivolt */ - return vch * 1000; + return vch; } /** @@ -562,14 +568,17 @@ static int ab8500_charger_get_usb_current(struct ab8500_charger *di) /* Only measure current if the charger is online */ if (di->usb.charger_online) { - ret = iio_read_channel_processed(di->adc_usb_charger_c, &ich); - if (ret < 0) - dev_err(di->dev, "%s ADC conv failed,\n", __func__); + /* Return microamperes */ + ret = iio_read_channel_processed_scale(di->adc_usb_charger_c, + &ich, 1000); + if (ret < 0) { + dev_err(di->dev, "%s ADC conv failed\n", __func__); + return ret; + } } else { ich = 0; } - /* Return microamperes */ - return ich * 1000; + return ich; } /** @@ -585,14 +594,17 @@ static int ab8500_charger_get_ac_current(struct ab8500_charger *di) /* Only measure current if the charger is online */ if (di->ac.charger_online) { - ret = iio_read_channel_processed(di->adc_main_charger_c, &ich); - if (ret < 0) - dev_err(di->dev, "%s ADC conv failed,\n", __func__); + /* Return microamperes */ + ret = iio_read_channel_processed_scale(di->adc_main_charger_c, + &ich, 1000); + if (ret < 0) { + dev_err(di->dev, "%s ADC conv failed\n", __func__); + return ret; + } } else { ich = 0; } - /* Return microamperes */ - return ich * 1000; + return ich; } /** @@ -1882,10 +1894,9 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *charger, return ret; } -static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data) +static int ab8500_charger_get_ext_psy_data(struct power_supply *ext, void *data) { struct power_supply *psy; - struct power_supply *ext = dev_get_drvdata(dev); const char **supplicants = (const char **)ext->supplied_to; struct ab8500_charger *di; union power_supply_propval ret; @@ -1949,8 +1960,7 @@ static void ab8500_charger_check_vbat_work(struct work_struct *work) struct ab8500_charger *di = container_of(work, struct ab8500_charger, check_vbat_work.work); - class_for_each_device(power_supply_class, NULL, - &di->usb_chg, ab8500_charger_get_ext_psy_data); + power_supply_for_each_psy(&di->usb_chg, ab8500_charger_get_ext_psy_data); /* First run old_vbat is 0. */ if (di->old_vbat == 0) @@ -3484,11 +3494,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; @@ -3679,7 +3689,7 @@ remove_ab8500_bm: return ret; } -static int ab8500_charger_remove(struct platform_device *pdev) +static void ab8500_charger_remove(struct platform_device *pdev) { struct ab8500_charger *di = platform_get_drvdata(pdev); @@ -3688,8 +3698,6 @@ static int ab8500_charger_remove(struct platform_device *pdev) usb_unregister_notifier(di->usb_phy, &di->nb); ab8500_bm_of_remove(di->usb_chg.psy, di->bm); usb_put_phy(di->usb_phy); - - return 0; } static SIMPLE_DEV_PM_OPS(ab8500_charger_pm_ops, ab8500_charger_suspend, ab8500_charger_resume); diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index c6c9804280db..9dd99722667a 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -149,11 +149,6 @@ struct ab8500_fg_flags { bool batt_id_received; }; -struct inst_curr_result_list { - struct list_head list; - int *result; -}; - /** * struct ab8500_fg - ab8500 FG device information * @dev: Pointer to the structure device @@ -2179,10 +2174,9 @@ static int ab8500_fg_get_property(struct power_supply *psy, return 0; } -static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) +static int ab8500_fg_get_ext_psy_data(struct power_supply *ext, void *data) { struct power_supply *psy; - struct power_supply *ext = dev_get_drvdata(dev); const char **supplicants = (const char **)ext->supplied_to; struct ab8500_fg *di; struct power_supply_battery_info *bi; @@ -2407,10 +2401,7 @@ out: */ static void ab8500_fg_external_power_changed(struct power_supply *psy) { - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - class_for_each_device(power_supply_class, NULL, - di->fg_psy, ab8500_fg_get_ext_psy_data); + power_supply_for_each_psy(psy, ab8500_fg_get_ext_psy_data); } /** @@ -2453,7 +2444,7 @@ struct ab8500_fg_sysfs_entry { static ssize_t charge_full_show(struct ab8500_fg *di, char *buf) { - return sprintf(buf, "%d\n", di->bat_cap.max_mah); + return sysfs_emit(buf, "%d\n", di->bat_cap.max_mah); } static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf, @@ -2472,7 +2463,7 @@ static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf, static ssize_t charge_now_show(struct ab8500_fg *di, char *buf) { - return sprintf(buf, "%d\n", di->bat_cap.prev_mah); + return sysfs_emit(buf, "%d\n", di->bat_cap.prev_mah); } static ssize_t charge_now_store(struct ab8500_fg *di, const char *buf, @@ -2539,7 +2530,7 @@ static struct attribute *ab8500_fg_attrs[] = { }; ATTRIBUTE_GROUPS(ab8500_fg); -static struct kobj_type ab8500_fg_ktype = { +static const struct kobj_type ab8500_fg_ktype = { .sysfs_ops = &ab8500_fg_sysfs_ops, .default_groups = ab8500_fg_groups, }; @@ -2583,7 +2574,7 @@ static ssize_t ab8505_powercut_flagtime_read(struct device *dev, { int ret; u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, @@ -2594,7 +2585,7 @@ static ssize_t ab8505_powercut_flagtime_read(struct device *dev, goto fail; } - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + return sysfs_emit(buf, "%d\n", (reg_value & 0x7F)); fail: return ret; @@ -2606,7 +2597,7 @@ static ssize_t ab8505_powercut_flagtime_write(struct device *dev, { int ret; int reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); if (kstrtoint(buf, 10, ®_value)) @@ -2633,7 +2624,7 @@ static ssize_t ab8505_powercut_maxtime_read(struct device *dev, { int ret; u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, @@ -2644,7 +2635,7 @@ static ssize_t ab8505_powercut_maxtime_read(struct device *dev, goto fail; } - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + return sysfs_emit(buf, "%d\n", (reg_value & 0x7F)); fail: return ret; @@ -2657,7 +2648,7 @@ static ssize_t ab8505_powercut_maxtime_write(struct device *dev, { int ret; int reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); if (kstrtoint(buf, 10, ®_value)) @@ -2684,7 +2675,7 @@ static ssize_t ab8505_powercut_restart_read(struct device *dev, { int ret; u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, @@ -2695,7 +2686,7 @@ static ssize_t ab8505_powercut_restart_read(struct device *dev, goto fail; } - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF)); + return sysfs_emit(buf, "%d\n", (reg_value & 0xF)); fail: return ret; @@ -2707,7 +2698,7 @@ static ssize_t ab8505_powercut_restart_write(struct device *dev, { int ret; int reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); if (kstrtoint(buf, 10, ®_value)) @@ -2735,7 +2726,7 @@ static ssize_t ab8505_powercut_timer_read(struct device *dev, { int ret; u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, @@ -2746,7 +2737,7 @@ static ssize_t ab8505_powercut_timer_read(struct device *dev, goto fail; } - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + return sysfs_emit(buf, "%d\n", (reg_value & 0x7F)); fail: return ret; @@ -2758,7 +2749,7 @@ static ssize_t ab8505_powercut_restart_counter_read(struct device *dev, { int ret; u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, @@ -2769,7 +2760,7 @@ static ssize_t ab8505_powercut_restart_counter_read(struct device *dev, goto fail; } - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF0) >> 4); + return sysfs_emit(buf, "%d\n", (reg_value & 0xF0) >> 4); fail: return ret; @@ -2781,7 +2772,7 @@ static ssize_t ab8505_powercut_read(struct device *dev, { int ret; u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, @@ -2790,7 +2781,7 @@ static ssize_t ab8505_powercut_read(struct device *dev, if (ret < 0) goto fail; - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x1)); + return sysfs_emit(buf, "%d\n", (reg_value & 0x1)); fail: return ret; @@ -2802,7 +2793,7 @@ static ssize_t ab8505_powercut_write(struct device *dev, { int ret; int reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); if (kstrtoint(buf, 10, ®_value)) @@ -2830,7 +2821,7 @@ static ssize_t ab8505_powercut_flag_read(struct device *dev, int ret; u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, @@ -2841,7 +2832,7 @@ static ssize_t ab8505_powercut_flag_read(struct device *dev, goto fail; } - return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x10) >> 4)); + return sysfs_emit(buf, "%d\n", ((reg_value & 0x10) >> 4)); fail: return ret; @@ -2853,7 +2844,7 @@ static ssize_t ab8505_powercut_debounce_read(struct device *dev, { int ret; u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, @@ -2864,7 +2855,7 @@ static ssize_t ab8505_powercut_debounce_read(struct device *dev, goto fail; } - return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7)); + return sysfs_emit(buf, "%d\n", (reg_value & 0x7)); fail: return ret; @@ -2876,7 +2867,7 @@ static ssize_t ab8505_powercut_debounce_write(struct device *dev, { int ret; int reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); if (kstrtoint(buf, 10, ®_value)) @@ -2903,7 +2894,7 @@ static ssize_t ab8505_powercut_enable_status_read(struct device *dev, { int ret; u8 reg_value; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct ab8500_fg *di = power_supply_get_drvdata(psy); ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, @@ -2914,7 +2905,7 @@ static ssize_t ab8505_powercut_enable_status_read(struct device *dev, goto fail; } - return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x20) >> 5)); + return sysfs_emit(buf, "%d\n", ((reg_value & 0x20) >> 5)); fail: return ret; @@ -3229,7 +3220,7 @@ static int ab8500_fg_probe(struct platform_device *pdev) return component_add(dev, &ab8500_fg_component_ops); } -static int ab8500_fg_remove(struct platform_device *pdev) +static void ab8500_fg_remove(struct platform_device *pdev) { struct ab8500_fg *di = platform_get_drvdata(pdev); @@ -3238,8 +3229,6 @@ static int ab8500_fg_remove(struct platform_device *pdev) list_del(&di->node); ab8500_fg_sysfs_exit(di); ab8500_fg_sysfs_psy_remove_attrs(di); - - return 0; } static SIMPLE_DEV_PM_OPS(ab8500_fg_pm_ops, ab8500_fg_suspend, ab8500_fg_resume); diff --git a/drivers/power/supply/acer_a500_battery.c b/drivers/power/supply/acer_a500_battery.c index 32a0bfcac08f..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,16 +232,17 @@ 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; bat->regmap = dev_get_regmap(pdev->dev.parent, "KB930"); if (!bat->regmap) return -EINVAL; - bat->psy = devm_power_supply_register_no_ws(&pdev->dev, - &a500_battery_desc, - &psy_cfg); + bat->psy = devm_power_supply_register(&pdev->dev, + &a500_battery_desc, + &psy_cfg); if (IS_ERR(bat->psy)) return dev_err_probe(&pdev->dev, PTR_ERR(bat->psy), "failed to register battery\n"); @@ -251,13 +253,11 @@ static int a500_battery_probe(struct platform_device *pdev) return 0; } -static int a500_battery_remove(struct platform_device *pdev) +static void a500_battery_remove(struct platform_device *pdev) { struct a500_battery *bat = dev_get_drvdata(&pdev->dev); cancel_delayed_work_sync(&bat->poll_work); - - return 0; } static int __maybe_unused a500_battery_suspend(struct device *dev) diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index e9b5f4283772..3901a02f326a 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -614,7 +614,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, @@ -638,14 +638,12 @@ static int act8945a_charger_probe(struct platform_device *pdev) return 0; } -static int act8945a_charger_remove(struct platform_device *pdev) +static void act8945a_charger_remove(struct platform_device *pdev) { struct act8945a_charger *charger = platform_get_drvdata(pdev); charger->init_done = false; cancel_work_sync(&charger->work); - - return 0; } static struct platform_driver act8945a_charger_driver = { @@ -653,7 +651,7 @@ static struct platform_driver act8945a_charger_driver = { .name = "act8945a-charger", }, .probe = act8945a_charger_probe, - .remove = act8945a_charger_remove, + .remove = act8945a_charger_remove, }; module_platform_driver(act8945a_charger_driver); 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/adp5061.c b/drivers/power/supply/adp5061.c index 840db629a46c..458fd3024373 100644 --- a/drivers/power/supply/adp5061.c +++ b/drivers/power/supply/adp5061.c @@ -590,7 +590,7 @@ static int adp5061_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_VOLTAGE_AVG: /* * This property is used to set the VWEAK threshold - * bellow this value, weak charge mode is entered + * below this value, weak charge mode is entered * above this value, fast chargerge mode is entered */ return adp5061_get_vweak_th(st, val); @@ -727,7 +727,7 @@ static int adp5061_probe(struct i2c_client *client) } static const struct i2c_device_id adp5061_id[] = { - { "adp5061", 0}, + { "adp5061" }, { } }; MODULE_DEVICE_TABLE(i2c, adp5061_id); @@ -736,7 +736,7 @@ static struct i2c_driver adp5061_driver = { .driver = { .name = KBUILD_MODNAME, }, - .probe_new = adp5061_probe, + .probe = adp5061_probe, .id_table = adp5061_id, }; module_i2c_driver(adp5061_driver); diff --git a/drivers/power/supply/apm_power.c b/drivers/power/supply/apm_power.c index 9d1a7fbcaed4..9933cdc5c387 100644 --- a/drivers/power/supply/apm_power.c +++ b/drivers/power/supply/apm_power.c @@ -42,11 +42,11 @@ struct find_bat_param { int max_energy; }; -static int __find_main_battery(struct device *dev, void *data) +static int __find_main_battery(struct power_supply *psy, void *data) { struct find_bat_param *bp = (struct find_bat_param *)data; - bp->bat = dev_get_drvdata(dev); + bp->bat = psy; if (bp->bat->desc->use_for_apm) { /* nice, we explicitly asked to report this battery. */ @@ -79,8 +79,7 @@ static void find_main_battery(void) main_battery = NULL; bp.main = main_battery; - error = class_for_each_device(power_supply_class, NULL, &bp, - __find_main_battery); + error = power_supply_for_each_psy(&bp, __find_main_battery); if (error) { main_battery = bp.main; return; @@ -365,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 57e50208d537..5f6ea416fa30 100644 --- a/drivers/power/supply/axp20x_ac_power.c +++ b/drivers/power/supply/axp20x_ac_power.c @@ -13,7 +13,6 @@ #include <linux/mfd/axp20x.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pm.h> #include <linux/power_supply.h> @@ -46,7 +45,7 @@ struct axp20x_ac_power { struct iio_channel *acin_i; bool has_acin_path_sel; unsigned int num_irqs; - unsigned int irqs[]; + unsigned int irqs[] __counted_by(num_irqs); }; static irqreturn_t axp20x_ac_power_irq(int irq, void *devid) @@ -365,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 9106077c0dbb..50ca8e110085 100644 --- a/drivers/power/supply/axp20x_battery.c +++ b/drivers/power/supply/axp20x_battery.c @@ -17,12 +17,12 @@ * GNU General Public License for more details. */ +#include <linux/bitfield.h> #include <linux/err.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/regmap.h> @@ -33,9 +33,19 @@ #include <linux/mfd/axp20x.h> #define AXP20X_PWR_STATUS_BAT_CHARGING BIT(2) +#define AXP717_PWR_STATUS_MASK GENMASK(6, 5) +#define AXP717_PWR_STATUS_BAT_STANDBY 0 +#define AXP717_PWR_STATUS_BAT_CHRG 1 +#define AXP717_PWR_STATUS_BAT_DISCHRG 2 #define AXP20X_PWR_OP_BATT_PRESENT BIT(5) #define AXP20X_PWR_OP_BATT_ACTIVATED BIT(3) +#define AXP717_PWR_OP_BATT_PRESENT BIT(3) + +#define AXP717_BATT_PMU_FAULT_MASK GENMASK(2, 0) +#define AXP717_BATT_UVLO_2_5V BIT(2) +#define AXP717_BATT_OVER_TEMP BIT(1) +#define AXP717_BATT_UNDER_TEMP BIT(0) #define AXP209_FG_PERCENT GENMASK(6, 0) #define AXP22X_FG_VALID BIT(7) @@ -50,20 +60,53 @@ #define AXP22X_CHRG_CTRL1_TGT_4_22V (1 << 5) #define AXP22X_CHRG_CTRL1_TGT_4_24V (3 << 5) +#define AXP717_CHRG_ENABLE BIT(1) +#define AXP717_CHRG_CV_VOLT_MASK GENMASK(2, 0) +#define AXP717_CHRG_CV_4_0V 0 +#define AXP717_CHRG_CV_4_1V 1 +#define AXP717_CHRG_CV_4_2V 2 +#define AXP717_CHRG_CV_4_35V 3 +#define AXP717_CHRG_CV_4_4V 4 +/* Values 5 and 6 reserved. */ +#define AXP717_CHRG_CV_5_0V 7 + #define AXP813_CHRG_CTRL1_TGT_4_35V (3 << 5) #define AXP20X_CHRG_CTRL1_TGT_CURR GENMASK(3, 0) +#define AXP717_ICC_CHARGER_LIM_MASK GENMASK(5, 0) + +#define AXP717_ITERM_CHG_LIM_MASK GENMASK(3, 0) +#define AXP717_ITERM_CC_STEP 64000 #define AXP20X_V_OFF_MASK GENMASK(2, 0) +#define AXP717_V_OFF_MASK GENMASK(6, 4) + +#define AXP717_BAT_VMIN_MIN_UV 2600000 +#define AXP717_BAT_VMIN_MAX_UV 3300000 +#define AXP717_BAT_VMIN_STEP 100000 +#define AXP717_BAT_CV_MIN_UV 4000000 +#define AXP717_BAT_CV_MAX_UV 5000000 +#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 { - int ccc_scale; - int ccc_offset; - bool has_fg_valid; + int ccc_scale; + int ccc_offset; + unsigned int ccc_reg; + unsigned int ccc_mask; + bool has_fg_valid; + const struct power_supply_desc *bat_ps_desc; int (*get_max_voltage)(struct axp20x_batt_ps *batt, int *val); int (*set_max_voltage)(struct axp20x_batt_ps *batt, int val); + int (*cfg_iio_chan)(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt); + void (*set_bat_info)(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt, + struct power_supply_battery_info *info); }; struct axp20x_batt_ps { @@ -76,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, @@ -136,6 +180,39 @@ static int axp22x_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt, return 0; } +static int axp717_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt, + int *val) +{ + int ret, reg; + + ret = regmap_read(axp20x_batt->regmap, AXP717_CV_CHG_SET, ®); + if (ret) + return ret; + + switch (reg & AXP717_CHRG_CV_VOLT_MASK) { + case AXP717_CHRG_CV_4_0V: + *val = 4000000; + return 0; + case AXP717_CHRG_CV_4_1V: + *val = 4100000; + return 0; + case AXP717_CHRG_CV_4_2V: + *val = 4200000; + return 0; + case AXP717_CHRG_CV_4_35V: + *val = 4350000; + return 0; + case AXP717_CHRG_CV_4_4V: + *val = 4400000; + return 0; + case AXP717_CHRG_CV_5_0V: + *val = 5000000; + return 0; + default: + return -EINVAL; + } +} + static int axp813_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt, int *val) { @@ -181,6 +258,21 @@ static int axp20x_get_constant_charge_current(struct axp20x_batt_ps *axp, return 0; } +static int axp717_get_constant_charge_current(struct axp20x_batt_ps *axp, + int *val) +{ + int ret; + + ret = regmap_read(axp->regmap, AXP717_ICC_CHG_SET, val); + if (ret) + return ret; + + *val = FIELD_GET(AXP717_ICC_CHARGER_LIM_MASK, *val) * + axp->data->ccc_scale; + + return 0; +} + static int axp20x_battery_get_prop(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -265,17 +357,18 @@ static int axp20x_battery_get_prop(struct power_supply *psy, if (ret) return ret; + /* IIO framework gives mA but Power Supply framework gives uA */ if (reg & AXP20X_PWR_STATUS_BAT_CHARGING) { - ret = iio_read_channel_processed(axp20x_batt->batt_chrg_i, &val->intval); + ret = iio_read_channel_processed_scale(axp20x_batt->batt_chrg_i, + &val->intval, 1000); } else { - ret = iio_read_channel_processed(axp20x_batt->batt_dischrg_i, &val1); + ret = iio_read_channel_processed_scale(axp20x_batt->batt_dischrg_i, + &val1, 1000); val->intval = -val1; } if (ret) return ret; - /* IIO framework gives mA but Power Supply framework gives uA */ - val->intval *= 1000; break; case POWER_SUPPLY_PROP_CAPACITY: @@ -304,11 +397,11 @@ static int axp20x_battery_get_prop(struct power_supply *psy, val->intval = reg & AXP209_FG_PERCENT; break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: return axp20x_batt->data->get_max_voltage(axp20x_batt, &val->intval); - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_MIN: ret = regmap_read(axp20x_batt->regmap, AXP20X_V_OFF, ®); if (ret) return ret; @@ -317,13 +410,12 @@ static int axp20x_battery_get_prop(struct power_supply *psy, break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = iio_read_channel_processed(axp20x_batt->batt_v, - &val->intval); + /* IIO framework gives mV but Power Supply framework gives uV */ + ret = iio_read_channel_processed_scale(axp20x_batt->batt_v, + &val->intval, 1000); if (ret) return ret; - /* IIO framework gives mV but Power Supply framework gives uV */ - val->intval *= 1000; break; default: @@ -333,6 +425,171 @@ static int axp20x_battery_get_prop(struct power_supply *psy, return 0; } +static int axp717_battery_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); + int ret = 0, reg; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + ret = regmap_read(axp20x_batt->regmap, AXP717_ON_INDICATE, + ®); + if (ret) + return ret; + + val->intval = FIELD_GET(AXP717_PWR_OP_BATT_PRESENT, reg); + return 0; + + case POWER_SUPPLY_PROP_STATUS: + ret = regmap_read(axp20x_batt->regmap, AXP717_PMU_STATUS_2, + ®); + if (ret) + return ret; + + switch (FIELD_GET(AXP717_PWR_STATUS_MASK, reg)) { + case AXP717_PWR_STATUS_BAT_STANDBY: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + return 0; + + case AXP717_PWR_STATUS_BAT_CHRG: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + return 0; + + case AXP717_PWR_STATUS_BAT_DISCHRG: + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + } + + /* + * If a fault is detected it must also be cleared; if the + * condition persists it should reappear. A restart was not + * sufficient to clear the bit in testing despite the register + * listed as POR. + */ + case POWER_SUPPLY_PROP_HEALTH: + ret = regmap_read(axp20x_batt->regmap, AXP717_PMU_FAULT, + ®); + if (ret) + return ret; + + switch (reg & AXP717_BATT_PMU_FAULT_MASK) { + case AXP717_BATT_UVLO_2_5V: + val->intval = POWER_SUPPLY_HEALTH_DEAD; + regmap_write_bits(axp20x_batt->regmap, + AXP717_PMU_FAULT, + AXP717_BATT_UVLO_2_5V, + AXP717_BATT_UVLO_2_5V); + return 0; + + case AXP717_BATT_OVER_TEMP: + val->intval = POWER_SUPPLY_HEALTH_HOT; + regmap_write_bits(axp20x_batt->regmap, + AXP717_PMU_FAULT, + AXP717_BATT_OVER_TEMP, + AXP717_BATT_OVER_TEMP); + return 0; + + case AXP717_BATT_UNDER_TEMP: + val->intval = POWER_SUPPLY_HEALTH_COLD; + regmap_write_bits(axp20x_batt->regmap, + AXP717_PMU_FAULT, + AXP717_BATT_UNDER_TEMP, + AXP717_BATT_UNDER_TEMP); + return 0; + + default: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + return 0; + } + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = axp717_get_constant_charge_current(axp20x_batt, + &val->intval); + if (ret) + return ret; + return 0; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + /* + * The offset of this value is currently unknown and is + * not documented in the datasheet. Based on + * observation it's assumed to be somewhere around + * 450ma. I will leave the value raw for now. Note that + * IIO framework gives mA but Power Supply framework + * gives uA. + */ + ret = iio_read_channel_processed_scale(axp20x_batt->batt_chrg_i, + &val->intval, 1000); + if (ret) + return ret; + + return 0; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = regmap_read(axp20x_batt->regmap, AXP717_ON_INDICATE, + ®); + if (ret) + return ret; + + if (!FIELD_GET(AXP717_PWR_OP_BATT_PRESENT, reg)) + return -ENODEV; + + ret = regmap_read(axp20x_batt->regmap, + AXP717_BATT_PERCENT_DATA, ®); + if (ret) + return ret; + + /* + * Fuel Gauge data takes 7 bits but the stored value seems to be + * directly the raw percentage without any scaling to 7 bits. + */ + val->intval = reg & AXP209_FG_PERCENT; + return 0; + + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return axp20x_batt->data->get_max_voltage(axp20x_batt, + &val->intval); + + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = regmap_read(axp20x_batt->regmap, + AXP717_VSYS_V_POWEROFF, ®); + if (ret) + return ret; + + val->intval = AXP717_BAT_VMIN_MIN_UV + AXP717_BAT_VMIN_STEP * + (reg & AXP717_V_OFF_MASK); + return 0; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + /* IIO framework gives mV but Power Supply framework gives uV */ + ret = iio_read_channel_processed_scale(axp20x_batt->batt_v, + &val->intval, 1000); + if (ret) + return ret; + + return 0; + + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + ret = regmap_read(axp20x_batt->regmap, + AXP717_ITERM_CHG_SET, ®); + if (ret) + return ret; + + val->intval = (reg & AXP717_ITERM_CHG_LIM_MASK) * AXP717_ITERM_CC_STEP; + return 0; + + default: + return -EINVAL; + } +} + static int axp22x_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt, int val) { @@ -389,6 +646,35 @@ static int axp20x_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt, AXP20X_CHRG_CTRL1_TGT_VOLT, val); } +static int axp717_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt, + int val) +{ + switch (val) { + case 4000000: + val = AXP717_CHRG_CV_4_0V; + break; + + case 4100000: + val = AXP717_CHRG_CV_4_1V; + break; + + case 4200000: + val = AXP717_CHRG_CV_4_2V; + break; + + default: + /* + * AXP717 can go up to 4.35, 4.4, and 5.0 volts which + * seem too high for lithium batteries, so do not allow. + */ + return -EINVAL; + } + + return regmap_update_bits(axp20x_batt->regmap, + AXP717_CV_CHG_SET, + AXP717_CHRG_CV_VOLT_MASK, val); +} + static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt, int charge_current) { @@ -405,6 +691,24 @@ static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt, AXP20X_CHRG_CTRL1_TGT_CURR, charge_current); } +static int axp717_set_constant_charge_current(struct axp20x_batt_ps *axp, + int charge_current) +{ + int val; + + if (charge_current > axp->max_ccc) + return -EINVAL; + + if (charge_current > AXP717_BAT_CC_MAX_UA || charge_current < 0) + return -EINVAL; + + val = (charge_current - axp->data->ccc_offset) / + axp->data->ccc_scale; + + return regmap_update_bits(axp->regmap, AXP717_ICC_CHG_SET, + AXP717_ICC_CHARGER_LIM_MASK, val); +} + static int axp20x_set_max_constant_charge_current(struct axp20x_batt_ps *axp, int charge_current) { @@ -449,6 +753,19 @@ static int axp20x_set_voltage_min_design(struct axp20x_batt_ps *axp_batt, AXP20X_V_OFF_MASK, val1); } +static int axp717_set_voltage_min_design(struct axp20x_batt_ps *axp_batt, + int min_voltage) +{ + int val1 = (min_voltage - AXP717_BAT_VMIN_MIN_UV) / AXP717_BAT_VMIN_STEP; + + if (val1 < 0 || val1 > AXP717_V_OFF_MASK) + return -EINVAL; + + return regmap_update_bits(axp_batt->regmap, + AXP717_VSYS_V_POWEROFF, + AXP717_V_OFF_MASK, val1); +} + static int axp20x_battery_set_prop(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) @@ -456,10 +773,10 @@ static int axp20x_battery_set_prop(struct power_supply *psy, struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); switch (psp) { - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_MIN: return axp20x_set_voltage_min_design(axp20x_batt, val->intval); - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: return axp20x_batt->data->set_max_voltage(axp20x_batt, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: @@ -485,6 +802,42 @@ static int axp20x_battery_set_prop(struct power_supply *psy, } } +static int axp717_battery_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + return axp717_set_voltage_min_design(axp20x_batt, val->intval); + + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return axp20x_batt->data->set_max_voltage(axp20x_batt, val->intval); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return axp717_set_constant_charge_current(axp20x_batt, + val->intval); + case POWER_SUPPLY_PROP_STATUS: + switch (val->intval) { + case POWER_SUPPLY_STATUS_CHARGING: + return regmap_update_bits(axp20x_batt->regmap, + AXP717_MODULE_EN_CONTROL_2, + AXP717_CHRG_ENABLE, + AXP717_CHRG_ENABLE); + + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + return regmap_update_bits(axp20x_batt->regmap, + AXP717_MODULE_EN_CONTROL_2, + AXP717_CHRG_ENABLE, 0); + } + return -EINVAL; + default: + return -EINVAL; + } +} + static enum power_supply_property axp20x_battery_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, @@ -494,22 +847,45 @@ static enum power_supply_property axp20x_battery_props[] = { POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN, POWER_SUPPLY_PROP_CAPACITY, }; +static enum power_supply_property axp717_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, +}; + static int axp20x_battery_prop_writeable(struct power_supply *psy, enum power_supply_property psp) { return psp == POWER_SUPPLY_PROP_STATUS || - psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN || - psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN || + psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || + psp == POWER_SUPPLY_PROP_VOLTAGE_MAX || psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT || psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; } -static const struct power_supply_desc axp20x_batt_ps_desc = { +static int axp717_battery_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_STATUS || + psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || + psp == POWER_SUPPLY_PROP_VOLTAGE_MAX || + psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; +} + +static const struct power_supply_desc axp209_batt_ps_desc = { .name = "axp20x-battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = axp20x_battery_props, @@ -519,27 +895,181 @@ static const struct power_supply_desc axp20x_batt_ps_desc = { .set_property = axp20x_battery_set_prop, }; +static const struct power_supply_desc axp717_batt_ps_desc = { + .name = "axp20x-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = axp717_battery_props, + .num_properties = ARRAY_SIZE(axp717_battery_props), + .property_is_writeable = axp717_battery_prop_writeable, + .get_property = axp717_battery_get_prop, + .set_property = axp717_battery_set_prop, +}; + +static int axp209_bat_cfg_iio_channels(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt) +{ + axp_batt->batt_v = devm_iio_channel_get(&pdev->dev, "batt_v"); + if (IS_ERR(axp_batt->batt_v)) { + if (PTR_ERR(axp_batt->batt_v) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(axp_batt->batt_v); + } + + axp_batt->batt_chrg_i = devm_iio_channel_get(&pdev->dev, + "batt_chrg_i"); + if (IS_ERR(axp_batt->batt_chrg_i)) { + if (PTR_ERR(axp_batt->batt_chrg_i) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(axp_batt->batt_chrg_i); + } + + axp_batt->batt_dischrg_i = devm_iio_channel_get(&pdev->dev, + "batt_dischrg_i"); + if (IS_ERR(axp_batt->batt_dischrg_i)) { + if (PTR_ERR(axp_batt->batt_dischrg_i) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(axp_batt->batt_dischrg_i); + } + + return 0; +} + +static int axp717_bat_cfg_iio_channels(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt) +{ + axp_batt->batt_v = devm_iio_channel_get(&pdev->dev, "batt_v"); + if (IS_ERR(axp_batt->batt_v)) { + if (PTR_ERR(axp_batt->batt_v) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(axp_batt->batt_v); + } + + axp_batt->batt_chrg_i = devm_iio_channel_get(&pdev->dev, + "batt_chrg_i"); + if (IS_ERR(axp_batt->batt_chrg_i)) { + if (PTR_ERR(axp_batt->batt_chrg_i) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(axp_batt->batt_chrg_i); + } + + return 0; +} + +static void axp209_set_battery_info(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt, + struct power_supply_battery_info *info) +{ + int vmin = info->voltage_min_design_uv; + int ccc = info->constant_charge_current_max_ua; + + if (vmin > 0 && axp20x_set_voltage_min_design(axp_batt, vmin)) + dev_err(&pdev->dev, + "couldn't set voltage_min_design\n"); + + /* Set max to unverified value to be able to set CCC */ + axp_batt->max_ccc = ccc; + + if (ccc <= 0 || axp20x_set_constant_charge_current(axp_batt, ccc)) { + dev_err(&pdev->dev, + "couldn't set ccc from DT: fallback to min value\n"); + ccc = 300000; + axp_batt->max_ccc = ccc; + axp20x_set_constant_charge_current(axp_batt, ccc); + } +} + +static void axp717_set_battery_info(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt, + struct power_supply_battery_info *info) +{ + int vmin = info->voltage_min_design_uv; + int vmax = info->voltage_max_design_uv; + 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"); + + if (vmax > 0 && axp717_battery_set_max_voltage(axp_batt, vmax)) + dev_err(&pdev->dev, + "couldn't set voltage_max_design\n"); + + axp717_get_constant_charge_current(axp_batt, &val); + axp_batt->max_ccc = ccc; + if (ccc <= 0 || axp717_set_constant_charge_current(axp_batt, ccc)) { + dev_err(&pdev->dev, + "couldn't set ccc from DT: current ccc is %d\n", + val); + } +} + static const struct axp_data axp209_data = { .ccc_scale = 100000, .ccc_offset = 300000, + .ccc_reg = AXP20X_CHRG_CTRL1, + .ccc_mask = AXP20X_CHRG_CTRL1_TGT_CURR, + .bat_ps_desc = &axp209_batt_ps_desc, .get_max_voltage = axp20x_battery_get_max_voltage, .set_max_voltage = axp20x_battery_set_max_voltage, + .cfg_iio_chan = axp209_bat_cfg_iio_channels, + .set_bat_info = axp209_set_battery_info, }; static const struct axp_data axp221_data = { .ccc_scale = 150000, .ccc_offset = 300000, + .ccc_reg = AXP20X_CHRG_CTRL1, + .ccc_mask = AXP20X_CHRG_CTRL1_TGT_CURR, .has_fg_valid = true, + .bat_ps_desc = &axp209_batt_ps_desc, .get_max_voltage = axp22x_battery_get_max_voltage, .set_max_voltage = axp22x_battery_set_max_voltage, + .cfg_iio_chan = axp209_bat_cfg_iio_channels, + .set_bat_info = axp209_set_battery_info, +}; + +static const struct axp_data axp717_data = { + .ccc_scale = 64000, + .ccc_offset = 0, + .ccc_reg = AXP717_ICC_CHG_SET, + .ccc_mask = AXP717_ICC_CHARGER_LIM_MASK, + .bat_ps_desc = &axp717_batt_ps_desc, + .get_max_voltage = axp717_battery_get_max_voltage, + .set_max_voltage = axp717_battery_set_max_voltage, + .cfg_iio_chan = axp717_bat_cfg_iio_channels, + .set_bat_info = axp717_set_battery_info, }; static const struct axp_data axp813_data = { .ccc_scale = 200000, .ccc_offset = 200000, + .ccc_reg = AXP20X_CHRG_CTRL1, + .ccc_mask = AXP20X_CHRG_CTRL1_TGT_CURR, .has_fg_valid = true, + .bat_ps_desc = &axp209_batt_ps_desc, .get_max_voltage = axp813_battery_get_max_voltage, .set_max_voltage = axp20x_battery_set_max_voltage, + .cfg_iio_chan = axp209_bat_cfg_iio_channels, + .set_bat_info = axp209_set_battery_info, }; static const struct of_device_id axp20x_battery_ps_id[] = { @@ -550,6 +1080,9 @@ static const struct of_device_id axp20x_battery_ps_id[] = { .compatible = "x-powers,axp221-battery-power-supply", .data = (void *)&axp221_data, }, { + .compatible = "x-powers,axp717-battery-power-supply", + .data = (void *)&axp717_data, + }, { .compatible = "x-powers,axp813-battery-power-supply", .data = (void *)&axp813_data, }, { /* sentinel */ }, @@ -562,6 +1095,7 @@ static int axp20x_power_probe(struct platform_device *pdev) struct power_supply_config psy_cfg = {}; struct power_supply_battery_info *info; struct device *dev = &pdev->dev; + int ret; if (!of_device_is_available(pdev->dev.of_node)) return -ENODEV; @@ -573,39 +1107,20 @@ static int axp20x_power_probe(struct platform_device *pdev) axp20x_batt->dev = &pdev->dev; - axp20x_batt->batt_v = devm_iio_channel_get(&pdev->dev, "batt_v"); - if (IS_ERR(axp20x_batt->batt_v)) { - if (PTR_ERR(axp20x_batt->batt_v) == -ENODEV) - return -EPROBE_DEFER; - return PTR_ERR(axp20x_batt->batt_v); - } - - axp20x_batt->batt_chrg_i = devm_iio_channel_get(&pdev->dev, - "batt_chrg_i"); - if (IS_ERR(axp20x_batt->batt_chrg_i)) { - if (PTR_ERR(axp20x_batt->batt_chrg_i) == -ENODEV) - return -EPROBE_DEFER; - return PTR_ERR(axp20x_batt->batt_chrg_i); - } - - axp20x_batt->batt_dischrg_i = devm_iio_channel_get(&pdev->dev, - "batt_dischrg_i"); - if (IS_ERR(axp20x_batt->batt_dischrg_i)) { - if (PTR_ERR(axp20x_batt->batt_dischrg_i) == -ENODEV) - return -EPROBE_DEFER; - return PTR_ERR(axp20x_batt->batt_dischrg_i); - } - axp20x_batt->regmap = dev_get_regmap(pdev->dev.parent, NULL); 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); + ret = axp20x_batt->data->cfg_iio_chan(pdev, axp20x_batt); + if (ret) + return ret; + axp20x_batt->batt = devm_power_supply_register(&pdev->dev, - &axp20x_batt_ps_desc, + axp20x_batt->data->bat_ps_desc, &psy_cfg); if (IS_ERR(axp20x_batt->batt)) { dev_err(&pdev->dev, "failed to register power supply: %ld\n", @@ -614,33 +1129,15 @@ static int axp20x_power_probe(struct platform_device *pdev) } if (!power_supply_get_battery_info(axp20x_batt->batt, &info)) { - int vmin = info->voltage_min_design_uv; - int ccc = info->constant_charge_current_max_ua; - - if (vmin > 0 && axp20x_set_voltage_min_design(axp20x_batt, - vmin)) - dev_err(&pdev->dev, - "couldn't set voltage_min_design\n"); - - /* Set max to unverified value to be able to set CCC */ - axp20x_batt->max_ccc = ccc; - - if (ccc <= 0 || axp20x_set_constant_charge_current(axp20x_batt, - ccc)) { - dev_err(&pdev->dev, - "couldn't set constant charge current from DT: fallback to minimum value\n"); - ccc = 300000; - axp20x_batt->max_ccc = ccc; - axp20x_set_constant_charge_current(axp20x_batt, ccc); - } + axp20x_batt->data->set_bat_info(pdev, axp20x_batt, info); + power_supply_put_battery_info(axp20x_batt->batt, info); } /* * Update max CCC to a valid value if battery info is present or set it * to current register value by default. */ - axp20x_get_constant_charge_current(axp20x_batt, - &axp20x_batt->max_ccc); + axp20x_get_constant_charge_current(axp20x_batt, &axp20x_batt->max_ccc); return 0; } diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index a1e6d1d44808..e75d1e377ac1 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -15,7 +15,6 @@ #include <linux/mfd/axp20x.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pm.h> #include <linux/power_supply.h> @@ -26,34 +25,30 @@ #define DRVNAME "axp20x-usb-power-supply" +#define AXP192_USB_OTG_STATUS 0x04 + #define AXP20X_PWR_STATUS_VBUS_PRESENT BIT(5) #define AXP20X_PWR_STATUS_VBUS_USED BIT(4) +#define AXP717_PWR_STATUS_VBUS_GOOD BIT(5) + #define AXP20X_USB_STATUS_VBUS_VALID BIT(2) -#define AXP20X_VBUS_PATH_SEL BIT(7) -#define AXP20X_VBUS_PATH_SEL_OFFSET 7 +#define AXP717_PMU_FAULT_VBUS BIT(5) +#define AXP717_PMU_FAULT_VSYS BIT(3) #define AXP20X_VBUS_VHOLD_uV(b) (4000000 + (((b) >> 3) & 7) * 100000) #define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3) #define AXP20X_VBUS_VHOLD_OFFSET 3 -#define AXP20X_VBUS_CLIMIT_MASK 3 -#define AXP20X_VBUS_CLIMIT_900mA 0 -#define AXP20X_VBUS_CLIMIT_500mA 1 -#define AXP20X_VBUS_CLIMIT_100mA 2 -#define AXP20X_VBUS_CLIMIT_NONE 3 - -#define AXP813_VBUS_CLIMIT_900mA 0 -#define AXP813_VBUS_CLIMIT_1500mA 1 -#define AXP813_VBUS_CLIMIT_2000mA 2 -#define AXP813_VBUS_CLIMIT_2500mA 3 #define AXP20X_ADC_EN1_VBUS_CURR BIT(2) #define AXP20X_ADC_EN1_VBUS_VOLT BIT(3) -#define AXP20X_VBUS_MON_VBUS_VALID BIT(3) +#define AXP717_INPUT_VOL_LIMIT_MASK GENMASK(3, 0) +#define AXP717_INPUT_CUR_LIMIT_MASK GENMASK(5, 0) +#define AXP717_ADC_DATA_MASK GENMASK(14, 0) -#define AXP813_BC_EN BIT(0) +#define AXP717_ADC_EN_VBUS_VOLT BIT(2) /* * Note do not raise the debounce time, we must report Vusb high within @@ -61,17 +56,46 @@ */ #define DEBOUNCE_TIME msecs_to_jiffies(50) +struct axp20x_usb_power; + +struct axp_data { + const struct power_supply_desc *power_desc; + const char * const *irq_names; + unsigned int num_irq_names; + const int *curr_lim_table; + int curr_lim_table_size; + struct reg_field curr_lim_fld; + struct reg_field vbus_valid_bit; + struct reg_field vbus_mon_bit; + struct reg_field usb_bc_en_bit; + struct reg_field usb_bc_det_fld; + struct reg_field vbus_disable_bit; + bool vbus_needs_polling: 1; + void (*axp20x_read_vbus)(struct work_struct *work); + int (*axp20x_cfg_iio_chan)(struct platform_device *pdev, + struct axp20x_usb_power *power); + int (*axp20x_cfg_adc_reg)(struct axp20x_usb_power *power); +}; + struct axp20x_usb_power { + struct device *dev; struct regmap *regmap; + struct regmap_field *curr_lim_fld; + struct regmap_field *vbus_valid_bit; + struct regmap_field *vbus_mon_bit; + struct regmap_field *usb_bc_en_bit; + struct regmap_field *usb_bc_det_fld; + struct regmap_field *vbus_disable_bit; struct power_supply *supply; - enum axp20x_variants axp20x_id; + const struct axp_data *axp_data; struct iio_channel *vbus_v; struct iio_channel *vbus_i; struct delayed_work vbus_detect; + int max_input_cur; unsigned int old_status; unsigned int online; unsigned int num_irqs; - unsigned int irqs[]; + unsigned int irqs[] __counted_by(num_irqs); }; static bool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power) @@ -81,7 +105,7 @@ static bool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power) * present->absent transition implies an online->offline transition * and will trigger the VBUS_REMOVAL IRQ. */ - if (power->axp20x_id >= AXP221_ID && !power->online) + if (power->axp_data->vbus_needs_polling && !power->online) return true; return false; @@ -113,6 +137,15 @@ static void axp20x_usb_power_poll_vbus(struct work_struct *work) if (val != power->old_status) power_supply_changed(power->supply); + if (power->usb_bc_en_bit && (val & AXP20X_PWR_STATUS_VBUS_PRESENT) != + (power->old_status & AXP20X_PWR_STATUS_VBUS_PRESENT)) { + dev_dbg(power->dev, "Cable status changed, re-enabling USB BC"); + ret = regmap_field_write(power->usb_bc_en_bit, 1); + if (ret) + dev_err(power->dev, "failed to enable USB BC: errno %d", + ret); + } + power->old_status = val; power->online = val & AXP20X_PWR_STATUS_VBUS_USED; @@ -121,57 +154,52 @@ out: mod_delayed_work(system_power_efficient_wq, &power->vbus_detect, DEBOUNCE_TIME); } -static int axp20x_get_current_max(struct axp20x_usb_power *power, int *val) +static void axp717_usb_power_poll_vbus(struct work_struct *work) { - unsigned int v; - int ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); + struct axp20x_usb_power *power = + container_of(work, struct axp20x_usb_power, vbus_detect.work); + unsigned int val; + int ret; + ret = regmap_read(power->regmap, AXP717_ON_INDICATE, &val); if (ret) - return ret; + return; - switch (v & AXP20X_VBUS_CLIMIT_MASK) { - case AXP20X_VBUS_CLIMIT_100mA: - if (power->axp20x_id == AXP221_ID) - *val = -1; /* No 100mA limit */ - else - *val = 100000; - break; - case AXP20X_VBUS_CLIMIT_500mA: - *val = 500000; - break; - case AXP20X_VBUS_CLIMIT_900mA: - *val = 900000; - break; - case AXP20X_VBUS_CLIMIT_NONE: - *val = -1; - break; - } + val &= AXP717_PWR_STATUS_VBUS_GOOD; + if (val != power->old_status) + power_supply_changed(power->supply); - return 0; + power->old_status = val; } -static int axp813_get_current_max(struct axp20x_usb_power *power, int *val) +static int axp20x_get_usb_type(struct axp20x_usb_power *power, + union power_supply_propval *val) { - unsigned int v; - int ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); + unsigned int reg; + int ret; + if (!power->usb_bc_det_fld) + return -EINVAL; + + ret = regmap_field_read(power->usb_bc_det_fld, ®); if (ret) return ret; - switch (v & AXP20X_VBUS_CLIMIT_MASK) { - case AXP813_VBUS_CLIMIT_900mA: - *val = 900000; + switch (reg) { + case 1: + val->intval = POWER_SUPPLY_USB_TYPE_SDP; break; - case AXP813_VBUS_CLIMIT_1500mA: - *val = 1500000; + case 2: + val->intval = POWER_SUPPLY_USB_TYPE_CDP; break; - case AXP813_VBUS_CLIMIT_2000mA: - *val = 2000000; + case 3: + val->intval = POWER_SUPPLY_USB_TYPE_DCP; break; - case AXP813_VBUS_CLIMIT_2500mA: - *val = 2500000; + default: + val->intval = POWER_SUPPLY_USB_TYPE_UNKNOWN; break; } + return 0; } @@ -192,16 +220,15 @@ static int axp20x_usb_power_get_property(struct power_supply *psy, return 0; case POWER_SUPPLY_PROP_VOLTAGE_NOW: if (IS_ENABLED(CONFIG_AXP20X_ADC)) { - ret = iio_read_channel_processed(power->vbus_v, - &val->intval); - if (ret) - return ret; - /* * IIO framework gives mV but Power Supply framework * gives uV. */ - val->intval *= 1000; + ret = iio_read_channel_processed_scale(power->vbus_v, + &val->intval, 1000); + if (ret) + return ret; + return 0; } @@ -212,22 +239,28 @@ static int axp20x_usb_power_get_property(struct power_supply *psy, val->intval = ret * 1700; /* 1 step = 1.7 mV */ return 0; - case POWER_SUPPLY_PROP_CURRENT_MAX: - if (power->axp20x_id == AXP813_ID) - return axp813_get_current_max(power, &val->intval); - return axp20x_get_current_max(power, &val->intval); + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = regmap_field_read(power->curr_lim_fld, &v); + if (ret) + return ret; + + if (v < power->axp_data->curr_lim_table_size) + val->intval = power->axp_data->curr_lim_table[v]; + else + val->intval = power->axp_data->curr_lim_table[ + power->axp_data->curr_lim_table_size - 1]; + return 0; case POWER_SUPPLY_PROP_CURRENT_NOW: if (IS_ENABLED(CONFIG_AXP20X_ADC)) { - ret = iio_read_channel_processed(power->vbus_i, - &val->intval); - if (ret) - return ret; - /* * IIO framework gives mA but Power Supply framework * gives uA. */ - val->intval *= 1000; + ret = iio_read_channel_processed_scale(power->vbus_i, + &val->intval, 1000); + if (ret) + return ret; + return 0; } @@ -238,6 +271,9 @@ static int axp20x_usb_power_get_property(struct power_supply *psy, val->intval = ret * 375; /* 1 step = 0.375 mA */ return 0; + + case POWER_SUPPLY_PROP_USB_TYPE: + return axp20x_get_usb_type(power, val); default: break; } @@ -256,16 +292,15 @@ static int axp20x_usb_power_get_property(struct power_supply *psy, val->intval = POWER_SUPPLY_HEALTH_GOOD; - if (power->axp20x_id == AXP202_ID) { - ret = regmap_read(power->regmap, - AXP20X_USB_OTG_STATUS, &v); + if (power->vbus_valid_bit) { + ret = regmap_field_read(power->vbus_valid_bit, &v); if (ret) return ret; - if (!(v & AXP20X_USB_STATUS_VBUS_VALID)) - val->intval = - POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + if (v == 0) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; } + break; case POWER_SUPPLY_PROP_PRESENT: val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_PRESENT); @@ -280,14 +315,88 @@ static int axp20x_usb_power_get_property(struct power_supply *psy, return 0; } -static int axp813_usb_power_set_online(struct axp20x_usb_power *power, - int intval) +static int axp717_usb_power_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) { - int val = !intval << AXP20X_VBUS_PATH_SEL_OFFSET; + struct axp20x_usb_power *power = power_supply_get_drvdata(psy); + unsigned int v; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + ret = regmap_read(power->regmap, AXP717_ON_INDICATE, &v); + if (ret) + return ret; + + if (!(v & AXP717_PWR_STATUS_VBUS_GOOD)) + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + + ret = regmap_read(power->regmap, AXP717_PMU_FAULT_VBUS, &v); + if (ret) + return ret; + + v &= (AXP717_PMU_FAULT_VBUS | AXP717_PMU_FAULT_VSYS); + if (v) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + regmap_write(power->regmap, AXP717_PMU_FAULT_VBUS, v); + } + + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = regmap_read(power->regmap, AXP717_INPUT_CUR_LIMIT_CTRL, &v); + if (ret) + return ret; + + /* 50ma step size with 100ma offset. */ + v &= AXP717_INPUT_CUR_LIMIT_MASK; + val->intval = (v * 50000) + 100000; + break; + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_PRESENT: + ret = regmap_read(power->regmap, AXP717_ON_INDICATE, &v); + if (ret) + return ret; + val->intval = !!(v & AXP717_PWR_STATUS_VBUS_GOOD); + break; + case POWER_SUPPLY_PROP_USB_TYPE: + return axp20x_get_usb_type(power, val); + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = regmap_read(power->regmap, AXP717_INPUT_VOL_LIMIT_CTRL, &v); + if (ret) + return ret; + + /* 80mv step size with 3.88v offset. */ + v &= AXP717_INPUT_VOL_LIMIT_MASK; + val->intval = (v * 80000) + 3880000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (IS_ENABLED(CONFIG_AXP20X_ADC)) { + /* + * IIO framework gives mV but Power Supply framework + * gives uV. + */ + ret = iio_read_channel_processed_scale(power->vbus_v, + &val->intval, 1000); + if (ret) + return ret; + + return 0; + } + + ret = axp20x_read_variable_width(power->regmap, + AXP717_VBUS_V_H, 16); + if (ret < 0) + return ret; + + val->intval = (ret % AXP717_ADC_DATA_MASK) * 1000; + break; + default: + return -EINVAL; + } + + return 0; - return regmap_update_bits(power->regmap, - AXP20X_VBUS_IPSOUT_MGMT, - AXP20X_VBUS_PATH_SEL, val); } static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power, @@ -316,52 +425,83 @@ static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power, return -EINVAL; } -static int axp813_usb_power_set_current_max(struct axp20x_usb_power *power, +static int axp717_usb_power_set_voltage_min(struct axp20x_usb_power *power, int intval) { int val; - switch (intval) { - case 900000: - return regmap_update_bits(power->regmap, - AXP20X_VBUS_IPSOUT_MGMT, - AXP20X_VBUS_CLIMIT_MASK, - AXP813_VBUS_CLIMIT_900mA); - case 1500000: - case 2000000: - case 2500000: - val = (intval - 1000000) / 500000; - return regmap_update_bits(power->regmap, - AXP20X_VBUS_IPSOUT_MGMT, - AXP20X_VBUS_CLIMIT_MASK, val); - default: + /* Minimum value of 3.88v and maximum of 5.08v. */ + if (intval < 3880000 || intval > 5080000) + return -EINVAL; + + /* step size of 80ma with 3.88v offset. */ + val = (intval - 3880000) / 80000; + return regmap_update_bits(power->regmap, + AXP717_INPUT_VOL_LIMIT_CTRL, + AXP717_INPUT_VOL_LIMIT_MASK, val); +} + +static int axp20x_usb_power_set_input_current_limit(struct axp20x_usb_power *power, + int intval) +{ + int ret; + unsigned int reg; + const unsigned int max = power->axp_data->curr_lim_table_size; + + if (intval == -1) return -EINVAL; + + if (power->max_input_cur && (intval > power->max_input_cur)) { + dev_warn(power->dev, + "requested current %d clamped to max current %d\n", + intval, power->max_input_cur); + intval = power->max_input_cur; } - return -EINVAL; + /* + * BC1.2 detection can cause a race condition if we try to set a current + * limit while it's in progress. When it finishes it will overwrite the + * current limit we just set. + */ + if (power->usb_bc_en_bit) { + dev_dbg(power->dev, + "disabling BC1.2 detection because current limit was set"); + ret = regmap_field_write(power->usb_bc_en_bit, 0); + if (ret) + return ret; + } + + for (reg = max - 1; reg > 0; reg--) + if (power->axp_data->curr_lim_table[reg] <= intval) + break; + + dev_dbg(power->dev, "setting input current limit reg to %d (%d uA), requested %d uA", + reg, power->axp_data->curr_lim_table[reg], intval); + + return regmap_field_write(power->curr_lim_fld, reg); } -static int axp20x_usb_power_set_current_max(struct axp20x_usb_power *power, - int intval) +static int axp717_usb_power_set_input_current_limit(struct axp20x_usb_power *power, + int intval) { - int val; + int tmp; - switch (intval) { - case 100000: - if (power->axp20x_id == AXP221_ID) - return -EINVAL; - fallthrough; - case 500000: - case 900000: - val = (900000 - intval) / 400000; - return regmap_update_bits(power->regmap, - AXP20X_VBUS_IPSOUT_MGMT, - AXP20X_VBUS_CLIMIT_MASK, val); - default: + /* Minimum value of 100mA and maximum value of 3.25A*/ + if (intval < 100000 || intval > 3250000) return -EINVAL; + + if (power->max_input_cur && (intval > power->max_input_cur)) { + dev_warn(power->dev, + "requested current %d clamped to max current %d\n", + intval, power->max_input_cur); + intval = power->max_input_cur; } - return -EINVAL; + /* Minimum value of 100mA with step size of 50mA. */ + tmp = (intval - 100000) / 50000; + return regmap_update_bits(power->regmap, + AXP717_INPUT_CUR_LIMIT_CTRL, + AXP717_INPUT_CUR_LIMIT_MASK, tmp); } static int axp20x_usb_power_set_property(struct power_supply *psy, @@ -372,18 +512,34 @@ static int axp20x_usb_power_set_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_ONLINE: - if (power->axp20x_id != AXP813_ID) + if (!power->vbus_disable_bit) return -EINVAL; - return axp813_usb_power_set_online(power, val->intval); + + return regmap_field_write(power->vbus_disable_bit, !val->intval); case POWER_SUPPLY_PROP_VOLTAGE_MIN: return axp20x_usb_power_set_voltage_min(power, val->intval); - case POWER_SUPPLY_PROP_CURRENT_MAX: - if (power->axp20x_id == AXP813_ID) - return axp813_usb_power_set_current_max(power, - val->intval); - return axp20x_usb_power_set_current_max(power, val->intval); + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return axp20x_usb_power_set_input_current_limit(power, val->intval); + + default: + return -EINVAL; + } +} + +static int axp717_usb_power_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct axp20x_usb_power *power = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return axp717_usb_power_set_input_current_limit(power, val->intval); + + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + return axp717_usb_power_set_voltage_min(power, val->intval); default: return -EINVAL; @@ -405,10 +561,68 @@ static int axp20x_usb_power_prop_writeable(struct power_supply *psy, * the VBUS input offline. */ if (psp == POWER_SUPPLY_PROP_ONLINE) - return power->axp20x_id == AXP813_ID; + return power->vbus_disable_bit != NULL; return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || - psp == POWER_SUPPLY_PROP_CURRENT_MAX; + psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT; +} + +static int axp717_usb_power_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || + psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT; +} + +static int axp20x_configure_iio_channels(struct platform_device *pdev, + struct axp20x_usb_power *power) +{ + power->vbus_v = devm_iio_channel_get(&pdev->dev, "vbus_v"); + if (IS_ERR(power->vbus_v)) { + if (PTR_ERR(power->vbus_v) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(power->vbus_v); + } + + power->vbus_i = devm_iio_channel_get(&pdev->dev, "vbus_i"); + if (IS_ERR(power->vbus_i)) { + if (PTR_ERR(power->vbus_i) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(power->vbus_i); + } + + return 0; +} + +static int axp717_configure_iio_channels(struct platform_device *pdev, + struct axp20x_usb_power *power) +{ + power->vbus_v = devm_iio_channel_get(&pdev->dev, "vbus_v"); + if (IS_ERR(power->vbus_v)) { + if (PTR_ERR(power->vbus_v) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(power->vbus_v); + } + + return 0; +} + +static int axp20x_configure_adc_registers(struct axp20x_usb_power *power) +{ + /* Enable vbus voltage and current measurement */ + return regmap_update_bits(power->regmap, AXP20X_ADC_EN1, + AXP20X_ADC_EN1_VBUS_CURR | + AXP20X_ADC_EN1_VBUS_VOLT, + AXP20X_ADC_EN1_VBUS_CURR | + AXP20X_ADC_EN1_VBUS_VOLT); +} + +static int axp717_configure_adc_registers(struct axp20x_usb_power *power) +{ + /* Enable vbus voltage measurement */ + return regmap_update_bits(power->regmap, AXP717_ADC_CH_EN_CONTROL, + AXP717_ADC_EN_VBUS_VOLT, + AXP717_ADC_EN_VBUS_VOLT); } static enum power_supply_property axp20x_usb_power_properties[] = { @@ -417,7 +631,7 @@ static enum power_supply_property axp20x_usb_power_properties[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_MIN, POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_CURRENT_NOW, }; @@ -426,7 +640,26 @@ static enum power_supply_property axp22x_usb_power_properties[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_MIN, - POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, +}; + +static enum power_supply_property axp717_usb_power_properties[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static enum power_supply_property axp813_usb_power_properties[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_USB_TYPE, }; static const struct power_supply_desc axp20x_usb_power_desc = { @@ -449,6 +682,34 @@ static const struct power_supply_desc axp22x_usb_power_desc = { .set_property = axp20x_usb_power_set_property, }; +static const struct power_supply_desc axp717_usb_power_desc = { + .name = "axp20x-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = axp717_usb_power_properties, + .num_properties = ARRAY_SIZE(axp717_usb_power_properties), + .property_is_writeable = axp717_usb_power_prop_writeable, + .get_property = axp717_usb_power_get_property, + .set_property = axp717_usb_power_set_property, + .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), +}; + +static const struct power_supply_desc axp813_usb_power_desc = { + .name = "axp20x-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = axp813_usb_power_properties, + .num_properties = ARRAY_SIZE(axp813_usb_power_properties), + .property_is_writeable = axp20x_usb_power_prop_writeable, + .get_property = axp20x_usb_power_get_property, + .set_property = axp20x_usb_power_set_property, + .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), +}; + static const char * const axp20x_irq_names[] = { "VBUS_PLUGIN", "VBUS_REMOVAL", @@ -461,39 +722,127 @@ static const char * const axp22x_irq_names[] = { "VBUS_REMOVAL", }; -struct axp_data { - const struct power_supply_desc *power_desc; - const char * const *irq_names; - unsigned int num_irq_names; - enum axp20x_variants axp20x_id; +static const char * const axp717_irq_names[] = { + "VBUS_PLUGIN", + "VBUS_REMOVAL", + "VBUS_OVER_V", +}; + +static int axp192_usb_curr_lim_table[] = { + -1, + -1, + 500000, + 100000, +}; + +static int axp20x_usb_curr_lim_table[] = { + 900000, + 500000, + 100000, + -1, +}; + +static int axp221_usb_curr_lim_table[] = { + 900000, + 500000, + -1, + -1, +}; + +static int axp813_usb_curr_lim_table[] = { + 100000, + 500000, + 900000, + 1500000, + 2000000, + 2500000, + 3000000, + 3500000, + 4000000, +}; + +static const struct axp_data axp192_data = { + .power_desc = &axp20x_usb_power_desc, + .irq_names = axp20x_irq_names, + .num_irq_names = ARRAY_SIZE(axp20x_irq_names), + .curr_lim_table = axp192_usb_curr_lim_table, + .curr_lim_table_size = ARRAY_SIZE(axp192_usb_curr_lim_table), + .curr_lim_fld = REG_FIELD(AXP20X_VBUS_IPSOUT_MGMT, 0, 1), + .vbus_valid_bit = REG_FIELD(AXP192_USB_OTG_STATUS, 2, 2), + .vbus_mon_bit = REG_FIELD(AXP20X_VBUS_MON, 3, 3), + .axp20x_read_vbus = &axp20x_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp20x_configure_iio_channels, + .axp20x_cfg_adc_reg = axp20x_configure_adc_registers, }; static const struct axp_data axp202_data = { .power_desc = &axp20x_usb_power_desc, .irq_names = axp20x_irq_names, .num_irq_names = ARRAY_SIZE(axp20x_irq_names), - .axp20x_id = AXP202_ID, + .curr_lim_table = axp20x_usb_curr_lim_table, + .curr_lim_table_size = ARRAY_SIZE(axp20x_usb_curr_lim_table), + .curr_lim_fld = REG_FIELD(AXP20X_VBUS_IPSOUT_MGMT, 0, 1), + .vbus_valid_bit = REG_FIELD(AXP20X_USB_OTG_STATUS, 2, 2), + .vbus_mon_bit = REG_FIELD(AXP20X_VBUS_MON, 3, 3), + .axp20x_read_vbus = &axp20x_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp20x_configure_iio_channels, + .axp20x_cfg_adc_reg = axp20x_configure_adc_registers, }; static const struct axp_data axp221_data = { .power_desc = &axp22x_usb_power_desc, .irq_names = axp22x_irq_names, .num_irq_names = ARRAY_SIZE(axp22x_irq_names), - .axp20x_id = AXP221_ID, + .curr_lim_table = axp221_usb_curr_lim_table, + .curr_lim_table_size = ARRAY_SIZE(axp221_usb_curr_lim_table), + .curr_lim_fld = REG_FIELD(AXP20X_VBUS_IPSOUT_MGMT, 0, 1), + .vbus_needs_polling = true, + .axp20x_read_vbus = &axp20x_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp20x_configure_iio_channels, + .axp20x_cfg_adc_reg = axp20x_configure_adc_registers, }; static const struct axp_data axp223_data = { .power_desc = &axp22x_usb_power_desc, .irq_names = axp22x_irq_names, .num_irq_names = ARRAY_SIZE(axp22x_irq_names), - .axp20x_id = AXP223_ID, + .curr_lim_table = axp20x_usb_curr_lim_table, + .curr_lim_table_size = ARRAY_SIZE(axp20x_usb_curr_lim_table), + .curr_lim_fld = REG_FIELD(AXP20X_VBUS_IPSOUT_MGMT, 0, 1), + .vbus_needs_polling = true, + .axp20x_read_vbus = &axp20x_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp20x_configure_iio_channels, + .axp20x_cfg_adc_reg = axp20x_configure_adc_registers, +}; + +static const struct axp_data axp717_data = { + .power_desc = &axp717_usb_power_desc, + .irq_names = axp717_irq_names, + .num_irq_names = ARRAY_SIZE(axp717_irq_names), + .curr_lim_fld = REG_FIELD(AXP717_INPUT_CUR_LIMIT_CTRL, 0, 5), + .usb_bc_en_bit = REG_FIELD(AXP717_MODULE_EN_CONTROL_1, 4, 4), + .usb_bc_det_fld = REG_FIELD(AXP717_BC_DETECT, 5, 7), + .vbus_mon_bit = REG_FIELD(AXP717_ADC_CH_EN_CONTROL, 2, 2), + .vbus_needs_polling = false, + .axp20x_read_vbus = &axp717_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp717_configure_iio_channels, + .axp20x_cfg_adc_reg = axp717_configure_adc_registers, }; static const struct axp_data axp813_data = { - .power_desc = &axp22x_usb_power_desc, + .power_desc = &axp813_usb_power_desc, .irq_names = axp22x_irq_names, .num_irq_names = ARRAY_SIZE(axp22x_irq_names), - .axp20x_id = AXP813_ID, + .curr_lim_table = axp813_usb_curr_lim_table, + .curr_lim_table_size = ARRAY_SIZE(axp813_usb_curr_lim_table), + .curr_lim_fld = REG_FIELD(AXP22X_CHRG_CTRL3, 4, 7), + .usb_bc_en_bit = REG_FIELD(AXP288_BC_GLOBAL, 0, 0), + .usb_bc_det_fld = REG_FIELD(AXP288_BC_DET_STAT, 5, 7), + .vbus_disable_bit = REG_FIELD(AXP20X_VBUS_IPSOUT_MGMT, 7, 7), + .vbus_needs_polling = true, + .axp20x_read_vbus = &axp20x_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp20x_configure_iio_channels, + .axp20x_cfg_adc_reg = axp20x_configure_adc_registers, }; #ifdef CONFIG_PM_SLEEP @@ -535,34 +884,36 @@ static int axp20x_usb_power_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(axp20x_usb_power_pm_ops, axp20x_usb_power_suspend, axp20x_usb_power_resume); -static int configure_iio_channels(struct platform_device *pdev, - struct axp20x_usb_power *power) +static int axp20x_regmap_field_alloc_optional(struct device *dev, + struct regmap *regmap, + struct reg_field fdesc, + struct regmap_field **fieldp) { - power->vbus_v = devm_iio_channel_get(&pdev->dev, "vbus_v"); - if (IS_ERR(power->vbus_v)) { - if (PTR_ERR(power->vbus_v) == -ENODEV) - return -EPROBE_DEFER; - return PTR_ERR(power->vbus_v); - } + struct regmap_field *field; - power->vbus_i = devm_iio_channel_get(&pdev->dev, "vbus_i"); - if (IS_ERR(power->vbus_i)) { - if (PTR_ERR(power->vbus_i) == -ENODEV) - return -EPROBE_DEFER; - return PTR_ERR(power->vbus_i); + if (fdesc.reg == 0) { + *fieldp = NULL; + return 0; } + field = devm_regmap_field_alloc(dev, regmap, fdesc); + if (IS_ERR(field)) + return PTR_ERR(field); + + *fieldp = field; return 0; } -static int configure_adc_registers(struct axp20x_usb_power *power) +/* Optionally allow users to specify a maximum charging current. */ +static void axp20x_usb_power_parse_dt(struct device *dev, + struct axp20x_usb_power *power) { - /* Enable vbus voltage and current measurement */ - return regmap_update_bits(power->regmap, AXP20X_ADC_EN1, - AXP20X_ADC_EN1_VBUS_CURR | - AXP20X_ADC_EN1_VBUS_VOLT, - AXP20X_ADC_EN1_VBUS_CURR | - AXP20X_ADC_EN1_VBUS_VOLT); + int ret; + + ret = device_property_read_u32(dev, "input-current-limit-microamp", + &power->max_input_cur); + if (ret) + dev_dbg(dev, "%s() no input-current-limit specified\n", __func__); } static int axp20x_usb_power_probe(struct platform_device *pdev) @@ -591,41 +942,76 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) platform_set_drvdata(pdev, power); - power->axp20x_id = axp_data->axp20x_id; + power->dev = &pdev->dev; + power->axp_data = axp_data; power->regmap = axp20x->regmap; power->num_irqs = axp_data->num_irq_names; + power->curr_lim_fld = devm_regmap_field_alloc(&pdev->dev, power->regmap, + axp_data->curr_lim_fld); + if (IS_ERR(power->curr_lim_fld)) + return PTR_ERR(power->curr_lim_fld); + + axp20x_usb_power_parse_dt(&pdev->dev, power); + + ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap, + axp_data->vbus_valid_bit, + &power->vbus_valid_bit); + if (ret) + return ret; + + ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap, + axp_data->vbus_mon_bit, + &power->vbus_mon_bit); + if (ret) + return ret; + + ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap, + axp_data->usb_bc_en_bit, + &power->usb_bc_en_bit); + if (ret) + return ret; + + ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap, + axp_data->usb_bc_det_fld, + &power->usb_bc_det_fld); + if (ret) + return ret; + + ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap, + axp_data->vbus_disable_bit, + &power->vbus_disable_bit); + if (ret) + return ret; + ret = devm_delayed_work_autocancel(&pdev->dev, &power->vbus_detect, - axp20x_usb_power_poll_vbus); + axp_data->axp20x_read_vbus); if (ret) return ret; - if (power->axp20x_id == AXP202_ID) { + if (power->vbus_mon_bit) { /* Enable vbus valid checking */ - ret = regmap_update_bits(power->regmap, AXP20X_VBUS_MON, - AXP20X_VBUS_MON_VBUS_VALID, - AXP20X_VBUS_MON_VBUS_VALID); + ret = regmap_field_write(power->vbus_mon_bit, 1); if (ret) return ret; if (IS_ENABLED(CONFIG_AXP20X_ADC)) - ret = configure_iio_channels(pdev, power); + ret = axp_data->axp20x_cfg_iio_chan(pdev, power); else - ret = configure_adc_registers(power); + ret = axp_data->axp20x_cfg_adc_reg(power); if (ret) return ret; } - if (power->axp20x_id == AXP813_ID) { + if (power->usb_bc_en_bit) { /* Enable USB Battery Charging specification detection */ - ret = regmap_update_bits(axp20x->regmap, AXP288_BC_GLOBAL, - AXP813_BC_EN, AXP813_BC_EN); + ret = regmap_field_write(power->usb_bc_en_bit, 1); if (ret) 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, @@ -659,6 +1045,9 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) static const struct of_device_id axp20x_usb_power_match[] = { { + .compatible = "x-powers,axp192-usb-power-supply", + .data = &axp192_data, + }, { .compatible = "x-powers,axp202-usb-power-supply", .data = &axp202_data, }, { @@ -668,6 +1057,9 @@ static const struct of_device_id axp20x_usb_power_match[] = { .compatible = "x-powers,axp223-usb-power-supply", .data = &axp223_data, }, { + .compatible = "x-powers,axp717-usb-power-supply", + .data = &axp717_data, + }, { .compatible = "x-powers,axp813-usb-power-supply", .data = &axp813_data, }, { /* sentinel */ } diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c index 15219ed43ce9..ac05942e4e6a 100644 --- a/drivers/power/supply/axp288_charger.c +++ b/drivers/power/supply/axp288_charger.c @@ -178,18 +178,18 @@ static inline int axp288_charger_set_cv(struct axp288_chrg_info *info, int cv) u8 reg_val; int ret; - if (cv <= CV_4100MV) { - reg_val = CHRG_CCCV_CV_4100MV; - cv = CV_4100MV; - } else if (cv <= CV_4150MV) { - reg_val = CHRG_CCCV_CV_4150MV; - cv = CV_4150MV; - } else if (cv <= CV_4200MV) { + if (cv >= CV_4350MV) { + reg_val = CHRG_CCCV_CV_4350MV; + cv = CV_4350MV; + } else if (cv >= CV_4200MV) { reg_val = CHRG_CCCV_CV_4200MV; cv = CV_4200MV; + } else if (cv >= CV_4150MV) { + reg_val = CHRG_CCCV_CV_4150MV; + cv = CV_4150MV; } else { - reg_val = CHRG_CCCV_CV_4350MV; - cv = CV_4350MV; + reg_val = CHRG_CCCV_CV_4100MV; + cv = CV_4100MV; } reg_val = reg_val << CHRG_CCCV_CV_BIT_POS; @@ -337,8 +337,8 @@ static int axp288_charger_usb_set_property(struct power_supply *psy, } break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - scaled_val = min(val->intval, info->max_cv); - scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); + scaled_val = DIV_ROUND_CLOSEST(val->intval, 1000); + scaled_val = min(scaled_val, info->max_cv); ret = axp288_charger_set_cv(info, scaled_val); if (ret < 0) { dev_warn(&info->pdev->dev, "set charge voltage failed\n"); @@ -836,6 +836,7 @@ static int axp288_charger_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); struct power_supply_config charger_cfg = {}; + const char *extcon_name = NULL; unsigned int val; /* @@ -872,8 +873,18 @@ static int axp288_charger_probe(struct platform_device *pdev) return PTR_ERR(info->cable.edev); } - if (acpi_dev_present(USB_HOST_EXTCON_HID, NULL, -1)) { - info->otg.cable = extcon_get_extcon_dev(USB_HOST_EXTCON_NAME); + /* + * On devices with broken ACPI GPIO event handlers there also is no ACPI + * "INT3496" (USB_HOST_EXTCON_HID) device. x86-android-tablets.ko + * instantiates an "intel-int3496" extcon on these devs as a workaround. + */ + if (acpi_quirk_skip_gpio_event_handlers()) + extcon_name = "intel-int3496"; + else if (acpi_dev_present(USB_HOST_EXTCON_HID, NULL, -1)) + extcon_name = USB_HOST_EXTCON_NAME; + + 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", diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index 8e6f8a655079..a3d71fc72064 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -21,7 +21,7 @@ #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/iio/consumer.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <asm/iosf_mbi.h> #define PS_STAT_VBUS_TRIGGER (1 << 0) @@ -507,7 +507,7 @@ static void fuel_gauge_external_power_changed(struct power_supply *psy) mutex_lock(&info->lock); info->valid = 0; /* Force updating of the cached registers */ mutex_unlock(&info->lock); - power_supply_changed(info->bat); + power_supply_changed(psy); } static struct power_supply_desc fuel_gauge_desc = { @@ -550,18 +550,20 @@ static const struct dmi_system_id axp288_quirks[] = { .driver_data = (void *)AXP288_QUIRK_NO_BATTERY, }, { - /* Intel Cherry Trail Compute Stick, Windows version */ + /* Intel Bay Trail Compute Stick */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Intel"), - DMI_MATCH(DMI_PRODUCT_NAME, "STK1AW32SC"), + /* Partial match for STCK1A32WFC STCK1A32FC, STCK1A8LFC variants */ + DMI_MATCH(DMI_PRODUCT_NAME, "STCK1A"), }, .driver_data = (void *)AXP288_QUIRK_NO_BATTERY, }, { - /* Intel Cherry Trail Compute Stick, version without an OS */ + /* Intel Cherry Trail Compute Stick */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Intel"), - DMI_MATCH(DMI_PRODUCT_NAME, "STK1A32SC"), + /* Partial match for STK1AW32SC and STK1A32SC variants */ + DMI_MATCH(DMI_PRODUCT_NAME, "STK1A"), }, .driver_data = (void *)AXP288_QUIRK_NO_BATTERY, }, @@ -600,6 +602,14 @@ static const struct dmi_system_id axp288_quirks[] = { .driver_data = NULL, }, { + /* Radxa ROCK Pi X Single Board Computer */ + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "ROCK Pi X"), + DMI_MATCH(DMI_BOARD_VENDOR, "Radxa"), + }, + .driver_data = (void *)AXP288_QUIRK_NO_BATTERY, + }, + { /* * Various Ace PC/Meegopad/MinisForum/Wintel Mini-PCs/HDMI-sticks * This entry must be last because it is generic, this allows @@ -724,6 +734,8 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) for (i = 0; i < AXP288_FG_INTR_NUM; i++) { pirq = platform_get_irq(pdev, i); + if (pirq < 0) + continue; ret = regmap_irq_get_virq(axp20x->regmap_irqc, pirq); if (ret < 0) return dev_err_probe(dev, ret, "getting vIRQ %d\n", pirq); diff --git a/drivers/power/supply/bd71828-power.c b/drivers/power/supply/bd71828-power.c new file mode 100644 index 000000000000..f667baedeb77 --- /dev/null +++ b/drivers/power/supply/bd71828-power.c @@ -0,0 +1,1049 @@ +// 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/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_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 { + u8 vbat_avg; + u8 ibat; + u8 ibat_avg; + u8 btemp_vth; + u8 chg_state; + u8 bat_temp; + u8 dcin_stat; + u8 dcin_collapse_limit; + u8 chg_set1; + u8 chg_en; + u8 vbat_alm_limit_u; + u8 conf; + u8 vdcin; +}; + +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, + .btemp_vth = BD71828_REG_VM_BTMP_U, + .chg_state = BD71828_REG_CHG_STATE, + .bat_temp = BD71828_REG_BAT_TEMP, + .dcin_stat = BD71828_REG_DCIN_STAT, + .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, +}; + +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_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, +}; + +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 & BD7182x_MASK_DCIN_DET) != 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) + regmap_update_bits(pwr->regmap, pwr->regs->conf, + BD7182x_MASK_CONF_PON, 0); + + 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 ? */ + 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 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, + BD7182x_MASK_VDCIN_U, &tmp); + if (ret) + return ret; + + vot = tmp; + /* 5 milli volt steps */ + val->intval = 5000 * vot; + break; + default: + return -EINVAL; + } + + return 0; +} + +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_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, +}; + +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(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), + }; + 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; + 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; + struct regmap *regmap; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) { + dev_err(&pdev->dev, "No parent regmap\n"); + return -EINVAL; + } + + pwr = devm_kzalloc(&pdev->dev, sizeof(*pwr), GFP_KERNEL); + if (!pwr) + return -ENOMEM; + + pwr->regmap = regmap; + pwr->dev = &pdev->dev; + pwr->chip_type = platform_get_device_id(pdev)->driver_data; + + 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; + default: + dev_err(pwr->dev, "Unknown PMIC\n"); + return -EINVAL; + } + + 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 }, + { }, +}; +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 250362e15c98..d03a70cf8406 100644 --- a/drivers/power/supply/bd99954-charger.c +++ b/drivers/power/supply/bd99954-charger.c @@ -70,13 +70,6 @@ #include "bd99954-charger.h" -struct battery_data { - u16 precharge_current; /* Trickle-charge Current */ - u16 fc_reg_voltage; /* Fast Charging Regulation Voltage */ - u16 voltage_min; - u16 voltage_max; -}; - /* Initial field values, converted to initial register values */ struct bd9995x_init_data { u16 vsysreg_set; /* VSYS Regulation Setting */ @@ -163,7 +156,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), @@ -536,7 +529,7 @@ static irqreturn_t bd9995x_irq_handler_thread(int irq, void *private) for_each_set_bit(i, &tmp, 7) { int sub_status, sub_mask; - int sub_status_reg[] = { + static const int sub_status_reg[] = { INT1_STATUS, INT2_STATUS, INT3_STATUS, INT4_STATUS, INT5_STATUS, INT6_STATUS, INT7_STATUS, }; @@ -989,7 +982,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); @@ -1077,7 +1070,7 @@ static struct i2c_driver bd9995x_driver = { .name = "bd9995x-charger", .of_match_table = bd9995x_of_match, }, - .probe_new = bd9995x_probe, + .probe = bd9995x_probe, }; module_i2c_driver(bd9995x_driver); diff --git a/drivers/power/supply/bq2415x_charger.c b/drivers/power/supply/bq2415x_charger.c index d2cb7431dced..b50a28b9dd38 100644 --- a/drivers/power/supply/bq2415x_charger.c +++ b/drivers/power/supply/bq2415x_charger.c @@ -171,6 +171,7 @@ struct bq2415x_device { char *name; int autotimer; /* 1 - if driver automatically reset timer, 0 - not */ int automode; /* 1 - enabled, 0 - disabled; -1 - not supported */ + int charge_status; int id; }; @@ -835,11 +836,13 @@ static int bq2415x_notifier_call(struct notifier_block *nb, if (!bq2415x_update_reported_mode(bq, prop.intval)) return NOTIFY_OK; + power_supply_changed(bq->charger); + /* if automode is not enabled do not tell about reported_mode */ if (bq->automode < 1) return NOTIFY_OK; - schedule_delayed_work(&bq->work, 0); + mod_delayed_work(system_percpu_wq, &bq->work, 0); return NOTIFY_OK; } @@ -889,12 +892,19 @@ static void bq2415x_timer_work(struct work_struct *work) int ret; int error; int boost; + int charge; if (bq->automode > 0 && (bq->reported_mode != bq->mode)) { sysfs_notify(&bq->charger->dev.kobj, NULL, "reported_mode"); bq2415x_set_mode(bq, bq->reported_mode); } + charge = bq2415x_exec_command(bq, BQ2415X_CHARGE_STATUS); + if (bq->charge_status != charge) { + power_supply_changed(bq->charger); + bq->charge_status = charge; + } + if (!bq->autotimer) return; @@ -991,6 +1001,7 @@ static enum power_supply_property bq2415x_power_supply_props[] = { /* TODO: maybe add more power supply properties */ POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_ONLINE, }; static int bq2415x_power_supply_get_property(struct power_supply *psy, @@ -1017,6 +1028,15 @@ static int bq2415x_power_supply_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = bq->model; break; + case POWER_SUPPLY_PROP_ONLINE: + /* VBUS is present for all charging and fault states, + * except the 'Ready' state. + */ + ret = bq2415x_exec_command(bq, BQ2415X_CHARGE_STATUS); + if (ret < 0) + return ret; + val->intval = ret > 0; + break; default: return -EINVAL; } @@ -1040,7 +1060,7 @@ static ssize_t bq2415x_sysfs_show_status(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); enum bq2415x_command command; int ret; @@ -1059,7 +1079,7 @@ static ssize_t bq2415x_sysfs_show_status(struct device *dev, ret = bq2415x_exec_command(bq, command); if (ret < 0) return ret; - return sprintf(buf, "%d\n", ret); + return sysfs_emit(buf, "%d\n", ret); } /* @@ -1073,7 +1093,7 @@ static ssize_t bq2415x_sysfs_set_timer(struct device *dev, const char *buf, size_t count) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); int ret = 0; @@ -1094,15 +1114,15 @@ static ssize_t bq2415x_sysfs_show_timer(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); if (bq->timer_error) - return sprintf(buf, "%s\n", bq->timer_error); + return sysfs_emit(buf, "%s\n", bq->timer_error); if (bq->autotimer) - return sprintf(buf, "auto\n"); - return sprintf(buf, "off\n"); + return sysfs_emit(buf, "auto\n"); + return sysfs_emit(buf, "off\n"); } /* @@ -1118,7 +1138,7 @@ static ssize_t bq2415x_sysfs_set_mode(struct device *dev, const char *buf, size_t count) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); enum bq2415x_mode mode; int ret = 0; @@ -1170,35 +1190,35 @@ static ssize_t bq2415x_sysfs_show_mode(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); ssize_t ret = 0; if (bq->automode > 0) - ret += sprintf(buf+ret, "auto ("); + ret += sysfs_emit_at(buf, ret, "auto ("); switch (bq->mode) { case BQ2415X_MODE_OFF: - ret += sprintf(buf+ret, "off"); + ret += sysfs_emit_at(buf, ret, "off"); break; case BQ2415X_MODE_NONE: - ret += sprintf(buf+ret, "none"); + ret += sysfs_emit_at(buf, ret, "none"); break; case BQ2415X_MODE_HOST_CHARGER: - ret += sprintf(buf+ret, "host"); + ret += sysfs_emit_at(buf, ret, "host"); break; case BQ2415X_MODE_DEDICATED_CHARGER: - ret += sprintf(buf+ret, "dedicated"); + ret += sysfs_emit_at(buf, ret, "dedicated"); break; case BQ2415X_MODE_BOOST: - ret += sprintf(buf+ret, "boost"); + ret += sysfs_emit_at(buf, ret, "boost"); break; } if (bq->automode > 0) - ret += sprintf(buf+ret, ")"); + ret += sysfs_emit_at(buf, ret, ")"); - ret += sprintf(buf+ret, "\n"); + ret += sysfs_emit_at(buf, ret, "\n"); return ret; } @@ -1207,7 +1227,7 @@ static ssize_t bq2415x_sysfs_show_reported_mode(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); if (bq->automode < 0) @@ -1215,15 +1235,15 @@ static ssize_t bq2415x_sysfs_show_reported_mode(struct device *dev, switch (bq->reported_mode) { case BQ2415X_MODE_OFF: - return sprintf(buf, "off\n"); + return sysfs_emit(buf, "off\n"); case BQ2415X_MODE_NONE: - return sprintf(buf, "none\n"); + return sysfs_emit(buf, "none\n"); case BQ2415X_MODE_HOST_CHARGER: - return sprintf(buf, "host\n"); + return sysfs_emit(buf, "host\n"); case BQ2415X_MODE_DEDICATED_CHARGER: - return sprintf(buf, "dedicated\n"); + return sysfs_emit(buf, "dedicated\n"); case BQ2415X_MODE_BOOST: - return sprintf(buf, "boost\n"); + return sysfs_emit(buf, "boost\n"); } return -EINVAL; @@ -1235,7 +1255,7 @@ static ssize_t bq2415x_sysfs_set_registers(struct device *dev, const char *buf, size_t count) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); ssize_t ret = 0; unsigned int reg; @@ -1261,8 +1281,8 @@ static ssize_t bq2415x_sysfs_print_reg(struct bq2415x_device *bq, int ret = bq2415x_i2c_read(bq, reg); if (ret < 0) - return sprintf(buf, "%#.2x=error %d\n", reg, ret); - return sprintf(buf, "%#.2x=%#.2x\n", reg, ret); + return sysfs_emit(buf, "%#.2x=error %d\n", reg, ret); + return sysfs_emit(buf, "%#.2x=%#.2x\n", reg, ret); } /* show all raw values of chip register, format per line: 'register=value' */ @@ -1270,7 +1290,7 @@ static ssize_t bq2415x_sysfs_show_registers(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); ssize_t ret = 0; @@ -1288,7 +1308,7 @@ static ssize_t bq2415x_sysfs_set_limit(struct device *dev, const char *buf, size_t count) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); long val; int ret; @@ -1319,7 +1339,7 @@ static ssize_t bq2415x_sysfs_show_limit(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); int ret; @@ -1338,7 +1358,7 @@ static ssize_t bq2415x_sysfs_show_limit(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%d\n", ret); + return sysfs_emit(buf, "%d\n", ret); } /* set *_enable entries */ @@ -1347,7 +1367,7 @@ static ssize_t bq2415x_sysfs_set_enable(struct device *dev, const char *buf, size_t count) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); enum bq2415x_command command; long val; @@ -1382,7 +1402,7 @@ static ssize_t bq2415x_sysfs_show_enable(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); enum bq2415x_command command; int ret; @@ -1401,7 +1421,7 @@ static ssize_t bq2415x_sysfs_show_enable(struct device *dev, ret = bq2415x_exec_command(bq, command); if (ret < 0) return ret; - return sprintf(buf, "%d\n", ret); + return sysfs_emit(buf, "%d\n", ret); } static DEVICE_ATTR(current_limit, S_IWUSR | S_IRUGO, @@ -1477,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, }; @@ -1496,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); @@ -1654,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; @@ -1780,7 +1800,7 @@ static struct i2c_driver bq2415x_driver = { .of_match_table = of_match_ptr(bq2415x_of_match_table), .acpi_match_table = ACPI_PTR(bq2415x_i2c_acpi_match), }, - .probe_new = bq2415x_probe, + .probe = bq2415x_probe, .remove = bq2415x_remove, .id_table = bq2415x_i2c_id_table, }; diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 2b2c3a4391c1..ed0ceae8d90b 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -36,10 +36,16 @@ #define BQ24190_REG_POC_WDT_RESET_SHIFT 6 #define BQ24190_REG_POC_CHG_CONFIG_MASK (BIT(5) | BIT(4)) #define BQ24190_REG_POC_CHG_CONFIG_SHIFT 4 -#define BQ24190_REG_POC_CHG_CONFIG_DISABLE 0x0 -#define BQ24190_REG_POC_CHG_CONFIG_CHARGE 0x1 -#define BQ24190_REG_POC_CHG_CONFIG_OTG 0x2 -#define BQ24190_REG_POC_CHG_CONFIG_OTG_ALT 0x3 +#define BQ24190_REG_POC_CHG_CONFIG_DISABLE 0x0 +#define BQ24190_REG_POC_CHG_CONFIG_CHARGE 0x1 +#define BQ24190_REG_POC_CHG_CONFIG_OTG 0x2 +#define BQ24190_REG_POC_CHG_CONFIG_OTG_ALT 0x3 +#define BQ24296_REG_POC_OTG_CONFIG_MASK BIT(5) +#define BQ24296_REG_POC_OTG_CONFIG_SHIFT 5 +#define BQ24296_REG_POC_CHG_CONFIG_MASK BIT(4) +#define BQ24296_REG_POC_CHG_CONFIG_SHIFT 4 +#define BQ24296_REG_POC_OTG_CONFIG_DISABLE 0x0 +#define BQ24296_REG_POC_OTG_CONFIG_OTG 0x1 #define BQ24190_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1)) #define BQ24190_REG_POC_SYS_MIN_SHIFT 1 #define BQ24190_REG_POC_SYS_MIN_MIN 3000 @@ -134,59 +140,25 @@ #define BQ24190_REG_F_BAT_FAULT_SHIFT 3 #define BQ24190_REG_F_NTC_FAULT_MASK (BIT(2) | BIT(1) | BIT(0)) #define BQ24190_REG_F_NTC_FAULT_SHIFT 0 +#define BQ24296_REG_F_NTC_FAULT_MASK (BIT(1) | BIT(0)) +#define BQ24296_REG_F_NTC_FAULT_SHIFT 0 #define BQ24190_REG_VPRS 0x0A /* Vendor/Part/Revision Status */ #define BQ24190_REG_VPRS_PN_MASK (BIT(5) | BIT(4) | BIT(3)) #define BQ24190_REG_VPRS_PN_SHIFT 3 -#define BQ24190_REG_VPRS_PN_24190 0x4 -#define BQ24190_REG_VPRS_PN_24192 0x5 /* Also 24193, 24196 */ -#define BQ24190_REG_VPRS_PN_24192I 0x3 +#define BQ24190_REG_VPRS_PN_24190 0x4 +#define BQ24190_REG_VPRS_PN_24192 0x5 /* Also 24193, 24196 */ +#define BQ24190_REG_VPRS_PN_24192I 0x3 +#define BQ24296_REG_VPRS_PN_MASK (BIT(7) | BIT(6) | BIT(5)) +#define BQ24296_REG_VPRS_PN_SHIFT 5 +#define BQ24296_REG_VPRS_PN_24296 0x1 +#define BQ24296_REG_VPRS_PN_24297 0x3 #define BQ24190_REG_VPRS_TS_PROFILE_MASK BIT(2) #define BQ24190_REG_VPRS_TS_PROFILE_SHIFT 2 #define BQ24190_REG_VPRS_DEV_REG_MASK (BIT(1) | BIT(0)) #define BQ24190_REG_VPRS_DEV_REG_SHIFT 0 /* - * The FAULT register is latched by the bq24190 (except for NTC_FAULT) - * so the first read after a fault returns the latched value and subsequent - * reads return the current value. In order to return the fault status - * to the user, have the interrupt handler save the reg's value and retrieve - * it in the appropriate health/status routine. - */ -struct bq24190_dev_info { - struct i2c_client *client; - struct device *dev; - struct extcon_dev *edev; - struct power_supply *charger; - struct power_supply *battery; - struct delayed_work input_current_limit_work; - char model_name[I2C_NAME_SIZE]; - bool initialized; - bool irq_event; - bool otg_vbus_enabled; - int charge_type; - u16 sys_min; - u16 iprechg; - u16 iterm; - u32 ichg; - u32 ichg_max; - u32 vreg; - u32 vreg_max; - struct mutex f_reg_lock; - u8 f_reg; - u8 ss_reg; - u8 watchdog; -}; - -static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi, - const union power_supply_propval *val); - -static const unsigned int bq24190_usb_extcon_cable[] = { - EXTCON_USB, - EXTCON_NONE, -}; - -/* * The tables below provide a 2-way mapping for the value that goes in * the register field and the real-world value that it represents. * The index of the array is the value that goes in the register; the @@ -211,6 +183,9 @@ static const int bq24190_ccc_ichg_values[] = { 4096000, 4160000, 4224000, 4288000, 4352000, 4416000, 4480000, 4544000 }; +/* ICHG higher than 3008mA is not supported in BQ24296 */ +#define BQ24296_CCC_ICHG_VALUES_LEN 40 + /* REG04[7:2] (VREG) in uV */ static const int bq24190_cvc_vreg_values[] = { 3504000, 3520000, 3536000, 3552000, 3568000, 3584000, 3600000, 3616000, @@ -228,6 +203,70 @@ static const int bq24190_ictrc_treg_values[] = { 600, 800, 1000, 1200 }; +enum bq24190_chip { + BQ24190, + BQ24192, + BQ24192i, + BQ24193, + BQ24196, + BQ24296, + BQ24297, +}; + +/* + * The FAULT register is latched by the bq24190 (except for NTC_FAULT) + * so the first read after a fault returns the latched value and subsequent + * reads return the current value. In order to return the fault status + * to the user, have the interrupt handler save the reg's value and retrieve + * it in the appropriate health/status routine. + */ +struct bq24190_dev_info { + struct i2c_client *client; + struct device *dev; + struct extcon_dev *edev; + struct power_supply *charger; + struct power_supply *battery; + struct delayed_work input_current_limit_work; + char model_name[I2C_NAME_SIZE]; + bool initialized; + bool irq_event; + bool otg_vbus_enabled; + int charge_type; + u16 sys_min; + u16 iprechg; + u16 iterm; + u32 ichg; + u32 ichg_max; + u32 vreg; + u32 vreg_max; + struct mutex f_reg_lock; + u8 f_reg; + u8 ss_reg; + u8 watchdog; + const struct bq24190_chip_info *info; +}; + +struct bq24190_chip_info { + int ichg_array_size; +#ifdef CONFIG_REGULATOR + const struct regulator_desc *vbus_desc; +#endif + int (*check_chip)(struct bq24190_dev_info *bdi); + int (*set_chg_config)(struct bq24190_dev_info *bdi, const u8 chg_config); + int (*set_otg_vbus)(struct bq24190_dev_info *bdi, bool enable); + u8 ntc_fault_mask; + int (*get_ntc_status)(const u8 value); +}; + +static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi, + const union power_supply_propval *val); + +static const unsigned int bq24190_usb_extcon_cable[] = { + EXTCON_USB, + EXTCON_NONE, +}; + + /* * Return the index in 'tbl' of greatest value that is less than or equal to * 'val'. The index range returned is 0 to 'tbl_size' - 1. Assumes that @@ -386,7 +425,7 @@ static struct bq24190_sysfs_field_info bq24190_sysfs_field_tbl[] = { BQ24190_SYSFS_FIELD_RO(watchdog, CTTC, WATCHDOG), BQ24190_SYSFS_FIELD_RW(en_timer, CTTC, EN_TIMER), BQ24190_SYSFS_FIELD_RW(chg_timer, CTTC, CHG_TIMER), - BQ24190_SYSFS_FIELD_RW(jeta_iset, CTTC, JEITA_ISET), + BQ24190_SYSFS_FIELD_RW(jeita_iset, CTTC, JEITA_ISET), BQ24190_SYSFS_FIELD_RW(bat_comp, ICTRC, BAT_COMP), BQ24190_SYSFS_FIELD_RW(vclamp, ICTRC, VCLAMP), BQ24190_SYSFS_FIELD_RW(treg, ICTRC, TREG), @@ -444,7 +483,7 @@ static struct bq24190_sysfs_field_info *bq24190_sysfs_field_lookup( static ssize_t bq24190_sysfs_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); struct bq24190_sysfs_field_info *info; ssize_t count; @@ -463,9 +502,8 @@ static ssize_t bq24190_sysfs_show(struct device *dev, if (ret) count = ret; else - count = scnprintf(buf, PAGE_SIZE, "%hhx\n", v); + count = sysfs_emit(buf, "%hhx\n", v); - pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); return count; @@ -474,7 +512,7 @@ static ssize_t bq24190_sysfs_show(struct device *dev, static ssize_t bq24190_sysfs_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy); struct bq24190_sysfs_field_info *info; int ret; @@ -496,7 +534,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; @@ -523,7 +560,48 @@ 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; +} + +static int bq24296_set_otg_vbus(struct bq24190_dev_info *bdi, bool enable) +{ + union power_supply_propval val = { .intval = bdi->charge_type }; + int ret; + + ret = pm_runtime_resume_and_get(bdi->dev); + if (ret < 0) { + dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", ret); + return ret; + } + + bdi->otg_vbus_enabled = enable; + if (enable) { + ret = bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24296_REG_POC_CHG_CONFIG_MASK, + BQ24296_REG_POC_CHG_CONFIG_SHIFT, + BQ24190_REG_POC_CHG_CONFIG_DISABLE); + + if (ret < 0) + goto out; + + ret = bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24296_REG_POC_OTG_CONFIG_MASK, + BQ24296_REG_POC_OTG_CONFIG_SHIFT, + BQ24296_REG_POC_OTG_CONFIG_OTG); + } else { + ret = bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24296_REG_POC_OTG_CONFIG_MASK, + BQ24296_REG_POC_OTG_CONFIG_SHIFT, + BQ24296_REG_POC_OTG_CONFIG_DISABLE); + if (ret < 0) + goto out; + + ret = bq24190_charger_set_charge_type(bdi, &val); + } + +out: pm_runtime_put_autosuspend(bdi->dev); return ret; @@ -556,7 +634,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) @@ -567,6 +644,42 @@ static int bq24190_vbus_is_enabled(struct regulator_dev *dev) return bdi->otg_vbus_enabled; } +static int bq24296_vbus_enable(struct regulator_dev *dev) +{ + return bq24296_set_otg_vbus(rdev_get_drvdata(dev), true); +} + +static int bq24296_vbus_disable(struct regulator_dev *dev) +{ + return bq24296_set_otg_vbus(rdev_get_drvdata(dev), false); +} + +static int bq24296_vbus_is_enabled(struct regulator_dev *dev) +{ + struct bq24190_dev_info *bdi = rdev_get_drvdata(dev); + int ret; + u8 val; + + ret = pm_runtime_resume_and_get(bdi->dev); + if (ret < 0) { + dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", ret); + return ret; + } + + ret = bq24190_read_mask(bdi, BQ24190_REG_POC, + BQ24296_REG_POC_OTG_CONFIG_MASK, + BQ24296_REG_POC_OTG_CONFIG_SHIFT, &val); + + pm_runtime_put_autosuspend(bdi->dev); + + if (ret) + return ret; + + bdi->otg_vbus_enabled = (val == BQ24296_REG_POC_OTG_CONFIG_OTG); + + return bdi->otg_vbus_enabled; +} + static const struct regulator_ops bq24190_vbus_ops = { .enable = bq24190_vbus_enable, .disable = bq24190_vbus_disable, @@ -583,6 +696,22 @@ static const struct regulator_desc bq24190_vbus_desc = { .n_voltages = 1, }; +static const struct regulator_ops bq24296_vbus_ops = { + .enable = bq24296_vbus_enable, + .disable = bq24296_vbus_disable, + .is_enabled = bq24296_vbus_is_enabled, +}; + +static const struct regulator_desc bq24296_vbus_desc = { + .name = "usb_otg_vbus", + .of_match = "usb-otg-vbus", + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &bq24296_vbus_ops, + .fixed_uV = 5000000, + .n_voltages = 1, +}; + static const struct regulator_init_data bq24190_vbus_init_data = { .constraints = { .valid_ops_mask = REGULATOR_CHANGE_STATUS, @@ -602,7 +731,7 @@ static int bq24190_register_vbus_regulator(struct bq24190_dev_info *bdi) else cfg.init_data = &bq24190_vbus_init_data; cfg.driver_data = bdi; - reg = devm_regulator_register(bdi->dev, &bq24190_vbus_desc, &cfg); + reg = devm_regulator_register(bdi->dev, bdi->info->vbus_desc, &cfg); if (IS_ERR(reg)) { ret = PTR_ERR(reg); dev_err(bdi->dev, "Can't register regulator: %d\n", ret); @@ -678,7 +807,7 @@ static int bq24190_set_config(struct bq24190_dev_info *bdi) BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT, bq24190_ccc_ichg_values, - ARRAY_SIZE(bq24190_ccc_ichg_values), + bdi->info->ichg_array_size, bdi->ichg); if (ret < 0) return ret; @@ -777,6 +906,24 @@ static int bq24190_charger_get_charge_type(struct bq24190_dev_info *bdi, return 0; } +static int bq24190_battery_set_chg_config(struct bq24190_dev_info *bdi, + const u8 chg_config) +{ + return bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_CHG_CONFIG_MASK, + BQ24190_REG_POC_CHG_CONFIG_SHIFT, + chg_config); +} + +static int bq24296_battery_set_chg_config(struct bq24190_dev_info *bdi, + const u8 chg_config) +{ + return bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24296_REG_POC_CHG_CONFIG_MASK, + BQ24296_REG_POC_CHG_CONFIG_SHIFT, + chg_config); +} + static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi, const union power_supply_propval *val) { @@ -835,9 +982,50 @@ static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi, return ret; } - return bq24190_write_mask(bdi, BQ24190_REG_POC, - BQ24190_REG_POC_CHG_CONFIG_MASK, - BQ24190_REG_POC_CHG_CONFIG_SHIFT, chg_config); + return bdi->info->set_chg_config(bdi, chg_config); +} + +static int bq24190_charger_get_ntc_status(u8 value) +{ + int health; + + switch (value >> BQ24190_REG_F_NTC_FAULT_SHIFT & 0x7) { + case 0x1: /* TS1 Cold */ + case 0x3: /* TS2 Cold */ + case 0x5: /* Both Cold */ + health = POWER_SUPPLY_HEALTH_COLD; + break; + case 0x2: /* TS1 Hot */ + case 0x4: /* TS2 Hot */ + case 0x6: /* Both Hot */ + health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + default: + health = POWER_SUPPLY_HEALTH_UNKNOWN; + } + + return health; +} + +static int bq24296_charger_get_ntc_status(u8 value) +{ + int health; + + switch (value >> BQ24296_REG_F_NTC_FAULT_SHIFT & 0x3) { + case 0x0: /* Normal */ + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x1: /* Hot */ + health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case 0x2: /* Cold */ + health = POWER_SUPPLY_HEALTH_COLD; + break; + default: + health = POWER_SUPPLY_HEALTH_UNKNOWN; + } + + return health; } static int bq24190_charger_get_health(struct bq24190_dev_info *bdi, @@ -850,21 +1038,8 @@ static int bq24190_charger_get_health(struct bq24190_dev_info *bdi, v = bdi->f_reg; mutex_unlock(&bdi->f_reg_lock); - if (v & BQ24190_REG_F_NTC_FAULT_MASK) { - switch (v >> BQ24190_REG_F_NTC_FAULT_SHIFT & 0x7) { - case 0x1: /* TS1 Cold */ - case 0x3: /* TS2 Cold */ - case 0x5: /* Both Cold */ - health = POWER_SUPPLY_HEALTH_COLD; - break; - case 0x2: /* TS1 Hot */ - case 0x4: /* TS2 Hot */ - case 0x6: /* Both Hot */ - health = POWER_SUPPLY_HEALTH_OVERHEAT; - break; - default: - health = POWER_SUPPLY_HEALTH_UNKNOWN; - } + if (v & bdi->info->ntc_fault_mask) { + health = bdi->info->get_ntc_status(v); } else if (v & BQ24190_REG_F_BAT_FAULT_MASK) { health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; } else if (v & BQ24190_REG_F_CHRG_FAULT_MASK) { @@ -965,7 +1140,7 @@ static int bq24190_charger_get_precharge(struct bq24190_dev_info *bdi, union power_supply_propval *val) { u8 v; - int ret; + int curr, ret; ret = bq24190_read_mask(bdi, BQ24190_REG_PCTCC, BQ24190_REG_PCTCC_IPRECHG_MASK, @@ -973,7 +1148,20 @@ static int bq24190_charger_get_precharge(struct bq24190_dev_info *bdi, if (ret < 0) return ret; - val->intval = ++v * 128 * 1000; + curr = ++v * 128 * 1000; + + ret = bq24190_read_mask(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_FORCE_20PCT_MASK, + BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v); + if (ret < 0) + return ret; + + /* If FORCE_20PCT is enabled, then current is 50% of IPRECHG value */ + if (v) + curr /= 2; + + val->intval = curr; + return 0; } @@ -1002,7 +1190,7 @@ static int bq24190_charger_get_current(struct bq24190_dev_info *bdi, ret = bq24190_get_field_val(bdi, BQ24190_REG_CCC, BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT, bq24190_ccc_ichg_values, - ARRAY_SIZE(bq24190_ccc_ichg_values), &curr); + bdi->info->ichg_array_size, &curr); if (ret < 0) return ret; @@ -1042,7 +1230,7 @@ static int bq24190_charger_set_current(struct bq24190_dev_info *bdi, ret = bq24190_set_field_val(bdi, BQ24190_REG_CCC, BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT, bq24190_ccc_ichg_values, - ARRAY_SIZE(bq24190_ccc_ichg_values), curr); + bdi->info->ichg_array_size, curr); if (ret < 0) return ret; @@ -1128,6 +1316,7 @@ static int bq24190_charger_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_CHARGE_TYPES: ret = bq24190_charger_get_charge_type(bdi, val); break; case POWER_SUPPLY_PROP_HEALTH: @@ -1181,7 +1370,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; @@ -1208,6 +1396,7 @@ static int bq24190_charger_set_property(struct power_supply *psy, ret = bq24190_charger_set_temp_alert_max(bdi, val); break; case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_CHARGE_TYPES: ret = bq24190_charger_set_charge_type(bdi, val); break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: @@ -1223,7 +1412,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; @@ -1236,6 +1424,7 @@ static int bq24190_charger_property_is_writeable(struct power_supply *psy, case POWER_SUPPLY_PROP_ONLINE: case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_CHARGE_TYPES: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: @@ -1262,6 +1451,7 @@ static void bq24190_input_current_limit_work(struct work_struct *work) bq24190_charger_set_property(bdi->charger, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val); + power_supply_changed(bdi->charger); } /* Sync the input-current-limit with our parent supply (if we have one) */ @@ -1277,12 +1467,13 @@ 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)); } static enum power_supply_property bq24190_charger_properties[] = { POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CHARGE_TYPES, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_STATUS, @@ -1312,6 +1503,9 @@ static const struct power_supply_desc bq24190_charger_desc = { .set_property = bq24190_charger_set_property, .property_is_writeable = bq24190_charger_property_is_writeable, .external_power_changed = bq24190_charger_external_power_changed, + .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_NONE) | + BIT(POWER_SUPPLY_CHARGE_TYPE_TRICKLE) | + BIT(POWER_SUPPLY_CHARGE_TYPE_FAST), }; /* Battery power supply property routines */ @@ -1381,26 +1575,9 @@ static int bq24190_battery_get_health(struct bq24190_dev_info *bdi, if (v & BQ24190_REG_F_BAT_FAULT_MASK) { health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; } else { - v &= BQ24190_REG_F_NTC_FAULT_MASK; - v >>= BQ24190_REG_F_NTC_FAULT_SHIFT; + v &= bdi->info->ntc_fault_mask; - switch (v) { - case 0x0: /* Normal */ - health = POWER_SUPPLY_HEALTH_GOOD; - break; - case 0x1: /* TS1 Cold */ - case 0x3: /* TS2 Cold */ - case 0x5: /* Both Cold */ - health = POWER_SUPPLY_HEALTH_COLD; - break; - case 0x2: /* TS1 Hot */ - case 0x4: /* TS2 Hot */ - case 0x6: /* Both Hot */ - health = POWER_SUPPLY_HEALTH_OVERHEAT; - break; - default: - health = POWER_SUPPLY_HEALTH_UNKNOWN; - } + health = v ? bdi->info->get_ntc_status(v) : POWER_SUPPLY_HEALTH_GOOD; } val->intval = health; @@ -1497,7 +1674,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; @@ -1528,7 +1704,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; @@ -1587,12 +1762,13 @@ static int bq24190_configure_usb_otg(struct bq24190_dev_info *bdi, u8 ss_reg) static void bq24190_check_status(struct bq24190_dev_info *bdi) { const u8 battery_mask_ss = BQ24190_REG_SS_CHRG_STAT_MASK; - const u8 battery_mask_f = BQ24190_REG_F_BAT_FAULT_MASK - | BQ24190_REG_F_NTC_FAULT_MASK; + u8 battery_mask_f = BQ24190_REG_F_BAT_FAULT_MASK; bool alert_charger = false, alert_battery = false; u8 ss_reg = 0, f_reg = 0; int i, ret; + battery_mask_f |= bdi->info->ntc_fault_mask; + ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg); if (ret < 0) { dev_err(bdi->dev, "Can't read SS reg: %d\n", ret); @@ -1619,7 +1795,7 @@ static void bq24190_check_status(struct bq24190_dev_info *bdi) !!(f_reg & BQ24190_REG_F_BOOST_FAULT_MASK), !!(f_reg & BQ24190_REG_F_CHRG_FAULT_MASK), !!(f_reg & BQ24190_REG_F_BAT_FAULT_MASK), - !!(f_reg & BQ24190_REG_F_NTC_FAULT_MASK)); + !!(f_reg & bdi->info->ntc_fault_mask)); mutex_lock(&bdi->f_reg_lock); if ((bdi->f_reg & battery_mask_f) != (f_reg & battery_mask_f)) @@ -1675,19 +1851,17 @@ 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; return IRQ_HANDLED; } -static int bq24190_hw_init(struct bq24190_dev_info *bdi) +static int bq24190_check_chip(struct bq24190_dev_info *bdi) { u8 v; int ret; - /* First check that the device really is what its supposed to be */ ret = bq24190_read_mask(bdi, BQ24190_REG_VPRS, BQ24190_REG_VPRS_PN_MASK, BQ24190_REG_VPRS_PN_SHIFT, @@ -1705,6 +1879,41 @@ static int bq24190_hw_init(struct bq24190_dev_info *bdi) return -ENODEV; } + return 0; +} + +static int bq24296_check_chip(struct bq24190_dev_info *bdi) +{ + u8 v; + int ret; + + ret = bq24190_read_mask(bdi, BQ24190_REG_VPRS, + BQ24296_REG_VPRS_PN_MASK, + BQ24296_REG_VPRS_PN_SHIFT, + &v); + if (ret < 0) + return ret; + + switch (v) { + case BQ24296_REG_VPRS_PN_24296: + case BQ24296_REG_VPRS_PN_24297: + break; + default: + dev_err(bdi->dev, "Error unknown model: 0x%02x\n", v); + return -ENODEV; + } + + return 0; +} + +static int bq24190_hw_init(struct bq24190_dev_info *bdi) +{ + int ret; + + ret = bdi->info->check_chip(bdi); + if (ret < 0) + return ret; + ret = bq24190_register_reset(bdi); if (ret < 0) return ret; @@ -1722,7 +1931,8 @@ static int bq24190_get_config(struct bq24190_dev_info *bdi) struct power_supply_battery_info *info; int v, idx; - idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1; + idx = bdi->info->ichg_array_size - 1; + bdi->ichg_max = bq24190_ccc_ichg_values[idx]; idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1; @@ -1762,11 +1972,93 @@ 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; } +static const struct bq24190_chip_info bq24190_chip_info_tbl[] = { + [BQ24190] = { + .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, + }, + [BQ24192] = { + .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, + }, + [BQ24192i] = { + .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, + }, + [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 + .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, + }, + [BQ24296] = { + .ichg_array_size = BQ24296_CCC_ICHG_VALUES_LEN, +#ifdef CONFIG_REGULATOR + .vbus_desc = &bq24296_vbus_desc, +#endif + .check_chip = bq24296_check_chip, + .set_chg_config = bq24296_battery_set_chg_config, + .ntc_fault_mask = BQ24296_REG_F_NTC_FAULT_MASK, + .get_ntc_status = bq24296_charger_get_ntc_status, + .set_otg_vbus = bq24296_set_otg_vbus, + }, + [BQ24297] = { + .ichg_array_size = BQ24296_CCC_ICHG_VALUES_LEN, +#ifdef CONFIG_REGULATOR + .vbus_desc = &bq24296_vbus_desc, +#endif + .check_chip = bq24296_check_chip, + .set_chg_config = bq24296_battery_set_chg_config, + .ntc_fault_mask = BQ24296_REG_F_NTC_FAULT_MASK, + .get_ntc_status = bq24296_charger_get_ntc_status, + .set_otg_vbus = bq24296_set_otg_vbus, + }, +}; + static int bq24190_probe(struct i2c_client *client) { const struct i2c_device_id *id = i2c_client_get_device_id(client); @@ -1789,7 +2081,8 @@ static int bq24190_probe(struct i2c_client *client) bdi->client = client; bdi->dev = dev; - strncpy(bdi->model_name, id->name, I2C_NAME_SIZE); + strscpy(bdi->model_name, id->name, sizeof(bdi->model_name)); + bdi->info = i2c_get_match_data(client); mutex_init(&bdi->f_reg_lock); bdi->charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST; bdi->f_reg = 0; @@ -1827,7 +2120,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, @@ -1884,7 +2177,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; @@ -1906,6 +2198,7 @@ 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); @@ -1925,7 +2218,7 @@ static void bq24190_shutdown(struct i2c_client *client) struct bq24190_dev_info *bdi = i2c_get_clientdata(client); /* Turn off 5V boost regulator on shutdown */ - bq24190_set_otg_vbus(bdi, false); + bdi->info->set_otg_vbus(bdi, false); } static __maybe_unused int bq24190_runtime_suspend(struct device *dev) @@ -1970,7 +2263,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); } @@ -1995,7 +2287,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); } @@ -2014,25 +2305,31 @@ static const struct dev_pm_ops bq24190_pm_ops = { }; static const struct i2c_device_id bq24190_i2c_ids[] = { - { "bq24190" }, - { "bq24192" }, - { "bq24192i" }, - { "bq24196" }, + { "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] }, { }, }; MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids); static const struct of_device_id bq24190_of_match[] = { - { .compatible = "ti,bq24190", }, - { .compatible = "ti,bq24192", }, - { .compatible = "ti,bq24192i", }, - { .compatible = "ti,bq24196", }, + { .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] }, { }, }; MODULE_DEVICE_TABLE(of, bq24190_of_match); static struct i2c_driver bq24190_driver = { - .probe_new = bq24190_probe, + .probe = bq24190_probe, .remove = bq24190_remove, .shutdown = bq24190_shutdown, .id_table = bq24190_i2c_ids, diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c index ab4c49788c58..766eecb35694 100644 --- a/drivers/power/supply/bq24257_charger.c +++ b/drivers/power/supply/bq24257_charger.c @@ -35,20 +35,15 @@ #define BQ24257_ILIM_SET_DELAY 1000 /* msec */ -/* - * When adding support for new devices make sure that enum bq2425x_chip and - * bq2425x_chip_name[] always stay in sync! - */ enum bq2425x_chip { BQ24250, BQ24251, BQ24257, }; -static const char *const bq2425x_chip_name[] = { - "bq24250", - "bq24251", - "bq24257", +struct bq2425x_chip_info { + const char *const name; + enum bq2425x_chip chip; }; enum bq24257_fields { @@ -84,7 +79,7 @@ struct bq24257_device { struct device *dev; struct power_supply *charger; - enum bq2425x_chip chip; + const struct bq2425x_chip_info *info; struct regmap *rmap; struct regmap_field *rmap_fields[F_MAX_FIELDS]; @@ -118,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, }; @@ -329,7 +324,7 @@ static int bq24257_power_supply_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = bq2425x_chip_name[bq->chip]; + val->strval = bq->info->name; break; case POWER_SUPPLY_PROP_ONLINE: @@ -764,29 +759,27 @@ static ssize_t bq24257_show_ovp_voltage(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq24257_device *bq = power_supply_get_drvdata(psy); - return scnprintf(buf, PAGE_SIZE, "%u\n", - bq24257_vovp_map[bq->init_data.vovp]); + return sysfs_emit(buf, "%u\n", bq24257_vovp_map[bq->init_data.vovp]); } static ssize_t bq24257_show_in_dpm_voltage(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq24257_device *bq = power_supply_get_drvdata(psy); - return scnprintf(buf, PAGE_SIZE, "%u\n", - bq24257_vindpm_map[bq->init_data.vindpm]); + return sysfs_emit(buf, "%u\n", bq24257_vindpm_map[bq->init_data.vindpm]); } static ssize_t bq24257_sysfs_show_enable(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq24257_device *bq = power_supply_get_drvdata(psy); int ret; @@ -800,7 +793,7 @@ static ssize_t bq24257_sysfs_show_enable(struct device *dev, if (ret < 0) return ret; - return scnprintf(buf, PAGE_SIZE, "%d\n", ret); + return sysfs_emit(buf, "%d\n", ret); } static ssize_t bq24257_sysfs_set_enable(struct device *dev, @@ -808,7 +801,7 @@ static ssize_t bq24257_sysfs_set_enable(struct device *dev, const char *buf, size_t count) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct bq24257_device *bq = power_supply_get_drvdata(psy); long val; int ret; @@ -949,10 +942,8 @@ static int bq24257_fw_probe(struct bq24257_device *bq) static int bq24257_probe(struct i2c_client *client) { - const struct i2c_device_id *id = i2c_client_get_device_id(client); struct i2c_adapter *adapter = client->adapter; struct device *dev = &client->dev; - const struct acpi_device_id *acpi_id; struct bq24257_device *bq; int ret; int i; @@ -969,17 +960,9 @@ static int bq24257_probe(struct i2c_client *client) bq->client = client; bq->dev = dev; - if (ACPI_HANDLE(dev)) { - acpi_id = acpi_match_device(dev->driver->acpi_match_table, - &client->dev); - if (!acpi_id) { - dev_err(dev, "Failed to match ACPI device\n"); - return -ENODEV; - } - bq->chip = (enum bq2425x_chip)acpi_id->driver_data; - } else { - bq->chip = (enum bq2425x_chip)id->driver_data; - } + bq->info = i2c_get_match_data(client); + if (!bq->info) + return dev_err_probe(dev, -ENODEV, "Failed to match device\n"); mutex_init(&bq->lock); @@ -1017,7 +1000,7 @@ static int bq24257_probe(struct i2c_client *client) * used for the automatic setting of the input current limit setting so * explicitly disable that feature. */ - if (bq->chip == BQ24250) + if (bq->info->chip == BQ24250) bq->iilimit_autoset_enable = false; if (bq->iilimit_autoset_enable) @@ -1030,7 +1013,7 @@ static int bq24257_probe(struct i2c_client *client) * the PG state. We also use a SW-based approach for all other devices * if the PG pin is either not defined or can't be probed. */ - if (bq->chip != BQ24250) + if (bq->info->chip != BQ24250) bq24257_pg_gpio_probe(bq); if (PTR_ERR(bq->pg) == -EPROBE_DEFER) @@ -1068,7 +1051,7 @@ static int bq24257_probe(struct i2c_client *client) bq24257_irq_handler_thread, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, - bq2425x_chip_name[bq->chip], bq); + bq->info->name, bq); if (ret) { dev_err(dev, "Failed to request IRQ #%d\n", client->irq); return ret; @@ -1134,28 +1117,43 @@ static const struct dev_pm_ops bq24257_pm = { SET_SYSTEM_SLEEP_PM_OPS(bq24257_suspend, bq24257_resume) }; +static const struct bq2425x_chip_info bq24250_info = { + .name = "bq24250", + .chip = BQ24250, +}; + +static const struct bq2425x_chip_info bq24251_info = { + .name = "bq24251", + .chip = BQ24251, +}; + +static const struct bq2425x_chip_info bq24257_info = { + .name = "bq24257", + .chip = BQ24257, +}; + static const struct i2c_device_id bq24257_i2c_ids[] = { - { "bq24250", BQ24250 }, - { "bq24251", BQ24251 }, - { "bq24257", BQ24257 }, - {}, + { "bq24250", (kernel_ulong_t)&bq24250_info }, + { "bq24251", (kernel_ulong_t)&bq24251_info }, + { "bq24257", (kernel_ulong_t)&bq24257_info }, + {} }; MODULE_DEVICE_TABLE(i2c, bq24257_i2c_ids); -static const struct of_device_id bq24257_of_match[] = { - { .compatible = "ti,bq24250", }, - { .compatible = "ti,bq24251", }, - { .compatible = "ti,bq24257", }, - { }, +static const struct of_device_id bq24257_of_match[] __maybe_unused = { + { .compatible = "ti,bq24250", &bq24250_info }, + { .compatible = "ti,bq24251", &bq24251_info }, + { .compatible = "ti,bq24257", &bq24257_info }, + {} }; MODULE_DEVICE_TABLE(of, bq24257_of_match); #ifdef CONFIG_ACPI static const struct acpi_device_id bq24257_acpi_match[] = { - { "BQ242500", BQ24250 }, - { "BQ242510", BQ24251 }, - { "BQ242570", BQ24257 }, - {}, + { "BQ242500", (kernel_ulong_t)&bq24250_info }, + { "BQ242510", (kernel_ulong_t)&bq24251_info }, + { "BQ242570", (kernel_ulong_t)&bq24257_info }, + {} }; MODULE_DEVICE_TABLE(acpi, bq24257_acpi_match); #endif @@ -1167,7 +1165,7 @@ static struct i2c_driver bq24257_driver = { .acpi_match_table = ACPI_PTR(bq24257_acpi_match), .pm = &bq24257_pm, }, - .probe_new = bq24257_probe, + .probe = bq24257_probe, .remove = bq24257_remove, .id_table = bq24257_i2c_ids, }; diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c index cfca3a82d5a8..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); @@ -489,7 +489,7 @@ static int bq24735_charger_probe(struct i2c_client *client) } static const struct i2c_device_id bq24735_charger_id[] = { - { "bq24735-charger", 0 }, + { "bq24735-charger" }, {} }; MODULE_DEVICE_TABLE(i2c, bq24735_charger_id); @@ -505,7 +505,7 @@ static struct i2c_driver bq24735_charger_driver = { .name = "bq24735-charger", .of_match_table = bq24735_match_ids, }, - .probe_new = bq24735_charger_probe, + .probe = bq24735_charger_probe, .id_table = bq24735_charger_id, }; diff --git a/drivers/power/supply/bq2515x_charger.c b/drivers/power/supply/bq2515x_charger.c index da224ae8dc61..437bff5bc420 100644 --- a/drivers/power/supply/bq2515x_charger.c +++ b/drivers/power/supply/bq2515x_charger.c @@ -147,9 +147,14 @@ struct bq2515x_init_data { int iprechg; }; -enum bq2515x_id { - BQ25150, - BQ25155, +/** + * struct bq2515x_info - + * @regmap_config: register map config + * @ilim: input current limit + */ +struct bq2515x_info { + const struct regmap_config *regmap_config; + int ilim; }; /** @@ -164,8 +169,8 @@ enum bq2515x_id { * @ac_detect_gpio: power good (PG) pin * @ce_gpio: charge enable (CE) pin * + * @info: device info * @model_name: string value describing device model - * @device_id: value of device_id * @mains_online: boolean value indicating power supply online * * @init_data: charger initialization data structure @@ -181,8 +186,8 @@ struct bq2515x_device { struct gpio_desc *ac_detect_gpio; struct gpio_desc *ce_gpio; + const struct bq2515x_info *info; char model_name[I2C_NAME_SIZE]; - int device_id; bool mains_online; struct bq2515x_init_data init_data; @@ -998,16 +1003,8 @@ static int bq2515x_read_properties(struct bq2515x_device *bq2515x) ret = device_property_read_u32(bq2515x->dev, "input-current-limit-microamp", &bq2515x->init_data.ilim); - if (ret) { - switch (bq2515x->device_id) { - case BQ25150: - bq2515x->init_data.ilim = BQ25150_DEFAULT_ILIM_UA; - break; - case BQ25155: - bq2515x->init_data.ilim = BQ25155_DEFAULT_ILIM_UA; - break; - } - } + if (ret) + bq2515x->init_data.ilim = bq2515x->info->ilim; bq2515x->ac_detect_gpio = devm_gpiod_get_optional(bq2515x->dev, "ac-detect", GPIOD_IN); @@ -1063,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, }; @@ -1074,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, }; @@ -1092,21 +1089,11 @@ static int bq2515x_probe(struct i2c_client *client) bq2515x->dev = dev; - strncpy(bq2515x->model_name, id->name, I2C_NAME_SIZE); - - bq2515x->device_id = id->driver_data; - - switch (bq2515x->device_id) { - case BQ25150: - bq2515x->regmap = devm_regmap_init_i2c(client, - &bq25150_regmap_config); - break; - case BQ25155: - bq2515x->regmap = devm_regmap_init_i2c(client, - &bq25155_regmap_config); - break; - } + strscpy(bq2515x->model_name, id->name, sizeof(bq2515x->model_name)); + bq2515x->info = i2c_get_match_data(client); + bq2515x->regmap = devm_regmap_init_i2c(client, + bq2515x->info->regmap_config); if (IS_ERR(bq2515x->regmap)) { dev_err(dev, "failed to allocate register map\n"); return PTR_ERR(bq2515x->regmap); @@ -1115,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) { @@ -1139,17 +1126,27 @@ static int bq2515x_probe(struct i2c_client *client) return 0; } +static const struct bq2515x_info bq25150 = { + .regmap_config = &bq25150_regmap_config, + .ilim = BQ25150_DEFAULT_ILIM_UA, +}; + +static const struct bq2515x_info bq25155 = { + .regmap_config = &bq25155_regmap_config, + .ilim = BQ25155_DEFAULT_ILIM_UA, +}; + static const struct i2c_device_id bq2515x_i2c_ids[] = { - { "bq25150", BQ25150, }, - { "bq25155", BQ25155, }, - {}, + { "bq25150", (kernel_ulong_t)&bq25150 }, + { "bq25155", (kernel_ulong_t)&bq25155 }, + {} }; MODULE_DEVICE_TABLE(i2c, bq2515x_i2c_ids); static const struct of_device_id bq2515x_of_match[] = { - { .compatible = "ti,bq25150", }, - { .compatible = "ti,bq25155", }, - { }, + { .compatible = "ti,bq25150", .data = &bq25150 }, + { .compatible = "ti,bq25155", .data = &bq25155 }, + {} }; MODULE_DEVICE_TABLE(of, bq2515x_of_match); @@ -1158,7 +1155,7 @@ static struct i2c_driver bq2515x_driver = { .name = "bq2515x-charger", .of_match_table = bq2515x_of_match, }, - .probe_new = bq2515x_probe, + .probe = bq2515x_probe, .id_table = bq2515x_i2c_ids, }; module_i2c_driver(bq2515x_driver); diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c index db13e288e439..ae14162f017a 100644 --- a/drivers/power/supply/bq256xx_charger.c +++ b/drivers/power/supply/bq256xx_charger.c @@ -41,6 +41,9 @@ #define BQ256XX_IINDPM_MAX_uA 3200000 #define BQ256XX_IINDPM_DEF_uA 2400000 +#define BQ256XX_TS_IGNORE BIT(6) +#define BQ256XX_TS_IGNORE_SHIFT 6 + #define BQ256XX_VINDPM_MASK GENMASK(3, 0) #define BQ256XX_VINDPM_STEP_uV 100000 #define BQ256XX_VINDPM_OFFSET_uV 3900000 @@ -70,6 +73,9 @@ #define BQ25611D_VBATREG_THRESH_uV 4290000 #define BQ25618_VBATREG_THRESH_uV 4300000 +#define BQ256XX_CHG_CONFIG_MASK BIT(4) +#define BQ256XX_CHG_CONFIG_BIT_SHIFT 4 + #define BQ256XX_ITERM_MASK GENMASK(3, 0) #define BQ256XX_ITERM_STEP_uA 60000 #define BQ256XX_ITERM_OFFSET_uA 60000 @@ -153,6 +159,7 @@ * @vindpm: input voltage limit * @ichg_max: maximum fast charge current * @vbatreg_max: maximum charge voltage + * @ts_ignore: TS_IGNORE flag */ struct bq256xx_init_data { u32 ichg; @@ -163,6 +170,7 @@ struct bq256xx_init_data { u32 vindpm; u32 ichg_max; u32 vbatreg_max; + bool ts_ignore; }; /** @@ -259,6 +267,8 @@ struct bq256xx_device { * @bq256xx_set_iterm: pointer to instance specific set_iterm function * @bq256xx_set_iprechg: pointer to instance specific set_iprechg function * @bq256xx_set_vindpm: pointer to instance specific set_vindpm function + * @bq256xx_set_charge_type: pointer to instance specific set_charge_type function + * @bq256xx_set_ts_ignore: pointer to instance specific set_ts_ignore function * * @bq256xx_def_ichg: default ichg value in microamps * @bq256xx_def_iindpm: default iindpm value in microamps @@ -290,6 +300,8 @@ struct bq256xx_chip_info { int (*bq256xx_set_iterm)(struct bq256xx_device *bq, int iterm); int (*bq256xx_set_iprechg)(struct bq256xx_device *bq, int iprechg); int (*bq256xx_set_vindpm)(struct bq256xx_device *bq, int vindpm); + int (*bq256xx_set_charge_type)(struct bq256xx_device *bq, int type); + int (*bq256xx_set_ts_ignore)(struct bq256xx_device *bq, bool ts_ignore); int bq256xx_def_ichg; int bq256xx_def_iindpm; @@ -322,14 +334,6 @@ static const int bq25618_619_ichg_values[] = { 1290000, 1360000, 1430000, 1500000 }; -static enum power_supply_usb_type bq256xx_usb_type[] = { - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_ACA, -}; - static int bq256xx_array_parse(int array_size, int val, const int array[]) { int i = 0; @@ -383,7 +387,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}, @@ -394,7 +398,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}, @@ -407,7 +411,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}, @@ -449,6 +453,27 @@ static int bq256xx_get_state(struct bq256xx_device *bq, return 0; } +static int bq256xx_set_charge_type(struct bq256xx_device *bq, int type) +{ + int chg_config = 0; + + switch (type) { + case POWER_SUPPLY_CHARGE_TYPE_NONE: + chg_config = 0x0; + break; + case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: + case POWER_SUPPLY_CHARGE_TYPE_FAST: + chg_config = 0x1; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(bq->regmap, BQ256XX_CHARGER_CONTROL_0, + BQ256XX_CHG_CONFIG_MASK, + (chg_config ? 1 : 0) << BQ256XX_CHG_CONFIG_BIT_SHIFT); +} + static int bq256xx_get_ichg_curr(struct bq256xx_device *bq) { unsigned int charge_current_limit; @@ -670,6 +695,12 @@ static int bq25601d_set_chrg_volt(struct bq256xx_device *bq, int vbatreg) BQ256XX_VBATREG_BIT_SHIFT); } +static int bq256xx_set_ts_ignore(struct bq256xx_device *bq, bool ts_ignore) +{ + return regmap_update_bits(bq->regmap, BQ256XX_INPUT_CURRENT_LIMIT, + BQ256XX_TS_IGNORE, (ts_ignore ? 1 : 0) << BQ256XX_TS_IGNORE_SHIFT); +} + static int bq256xx_get_prechrg_curr(struct bq256xx_device *bq) { unsigned int prechg_and_term_curr_lim; @@ -915,6 +946,12 @@ static int bq256xx_set_charger_property(struct power_supply *psy, return ret; break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = bq->chip_info->bq256xx_set_charge_type(bq, val->intval); + if (ret) + return ret; + break; + default: break; } @@ -1197,6 +1234,7 @@ static int bq256xx_property_is_writeable(struct power_supply *psy, case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: case POWER_SUPPLY_PROP_STATUS: case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + case POWER_SUPPLY_PROP_CHARGE_TYPE: return true; default: return false; @@ -1206,8 +1244,11 @@ static int bq256xx_property_is_writeable(struct power_supply *psy, static const struct power_supply_desc bq256xx_power_supply_desc = { .name = "bq256xx-charger", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = bq256xx_usb_type, - .num_usb_types = ARRAY_SIZE(bq256xx_usb_type), + .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_ACA) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = bq256xx_power_supply_props, .num_properties = ARRAY_SIZE(bq256xx_power_supply_props), .get_property = bq256xx_get_charger_property, @@ -1279,6 +1320,7 @@ static const struct bq256xx_chip_info bq256xx_chip_info_tbl[] = { .bq256xx_get_iterm = bq256xx_get_term_curr, .bq256xx_get_iprechg = bq256xx_get_prechrg_curr, .bq256xx_get_vindpm = bq256xx_get_input_volt_lim, + .bq256xx_set_ts_ignore = NULL, .bq256xx_set_ichg = bq256xx_set_ichg_curr, .bq256xx_set_iindpm = bq256xx_set_input_curr_lim, @@ -1286,6 +1328,7 @@ static const struct bq256xx_chip_info bq256xx_chip_info_tbl[] = { .bq256xx_set_iterm = bq256xx_set_term_curr, .bq256xx_set_iprechg = bq256xx_set_prechrg_curr, .bq256xx_set_vindpm = bq256xx_set_input_volt_lim, + .bq256xx_set_charge_type = bq256xx_set_charge_type, .bq256xx_def_ichg = BQ2560X_ICHG_DEF_uA, .bq256xx_def_iindpm = BQ256XX_IINDPM_DEF_uA, @@ -1316,6 +1359,8 @@ static const struct bq256xx_chip_info bq256xx_chip_info_tbl[] = { .bq256xx_set_iterm = bq256xx_set_term_curr, .bq256xx_set_iprechg = bq256xx_set_prechrg_curr, .bq256xx_set_vindpm = bq256xx_set_input_volt_lim, + .bq256xx_set_charge_type = bq256xx_set_charge_type, + .bq256xx_set_ts_ignore = NULL, .bq256xx_def_ichg = BQ2560X_ICHG_DEF_uA, .bq256xx_def_iindpm = BQ256XX_IINDPM_DEF_uA, @@ -1346,6 +1391,8 @@ static const struct bq256xx_chip_info bq256xx_chip_info_tbl[] = { .bq256xx_set_iterm = bq256xx_set_term_curr, .bq256xx_set_iprechg = bq256xx_set_prechrg_curr, .bq256xx_set_vindpm = bq256xx_set_input_volt_lim, + .bq256xx_set_charge_type = bq256xx_set_charge_type, + .bq256xx_set_ts_ignore = NULL, .bq256xx_def_ichg = BQ2560X_ICHG_DEF_uA, .bq256xx_def_iindpm = BQ256XX_IINDPM_DEF_uA, @@ -1376,6 +1423,8 @@ static const struct bq256xx_chip_info bq256xx_chip_info_tbl[] = { .bq256xx_set_iterm = bq256xx_set_term_curr, .bq256xx_set_iprechg = bq256xx_set_prechrg_curr, .bq256xx_set_vindpm = bq256xx_set_input_volt_lim, + .bq256xx_set_charge_type = bq256xx_set_charge_type, + .bq256xx_set_ts_ignore = NULL, .bq256xx_def_ichg = BQ2560X_ICHG_DEF_uA, .bq256xx_def_iindpm = BQ256XX_IINDPM_DEF_uA, @@ -1406,6 +1455,8 @@ static const struct bq256xx_chip_info bq256xx_chip_info_tbl[] = { .bq256xx_set_iterm = bq256xx_set_term_curr, .bq256xx_set_iprechg = bq256xx_set_prechrg_curr, .bq256xx_set_vindpm = bq256xx_set_input_volt_lim, + .bq256xx_set_charge_type = bq256xx_set_charge_type, + .bq256xx_set_ts_ignore = bq256xx_set_ts_ignore, .bq256xx_def_ichg = BQ25611D_ICHG_DEF_uA, .bq256xx_def_iindpm = BQ256XX_IINDPM_DEF_uA, @@ -1436,6 +1487,8 @@ static const struct bq256xx_chip_info bq256xx_chip_info_tbl[] = { .bq256xx_set_iterm = bq25618_619_set_term_curr, .bq256xx_set_iprechg = bq25618_619_set_prechrg_curr, .bq256xx_set_vindpm = bq256xx_set_input_volt_lim, + .bq256xx_set_charge_type = bq256xx_set_charge_type, + .bq256xx_set_ts_ignore = bq256xx_set_ts_ignore, .bq256xx_def_ichg = BQ25618_ICHG_DEF_uA, .bq256xx_def_iindpm = BQ256XX_IINDPM_DEF_uA, @@ -1466,6 +1519,8 @@ static const struct bq256xx_chip_info bq256xx_chip_info_tbl[] = { .bq256xx_set_iterm = bq25618_619_set_term_curr, .bq256xx_set_iprechg = bq25618_619_set_prechrg_curr, .bq256xx_set_vindpm = bq256xx_set_input_volt_lim, + .bq256xx_set_charge_type = bq256xx_set_charge_type, + .bq256xx_set_ts_ignore = bq256xx_set_ts_ignore, .bq256xx_def_ichg = BQ25618_ICHG_DEF_uA, .bq256xx_def_iindpm = BQ256XX_IINDPM_DEF_uA, @@ -1514,13 +1569,16 @@ static int bq256xx_hw_init(struct bq256xx_device *bq) wd_reg_val = i; break; } - if (bq->watchdog_timer > bq256xx_watchdog_time[i] && + if (i + 1 < BQ256XX_NUM_WD_VAL && + bq->watchdog_timer > bq256xx_watchdog_time[i] && bq->watchdog_timer < bq256xx_watchdog_time[i + 1]) wd_reg_val = i; } ret = regmap_update_bits(bq->regmap, BQ256XX_CHARGER_CONTROL_1, BQ256XX_WATCHDOG_MASK, wd_reg_val << BQ256XX_WDT_BIT_SHIFT); + if (ret) + return ret; ret = power_supply_get_battery_info(bq->charger, &bat_info); if (ret == -ENOMEM) @@ -1563,7 +1621,7 @@ static int bq256xx_hw_init(struct bq256xx_device *bq) return ret; ret = bq->chip_info->bq256xx_set_ichg(bq, - bat_info->constant_charge_current_max_ua); + bq->chip_info->bq256xx_def_ichg); if (ret) return ret; @@ -1573,7 +1631,7 @@ static int bq256xx_hw_init(struct bq256xx_device *bq) return ret; ret = bq->chip_info->bq256xx_set_vbatreg(bq, - bat_info->constant_charge_voltage_max_uv); + bq->chip_info->bq256xx_def_vbatreg); if (ret) return ret; @@ -1582,6 +1640,12 @@ static int bq256xx_hw_init(struct bq256xx_device *bq) if (ret) return ret; + if (bq->chip_info->bq256xx_set_ts_ignore) { + ret = bq->chip_info->bq256xx_set_ts_ignore(bq, bq->init_data.ts_ignore); + if (ret) + return ret; + } + power_supply_put_battery_info(bq->charger, bat_info); return 0; @@ -1593,7 +1657,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); @@ -1616,6 +1680,8 @@ static int bq256xx_parse_dt(struct bq256xx_device *bq, if (ret) bq->init_data.iindpm = bq->chip_info->bq256xx_def_iindpm; + bq->init_data.ts_ignore = device_property_read_bool(bq->dev, "ti,no-thermistor"); + return 0; } @@ -1634,11 +1700,11 @@ static int bq256xx_probe(struct i2c_client *client) bq->client = client; bq->dev = dev; - bq->chip_info = &bq256xx_chip_info_tbl[id->driver_data]; + bq->chip_info = i2c_get_match_data(client); mutex_init(&bq->lock); - strncpy(bq->model_name, id->name, I2C_NAME_SIZE); + strscpy(bq->model_name, id->name, sizeof(bq->model_name)); bq->regmap = devm_regmap_init_i2c(client, bq->chip_info->bq256xx_regmap_config); @@ -1703,38 +1769,38 @@ static int bq256xx_probe(struct i2c_client *client) } static const struct i2c_device_id bq256xx_i2c_ids[] = { - { "bq25600", BQ25600 }, - { "bq25600d", BQ25600D }, - { "bq25601", BQ25601 }, - { "bq25601d", BQ25601D }, - { "bq25611d", BQ25611D }, - { "bq25618", BQ25618 }, - { "bq25619", BQ25619 }, - {}, + { "bq25600", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25600] }, + { "bq25600d", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25600D] }, + { "bq25601", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25601] }, + { "bq25601d", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25601D] }, + { "bq25611d", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25611D] }, + { "bq25618", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25618] }, + { "bq25619", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25619] }, + {} }; MODULE_DEVICE_TABLE(i2c, bq256xx_i2c_ids); static const struct of_device_id bq256xx_of_match[] = { - { .compatible = "ti,bq25600", .data = (void *)BQ25600 }, - { .compatible = "ti,bq25600d", .data = (void *)BQ25600D }, - { .compatible = "ti,bq25601", .data = (void *)BQ25601 }, - { .compatible = "ti,bq25601d", .data = (void *)BQ25601D }, - { .compatible = "ti,bq25611d", .data = (void *)BQ25611D }, - { .compatible = "ti,bq25618", .data = (void *)BQ25618 }, - { .compatible = "ti,bq25619", .data = (void *)BQ25619 }, - { }, + { .compatible = "ti,bq25600", .data = &bq256xx_chip_info_tbl[BQ25600] }, + { .compatible = "ti,bq25600d", .data = &bq256xx_chip_info_tbl[BQ25600D] }, + { .compatible = "ti,bq25601", .data = &bq256xx_chip_info_tbl[BQ25601] }, + { .compatible = "ti,bq25601d", .data = &bq256xx_chip_info_tbl[BQ25601D] }, + { .compatible = "ti,bq25611d", .data = &bq256xx_chip_info_tbl[BQ25611D] }, + { .compatible = "ti,bq25618", .data = &bq256xx_chip_info_tbl[BQ25618] }, + { .compatible = "ti,bq25619", .data = &bq256xx_chip_info_tbl[BQ25619] }, + {} }; MODULE_DEVICE_TABLE(of, bq256xx_of_match); static const struct acpi_device_id bq256xx_acpi_match[] = { - { "bq25600", BQ25600 }, - { "bq25600d", BQ25600D }, - { "bq25601", BQ25601 }, - { "bq25601d", BQ25601D }, - { "bq25611d", BQ25611D }, - { "bq25618", BQ25618 }, - { "bq25619", BQ25619 }, - {}, + { "bq25600", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25600] }, + { "bq25600d", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25600D] }, + { "bq25601", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25601] }, + { "bq25601d", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25601D] }, + { "bq25611d", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25611D] }, + { "bq25618", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25618] }, + { "bq25619", (kernel_ulong_t)&bq256xx_chip_info_tbl[BQ25619] }, + {} }; MODULE_DEVICE_TABLE(acpi, bq256xx_acpi_match); @@ -1744,7 +1810,7 @@ static struct i2c_driver bq256xx_driver = { .of_match_table = bq256xx_of_match, .acpi_match_table = bq256xx_acpi_match, }, - .probe_new = bq256xx_probe, + .probe = bq256xx_probe, .id_table = bq256xx_i2c_ids, }; module_i2c_driver(bq256xx_driver); 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 2d731ea58323..868e86e1749b 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -95,6 +95,7 @@ struct bq25890_init_data { struct bq25890_state { u8 online; + u8 hiz; u8 chrg_status; u8 chrg_fault; u8 vsys_status; @@ -107,6 +108,10 @@ struct bq25890_device { struct i2c_client *client; struct device *dev; struct power_supply *charger; + struct power_supply *secondary_chrg; + struct power_supply_desc desc; + char name[28]; /* "bq25890-charger-%d" */ + int id; struct usb_phy *usb_phy; struct notifier_block usb_nb; @@ -119,7 +124,9 @@ struct bq25890_device { bool skip_reset; bool read_back_init_data; + bool force_hiz; u32 pump_express_vbus_max; + u32 iinlim_percentage; enum bq25890_chip_version chip_version; struct bq25890_init_data init_data; struct bq25890_state state; @@ -127,6 +134,9 @@ struct bq25890_device { struct mutex lock; /* protect state data */ }; +static DEFINE_IDR(bq25890_id); +static DEFINE_MUTEX(bq25890_id_mutex); + static const struct regmap_range bq25890_readonly_reg_ranges[] = { regmap_reg_range(0x0b, 0x0c), regmap_reg_range(0x0e, 0x13), @@ -154,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, @@ -454,20 +464,18 @@ static int bq25890_get_vbus_voltage(struct bq25890_device *bq) return bq25890_find_val(ret, TBL_VBUSV); } -static int bq25890_power_supply_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) +static void bq25890_update_state(struct bq25890_device *bq, + enum power_supply_property psp, + struct bq25890_state *state) { - struct bq25890_device *bq = power_supply_get_drvdata(psy); - struct bq25890_state state; bool do_adc_conv; int ret; mutex_lock(&bq->lock); /* update state in case we lost an interrupt */ __bq25890_handle_irq(bq); - state = bq->state; - do_adc_conv = !state.online && bq25890_is_adc_property(psp); + *state = bq->state; + do_adc_conv = (!state->online || state->hiz) && bq25890_is_adc_property(psp); if (do_adc_conv) bq25890_field_write(bq, F_CONV_START, 1); mutex_unlock(&bq->lock); @@ -475,10 +483,21 @@ static int bq25890_power_supply_get_property(struct power_supply *psy, if (do_adc_conv) regmap_field_read_poll_timeout(bq->rmap_fields[F_CONV_START], ret, !ret, 25000, 1000000); +} + +static int bq25890_power_supply_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bq25890_device *bq = power_supply_get_drvdata(psy); + struct bq25890_state state; + int ret; + + bq25890_update_state(bq, psp, &state); switch (psp) { case POWER_SUPPLY_PROP_STATUS: - if (!state.online) + if (!state.online || state.hiz) val->intval = POWER_SUPPLY_STATUS_DISCHARGING; else if (state.chrg_status == STATUS_NOT_CHARGING) val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; @@ -493,7 +512,8 @@ static int bq25890_power_supply_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CHARGE_TYPE: - if (!state.online || state.chrg_status == STATUS_NOT_CHARGING || + if (!state.online || state.hiz || + state.chrg_status == STATUS_NOT_CHARGING || state.chrg_status == STATUS_TERMINATION_DONE) val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; else if (state.chrg_status == STATUS_PRE_CHARGING) @@ -513,7 +533,7 @@ static int bq25890_power_supply_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_ONLINE: - val->intval = state.online; + val->intval = state.online && !state.hiz; break; case POWER_SUPPLY_PROP_HEALTH: @@ -667,7 +687,8 @@ static int bq25890_power_supply_set_property(struct power_supply *psy, const union power_supply_propval *val) { struct bq25890_device *bq = power_supply_get_drvdata(psy); - int maxval; + struct bq25890_state state; + int maxval, ret; u8 lval; switch (psp) { @@ -682,6 +703,12 @@ static int bq25890_power_supply_set_property(struct power_supply *psy, case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: lval = bq25890_find_idx(val->intval, TBL_IINLIM); return bq25890_field_write(bq, F_IINLIM, lval); + case POWER_SUPPLY_PROP_ONLINE: + ret = bq25890_field_write(bq, F_EN_HIZ, !val->intval); + if (!ret) + bq->force_hiz = !val->intval; + bq25890_update_state(bq, psp, &state); + return ret; default: return -EINVAL; } @@ -694,12 +721,25 @@ static int bq25890_power_supply_property_is_writeable(struct power_supply *psy, case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_ONLINE: return true; default: return false; } } +/* + * If there are multiple chargers the maximum current the external power-supply + * can deliver needs to be divided over the chargers. This is done according + * to the bq->iinlim_percentage setting. + */ +static int bq25890_charger_get_scaled_iinlim_regval(struct bq25890_device *bq, + int iinlim_ua) +{ + iinlim_ua = iinlim_ua * bq->iinlim_percentage / 100; + return bq25890_find_idx(iinlim_ua, TBL_IINLIM); +} + /* On the BQ25892 try to get charger-type info from our supplier */ static void bq25890_charger_external_power_changed(struct power_supply *psy) { @@ -710,7 +750,7 @@ static void bq25890_charger_external_power_changed(struct power_supply *psy) if (bq->chip_version != BQ25892) return; - ret = power_supply_get_property_from_supplier(bq->charger, + ret = power_supply_get_property_from_supplier(psy, POWER_SUPPLY_PROP_USB_TYPE, &val); if (ret) @@ -718,7 +758,7 @@ static void bq25890_charger_external_power_changed(struct power_supply *psy) switch (val.intval) { case POWER_SUPPLY_USB_TYPE_DCP: - input_current_limit = bq25890_find_idx(2000000, TBL_IINLIM); + input_current_limit = bq25890_charger_get_scaled_iinlim_regval(bq, 2000000); if (bq->pump_express_vbus_max) { queue_delayed_work(system_power_efficient_wq, &bq->pump_express_work, @@ -727,14 +767,15 @@ static void bq25890_charger_external_power_changed(struct power_supply *psy) break; case POWER_SUPPLY_USB_TYPE_CDP: case POWER_SUPPLY_USB_TYPE_ACA: - input_current_limit = bq25890_find_idx(1500000, TBL_IINLIM); + input_current_limit = bq25890_charger_get_scaled_iinlim_regval(bq, 1500000); break; case POWER_SUPPLY_USB_TYPE_SDP: default: - input_current_limit = bq25890_find_idx(500000, TBL_IINLIM); + input_current_limit = bq25890_charger_get_scaled_iinlim_regval(bq, 500000); } bq25890_field_write(bq, F_IINLIM, input_current_limit); + power_supply_changed(psy); } static int bq25890_get_chip_state(struct bq25890_device *bq, @@ -748,6 +789,7 @@ static int bq25890_get_chip_state(struct bq25890_device *bq, } state_fields[] = { {F_CHG_STAT, &state->chrg_status}, {F_PG_STAT, &state->online}, + {F_EN_HIZ, &state->hiz}, {F_VSYS_STAT, &state->vsys_status}, {F_BOOST_FAULT, &state->boost_fault}, {F_BAT_FAULT, &state->bat_fault}, @@ -763,16 +805,18 @@ static int bq25890_get_chip_state(struct bq25890_device *bq, *state_fields[i].data = ret; } - dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT/NTC=%d/%d/%d/%d\n", - state->chrg_status, state->online, state->vsys_status, - state->chrg_fault, state->boost_fault, state->bat_fault, - state->ntc_fault); + dev_dbg(bq->dev, "S:CHG/PG/HIZ/VSYS=%d/%d/%d/%d, F:CHG/BOOST/BAT/NTC=%d/%d/%d/%d\n", + state->chrg_status, state->online, + state->hiz, state->vsys_status, + state->chrg_fault, state->boost_fault, + state->bat_fault, state->ntc_fault); return 0; } static irqreturn_t __bq25890_handle_irq(struct bq25890_device *bq) { + bool adc_conv_rate, new_adc_conv_rate; struct bq25890_state new_state; int ret; @@ -783,14 +827,23 @@ static irqreturn_t __bq25890_handle_irq(struct bq25890_device *bq) if (!memcmp(&bq->state, &new_state, sizeof(new_state))) return IRQ_NONE; - if (!new_state.online && bq->state.online) { /* power removed */ - /* disable ADC */ - ret = bq25890_field_write(bq, F_CONV_RATE, 0); + /* + * Restore HiZ bit in case it was set by user. The chip does not retain + * this bit on cable replug, hence the bit must be reset manually here. + */ + if (new_state.online && !bq->state.online && bq->force_hiz) { + ret = bq25890_field_write(bq, F_EN_HIZ, bq->force_hiz); if (ret < 0) goto error; - } else if (new_state.online && !bq->state.online) { /* power inserted */ - /* enable ADC, to have control of charge current/voltage */ - ret = bq25890_field_write(bq, F_CONV_RATE, 1); + new_state.hiz = 1; + } + + /* Should period ADC sampling be enabled? */ + adc_conv_rate = bq->state.online && !bq->state.hiz; + new_adc_conv_rate = new_state.online && !new_state.hiz; + + if (new_adc_conv_rate != adc_conv_rate) { + ret = bq25890_field_write(bq, F_CONV_RATE, new_adc_conv_rate); if (ret < 0) goto error; } @@ -924,7 +977,7 @@ static int bq25890_hw_init(struct bq25890_device *bq) } /* Configure ADC for continuous conversions when charging */ - ret = bq25890_field_write(bq, F_CONV_RATE, !!bq->state.online); + ret = bq25890_field_write(bq, F_CONV_RATE, bq->state.online && !bq->state.hiz); if (ret < 0) { dev_dbg(bq->dev, "Config ADC failed %d\n", ret); return ret; @@ -957,7 +1010,6 @@ static char *bq25890_charger_supplied_to[] = { }; static const struct power_supply_desc bq25890_power_supply_desc = { - .name = "bq25890-charger", .type = POWER_SUPPLY_TYPE_USB, .properties = bq25890_power_supply_props, .num_properties = ARRAY_SIZE(bq25890_power_supply_props), @@ -971,12 +1023,21 @@ static int bq25890_power_supply_init(struct bq25890_device *bq) { struct power_supply_config psy_cfg = { .drv_data = bq, }; + /* Get ID for the device */ + mutex_lock(&bq25890_id_mutex); + bq->id = idr_alloc(&bq25890_id, bq, 0, 0, GFP_KERNEL); + mutex_unlock(&bq25890_id_mutex); + if (bq->id < 0) + return bq->id; + + snprintf(bq->name, sizeof(bq->name), "bq25890-charger-%d", bq->id); + bq->desc = bq25890_power_supply_desc; + bq->desc.name = bq->name; + psy_cfg.supplied_to = bq25890_charger_supplied_to; psy_cfg.num_supplicants = ARRAY_SIZE(bq25890_charger_supplied_to); - bq->charger = devm_power_supply_register(bq->dev, - &bq25890_power_supply_desc, - &psy_cfg); + bq->charger = devm_power_supply_register(bq->dev, &bq->desc, &psy_cfg); return PTR_ERR_OR_ZERO(bq->charger); } @@ -996,10 +1057,17 @@ static void bq25890_pump_express_work(struct work_struct *data) { struct bq25890_device *bq = container_of(data, struct bq25890_device, pump_express_work.work); + union power_supply_propval value; int voltage, i, ret; dev_dbg(bq->dev, "Start to request input voltage increasing\n"); + /* If there is a second charger put in Hi-Z mode */ + if (bq->secondary_chrg) { + value.intval = 0; + power_supply_set_property(bq->secondary_chrg, POWER_SUPPLY_PROP_ONLINE, &value); + } + /* Enable current pulse voltage control protocol */ ret = bq25890_field_write(bq, F_PUMPX_EN, 1); if (ret < 0) @@ -1031,9 +1099,16 @@ static void bq25890_pump_express_work(struct work_struct *data) bq25890_field_write(bq, F_PUMPX_EN, 0); + if (bq->secondary_chrg) { + value.intval = 1; + power_supply_set_property(bq->secondary_chrg, POWER_SUPPLY_PROP_ONLINE, &value); + } + dev_info(bq->dev, "Hi-voltage charging requested, input voltage is %d mV\n", voltage); + power_supply_changed(bq->charger); + return; error_print: bq25890_field_write(bq, F_PUMPX_EN, 0); @@ -1077,6 +1152,17 @@ static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val, static int bq25890_vbus_enable(struct regulator_dev *rdev) { struct bq25890_device *bq = rdev_get_drvdata(rdev); + union power_supply_propval val = { + .intval = 0, + }; + + /* + * When enabling 5V boost / Vbus output, we need to put the secondary + * charger in Hi-Z mode to avoid it trying to charge the secondary + * battery from the 5V boost output. + */ + if (bq->secondary_chrg) + power_supply_set_property(bq->secondary_chrg, POWER_SUPPLY_PROP_ONLINE, &val); return bq25890_set_otg_cfg(bq, 1); } @@ -1084,8 +1170,19 @@ static int bq25890_vbus_enable(struct regulator_dev *rdev) static int bq25890_vbus_disable(struct regulator_dev *rdev) { struct bq25890_device *bq = rdev_get_drvdata(rdev); + union power_supply_propval val = { + .intval = 1, + }; + int ret; + + ret = bq25890_set_otg_cfg(bq, 0); + if (ret) + return ret; - return bq25890_set_otg_cfg(bq, 0); + if (bq->secondary_chrg) + power_supply_set_property(bq->secondary_chrg, POWER_SUPPLY_PROP_ONLINE, &val); + + return 0; } static int bq25890_vbus_is_enabled(struct regulator_dev *rdev) @@ -1296,11 +1393,31 @@ static int bq25890_fw_probe(struct bq25890_device *bq) { int ret; struct bq25890_init_data *init = &bq->init_data; + const char *str; + u32 val; + + ret = device_property_read_string(bq->dev, "linux,secondary-charger-name", &str); + if (ret == 0) { + bq->secondary_chrg = power_supply_get_by_name(str); + if (!bq->secondary_chrg) + return -EPROBE_DEFER; + } /* Optional, left at 0 if property is not present */ device_property_read_u32(bq->dev, "linux,pump-express-vbus-max", &bq->pump_express_vbus_max); + ret = device_property_read_u32(bq->dev, "linux,iinlim-percentage", &val); + if (ret == 0) { + if (val > 100) { + dev_err(bq->dev, "Error linux,iinlim-percentage %u > 100\n", val); + return -EINVAL; + } + bq->iinlim_percentage = val; + } else { + bq->iinlim_percentage = 100; + } + bq->skip_reset = device_property_read_bool(bq->dev, "linux,skip-reset"); bq->read_back_init_data = device_property_read_bool(bq->dev, "linux,read-back-settings"); @@ -1322,6 +1439,12 @@ static void bq25890_non_devm_cleanup(void *data) struct bq25890_device *bq = data; cancel_delayed_work_sync(&bq->pump_express_work); + + if (bq->id >= 0) { + mutex_lock(&bq25890_id_mutex); + idr_remove(&bq25890_id, bq->id); + mutex_unlock(&bq25890_id_mutex); + } } static int bq25890_probe(struct i2c_client *client) @@ -1336,6 +1459,7 @@ static int bq25890_probe(struct i2c_client *client) bq->client = client; bq->dev = dev; + bq->id = -1; mutex_init(&bq->lock); INIT_DELAYED_WORK(&bq->pump_express_work, bq25890_pump_express_work); @@ -1493,15 +1617,15 @@ static const struct dev_pm_ops bq25890_pm = { }; static const struct i2c_device_id bq25890_i2c_ids[] = { - { "bq25890", 0 }, - { "bq25892", 0 }, - { "bq25895", 0 }, - { "bq25896", 0 }, - {}, + { "bq25890" }, + { "bq25892" }, + { "bq25895" }, + { "bq25896" }, + {} }; MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids); -static const struct of_device_id bq25890_of_match[] = { +static const struct of_device_id bq25890_of_match[] __maybe_unused = { { .compatible = "ti,bq25890", }, { .compatible = "ti,bq25892", }, { .compatible = "ti,bq25895", }, @@ -1525,7 +1649,7 @@ static struct i2c_driver bq25890_driver = { .acpi_match_table = ACPI_PTR(bq25890_acpi_match), .pm = &bq25890_pm, }, - .probe_new = bq25890_probe, + .probe = bq25890_probe, .remove = bq25890_remove, .shutdown = bq25890_shutdown, .id_table = bq25890_i2c_ids, diff --git a/drivers/power/supply/bq25980_charger.c b/drivers/power/supply/bq25980_charger.c index a59d9762bc91..723858d62d14 100644 --- a/drivers/power/supply/bq25980_charger.c +++ b/drivers/power/supply/bq25980_charger.c @@ -104,7 +104,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 +159,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 +214,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 +932,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 +943,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 +954,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 +1057,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); @@ -1223,7 +1223,7 @@ static int bq25980_probe(struct i2c_client *client) mutex_init(&bq->lock); - strncpy(bq->model_name, id->name, I2C_NAME_SIZE); + strscpy(bq->model_name, id->name, sizeof(bq->model_name)); bq->chip_info = &bq25980_chip_info_tbl[id->driver_data]; bq->regmap = devm_regmap_init_i2c(client, @@ -1287,7 +1287,7 @@ static struct i2c_driver bq25980_driver = { .name = "bq25980-charger", .of_match_table = bq25980_of_match, }, - .probe_new = bq25980_probe, + .probe = bq25980_probe, .id_table = bq25980_i2c_ids, }; module_i2c_driver(bq25980_driver); diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 8bf048fbd36a..19445e39651c 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -123,6 +123,8 @@ enum bq27xxx_reg_index { BQ27XXX_DM_BLOCK, /* Data Block */ 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 */ }; @@ -159,6 +161,8 @@ static u8 [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR, [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, @@ -184,6 +188,8 @@ static u8 [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR, [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, @@ -449,9 +455,29 @@ static u8 [BQ27XXX_REG_AP] = 0x18, BQ27XXX_DM_REG_ROWS, }, + bq27426_regs[BQ27XXX_REG_MAX] = { + [BQ27XXX_REG_CTRL] = 0x00, + [BQ27XXX_REG_TEMP] = 0x02, + [BQ27XXX_REG_INT_TEMP] = 0x1e, + [BQ27XXX_REG_VOLT] = 0x04, + [BQ27XXX_REG_AI] = 0x10, + [BQ27XXX_REG_FLAGS] = 0x06, + [BQ27XXX_REG_TTE] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTF] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTES] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, + [BQ27XXX_REG_NAC] = 0x08, + [BQ27XXX_REG_RC] = 0x0c, + [BQ27XXX_REG_FCC] = 0x0e, + [BQ27XXX_REG_CYCT] = INVALID_REG_ADDR, + [BQ27XXX_REG_AE] = INVALID_REG_ADDR, + [BQ27XXX_REG_SOC] = 0x1c, + [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, + [BQ27XXX_REG_AP] = 0x18, + BQ27XXX_DM_REG_ROWS, + }, #define bq27411_regs bq27421_regs #define bq27425_regs bq27421_regs -#define bq27426_regs bq27421_regs #define bq27441_regs bq27421_regs #define bq27621_regs bq27421_regs bq27z561_regs[BQ27XXX_REG_MAX] = { @@ -559,6 +585,8 @@ static enum power_supply_property bq27000_props[] = { POWER_SUPPLY_PROP_POWER_AVG, 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[] = { @@ -579,6 +607,8 @@ static enum power_supply_property bq27010_props[] = { POWER_SUPPLY_PROP_CYCLE_COUNT, 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 @@ -769,10 +799,23 @@ static enum power_supply_property bq27421_props[] = { }; #define bq27411_props bq27421_props #define bq27425_props bq27421_props -#define bq27426_props bq27421_props #define bq27441_props bq27421_props #define bq27621_props bq27421_props +static enum power_supply_property bq27426_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + static enum power_supply_property bq27z561_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, @@ -1083,10 +1126,8 @@ static int poll_interval_param_set(const char *val, const struct kernel_param *k return ret; mutex_lock(&bq27xxx_list_lock); - list_for_each_entry(di, &bq27xxx_battery_devices, list) { - cancel_delayed_work_sync(&di->work); - schedule_delayed_work(&di->work, 0); - } + list_for_each_entry(di, &bq27xxx_battery_devices, list) + mod_delayed_work(system_percpu_wq, &di->work, 0); mutex_unlock(&bq27xxx_list_lock); return ret; @@ -1547,7 +1588,8 @@ static int bq27xxx_battery_read_soc(struct bq27xxx_device_info *di) * Return a battery charge value in µAh * Or < 0 if something fails. */ -static int bq27xxx_battery_read_charge(struct bq27xxx_device_info *di, u8 reg) +static int bq27xxx_battery_read_charge(struct bq27xxx_device_info *di, u8 reg, + union power_supply_propval *val) { int charge; @@ -1563,51 +1605,63 @@ static int bq27xxx_battery_read_charge(struct bq27xxx_device_info *di, u8 reg) else charge *= 1000; - return charge; + val->intval = charge; + + return 0; } /* * Return the battery Nominal available capacity in µAh * Or < 0 if something fails. */ -static inline int bq27xxx_battery_read_nac(struct bq27xxx_device_info *di) +static inline int bq27xxx_battery_read_nac(struct bq27xxx_device_info *di, + union power_supply_propval *val) { - return bq27xxx_battery_read_charge(di, BQ27XXX_REG_NAC); + return bq27xxx_battery_read_charge(di, BQ27XXX_REG_NAC, val); } /* * Return the battery Remaining Capacity in µAh * Or < 0 if something fails. */ -static inline int bq27xxx_battery_read_rc(struct bq27xxx_device_info *di) +static inline int bq27xxx_battery_read_rc(struct bq27xxx_device_info *di, + union power_supply_propval *val) { - return bq27xxx_battery_read_charge(di, BQ27XXX_REG_RC); + return bq27xxx_battery_read_charge(di, BQ27XXX_REG_RC, val); } /* * Return the battery Full Charge Capacity in µAh * Or < 0 if something fails. */ -static inline int bq27xxx_battery_read_fcc(struct bq27xxx_device_info *di) +static inline int bq27xxx_battery_read_fcc(struct bq27xxx_device_info *di, + union power_supply_propval *val) { - return bq27xxx_battery_read_charge(di, BQ27XXX_REG_FCC); + return bq27xxx_battery_read_charge(di, BQ27XXX_REG_FCC, val); } /* * Return the Design Capacity in µAh * Or < 0 if something fails. */ -static int bq27xxx_battery_read_dcap(struct bq27xxx_device_info *di) +static int bq27xxx_battery_read_dcap(struct bq27xxx_device_info *di, + union power_supply_propval *val) { int dcap; + /* We only have to read charge design full once */ + if (di->charge_design_full > 0) { + val->intval = di->charge_design_full; + return 0; + } + if (di->opts & BQ27XXX_O_ZERO) dcap = bq27xxx_read(di, BQ27XXX_REG_DCAP, true); else dcap = bq27xxx_read(di, BQ27XXX_REG_DCAP, false); if (dcap < 0) { - dev_dbg(di->dev, "error reading initial last measured discharge\n"); + dev_dbg(di->dev, "error reading design capacity\n"); return dcap; } @@ -1616,14 +1670,20 @@ static int bq27xxx_battery_read_dcap(struct bq27xxx_device_info *di) else dcap *= 1000; - return dcap; + /* Save for later reads */ + di->charge_design_full = dcap; + + val->intval = dcap; + + return 0; } /* * Return the battery Available energy in µWh * Or < 0 if something fails. */ -static int bq27xxx_battery_read_energy(struct bq27xxx_device_info *di) +static int bq27xxx_battery_read_energy(struct bq27xxx_device_info *di, + union power_supply_propval *val) { int ae; @@ -1638,14 +1698,17 @@ static int bq27xxx_battery_read_energy(struct bq27xxx_device_info *di) else ae *= 1000; - return ae; + val->intval = ae; + + return 0; } /* - * Return the battery temperature in tenths of degree Kelvin + * Return the battery temperature in tenths of degree Celsius * Or < 0 if something fails. */ -static int bq27xxx_battery_read_temperature(struct bq27xxx_device_info *di) +static int bq27xxx_battery_read_temperature(struct bq27xxx_device_info *di, + union power_supply_propval *val) { int temp; @@ -1658,14 +1721,20 @@ static int bq27xxx_battery_read_temperature(struct bq27xxx_device_info *di) if (di->opts & BQ27XXX_O_ZERO) temp = 5 * temp / 2; - return temp; + /* Convert decidegree Kelvin to Celsius */ + temp -= 2731; + + val->intval = temp; + + return 0; } /* * Return the battery Cycle count total * Or < 0 if something fails. */ -static int bq27xxx_battery_read_cyct(struct bq27xxx_device_info *di) +static int bq27xxx_battery_read_cyct(struct bq27xxx_device_info *di, + union power_supply_propval *val) { int cyct; @@ -1673,14 +1742,17 @@ static int bq27xxx_battery_read_cyct(struct bq27xxx_device_info *di) if (cyct < 0) dev_err(di->dev, "error reading cycle count total\n"); - return cyct; + val->intval = cyct; + + return 0; } /* * Read a time register. * Return < 0 if something fails. */ -static int bq27xxx_battery_read_time(struct bq27xxx_device_info *di, u8 reg) +static int bq27xxx_battery_read_time(struct bq27xxx_device_info *di, u8 reg, + union power_supply_propval *val) { int tval; @@ -1694,7 +1766,9 @@ static int bq27xxx_battery_read_time(struct bq27xxx_device_info *di, u8 reg) if (tval == 65535) return -ENODATA; - return tval * 60; + val->intval = tval * 60; + + return 0; } /* @@ -1746,73 +1820,26 @@ static bool bq27xxx_battery_capacity_inaccurate(struct bq27xxx_device_info *di, return false; } -static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di) +static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di, + union power_supply_propval *val) { + int health; + /* Unlikely but important to return first */ if (unlikely(bq27xxx_battery_overtemp(di, di->cache.flags))) - return POWER_SUPPLY_HEALTH_OVERHEAT; - if (unlikely(bq27xxx_battery_undertemp(di, di->cache.flags))) - return POWER_SUPPLY_HEALTH_COLD; - if (unlikely(bq27xxx_battery_dead(di, di->cache.flags))) - return POWER_SUPPLY_HEALTH_DEAD; - if (unlikely(bq27xxx_battery_capacity_inaccurate(di, di->cache.flags))) - return POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED; - - return POWER_SUPPLY_HEALTH_GOOD; -} - -void bq27xxx_battery_update(struct bq27xxx_device_info *di) -{ - struct bq27xxx_reg_cache cache = {0, }; - 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 (cache.flags >= 0) { - cache.temperature = bq27xxx_battery_read_temperature(di); - if (di->regs[BQ27XXX_REG_TTE] != INVALID_REG_ADDR) - cache.time_to_empty = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTE); - if (di->regs[BQ27XXX_REG_TTECP] != INVALID_REG_ADDR) - cache.time_to_empty_avg = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTECP); - if (di->regs[BQ27XXX_REG_TTF] != INVALID_REG_ADDR) - cache.time_to_full = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTF); - - cache.charge_full = bq27xxx_battery_read_fcc(di); - cache.capacity = bq27xxx_battery_read_soc(di); - if (di->regs[BQ27XXX_REG_AE] != INVALID_REG_ADDR) - cache.energy = bq27xxx_battery_read_energy(di); - di->cache.flags = cache.flags; - cache.health = bq27xxx_battery_read_health(di); - if (di->regs[BQ27XXX_REG_CYCT] != INVALID_REG_ADDR) - cache.cycle_count = bq27xxx_battery_read_cyct(di); - - /* We only have to read charge design full once */ - if (di->charge_design_full <= 0) - di->charge_design_full = bq27xxx_battery_read_dcap(di); - } - - if ((di->cache.capacity != cache.capacity) || - (di->cache.flags != cache.flags)) - power_supply_changed(di->bat); - - if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) - di->cache = cache; - - di->last_update = jiffies; -} -EXPORT_SYMBOL_GPL(bq27xxx_battery_update); - -static void bq27xxx_battery_poll(struct work_struct *work) -{ - struct bq27xxx_device_info *di = - container_of(work, struct bq27xxx_device_info, - work.work); + health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (unlikely(bq27xxx_battery_undertemp(di, di->cache.flags))) + health = POWER_SUPPLY_HEALTH_COLD; + else if (unlikely(bq27xxx_battery_dead(di, di->cache.flags))) + health = POWER_SUPPLY_HEALTH_DEAD; + else if (unlikely(bq27xxx_battery_capacity_inaccurate(di, di->cache.flags))) + health = POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED; + else + health = POWER_SUPPLY_HEALTH_GOOD; - bq27xxx_battery_update(di); + val->intval = health; - if (poll_interval > 0) - schedule_delayed_work(&di->work, poll_interval * HZ); + return 0; } static bool bq27xxx_battery_is_full(struct bq27xxx_device_info *di, int flags) @@ -1833,7 +1860,8 @@ static bool bq27xxx_battery_is_full(struct bq27xxx_device_info *di, int flags) static int bq27xxx_battery_current_and_status( struct bq27xxx_device_info *di, union power_supply_propval *val_curr, - union power_supply_propval *val_status) + union power_supply_propval *val_status, + struct bq27xxx_reg_cache *cache) { bool single_flags = (di->opts & BQ27XXX_O_ZERO); int curr; @@ -1845,10 +1873,14 @@ static int bq27xxx_battery_current_and_status( return curr; } - flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, single_flags); - if (flags < 0) { - dev_err(di->dev, "error reading flags\n"); - return flags; + if (cache) { + flags = cache->flags; + } else { + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, single_flags); + if (flags < 0) { + dev_err(di->dev, "error reading flags\n"); + return flags; + } } if (di->opts & BQ27XXX_O_ZERO) { @@ -1867,22 +1899,72 @@ static int bq27xxx_battery_current_and_status( val_curr->intval = curr; if (val_status) { - if (curr > 0) { + if (bq27xxx_battery_is_full(di, flags)) + val_status->intval = POWER_SUPPLY_STATUS_FULL; + else if (curr > 0) val_status->intval = POWER_SUPPLY_STATUS_CHARGING; - } else if (curr < 0) { + else if (curr < 0) val_status->intval = POWER_SUPPLY_STATUS_DISCHARGING; - } else { - if (bq27xxx_battery_is_full(di, flags)) - val_status->intval = POWER_SUPPLY_STATUS_FULL; - else - val_status->intval = - POWER_SUPPLY_STATUS_NOT_CHARGING; - } + else + val_status->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; } return 0; } +static void bq27xxx_battery_update_unlocked(struct bq27xxx_device_info *di) +{ + union power_supply_propval status = di->last_status; + struct bq27xxx_reg_cache cache = {0, }; + bool has_singe_flag = di->opts & BQ27XXX_O_ZERO; + + cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag); + 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); + + /* + * On gauges with signed current reporting the current must be + * checked to detect charging <-> discharging status changes. + */ + if (!(di->opts & BQ27XXX_O_ZERO)) + bq27xxx_battery_current_and_status(di, NULL, &status, &cache); + } + + if ((di->cache.capacity != cache.capacity) || + (di->cache.flags != cache.flags) || + (di->last_status.intval != status.intval)) { + di->last_status.intval = status.intval; + power_supply_changed(di->bat); + } + + if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) + di->cache = cache; + + di->last_update = jiffies; + + if (!di->removed && poll_interval > 0) + mod_delayed_work(system_percpu_wq, &di->work, poll_interval * HZ); +} + +void bq27xxx_battery_update(struct bq27xxx_device_info *di) +{ + mutex_lock(&di->lock); + bq27xxx_battery_update_unlocked(di); + mutex_unlock(&di->lock); +} +EXPORT_SYMBOL_GPL(bq27xxx_battery_update); + +static void bq27xxx_battery_poll(struct work_struct *work) +{ + struct bq27xxx_device_info *di = + container_of(work, struct bq27xxx_device_info, + work.work); + + bq27xxx_battery_update(di); +} + /* * Get the average power in µW * Return < 0 if something fails. @@ -1917,10 +1999,10 @@ static int bq27xxx_battery_capacity_level(struct bq27xxx_device_info *di, if (di->opts & BQ27XXX_O_ZERO) { if (di->cache.flags & BQ27000_FLAG_FC) level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; - else if (di->cache.flags & BQ27000_FLAG_EDV1) - level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; else if (di->cache.flags & BQ27000_FLAG_EDVF) level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else if (di->cache.flags & BQ27000_FLAG_EDV1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; else level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; } else if (di->opts & BQ27Z561_O_BITS) { @@ -1933,10 +2015,10 @@ static int bq27xxx_battery_capacity_level(struct bq27xxx_device_info *di, } else { if (di->cache.flags & BQ27XXX_FLAG_FC) level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; - else if (di->cache.flags & BQ27XXX_FLAG_SOC1) - level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; else if (di->cache.flags & BQ27XXX_FLAG_SOCF) level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else if (di->cache.flags & BQ27XXX_FLAG_SOC1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; else level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; } @@ -1966,6 +2048,65 @@ static int bq27xxx_battery_voltage(struct bq27xxx_device_info *di, return 0; } +/* + * 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. + */ +static int bq27xxx_battery_read_dmin_volt(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int volt; + + /* We only have to read design minimum voltage once */ + if (di->voltage_min_design > 0) { + val->intval = di->voltage_min_design; + return 0; + } + + volt = bq27xxx_read(di, BQ27XXX_REG_SEDVF, true); + if (volt < 0) { + dev_err(di->dev, "error reading design min voltage\n"); + return volt; + } + + /* SEDVF = Design EDVF / 8 - 256 */ + val->intval = volt * 8000 + 2048000; + + /* Save for later reads */ + di->voltage_min_design = val->intval; + + return 0; +} + static int bq27xxx_simple_value(int value, union power_supply_propval *val) { @@ -1985,18 +2126,16 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); mutex_lock(&di->lock); - if (time_is_before_jiffies(di->last_update + 5 * HZ)) { - cancel_delayed_work_sync(&di->work); - bq27xxx_battery_poll(&di->work.work); - } + if (time_is_before_jiffies(di->last_update + 5 * HZ)) + bq27xxx_battery_update_unlocked(di); 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: - ret = bq27xxx_battery_current_and_status(di, NULL, val); + ret = bq27xxx_battery_current_and_status(di, NULL, val, NULL); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: ret = bq27xxx_battery_voltage(di, val); @@ -2005,7 +2144,7 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, val->intval = di->cache.flags < 0 ? 0 : 1; break; case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = bq27xxx_battery_current_and_status(di, val, NULL); + ret = bq27xxx_battery_current_and_status(di, val, NULL, NULL); break; case POWER_SUPPLY_PROP_CAPACITY: ret = bq27xxx_simple_value(di->cache.capacity, val); @@ -2014,18 +2153,16 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, ret = bq27xxx_battery_capacity_level(di, val); break; case POWER_SUPPLY_PROP_TEMP: - ret = bq27xxx_simple_value(di->cache.temperature, val); - if (ret == 0) - val->intval -= 2731; /* convert decidegree k to c */ + ret = bq27xxx_battery_read_temperature(di, val); break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: - ret = bq27xxx_simple_value(di->cache.time_to_empty, val); + ret = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTE, val); break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: - ret = bq27xxx_simple_value(di->cache.time_to_empty_avg, val); + ret = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTECP, val); break; case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: - ret = bq27xxx_simple_value(di->cache.time_to_full, val); + ret = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTF, val); break; case POWER_SUPPLY_PROP_TECHNOLOGY: if (di->opts & BQ27XXX_O_MUL_CHEM) @@ -2035,34 +2172,39 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CHARGE_NOW: if (di->regs[BQ27XXX_REG_NAC] != INVALID_REG_ADDR) - ret = bq27xxx_simple_value(bq27xxx_battery_read_nac(di), val); + ret = bq27xxx_battery_read_nac(di, val); else - ret = bq27xxx_simple_value(bq27xxx_battery_read_rc(di), val); + ret = bq27xxx_battery_read_rc(di, val); break; case POWER_SUPPLY_PROP_CHARGE_FULL: - ret = bq27xxx_simple_value(di->cache.charge_full, val); + ret = bq27xxx_battery_read_fcc(di, val); break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - ret = bq27xxx_simple_value(di->charge_design_full, val); + ret = bq27xxx_battery_read_dcap(di, val); break; /* * TODO: Implement these to make registers set from * power_supply_battery_info visible in sysfs. */ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: return -EINVAL; + 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_simple_value(di->cache.cycle_count, val); + ret = bq27xxx_battery_read_cyct(di, val); break; case POWER_SUPPLY_PROP_ENERGY_NOW: - ret = bq27xxx_simple_value(di->cache.energy, val); + ret = bq27xxx_battery_read_energy(di, val); break; case POWER_SUPPLY_PROP_POWER_AVG: ret = bq27xxx_battery_pwr_avg(di, val); break; case POWER_SUPPLY_PROP_HEALTH: - ret = bq27xxx_simple_value(di->cache.health, val); + ret = bq27xxx_battery_read_health(di, val); break; case POWER_SUPPLY_PROP_MANUFACTURER: val->strval = BQ27XXX_MANUFACTURER; @@ -2078,20 +2220,24 @@ static void bq27xxx_external_power_changed(struct power_supply *psy) { struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); - cancel_delayed_work_sync(&di->work); - schedule_delayed_work(&di->work, 0); + /* After charger plug in/out wait 0.5s for things to stabilize */ + 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_mutex_init(di->dev, &di->lock); + if (ret) + return ret; di->regs = bq27xxx_chip_data[di->chip].regs; di->unseal_key = bq27xxx_chip_data[di->chip].unseal_key; @@ -2109,7 +2255,7 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di) psy_desc->get_property = bq27xxx_battery_get_property; psy_desc->external_power_changed = bq27xxx_external_power_changed; - di->bat = power_supply_register_no_ws(di->dev, psy_desc, &psy_cfg); + di->bat = devm_power_supply_register(di->dev, psy_desc, &psy_cfg); if (IS_ERR(di->bat)) return dev_err_probe(di->dev, PTR_ERR(di->bat), "failed to register battery\n"); @@ -2127,26 +2273,41 @@ EXPORT_SYMBOL_GPL(bq27xxx_battery_setup); void bq27xxx_battery_teardown(struct bq27xxx_device_info *di) { - /* - * power_supply_unregister call bq27xxx_battery_get_property which - * call bq27xxx_battery_poll. - * Make sure that bq27xxx_battery_poll will not call - * schedule_delayed_work again after unregister (which cause OOPS). - */ - poll_interval = 0; - - cancel_delayed_work_sync(&di->work); - - power_supply_unregister(di->bat); - mutex_lock(&bq27xxx_list_lock); list_del(&di->list); mutex_unlock(&bq27xxx_list_lock); - mutex_destroy(&di->lock); + /* Set removed to avoid bq27xxx_battery_update() re-queuing the work */ + mutex_lock(&di->lock); + di->removed = true; + mutex_unlock(&di->lock); + + cancel_delayed_work_sync(&di->work); } EXPORT_SYMBOL_GPL(bq27xxx_battery_teardown); +#ifdef CONFIG_PM_SLEEP +static int bq27xxx_battery_suspend(struct device *dev) +{ + struct bq27xxx_device_info *di = dev_get_drvdata(dev); + + cancel_delayed_work(&di->work); + return 0; +} + +static int bq27xxx_battery_resume(struct device *dev) +{ + struct bq27xxx_device_info *di = dev_get_drvdata(dev); + + schedule_delayed_work(&di->work, 0); + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +SIMPLE_DEV_PM_OPS(bq27xxx_battery_battery_pm_ops, + bq27xxx_battery_suspend, bq27xxx_battery_resume); +EXPORT_SYMBOL_GPL(bq27xxx_battery_battery_pm_ops); + MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); MODULE_DESCRIPTION("BQ27xxx battery monitor driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c index f8768997333b..868e95f0887e 100644 --- a/drivers/power/supply/bq27xxx_battery_i2c.c +++ b/drivers/power/supply/bq27xxx_battery_i2c.c @@ -6,15 +6,15 @@ * Andrew F. Davis <afd@ti.com> */ +#include <linux/delay.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/module.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/power/bq27xxx_battery.h> -static DEFINE_IDR(battery_id); -static DEFINE_MUTEX(battery_mutex); +static DEFINE_IDA(battery_id); static irqreturn_t bq27xxx_battery_irq_handler_thread(int irq, void *data) { @@ -32,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; @@ -48,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; @@ -136,30 +146,39 @@ static int bq27xxx_battery_i2c_bulk_write(struct bq27xxx_device_info *di, return 0; } +static void bq27xxx_battery_i2c_devm_ida_free(void *data) +{ + int num = (long)data; + + ida_free(&battery_id, num); +} + static int bq27xxx_battery_i2c_probe(struct i2c_client *client) { const struct i2c_device_id *id = i2c_client_get_device_id(client); struct bq27xxx_device_info *di; int ret; char *name; - int num; + long num; /* Get new ID for the new battery device */ - mutex_lock(&battery_mutex); - num = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL); - mutex_unlock(&battery_mutex); + num = ida_alloc(&battery_id, GFP_KERNEL); if (num < 0) return num; + ret = devm_add_action_or_reset(&client->dev, + bq27xxx_battery_i2c_devm_ida_free, + (void *)num); + if (ret) + return ret; - name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s-%d", id->name, num); + name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s-%ld", id->name, num); if (!name) - goto err_mem; + return -ENOMEM; di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL); if (!di) - goto err_mem; + return -ENOMEM; - di->id = num; di->dev = &client->dev; di->chip = id->driver_data; di->name = name; @@ -171,7 +190,7 @@ static int bq27xxx_battery_i2c_probe(struct i2c_client *client) ret = bq27xxx_battery_setup(di); if (ret) - goto err_failed; + return ret; /* Schedule a polling after about 1 min */ schedule_delayed_work(&di->work, 60 * HZ); @@ -179,7 +198,7 @@ static int bq27xxx_battery_i2c_probe(struct i2c_client *client) i2c_set_clientdata(client, di); if (client->irq) { - ret = devm_request_threaded_irq(&client->dev, client->irq, + ret = request_threaded_irq(client->irq, NULL, bq27xxx_battery_irq_handler_thread, IRQF_ONESHOT, di->name, di); @@ -188,32 +207,21 @@ static int bq27xxx_battery_i2c_probe(struct i2c_client *client) "Unable to register IRQ %d error %d\n", client->irq, ret); bq27xxx_battery_teardown(di); - goto err_failed; + return ret; } } return 0; - -err_mem: - ret = -ENOMEM; - -err_failed: - mutex_lock(&battery_mutex); - idr_remove(&battery_id, num); - mutex_unlock(&battery_mutex); - - return ret; } static void bq27xxx_battery_i2c_remove(struct i2c_client *client) { struct bq27xxx_device_info *di = i2c_get_clientdata(client); - bq27xxx_battery_teardown(di); + if (client->irq) + free_irq(client->irq, di); - mutex_lock(&battery_mutex); - idr_remove(&battery_id, di->id); - mutex_unlock(&battery_mutex); + bq27xxx_battery_teardown(di); } static const struct i2c_device_id bq27xxx_i2c_id_table[] = { @@ -294,8 +302,9 @@ static struct i2c_driver bq27xxx_battery_i2c_driver = { .driver = { .name = "bq27xxx-battery", .of_match_table = of_match_ptr(bq27xxx_battery_i2c_of_match_table), + .pm = &bq27xxx_battery_battery_pm_ops, }, - .probe_new = bq27xxx_battery_i2c_probe, + .probe = bq27xxx_battery_i2c_probe, .remove = bq27xxx_battery_i2c_remove, .id_table = bq27xxx_i2c_id_table, }; 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/charger-manager.c b/drivers/power/supply/charger-manager.c index 92db79400a6a..c49e0e4d02f7 100644 --- a/drivers/power/supply/charger-manager.c +++ b/drivers/power/supply/charger-manager.c @@ -22,6 +22,7 @@ #include <linux/platform_device.h> #include <linux/power/charger-manager.h> #include <linux/regulator/consumer.h> +#include <linux/string_choices.h> #include <linux/sysfs.h> #include <linux/of.h> #include <linux/thermal.h> @@ -221,7 +222,7 @@ static bool is_charging(struct charger_manager *cm) /* If at least one of the charger is charging, return yes */ for (i = 0; cm->desc->psy_charger_stat[i]; i++) { - /* 1. The charger sholuld not be DISABLED */ + /* 1. The charger should not be DISABLED */ if (cm->emergency_stop) continue; if (!cm->charger_enabled) @@ -1075,7 +1076,7 @@ static ssize_t charger_name_show(struct device *dev, struct charger_regulator *charger = container_of(attr, struct charger_regulator, attr_name); - return sprintf(buf, "%s\n", charger->regulator_name); + return sysfs_emit(buf, "%s\n", charger->regulator_name); } static ssize_t charger_state_show(struct device *dev, @@ -1088,7 +1089,7 @@ static ssize_t charger_state_show(struct device *dev, if (!charger->externally_control) state = regulator_is_enabled(charger->consumer); - return sprintf(buf, "%s\n", state ? "enabled" : "disabled"); + return sysfs_emit(buf, "%s\n", str_enabled_disabled(state)); } static ssize_t charger_externally_control_show(struct device *dev, @@ -1097,7 +1098,7 @@ static ssize_t charger_externally_control_show(struct device *dev, struct charger_regulator *charger = container_of(attr, struct charger_regulator, attr_externally_control); - return sprintf(buf, "%d\n", charger->externally_control); + return sysfs_emit(buf, "%d\n", charger->externally_control); } static ssize_t charger_externally_control_store(struct device *dev, @@ -1331,7 +1332,7 @@ static struct charger_desc *of_cm_parse_desc(struct device *dev) of_property_read_string(np, "cm-thermal-zone", &desc->thermal_zone); of_property_read_u32(np, "cm-battery-cold", &desc->temp_min); - if (of_get_property(np, "cm-battery-cold-in-minus", NULL)) + if (of_property_read_bool(np, "cm-battery-cold-in-minus")) desc->temp_min *= -1; of_property_read_u32(np, "cm-battery-hot", &desc->temp_max); of_property_read_u32(np, "cm-battery-temp-diff", &desc->temp_diff); @@ -1412,10 +1413,9 @@ static inline struct charger_desc *cm_get_drv_data(struct platform_device *pdev) return dev_get_platdata(&pdev->dev); } -static enum alarmtimer_restart cm_timer_func(struct alarm *alarm, ktime_t now) +static void cm_timer_func(struct alarm *alarm, ktime_t now) { cm_timer_set = false; - return ALARMTIMER_NORESTART; } static int charger_manager_probe(struct platform_device *pdev) @@ -1516,9 +1516,11 @@ static int charger_manager_probe(struct platform_device *pdev) memcpy(&cm->charger_psy_desc, &psy_default, sizeof(psy_default)); if (!desc->psy_name) - strncpy(cm->psy_name_buf, psy_default.name, PSY_NAME_MAX); + strscpy(cm->psy_name_buf, psy_default.name, + sizeof(cm->psy_name_buf)); else - strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX); + strscpy(cm->psy_name_buf, desc->psy_name, + sizeof(cm->psy_name_buf)); cm->charger_psy_desc.name = cm->psy_name_buf; /* Allocate for psy properties because they may vary */ @@ -1628,7 +1630,7 @@ err_reg_extcon: return ret; } -static int charger_manager_remove(struct platform_device *pdev) +static void charger_manager_remove(struct platform_device *pdev) { struct charger_manager *cm = platform_get_drvdata(pdev); struct charger_desc *desc = cm->desc; @@ -1648,8 +1650,6 @@ static int charger_manager_remove(struct platform_device *pdev) power_supply_unregister(cm->charger_psy); try_charger_enable(cm, false); - - return 0; } static const struct platform_device_id charger_manager_id[] = { diff --git a/drivers/power/supply/collie_battery.c b/drivers/power/supply/collie_battery.c index 7fb9b549f2de..3daf7befc0bf 100644 --- a/drivers/power/supply/collie_battery.c +++ b/drivers/power/supply/collie_battery.c @@ -404,7 +404,7 @@ static int collie_bat_probe(struct ucb1x00_dev *dev) goto err_psy_reg_bu; } - ret = request_irq(gpio_to_irq(COLLIE_GPIO_CO), + ret = request_irq(gpiod_to_irq(collie_bat_main.gpio_full), collie_bat_gpio_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "main full", &collie_bat_main); @@ -440,7 +440,8 @@ err_put_gpio_full: static void collie_bat_remove(struct ucb1x00_dev *dev) { - free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main); + 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 d98d9244e394..8106d1edcbc2 100644 --- a/drivers/power/supply/cpcap-battery.c +++ b/drivers/power/supply/cpcap-battery.c @@ -15,7 +15,7 @@ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/reboot.h> @@ -1130,7 +1130,7 @@ static int cpcap_battery_probe(struct platform_device *pdev) 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, @@ -1151,7 +1151,7 @@ static int cpcap_battery_probe(struct platform_device *pdev) return 0; } -static int cpcap_battery_remove(struct platform_device *pdev) +static void cpcap_battery_remove(struct platform_device *pdev) { struct cpcap_battery_ddata *ddata = platform_get_drvdata(pdev); int error; @@ -1161,8 +1161,6 @@ static int cpcap_battery_remove(struct platform_device *pdev) 0xffff, 0); if (error) dev_err(&pdev->dev, "could not disable: %i\n", error); - - return 0; } static struct platform_driver cpcap_battery_driver = { @@ -1171,7 +1169,7 @@ static struct platform_driver cpcap_battery_driver = { .of_match_table = of_match_ptr(cpcap_battery_id_table), }, .probe = cpcap_battery_probe, - .remove = cpcap_battery_remove, + .remove = cpcap_battery_remove, }; module_platform_driver(cpcap_battery_driver); diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index be9764541d52..d0c3008db534 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -14,11 +14,11 @@ #include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include <linux/err.h> #include <linux/interrupt.h> #include <linux/notifier.h> -#include <linux/of.h> -#include <linux/of_platform.h> +#include <linux/mod_devicetable.h> #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/regmap.h> @@ -516,7 +516,7 @@ static void cpcap_charger_vbus_work(struct work_struct *work) out_err: cpcap_charger_update_state(ddata, POWER_SUPPLY_STATUS_UNKNOWN); dev_err(ddata->dev, "%s could not %s vbus: %i\n", __func__, - ddata->vbus_enabled ? "enable" : "disable", error); + str_enable_disable(ddata->vbus_enabled), error); } static int cpcap_charger_set_vbus(struct phy_companion *comparator, @@ -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; } @@ -865,7 +864,6 @@ static const struct power_supply_desc cpcap_charger_usb_desc = { .property_is_writeable = cpcap_charger_property_is_writeable, }; -#ifdef CONFIG_OF static const struct of_device_id cpcap_charger_id_table[] = { { .compatible = "motorola,mapphone-cpcap-charger", @@ -873,20 +871,13 @@ static const struct of_device_id cpcap_charger_id_table[] = { {}, }; MODULE_DEVICE_TABLE(of, cpcap_charger_id_table); -#endif static int cpcap_charger_probe(struct platform_device *pdev) { struct cpcap_charger_ddata *ddata; - const struct of_device_id *of_id; struct power_supply_config psy_cfg = {}; int error; - of_id = of_match_device(of_match_ptr(cpcap_charger_id_table), - &pdev->dev); - if (!of_id) - return -EINVAL; - ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); if (!ddata) return -ENOMEM; @@ -910,10 +901,10 @@ 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), + psy_cfg.num_supplicants = ARRAY_SIZE(cpcap_charger_supplied_to); ddata->usb = devm_power_supply_register(ddata->dev, &cpcap_charger_usb_desc, @@ -966,21 +957,19 @@ static void cpcap_charger_shutdown(struct platform_device *pdev) cancel_delayed_work_sync(&ddata->detect_work); } -static int cpcap_charger_remove(struct platform_device *pdev) +static void cpcap_charger_remove(struct platform_device *pdev) { cpcap_charger_shutdown(pdev); - - return 0; } static struct platform_driver cpcap_charger_driver = { .probe = cpcap_charger_probe, .driver = { .name = "cpcap-charger", - .of_match_table = of_match_ptr(cpcap_charger_id_table), + .of_match_table = cpcap_charger_id_table, }, .shutdown = cpcap_charger_shutdown, - .remove = cpcap_charger_remove, + .remove = cpcap_charger_remove, }; module_platform_driver(cpcap_charger_driver); diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c new file mode 100644 index 000000000000..53e6a77e03fc --- /dev/null +++ b/drivers/power/supply/cros_charge-control.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ChromeOS EC driver for charge control + * + * Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net> + */ +#include <acpi/battery.h> +#include <linux/container_of.h> +#include <linux/dmi.h> +#include <linux/lockdep.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> +#include <linux/platform_device.h> +#include <linux/types.h> + +#define EC_CHARGE_CONTROL_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \ + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | \ + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE)) + +/* + * Semantics of data *returned* from the EC API and Linux sysfs differ + * slightly, also the v1 API can not return any data. + * To match the expected sysfs API, data is never read back from the EC but + * cached in the driver. + * + * Changes to the EC bypassing the driver will not be reflected in sysfs. + * Any change to "charge_behaviour" will synchronize the EC with the driver state. + */ + +struct cros_chctl_priv { + struct device *dev; + struct cros_ec_device *cros_ec; + struct acpi_battery_hook battery_hook; + struct power_supply *hooked_battery; + u8 cmd_version; + + const struct power_supply_ext *psy_ext; + + struct mutex lock; /* protects fields below and cros_ec */ + enum power_supply_charge_behaviour current_behaviour; + u8 current_start_threshold, current_end_threshold; +}; + +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), + }; + + ret = cros_ec_cmd(cros_ec, cmd_version, EC_CMD_CHARGE_CONTROL, req, + outsizes[cmd_version], NULL, 0); + + if (ret < 0) + return ret; + + return 0; +} + +static int cros_chctl_configure_ec(struct cros_chctl_priv *priv) +{ + struct ec_params_charge_control req = {}; + + lockdep_assert_held(&priv->lock); + + req.cmd = EC_CHARGE_CONTROL_CMD_SET; + + switch (priv->current_behaviour) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: + req.mode = CHARGE_CONTROL_NORMAL; + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: + req.mode = CHARGE_CONTROL_IDLE; + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE: + req.mode = CHARGE_CONTROL_DISCHARGE; + break; + default: + return -EINVAL; + } + + if (priv->current_behaviour == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO && + !(priv->current_start_threshold == 0 && priv->current_end_threshold == 100)) { + req.sustain_soc.lower = priv->current_start_threshold; + req.sustain_soc.upper = priv->current_end_threshold; + } else { + /* Disable charging limits */ + req.sustain_soc.lower = -1; + req.sustain_soc.upper = -1; + } + + return cros_chctl_send_charge_control_cmd(priv->cros_ec, priv->cmd_version, &req); +} + +static int cros_chctl_psy_ext_get_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct cros_chctl_priv *priv = data; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + val->intval = priv->current_start_threshold; + return 0; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + val->intval = priv->current_end_threshold; + return 0; + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + val->intval = priv->current_behaviour; + return 0; + default: + return -EINVAL; + } +} + +static int cros_chctl_psy_ext_set_threshold(struct cros_chctl_priv *priv, + enum power_supply_property psp, + int val) +{ + int ret; + + if (val < 0 || val > 100) + return -EINVAL; + + if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) { + /* Start threshold is not exposed, use fixed value */ + if (priv->cmd_version == 2) + priv->current_start_threshold = val == 100 ? 0 : val; + + if (val < priv->current_start_threshold) + return -EINVAL; + priv->current_end_threshold = val; + } else { + if (val > priv->current_end_threshold) + return -EINVAL; + priv->current_start_threshold = val; + } + + if (priv->current_behaviour == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) { + ret = cros_chctl_configure_ec(priv); + if (ret < 0) + return ret; + } + + return 0; +} + + +static int cros_chctl_psy_ext_set_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct cros_chctl_priv *priv = data; + int ret; + + guard(mutex)(&priv->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + return cros_chctl_psy_ext_set_threshold(priv, psp, val->intval); + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + priv->current_behaviour = val->intval; + ret = cros_chctl_configure_ec(priv); + if (ret < 0) + return ret; + return 0; + default: + return -EINVAL; + } +} + +static int cros_chctl_psy_prop_is_writeable(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp) +{ + return true; +} + +#define DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(_name, ...) \ + static const enum power_supply_property _name ## _props[] = { \ + __VA_ARGS__, \ + }; \ + \ + static const struct power_supply_ext _name = { \ + .name = "cros-charge-control", \ + .properties = _name ## _props, \ + .num_properties = ARRAY_SIZE(_name ## _props), \ + .charge_behaviours = EC_CHARGE_CONTROL_BEHAVIOURS, \ + .get_property = cros_chctl_psy_ext_get_prop, \ + .set_property = cros_chctl_psy_ext_set_prop, \ + .property_is_writeable = cros_chctl_psy_prop_is_writeable, \ + } + +DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(cros_chctl_psy_ext_v1, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR +); + +DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(cros_chctl_psy_ext_v2, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD +); + +DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(cros_chctl_psy_ext_v3, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD +); + +static int cros_chctl_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + struct cros_chctl_priv *priv = container_of(hook, struct cros_chctl_priv, battery_hook); + + if (priv->hooked_battery) + return 0; + + priv->hooked_battery = battery; + return power_supply_register_extension(battery, priv->psy_ext, priv->dev, priv); +} + +static int cros_chctl_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + struct cros_chctl_priv *priv = container_of(hook, struct cros_chctl_priv, battery_hook); + + if (priv->hooked_battery == battery) { + power_supply_unregister_extension(battery, priv->psy_ext); + priv->hooked_battery = NULL; + } + + return 0; +} + +static bool probe_with_fwk_charge_control; +module_param(probe_with_fwk_charge_control, bool, 0644); +MODULE_PARM_DESC(probe_with_fwk_charge_control, + "Probe the driver in the presence of the custom Framework EC charge control"); + +static int cros_chctl_fwk_charge_control_versions(struct cros_ec_device *cros_ec) +{ + if (!dmi_match(DMI_SYS_VENDOR, "Framework")) + return 0; + + return cros_ec_get_cmd_versions(cros_ec, 0x3E03 /* FW_EC_CMD_CHARGE_LIMIT */); +} + +static int cros_chctl_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); + struct cros_ec_device *cros_ec = ec_dev->ec_dev; + struct cros_chctl_priv *priv; + int ret; + + ret = cros_chctl_fwk_charge_control_versions(cros_ec); + if (ret < 0) + return ret; + if (ret > 0 && !probe_with_fwk_charge_control) { + dev_info(dev, "Framework charge control detected, preventing load\n"); + return -ENODEV; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; + + ret = cros_ec_get_cmd_versions(cros_ec, EC_CMD_CHARGE_CONTROL); + if (ret < 0) + return ret; + else if (ret & EC_VER_MASK(3)) + priv->cmd_version = 3; + else if (ret & EC_VER_MASK(2)) + priv->cmd_version = 2; + else if (ret & EC_VER_MASK(1)) + priv->cmd_version = 1; + else + return -ENODEV; + + dev_dbg(dev, "Command version: %u\n", (unsigned int)priv->cmd_version); + + priv->dev = dev; + priv->cros_ec = cros_ec; + + if (priv->cmd_version == 1) + priv->psy_ext = &cros_chctl_psy_ext_v1; + else if (priv->cmd_version == 2) + priv->psy_ext = &cros_chctl_psy_ext_v2; + else + priv->psy_ext = &cros_chctl_psy_ext_v3; + + priv->battery_hook.name = dev_name(dev); + priv->battery_hook.add_battery = cros_chctl_add_battery; + priv->battery_hook.remove_battery = cros_chctl_remove_battery; + + priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + priv->current_start_threshold = 0; + priv->current_end_threshold = 100; + + /* Bring EC into well-known state */ + scoped_guard(mutex, &priv->lock) + ret = cros_chctl_configure_ec(priv); + if (ret < 0) + return ret; + + return devm_battery_hook_register(dev, &priv->battery_hook); +} + +static const struct platform_device_id cros_chctl_id[] = { + { "cros-charge-control", 0 }, + {} +}; + +static struct platform_driver cros_chctl_driver = { + .driver.name = "cros-charge-control", + .probe = cros_chctl_probe, + .id_table = cros_chctl_id, +}; +module_platform_driver(cros_chctl_driver); + +MODULE_DEVICE_TABLE(platform, cros_chctl_id); +MODULE_DESCRIPTION("ChromeOS EC charge control"); +MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/cros_peripheral_charger.c b/drivers/power/supply/cros_peripheral_charger.c index 1379afd9698d..962a6fd29832 100644 --- a/drivers/power/supply/cros_peripheral_charger.c +++ b/drivers/power/supply/cros_peripheral_charger.c @@ -5,6 +5,7 @@ * Copyright 2020 Google LLC. */ +#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/notifier.h> #include <linux/platform_data/cros_ec_commands.h> @@ -14,7 +15,7 @@ #include <linux/slab.h> #include <linux/stringify.h> #include <linux/types.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #define DRV_NAME "cros-ec-pchg" #define PCHG_DIR_PREFIX "peripheral" @@ -227,8 +228,7 @@ static int cros_pchg_get_prop(struct power_supply *psy, return 0; } -static int cros_pchg_event(const struct charger_data *charger, - unsigned long host_event) +static int cros_pchg_event(const struct charger_data *charger) { int i; @@ -256,7 +256,7 @@ static int cros_ec_notify(struct notifier_block *nb, if (!(host_event & EC_MKBP_PCHG_DEVICE_EVENT)) return NOTIFY_DONE; - return cros_pchg_event(charger, host_event); + return cros_pchg_event(charger); } static int cros_pchg_probe(struct platform_device *pdev) @@ -281,6 +281,8 @@ static int cros_pchg_probe(struct platform_device *pdev) charger->ec_dev = ec_dev; charger->ec_device = ec_device; + platform_set_drvdata(pdev, charger); + ret = cros_pchg_port_count(charger); if (ret <= 0) { /* @@ -349,15 +351,39 @@ static int cros_pchg_probe(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int __maybe_unused cros_pchg_resume(struct device *dev) +{ + struct charger_data *charger = dev_get_drvdata(dev); + + /* + * Sync all ports on resume in case reports from EC are lost during + * the last suspend. + */ + cros_pchg_event(charger); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(cros_pchg_pm_ops, NULL, cros_pchg_resume); + +static const struct platform_device_id cros_pchg_id[] = { + { DRV_NAME, 0 }, + {} +}; +MODULE_DEVICE_TABLE(platform, cros_pchg_id); + static struct platform_driver cros_pchg_driver = { .driver = { .name = DRV_NAME, + .pm = &cros_pchg_pm_ops, }, - .probe = cros_pchg_probe + .probe = cros_pchg_probe, + .id_table = cros_pchg_id, }; module_platform_driver(cros_pchg_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("ChromeOS EC peripheral device charger"); -MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c index cadb6a0c2cc7..47d3f58aa15c 100644 --- a/drivers/power/supply/cros_usbpd-charger.c +++ b/drivers/power/supply/cros_usbpd-charger.c @@ -5,6 +5,7 @@ * Copyright (c) 2014 - 2018 Google, Inc */ +#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_data/cros_ec_commands.h> #include <linux/platform_data/cros_ec_proto.h> @@ -72,17 +73,6 @@ static enum power_supply_property cros_usbpd_dedicated_charger_props[] = { POWER_SUPPLY_PROP_VOLTAGE_NOW, }; -static enum power_supply_usb_type cros_usbpd_charger_usb_types[] = { - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_C, - POWER_SUPPLY_USB_TYPE_PD, - POWER_SUPPLY_USB_TYPE_PD_DRP, - POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID -}; - /* Input voltage/current limit in mV/mA. Default to none. */ static u16 input_voltage_limit = EC_POWER_LIMIT_NONE; static u16 input_current_limit = EC_POWER_LIMIT_NONE; @@ -276,7 +266,7 @@ static int cros_usbpd_charger_get_power_info(struct port_data *port) port->psy_current_max = 0; break; default: - dev_err(dev, "Port %d: default case!\n", port->port_number); + dev_dbg(dev, "Port %d: default case!\n", port->port_number); port->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP; } @@ -628,6 +618,7 @@ static int cros_usbpd_charger_probe(struct platform_device *pd) psy_desc->external_power_changed = cros_usbpd_charger_power_changed; psy_cfg.drv_data = port; + psy_cfg.no_wakeup_source = true; if (cros_usbpd_charger_port_is_dedicated(port)) { sprintf(port->name, CHARGER_DEDICATED_DIR_NAME); @@ -642,15 +633,19 @@ static int cros_usbpd_charger_probe(struct platform_device *pd) psy_desc->properties = cros_usbpd_charger_props; psy_desc->num_properties = ARRAY_SIZE(cros_usbpd_charger_props); - psy_desc->usb_types = cros_usbpd_charger_usb_types; - psy_desc->num_usb_types = - ARRAY_SIZE(cros_usbpd_charger_usb_types); + psy_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) | + BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_DRP) | + BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID); } psy_desc->name = port->name; - psy = devm_power_supply_register_no_ws(dev, psy_desc, - &psy_cfg); + psy = devm_power_supply_register(dev, psy_desc, &psy_cfg); if (IS_ERR(psy)) { dev_err(dev, "Failed to register power supply\n"); continue; @@ -711,16 +706,22 @@ static int cros_usbpd_charger_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(cros_usbpd_charger_pm_ops, NULL, cros_usbpd_charger_resume); +static const struct platform_device_id cros_usbpd_charger_id[] = { + { DRV_NAME, 0 }, + {} +}; +MODULE_DEVICE_TABLE(platform, cros_usbpd_charger_id); + static struct platform_driver cros_usbpd_charger_driver = { .driver = { .name = DRV_NAME, .pm = &cros_usbpd_charger_pm_ops, }, - .probe = cros_usbpd_charger_probe + .probe = cros_usbpd_charger_probe, + .id_table = cros_usbpd_charger_id, }; module_platform_driver(cros_usbpd_charger_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("ChromeOS EC USBPD charger"); -MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/power/supply/cw2015_battery.c b/drivers/power/supply/cw2015_battery.c index 473522b4326a..0806abea2372 100644 --- a/drivers/power/supply/cw2015_battery.c +++ b/drivers/power/supply/cw2015_battery.c @@ -491,7 +491,7 @@ static int cw_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: if (cw_battery_valid_time_to_empty(cw_bat)) - val->intval = cw_bat->time_to_empty; + val->intval = cw_bat->time_to_empty * 60; else val->intval = 0; break; @@ -506,10 +506,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: @@ -702,8 +699,13 @@ static int cw_bat_probe(struct i2c_client *client) 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; @@ -731,7 +733,7 @@ static int __maybe_unused cw_bat_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(cw_bat_pm_ops, cw_bat_suspend, cw_bat_resume); static const struct i2c_device_id cw_bat_id_table[] = { - { "cw2015", 0 }, + { "cw2015" }, { } }; @@ -747,7 +749,7 @@ static struct i2c_driver cw_bat_driver = { .of_match_table = cw2015_of_match, .pm = &cw_bat_pm_ops, }, - .probe_new = cw_bat_probe, + .probe = cw_bat_probe, .id_table = cw_bat_id_table, }; diff --git a/drivers/power/supply/da9030_battery.c b/drivers/power/supply/da9030_battery.c index 0deba48d22d3..d25279c26030 100644 --- a/drivers/power/supply/da9030_battery.c +++ b/drivers/power/supply/da9030_battery.c @@ -15,6 +15,7 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/power_supply.h> +#include <linux/string_choices.h> #include <linux/mfd/da903x.h> #include <linux/debugfs.h> @@ -138,7 +139,7 @@ static int bat_debug_show(struct seq_file *s, void *data) { struct da9030_charger *charger = s->private; - seq_printf(s, "charger is %s\n", charger->is_on ? "on" : "off"); + seq_printf(s, "charger is %s\n", str_on_off(charger->is_on)); if (charger->chdet) { seq_printf(s, "iset = %dmA, vset = %dmV\n", charger->mA, charger->mV); @@ -269,7 +270,7 @@ static void da9030_charger_check_state(struct da9030_charger *charger) } if (charger->adc.vchmax_res > charger->thresholds.vcharge_max || charger->adc.vchmin_res < charger->thresholds.vcharge_min || - /* Tempreture readings are negative */ + /* Temperature readings are negative */ charger->adc.tbat_res < charger->thresholds.tbat_high || charger->adc.tbat_res > charger->thresholds.tbat_low) { /* disable charger */ @@ -470,7 +471,7 @@ static int da9030_battery_charger_init(struct da9030_charger *charger) if (ret) return ret; - /* enable auto ADC measuremnts */ + /* enable auto ADC measurements */ return da903x_write(charger->master, DA9030_ADC_AUTO_CONTROL, DA9030_ADC_TBAT_ENABLE | DA9030_ADC_VBAT_IN_TXON | DA9030_ADC_VCH_ENABLE | DA9030_ADC_ICH_ENABLE | @@ -501,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; @@ -530,8 +530,9 @@ static int da9030_battery_probe(struct platform_device *pdev) da9030_battery_setup_psy(charger); psy_cfg.drv_data = charger; - charger->psy = power_supply_register(&pdev->dev, &charger->psy_desc, - &psy_cfg); + charger->psy = devm_power_supply_register(&pdev->dev, + &charger->psy_desc, + &psy_cfg); if (IS_ERR(charger->psy)) { ret = PTR_ERR(charger->psy); goto err_ps_register; @@ -552,7 +553,7 @@ err_charger_init: return ret; } -static int da9030_battery_remove(struct platform_device *dev) +static void da9030_battery_remove(struct platform_device *dev) { struct da9030_charger *charger = platform_get_drvdata(dev); @@ -563,9 +564,6 @@ static int da9030_battery_remove(struct platform_device *dev) DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); cancel_delayed_work_sync(&charger->work); da9030_set_charge(charger, 0); - power_supply_unregister(charger->psy); - - return 0; } static struct platform_driver da903x_battery_driver = { diff --git a/drivers/power/supply/da9052-battery.c b/drivers/power/supply/da9052-battery.c index d87bdecc9501..6f17e2fa1a28 100644 --- a/drivers/power/supply/da9052-battery.c +++ b/drivers/power/supply/da9052-battery.c @@ -622,7 +622,7 @@ static s32 da9052_bat_probe(struct platform_device *pdev) } } - bat->psy = power_supply_register(&pdev->dev, &psy_desc, &psy_cfg); + bat->psy = devm_power_supply_register(&pdev->dev, &psy_desc, &psy_cfg); if (IS_ERR(bat->psy)) { ret = PTR_ERR(bat->psy); goto err; @@ -637,17 +637,13 @@ err: return ret; } -static int da9052_bat_remove(struct platform_device *pdev) +static void da9052_bat_remove(struct platform_device *pdev) { int i; struct da9052_battery *bat = platform_get_drvdata(pdev); for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat); - - power_supply_unregister(bat->psy); - - return 0; } static struct platform_driver da9052_bat_driver = { diff --git a/drivers/power/supply/da9150-charger.c b/drivers/power/supply/da9150-charger.c index f9314cc0cd75..27f36ef5b88d 100644 --- a/drivers/power/supply/da9150-charger.c +++ b/drivers/power/supply/da9150-charger.c @@ -11,8 +11,6 @@ #include <linux/slab.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/of.h> -#include <linux/of_platform.h> #include <linux/interrupt.h> #include <linux/power_supply.h> #include <linux/notifier.h> @@ -466,10 +464,8 @@ static int da9150_charger_register_irq(struct platform_device *pdev, int irq, ret; irq = platform_get_irq_byname(pdev, irq_name); - if (irq < 0) { - dev_err(dev, "Failed to get IRQ CHG_STATUS: %d\n", irq); + if (irq < 0) return irq; - } ret = request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, irq_name, charger); @@ -482,15 +478,12 @@ static int da9150_charger_register_irq(struct platform_device *pdev, static void da9150_charger_unregister_irq(struct platform_device *pdev, const char *irq_name) { - struct device *dev = &pdev->dev; struct da9150_charger *charger = platform_get_drvdata(pdev); int irq; irq = platform_get_irq_byname(pdev, irq_name); - if (irq < 0) { - dev_err(dev, "Failed to get IRQ CHG_STATUS: %d\n", irq); + if (irq < 0) return; - } free_irq(irq, charger); } @@ -528,42 +521,30 @@ static int da9150_charger_probe(struct platform_device *pdev) charger->dev = dev; /* Acquire ADC channels */ - charger->ibus_chan = iio_channel_get(dev, "CHAN_IBUS"); - if (IS_ERR(charger->ibus_chan)) { - ret = PTR_ERR(charger->ibus_chan); - goto ibus_chan_fail; - } + charger->ibus_chan = devm_iio_channel_get(dev, "CHAN_IBUS"); + if (IS_ERR(charger->ibus_chan)) + return PTR_ERR(charger->ibus_chan); - charger->vbus_chan = iio_channel_get(dev, "CHAN_VBUS"); - if (IS_ERR(charger->vbus_chan)) { - ret = PTR_ERR(charger->vbus_chan); - goto vbus_chan_fail; - } + charger->vbus_chan = devm_iio_channel_get(dev, "CHAN_VBUS"); + if (IS_ERR(charger->vbus_chan)) + return PTR_ERR(charger->vbus_chan); - charger->tjunc_chan = iio_channel_get(dev, "CHAN_TJUNC"); - if (IS_ERR(charger->tjunc_chan)) { - ret = PTR_ERR(charger->tjunc_chan); - goto tjunc_chan_fail; - } + charger->tjunc_chan = devm_iio_channel_get(dev, "CHAN_TJUNC"); + if (IS_ERR(charger->tjunc_chan)) + return PTR_ERR(charger->tjunc_chan); - charger->vbat_chan = iio_channel_get(dev, "CHAN_VBAT"); - if (IS_ERR(charger->vbat_chan)) { - ret = PTR_ERR(charger->vbat_chan); - goto vbat_chan_fail; - } + charger->vbat_chan = devm_iio_channel_get(dev, "CHAN_VBAT"); + if (IS_ERR(charger->vbat_chan)) + return PTR_ERR(charger->vbat_chan); /* Register power supplies */ - charger->usb = power_supply_register(dev, &usb_desc, NULL); - if (IS_ERR(charger->usb)) { - ret = PTR_ERR(charger->usb); - goto usb_fail; - } + charger->usb = devm_power_supply_register(dev, &usb_desc, NULL); + if (IS_ERR(charger->usb)) + return PTR_ERR(charger->usb); - charger->battery = power_supply_register(dev, &battery_desc, NULL); - if (IS_ERR(charger->battery)) { - ret = PTR_ERR(charger->battery); - goto battery_fail; - } + charger->battery = devm_power_supply_register(dev, &battery_desc, NULL); + if (IS_ERR(charger->battery)) + return PTR_ERR(charger->battery); /* Get initial online supply */ reg = da9150_reg_read(da9150, DA9150_STATUS_H); @@ -623,26 +604,11 @@ tjunc_irq_fail: chg_irq_fail: if (!IS_ERR_OR_NULL(charger->usb_phy)) usb_unregister_notifier(charger->usb_phy, &charger->otg_nb); -battery_fail: - power_supply_unregister(charger->usb); -usb_fail: - iio_channel_release(charger->vbat_chan); - -vbat_chan_fail: - iio_channel_release(charger->tjunc_chan); - -tjunc_chan_fail: - iio_channel_release(charger->vbus_chan); - -vbus_chan_fail: - iio_channel_release(charger->ibus_chan); - -ibus_chan_fail: return ret; } -static int da9150_charger_remove(struct platform_device *pdev) +static void da9150_charger_remove(struct platform_device *pdev) { struct da9150_charger *charger = platform_get_drvdata(pdev); int irq; @@ -662,17 +628,7 @@ static int da9150_charger_remove(struct platform_device *pdev) if (!IS_ERR_OR_NULL(charger->usb_phy)) usb_unregister_notifier(charger->usb_phy, &charger->otg_nb); - - power_supply_unregister(charger->battery); - power_supply_unregister(charger->usb); - - /* Release ADC channels */ - iio_channel_release(charger->ibus_chan); - iio_channel_release(charger->vbus_chan); - iio_channel_release(charger->tjunc_chan); - iio_channel_release(charger->vbat_chan); - - return 0; + cancel_work_sync(&charger->otg_work); } static struct platform_driver da9150_charger_driver = { diff --git a/drivers/power/supply/da9150-fg.c b/drivers/power/supply/da9150-fg.c index 8c5e2c49d6c1..4f28ef1bba1a 100644 --- a/drivers/power/supply/da9150-fg.c +++ b/drivers/power/supply/da9150-fg.c @@ -11,7 +11,6 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> -#include <linux/of_platform.h> #include <linux/slab.h> #include <linux/interrupt.h> #include <linux/delay.h> @@ -248,9 +247,9 @@ static int da9150_fg_current_avg(struct da9150_fg *fg, DA9150_QIF_SD_GAIN_SIZE); da9150_fg_read_sync_end(fg); - div = (u64) (sd_gain * shunt_val * 65536ULL); + div = 65536ULL * sd_gain * shunt_val; do_div(div, 1000000); - res = (u64) (iavg * 1000000ULL); + res = 1000000ULL * iavg; do_div(res, div); val->intval = (int) res; diff --git a/drivers/power/supply/ds2760_battery.c b/drivers/power/supply/ds2760_battery.c index 5f50da524f41..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; }; @@ -195,16 +194,16 @@ static int w1_ds2760_recall_eeprom(struct device *dev, int addr) } static ssize_t w1_slave_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); return w1_ds2760_read(dev, buf, off, count); } -static BIN_ATTR_RO(w1_slave, DS2760_DATA_SIZE); +static const BIN_ATTR_RO(w1_slave, DS2760_DATA_SIZE); -static struct bin_attribute *w1_ds2760_bin_attrs[] = { +static const struct bin_attribute *const w1_ds2760_bin_attrs[] = { &bin_attr_w1_slave, NULL, }; @@ -227,20 +226,12 @@ static int rated_capacities[] = { 920, /* NEC */ 1440, /* Samsung */ 1440, /* BYD */ -#ifdef CONFIG_MACH_H4700 - 1800, /* HP iPAQ hx4700 3.7V 1800mAh (359113-001) */ -#else 1440, /* Lishen */ -#endif 1440, /* NEC */ 2880, /* Samsung */ 2880, /* BYD */ 2880, /* Lishen */ 2880, /* NEC */ -#ifdef CONFIG_MACH_H4700 - 0, - 3600, /* HP iPAQ hx4700 3.7V 3600mAh (359114-001) */ -#endif }; /* array is level at temps 0°C, 10°C, 20°C, 30°C, 40°C @@ -497,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) @@ -700,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,7 +692,7 @@ static int w1_ds2760_add_slave(struct w1_slave *sl) if (current_accum) ds2760_battery_set_current_accum(di, current_accum); - di->bat = power_supply_register(dev, &di->bat_desc, &psy_cfg); + di->bat = devm_power_supply_register(dev, &di->bat_desc, &psy_cfg); if (IS_ERR(di->bat)) { dev_err(di->dev, "failed to register battery\n"); retval = PTR_ERR(di->bat); @@ -755,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; @@ -770,7 +713,6 @@ static int w1_ds2760_add_slave(struct w1_slave *sl) goto success; workqueue_failed: - power_supply_unregister(di->bat); batt_failed: di_alloc_failed: success: @@ -783,9 +725,7 @@ 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); - power_supply_unregister(di->bat); } #ifdef CONFIG_OF diff --git a/drivers/power/supply/ds2780_battery.c b/drivers/power/supply/ds2780_battery.c index 2b8c90d84325..5b57bbba79d4 100644 --- a/drivers/power/supply/ds2780_battery.c +++ b/drivers/power/supply/ds2780_battery.c @@ -454,7 +454,7 @@ static ssize_t ds2780_get_pmod_enabled(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%d\n", + return sysfs_emit(buf, "%d\n", !!(control_reg & DS2780_CONTROL_REG_PMOD)); } @@ -507,7 +507,7 @@ static ssize_t ds2780_get_sense_resistor_value(struct device *dev, if (ret < 0) return ret; - ret = sprintf(buf, "%d\n", sense_resistor); + ret = sysfs_emit(buf, "%d\n", sense_resistor); return ret; } @@ -545,7 +545,7 @@ static ssize_t ds2780_get_rsgain_setting(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%d\n", rsgain); + return sysfs_emit(buf, "%d\n", rsgain); } static ssize_t ds2780_set_rsgain_setting(struct device *dev, @@ -588,7 +588,7 @@ static ssize_t ds2780_get_pio_pin(struct device *dev, if (ret < 0) return ret; - ret = sprintf(buf, "%d\n", sfr & DS2780_SFR_REG_PIOSC); + ret = sysfs_emit(buf, "%d\n", sfr & DS2780_SFR_REG_PIOSC); return ret; } @@ -621,7 +621,7 @@ static ssize_t ds2780_set_pio_pin(struct device *dev, static ssize_t ds2780_read_param_eeprom_bin(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -634,7 +634,7 @@ static ssize_t ds2780_read_param_eeprom_bin(struct file *filp, static ssize_t ds2780_write_param_eeprom_bin(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -654,7 +654,7 @@ static ssize_t ds2780_write_param_eeprom_bin(struct file *filp, return count; } -static struct bin_attribute ds2780_param_eeprom_bin_attr = { +static const struct bin_attribute ds2780_param_eeprom_bin_attr = { .attr = { .name = "param_eeprom", .mode = S_IRUGO | S_IWUSR, @@ -666,7 +666,7 @@ static struct bin_attribute ds2780_param_eeprom_bin_attr = { static ssize_t ds2780_read_user_eeprom_bin(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -679,7 +679,7 @@ static ssize_t ds2780_read_user_eeprom_bin(struct file *filp, static ssize_t ds2780_write_user_eeprom_bin(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -699,7 +699,7 @@ static ssize_t ds2780_write_user_eeprom_bin(struct file *filp, return count; } -static struct bin_attribute ds2780_user_eeprom_bin_attr = { +static const struct bin_attribute ds2780_user_eeprom_bin_attr = { .attr = { .name = "user_eeprom", .mode = S_IRUGO | S_IWUSR, @@ -726,7 +726,7 @@ static struct attribute *ds2780_sysfs_attrs[] = { NULL }; -static struct bin_attribute *ds2780_sysfs_bin_attrs[] = { +static const struct bin_attribute *const ds2780_sysfs_bin_attrs[] = { &ds2780_param_eeprom_bin_attr, &ds2780_user_eeprom_bin_attr, NULL diff --git a/drivers/power/supply/ds2781_battery.c b/drivers/power/supply/ds2781_battery.c index 05b859bf2dc0..1319e02f3f95 100644 --- a/drivers/power/supply/ds2781_battery.c +++ b/drivers/power/supply/ds2781_battery.c @@ -456,7 +456,7 @@ static ssize_t ds2781_get_pmod_enabled(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%d\n", + return sysfs_emit(buf, "%d\n", !!(control_reg & DS2781_CONTROL_PMOD)); } @@ -509,7 +509,7 @@ static ssize_t ds2781_get_sense_resistor_value(struct device *dev, if (ret < 0) return ret; - ret = sprintf(buf, "%d\n", sense_resistor); + ret = sysfs_emit(buf, "%d\n", sense_resistor); return ret; } @@ -547,7 +547,7 @@ static ssize_t ds2781_get_rsgain_setting(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%d\n", rsgain); + return sysfs_emit(buf, "%d\n", rsgain); } static ssize_t ds2781_set_rsgain_setting(struct device *dev, @@ -590,7 +590,7 @@ static ssize_t ds2781_get_pio_pin(struct device *dev, if (ret < 0) return ret; - ret = sprintf(buf, "%d\n", sfr & DS2781_SFR_PIOSC); + ret = sysfs_emit(buf, "%d\n", sfr & DS2781_SFR_PIOSC); return ret; } @@ -623,7 +623,7 @@ static ssize_t ds2781_set_pio_pin(struct device *dev, static ssize_t ds2781_read_param_eeprom_bin(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -636,7 +636,7 @@ static ssize_t ds2781_read_param_eeprom_bin(struct file *filp, static ssize_t ds2781_write_param_eeprom_bin(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -656,7 +656,7 @@ static ssize_t ds2781_write_param_eeprom_bin(struct file *filp, return count; } -static struct bin_attribute ds2781_param_eeprom_bin_attr = { +static const struct bin_attribute ds2781_param_eeprom_bin_attr = { .attr = { .name = "param_eeprom", .mode = S_IRUGO | S_IWUSR, @@ -668,7 +668,7 @@ static struct bin_attribute ds2781_param_eeprom_bin_attr = { static ssize_t ds2781_read_user_eeprom_bin(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -682,7 +682,7 @@ static ssize_t ds2781_read_user_eeprom_bin(struct file *filp, static ssize_t ds2781_write_user_eeprom_bin(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -702,7 +702,7 @@ static ssize_t ds2781_write_user_eeprom_bin(struct file *filp, return count; } -static struct bin_attribute ds2781_user_eeprom_bin_attr = { +static const struct bin_attribute ds2781_user_eeprom_bin_attr = { .attr = { .name = "user_eeprom", .mode = S_IRUGO | S_IWUSR, @@ -729,7 +729,7 @@ static struct attribute *ds2781_sysfs_attrs[] = { NULL }; -static struct bin_attribute *ds2781_sysfs_bin_attrs[] = { +static const struct bin_attribute *const ds2781_sysfs_bin_attrs[] = { &ds2781_param_eeprom_bin_attr, &ds2781_user_eeprom_bin_attr, NULL, diff --git a/drivers/power/supply/ds2782_battery.c b/drivers/power/supply/ds2782_battery.c index 9b9619246902..cae95d35d398 100644 --- a/drivers/power/supply/ds2782_battery.c +++ b/drivers/power/supply/ds2782_battery.c @@ -11,6 +11,7 @@ * UEvent sending added by Evgeny Romanov <romanov@neurosoft.ru> */ +#include <linux/devm-helpers.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/types.h> @@ -57,14 +58,12 @@ struct ds278x_info { struct power_supply_desc battery_desc; const struct ds278x_battery_ops *ops; struct delayed_work bat_work; - int id; int rsns; int capacity; int status; /* State Of Charge */ }; -static DEFINE_IDR(battery_id); -static DEFINE_MUTEX(battery_lock); +static DEFINE_IDA(battery_id); static inline int ds278x_read_reg(struct ds278x_info *info, int reg, u8 *val) { @@ -312,21 +311,6 @@ static void ds278x_power_supply_init(struct power_supply_desc *battery) battery->external_power_changed = NULL; } -static void ds278x_battery_remove(struct i2c_client *client) -{ - struct ds278x_info *info = i2c_get_clientdata(client); - int id = info->id; - - power_supply_unregister(info->battery); - cancel_delayed_work_sync(&info->bat_work); - kfree(info->battery_desc.name); - kfree(info); - - mutex_lock(&battery_lock); - idr_remove(&battery_id, id); - mutex_unlock(&battery_lock); -} - #ifdef CONFIG_PM_SLEEP static int ds278x_suspend(struct device *dev) @@ -368,6 +352,13 @@ static const struct ds278x_battery_ops ds278x_ops[] = { } }; +static void ds278x_free_ida(void *data) +{ + int num = (uintptr_t)data; + + ida_free(&battery_id, num); +} + static int ds278x_battery_probe(struct i2c_client *client) { const struct i2c_device_id *id = i2c_client_get_device_id(client); @@ -387,32 +378,27 @@ static int ds278x_battery_probe(struct i2c_client *client) } /* Get an ID for this battery */ - mutex_lock(&battery_lock); - ret = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL); - mutex_unlock(&battery_lock); - if (ret < 0) - goto fail_id; - num = ret; - - info = kzalloc(sizeof(*info), GFP_KERNEL); - if (!info) { - ret = -ENOMEM; - goto fail_info; - } + num = ida_alloc(&battery_id, GFP_KERNEL); + if (num < 0) + return num; + ret = devm_add_action_or_reset(&client->dev, ds278x_free_ida, (void *)(uintptr_t)num); + if (ret) + return ret; - info->battery_desc.name = kasprintf(GFP_KERNEL, "%s-%d", - client->name, num); - if (!info->battery_desc.name) { - ret = -ENOMEM; - goto fail_name; - } + info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->battery_desc.name = devm_kasprintf(&client->dev, GFP_KERNEL, + "%s-%d", client->name, num); + if (!info->battery_desc.name) + return -ENOMEM; if (id->driver_data == DS2786) info->rsns = pdata->rsns; i2c_set_clientdata(client, info); info->client = client; - info->id = num; info->ops = &ds278x_ops[id->driver_data]; ds278x_power_supply_init(&info->battery_desc); psy_cfg.drv_data = info; @@ -420,30 +406,20 @@ static int ds278x_battery_probe(struct i2c_client *client) info->capacity = 100; info->status = POWER_SUPPLY_STATUS_FULL; - INIT_DELAYED_WORK(&info->bat_work, ds278x_bat_work); - - info->battery = power_supply_register(&client->dev, - &info->battery_desc, &psy_cfg); + info->battery = devm_power_supply_register(&client->dev, + &info->battery_desc, + &psy_cfg); if (IS_ERR(info->battery)) { dev_err(&client->dev, "failed to register battery\n"); - ret = PTR_ERR(info->battery); - goto fail_register; - } else { - schedule_delayed_work(&info->bat_work, DS278x_DELAY); + return PTR_ERR(info->battery); } - return 0; + ret = devm_delayed_work_autocancel(&client->dev, &info->bat_work, ds278x_bat_work); + if (ret) + return ret; + schedule_delayed_work(&info->bat_work, DS278x_DELAY); -fail_register: - kfree(info->battery_desc.name); -fail_name: - kfree(info); -fail_info: - mutex_lock(&battery_lock); - idr_remove(&battery_id, num); - mutex_unlock(&battery_lock); -fail_id: - return ret; + return 0; } static const struct i2c_device_id ds278x_id[] = { @@ -458,8 +434,7 @@ static struct i2c_driver ds278x_battery_driver = { .name = "ds2782-battery", .pm = &ds278x_battery_pm_ops, }, - .probe_new = ds278x_battery_probe, - .remove = ds278x_battery_remove, + .probe = ds278x_battery_probe, .id_table = ds278x_id, }; module_i2c_driver(ds278x_battery_driver); diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c index 66039c665dd1..f5f2566b3a32 100644 --- a/drivers/power/supply/generic-adc-battery.c +++ b/drivers/power/supply/generic-adc-battery.c @@ -1,13 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* - * Generic battery driver code using IIO - * Copyright (C) 2012, Anish Kumar <anish198519851985@gmail.com> - * based on jz4740-battery.c - * based on s3c_adc_battery.c - * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file COPYING in the main directory of this archive for - * more details. - * + * Generic battery driver using IIO + * Copyright (C) 2012, Anish Kumar <yesanishhere@gmail.com> + * Copyright (c) 2023, Sebastian Reichel <sre@kernel.org> */ #include <linux/interrupt.h> #include <linux/platform_device.h> @@ -22,7 +17,8 @@ #include <linux/slab.h> #include <linux/iio/consumer.h> #include <linux/iio/types.h> -#include <linux/power/generic-adc-battery.h> +#include <linux/of.h> +#include <linux/devm-helpers.h> #define JITTER_DEFAULT 10 /* hope 10ms is enough */ @@ -30,6 +26,7 @@ enum gab_chan_type { GAB_VOLTAGE = 0, GAB_CURRENT, GAB_POWER, + GAB_TEMP, GAB_MAX_CHAN_TYPE }; @@ -40,18 +37,16 @@ enum gab_chan_type { static const char *const gab_chan_name[] = { [GAB_VOLTAGE] = "voltage", [GAB_CURRENT] = "current", - [GAB_POWER] = "power", + [GAB_POWER] = "power", + [GAB_TEMP] = "temperature", }; struct gab { - struct power_supply *psy; - struct power_supply_desc psy_desc; - struct iio_channel *channel[GAB_MAX_CHAN_TYPE]; - struct gab_platform_data *pdata; + struct power_supply *psy; + struct power_supply_desc psy_desc; + struct iio_channel *channel[GAB_MAX_CHAN_TYPE]; struct delayed_work bat_work; - int level; - int status; - bool cable_plugged; + int status; struct gpio_desc *charge_finished; }; @@ -69,15 +64,6 @@ static void gab_ext_power_changed(struct power_supply *psy) static const enum power_supply_property gab_props[] = { POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_MODEL_NAME, }; /* @@ -88,6 +74,7 @@ static const enum power_supply_property gab_dyn_props[] = { POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_TEMP, }; static bool gab_charge_finished(struct gab *adc_bat) @@ -97,119 +84,53 @@ static bool gab_charge_finished(struct gab *adc_bat) return gpiod_get_value(adc_bat->charge_finished); } -static int gab_get_status(struct gab *adc_bat) -{ - struct gab_platform_data *pdata = adc_bat->pdata; - struct power_supply_info *bat_info; - - bat_info = &pdata->battery_info; - if (adc_bat->level == bat_info->charge_full_design) - return POWER_SUPPLY_STATUS_FULL; - return adc_bat->status; -} - -static enum gab_chan_type gab_prop_to_chan(enum power_supply_property psp) -{ - switch (psp) { - case POWER_SUPPLY_PROP_POWER_NOW: - return GAB_POWER; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - return GAB_VOLTAGE; - case POWER_SUPPLY_PROP_CURRENT_NOW: - return GAB_CURRENT; - default: - WARN_ON(1); - break; - } - return GAB_POWER; -} - -static int read_channel(struct gab *adc_bat, enum power_supply_property psp, +static int gab_read_channel(struct gab *adc_bat, enum gab_chan_type channel, int *result) { int ret; - int chan_index; - chan_index = gab_prop_to_chan(psp); - ret = iio_read_channel_processed(adc_bat->channel[chan_index], - result); + ret = iio_read_channel_processed(adc_bat->channel[channel], result); if (ret < 0) - pr_err("read channel error\n"); + dev_err(&adc_bat->psy->dev, "read channel error: %d\n", ret); + else + *result *= 1000; + return ret; } static int gab_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - struct gab *adc_bat; - struct gab_platform_data *pdata; - struct power_supply_info *bat_info; - int result = 0; - int ret = 0; - - adc_bat = to_generic_bat(psy); - if (!adc_bat) { - dev_err(&psy->dev, "no battery infos ?!\n"); - return -EINVAL; - } - pdata = adc_bat->pdata; - bat_info = &pdata->battery_info; + struct gab *adc_bat = to_generic_bat(psy); switch (psp) { case POWER_SUPPLY_PROP_STATUS: - val->intval = gab_get_status(adc_bat); - break; - case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN: - val->intval = 0; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: - val->intval = pdata->cal_charge(result); - break; + val->intval = adc_bat->status; + return 0; case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return gab_read_channel(adc_bat, GAB_VOLTAGE, &val->intval); case POWER_SUPPLY_PROP_CURRENT_NOW: + return gab_read_channel(adc_bat, GAB_CURRENT, &val->intval); case POWER_SUPPLY_PROP_POWER_NOW: - ret = read_channel(adc_bat, psp, &result); - if (ret < 0) - goto err; - val->intval = result; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = bat_info->technology; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = bat_info->voltage_min_design; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = bat_info->voltage_max_design; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval = bat_info->charge_full_design; - break; - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = bat_info->name; - break; + return gab_read_channel(adc_bat, GAB_POWER, &val->intval); + case POWER_SUPPLY_PROP_TEMP: + return gab_read_channel(adc_bat, GAB_TEMP, &val->intval); default: return -EINVAL; } -err: - return ret; } static void gab_work(struct work_struct *work) { struct gab *adc_bat; struct delayed_work *delayed_work; - bool is_plugged; int status; delayed_work = to_delayed_work(work); adc_bat = container_of(delayed_work, struct gab, bat_work); status = adc_bat->status; - is_plugged = power_supply_am_i_supplied(adc_bat->psy); - adc_bat->cable_plugged = is_plugged; - - if (!is_plugged) + if (!power_supply_am_i_supplied(adc_bat->psy)) adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING; else if (gab_charge_finished(adc_bat)) adc_bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING; @@ -223,12 +144,10 @@ static void gab_work(struct work_struct *work) static irqreturn_t gab_charged(int irq, void *dev_id) { struct gab *adc_bat = dev_id; - struct gab_platform_data *pdata = adc_bat->pdata; - int delay; - delay = pdata->jitter_delay ? pdata->jitter_delay : JITTER_DEFAULT; schedule_delayed_work(&adc_bat->bat_work, - msecs_to_jiffies(delay)); + msecs_to_jiffies(JITTER_DEFAULT)); + return IRQ_HANDLED; } @@ -237,7 +156,6 @@ static int gab_probe(struct platform_device *pdev) struct gab *adc_bat; struct power_supply_desc *psy_desc; struct power_supply_config psy_cfg = {}; - struct gab_platform_data *pdata = pdev->dev.platform_data; enum power_supply_property *properties; int ret = 0; int chan; @@ -245,35 +163,31 @@ static int gab_probe(struct platform_device *pdev) bool any = false; adc_bat = devm_kzalloc(&pdev->dev, sizeof(*adc_bat), GFP_KERNEL); - if (!adc_bat) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (!adc_bat) return -ENOMEM; - } + psy_cfg.fwnode = dev_fwnode(&pdev->dev); psy_cfg.drv_data = adc_bat; psy_desc = &adc_bat->psy_desc; - psy_desc->name = pdata->battery_info.name; + psy_desc->name = dev_name(&pdev->dev); /* bootup default values for the battery */ - adc_bat->cable_plugged = false; adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING; psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; psy_desc->get_property = gab_get_property; psy_desc->external_power_changed = gab_ext_power_changed; - adc_bat->pdata = pdata; /* * copying the static properties and allocating extra memory for holding * the extra configurable properties received from platform data. */ - properties = kcalloc(ARRAY_SIZE(gab_props) + - ARRAY_SIZE(gab_chan_name), - sizeof(*properties), - GFP_KERNEL); - if (!properties) { - ret = -ENOMEM; - goto first_mem_fail; - } + properties = devm_kcalloc(&pdev->dev, + ARRAY_SIZE(gab_props) + + ARRAY_SIZE(gab_chan_name), + sizeof(*properties), + GFP_KERNEL); + if (!properties) + return -ENOMEM; memcpy(properties, gab_props, sizeof(gab_props)); @@ -282,12 +196,13 @@ static int gab_probe(struct platform_device *pdev) * based on the channel supported by consumer device. */ for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) { - adc_bat->channel[chan] = iio_channel_get(&pdev->dev, - gab_chan_name[chan]); + adc_bat->channel[chan] = devm_iio_channel_get(&pdev->dev, gab_chan_name[chan]); if (IS_ERR(adc_bat->channel[chan])) { ret = PTR_ERR(adc_bat->channel[chan]); + if (ret != -ENODEV) + return dev_err_probe(&pdev->dev, ret, "Failed to get ADC channel %s\n", gab_chan_name[chan]); adc_bat->channel[chan] = NULL; - } else { + } else if (adc_bat->channel[chan]) { /* copying properties for supported channels only */ int index2; @@ -302,10 +217,8 @@ static int gab_probe(struct platform_device *pdev) } /* none of the channels are supported so let's bail out */ - if (!any) { - ret = -ENODEV; - goto second_mem_fail; - } + if (!any) + return dev_err_probe(&pdev->dev, -ENODEV, "Failed to get any ADC channel\n"); /* * Total number of properties is equal to static properties @@ -316,25 +229,24 @@ static int gab_probe(struct platform_device *pdev) psy_desc->properties = properties; psy_desc->num_properties = index; - adc_bat->psy = power_supply_register(&pdev->dev, psy_desc, &psy_cfg); - if (IS_ERR(adc_bat->psy)) { - ret = PTR_ERR(adc_bat->psy); - goto err_reg_fail; - } + adc_bat->psy = devm_power_supply_register(&pdev->dev, psy_desc, &psy_cfg); + if (IS_ERR(adc_bat->psy)) + return dev_err_probe(&pdev->dev, PTR_ERR(adc_bat->psy), "Failed to register power-supply device\n"); - INIT_DELAYED_WORK(&adc_bat->bat_work, gab_work); + ret = devm_delayed_work_autocancel(&pdev->dev, &adc_bat->bat_work, gab_work); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to register delayed work\n"); - adc_bat->charge_finished = devm_gpiod_get_optional(&pdev->dev, - "charged", GPIOD_IN); + adc_bat->charge_finished = devm_gpiod_get_optional(&pdev->dev, "charged", GPIOD_IN); if (adc_bat->charge_finished) { int irq; irq = gpiod_to_irq(adc_bat->charge_finished); - ret = request_any_context_irq(irq, gab_charged, + ret = devm_request_any_context_irq(&pdev->dev, irq, gab_charged, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "battery charged", adc_bat); if (ret < 0) - goto gpio_req_fail; + return dev_err_probe(&pdev->dev, ret, "Failed to register irq\n"); } platform_set_drvdata(pdev, adc_bat); @@ -343,38 +255,6 @@ static int gab_probe(struct platform_device *pdev) schedule_delayed_work(&adc_bat->bat_work, msecs_to_jiffies(0)); return 0; - -gpio_req_fail: - power_supply_unregister(adc_bat->psy); -err_reg_fail: - for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) { - if (adc_bat->channel[chan]) - iio_channel_release(adc_bat->channel[chan]); - } -second_mem_fail: - kfree(properties); -first_mem_fail: - return ret; -} - -static int gab_remove(struct platform_device *pdev) -{ - int chan; - struct gab *adc_bat = platform_get_drvdata(pdev); - - power_supply_unregister(adc_bat->psy); - - if (adc_bat->charge_finished) - free_irq(gpiod_to_irq(adc_bat->charge_finished), adc_bat); - - for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) { - if (adc_bat->channel[chan]) - iio_channel_release(adc_bat->channel[chan]); - } - - kfree(adc_bat->psy_desc.properties); - cancel_delayed_work_sync(&adc_bat->bat_work); - return 0; } static int __maybe_unused gab_suspend(struct device *dev) @@ -389,29 +269,32 @@ static int __maybe_unused gab_suspend(struct device *dev) static int __maybe_unused gab_resume(struct device *dev) { struct gab *adc_bat = dev_get_drvdata(dev); - struct gab_platform_data *pdata = adc_bat->pdata; - int delay; - - delay = pdata->jitter_delay ? pdata->jitter_delay : JITTER_DEFAULT; /* Schedule timer to check current status */ schedule_delayed_work(&adc_bat->bat_work, - msecs_to_jiffies(delay)); + msecs_to_jiffies(JITTER_DEFAULT)); + return 0; } static SIMPLE_DEV_PM_OPS(gab_pm_ops, gab_suspend, gab_resume); +static const struct of_device_id gab_match[] = { + { .compatible = "adc-battery" }, + { } +}; +MODULE_DEVICE_TABLE(of, gab_match); + static struct platform_driver gab_driver = { .driver = { .name = "generic-adc-battery", .pm = &gab_pm_ops, + .of_match_table = gab_match, }, .probe = gab_probe, - .remove = gab_remove, }; module_platform_driver(gab_driver); -MODULE_AUTHOR("anish kumar <anish198519851985@gmail.com>"); +MODULE_AUTHOR("anish kumar <yesanishhere@gmail.com>"); MODULE_DESCRIPTION("generic battery driver using IIO"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/goldfish_battery.c b/drivers/power/supply/goldfish_battery.c index a58d713d75ce..479195e35d73 100644 --- a/drivers/power/supply/goldfish_battery.c +++ b/drivers/power/supply/goldfish_battery.c @@ -232,32 +232,22 @@ static int goldfish_battery_probe(struct platform_device *pdev) psy_cfg.drv_data = data; - data->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg); + data->ac = devm_power_supply_register(&pdev->dev, + &ac_desc, + &psy_cfg); if (IS_ERR(data->ac)) return PTR_ERR(data->ac); - data->battery = power_supply_register(&pdev->dev, &battery_desc, - &psy_cfg); - if (IS_ERR(data->battery)) { - power_supply_unregister(data->ac); + data->battery = devm_power_supply_register(&pdev->dev, + &battery_desc, + &psy_cfg); + if (IS_ERR(data->battery)) return PTR_ERR(data->battery); - } - - platform_set_drvdata(pdev, data); GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK); return 0; } -static int goldfish_battery_remove(struct platform_device *pdev) -{ - struct goldfish_battery_data *data = platform_get_drvdata(pdev); - - power_supply_unregister(data->battery); - power_supply_unregister(data->ac); - return 0; -} - static const struct of_device_id goldfish_battery_of_match[] = { { .compatible = "google,goldfish-battery", }, {}, @@ -274,7 +264,6 @@ MODULE_DEVICE_TABLE(acpi, goldfish_battery_acpi_match); static struct platform_driver goldfish_battery_device = { .probe = goldfish_battery_probe, - .remove = goldfish_battery_remove, .driver = { .name = "goldfish-battery", .of_match_table = goldfish_battery_of_match, diff --git a/drivers/power/supply/gpio-charger.c b/drivers/power/supply/gpio-charger.c index 68212b39785b..2504190eba82 100644 --- a/drivers/power/supply/gpio-charger.c +++ b/drivers/power/supply/gpio-charger.c @@ -67,11 +67,20 @@ static int set_charge_current_limit(struct gpio_charger *gpio_charger, int val) if (gpio_charger->current_limit_map[i].limit_ua <= val) break; } + + /* + * If a valid charge current limit isn't found, default to smallest + * current limitation for safety reasons. + */ + if (i >= gpio_charger->current_limit_map_size) + i = gpio_charger->current_limit_map_size - 1; + mapping = gpio_charger->current_limit_map[i]; 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; @@ -187,6 +196,8 @@ static int init_charge_current_limit(struct device *dev, { int i, len; u32 cur_limit = U32_MAX; + bool set_def_limit; + u32 def_limit; gpio_charger->current_limit_gpios = devm_gpiod_get_array_optional(dev, "charge-current-limit", GPIOD_OUT_LOW); @@ -216,19 +227,30 @@ 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; - for (i=0; i < gpio_charger->current_limit_map_size; i++) { + 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++) { 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; } cur_limit = gpio_charger->current_limit_map[i].limit_ua; + if (set_def_limit && def_limit == cur_limit) { + set_charge_current_limit(gpio_charger, cur_limit); + return 0; + } } + if (set_def_limit) + dev_warn(dev, "charge-current-limit-default-microamp %u not listed in charge-current-limit-mapping\n", + def_limit); + /* default to smallest current limitation for safety reasons */ len = gpio_charger->current_limit_map_size - 1; set_charge_current_limit(gpio_charger, @@ -312,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) { @@ -345,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 2e7fdfde47ec..b111c7ce2be3 100644 --- a/drivers/power/supply/ingenic-battery.c +++ b/drivers/power/supply/ingenic-battery.c @@ -31,8 +31,9 @@ static int ingenic_battery_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_HEALTH: - ret = iio_read_channel_processed(bat->channel, &val->intval); - val->intval *= 1000; + ret = iio_read_channel_processed_scale(bat->channel, + &val->intval, + 1000); if (val->intval < info->voltage_min_design_uv) val->intval = POWER_SUPPLY_HEALTH_DEAD; else if (val->intval > info->voltage_max_design_uv) @@ -41,8 +42,9 @@ static int ingenic_battery_get_property(struct power_supply *psy, val->intval = POWER_SUPPLY_HEALTH_GOOD; return ret; case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = iio_read_channel_processed(bat->channel, &val->intval); - val->intval *= 1000; + ret = iio_read_channel_processed_scale(bat->channel, + &val->intval, + 1000); return ret; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: val->intval = info->voltage_min_design_uv; @@ -144,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 00221e9c0bfc..24eea7a91b30 100644 --- a/drivers/power/supply/ip5xxx_power.c +++ b/drivers/power/supply/ip5xxx_power.c @@ -7,76 +7,154 @@ #include <linux/power_supply.h> #include <linux/regmap.h> -#define IP5XXX_SYS_CTL0 0x01 -#define IP5XXX_SYS_CTL0_WLED_DET_EN BIT(4) -#define IP5XXX_SYS_CTL0_WLED_EN BIT(3) -#define IP5XXX_SYS_CTL0_BOOST_EN BIT(2) -#define IP5XXX_SYS_CTL0_CHARGER_EN BIT(1) -#define IP5XXX_SYS_CTL1 0x02 -#define IP5XXX_SYS_CTL1_LIGHT_SHDN_EN BIT(1) -#define IP5XXX_SYS_CTL1_LOAD_PWRUP_EN BIT(0) -#define IP5XXX_SYS_CTL2 0x0c -#define IP5XXX_SYS_CTL2_LIGHT_SHDN_TH GENMASK(7, 3) -#define IP5XXX_SYS_CTL3 0x03 -#define IP5XXX_SYS_CTL3_LONG_PRESS_TIME_SEL GENMASK(7, 6) -#define IP5XXX_SYS_CTL3_BTN_SHDN_EN BIT(5) -#define IP5XXX_SYS_CTL4 0x04 -#define IP5XXX_SYS_CTL4_SHDN_TIME_SEL GENMASK(7, 6) -#define IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN BIT(5) -#define IP5XXX_SYS_CTL5 0x07 -#define IP5XXX_SYS_CTL5_NTC_DIS BIT(6) -#define IP5XXX_SYS_CTL5_WLED_MODE_SEL BIT(1) -#define IP5XXX_SYS_CTL5_BTN_SHDN_SEL BIT(0) -#define IP5XXX_CHG_CTL1 0x22 -#define IP5XXX_CHG_CTL1_BOOST_UVP_SEL GENMASK(3, 2) -#define IP5XXX_CHG_CTL2 0x24 -#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL GENMASK(6, 5) -#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V (0x0 << 5) -#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V (0x1 << 5) -#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V (0x2 << 5) -#define IP5XXX_CHG_CTL2_CONST_VOLT_SEL GENMASK(2, 1) -#define IP5XXX_CHG_CTL4 0x26 -#define IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN BIT(6) -#define IP5XXX_CHG_CTL4A 0x25 -#define IP5XXX_CHG_CTL4A_CONST_CUR_SEL GENMASK(4, 0) -#define IP5XXX_MFP_CTL0 0x51 -#define IP5XXX_MFP_CTL1 0x52 -#define IP5XXX_GPIO_CTL2 0x53 -#define IP5XXX_GPIO_CTL2A 0x54 -#define IP5XXX_GPIO_CTL3 0x55 -#define IP5XXX_READ0 0x71 -#define IP5XXX_READ0_CHG_STAT GENMASK(7, 5) -#define IP5XXX_READ0_CHG_STAT_IDLE (0x0 << 5) -#define IP5XXX_READ0_CHG_STAT_TRICKLE (0x1 << 5) -#define IP5XXX_READ0_CHG_STAT_CONST_VOLT (0x2 << 5) -#define IP5XXX_READ0_CHG_STAT_CONST_CUR (0x3 << 5) -#define IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP (0x4 << 5) -#define IP5XXX_READ0_CHG_STAT_FULL (0x5 << 5) -#define IP5XXX_READ0_CHG_STAT_TIMEOUT (0x6 << 5) -#define IP5XXX_READ0_CHG_OP BIT(4) -#define IP5XXX_READ0_CHG_END BIT(3) -#define IP5XXX_READ0_CONST_VOLT_TIMEOUT BIT(2) -#define IP5XXX_READ0_CHG_TIMEOUT BIT(1) -#define IP5XXX_READ0_TRICKLE_TIMEOUT BIT(0) -#define IP5XXX_READ0_TIMEOUT GENMASK(2, 0) -#define IP5XXX_READ1 0x72 -#define IP5XXX_READ1_WLED_PRESENT BIT(7) -#define IP5XXX_READ1_LIGHT_LOAD BIT(6) -#define IP5XXX_READ1_VIN_OVERVOLT BIT(5) -#define IP5XXX_READ2 0x77 -#define IP5XXX_READ2_BTN_PRESS BIT(3) -#define IP5XXX_READ2_BTN_LONG_PRESS BIT(1) -#define IP5XXX_READ2_BTN_SHORT_PRESS BIT(0) -#define IP5XXX_BATVADC_DAT0 0xa2 -#define IP5XXX_BATVADC_DAT1 0xa3 -#define IP5XXX_BATIADC_DAT0 0xa4 -#define IP5XXX_BATIADC_DAT1 0xa5 -#define IP5XXX_BATOCV_DAT0 0xa8 -#define IP5XXX_BATOCV_DAT1 0xa9 +#define IP5XXX_BAT_TYPE_4_2V 0x0 +#define IP5XXX_BAT_TYPE_4_3V 0x1 +#define IP5XXX_BAT_TYPE_4_35V 0x2 +#define IP5XXX_BAT_TYPE_4_4V 0x3 +#define IP5XXX_CHG_STAT_IDLE 0x0 +#define IP5XXX_CHG_STAT_TRICKLE 0x1 +#define IP5XXX_CHG_STAT_CONST_VOLT 0x2 +#define IP5XXX_CHG_STAT_CONST_CUR 0x3 +#define IP5XXX_CHG_STAT_CONST_VOLT_STOP 0x4 +#define IP5XXX_CHG_STAT_FULL 0x5 +#define IP5XXX_CHG_STAT_TIMEOUT 0x6 struct ip5xxx { struct regmap *regmap; bool initialized; + struct { + struct { + /* Charger enable */ + struct regmap_field *enable; + /* Constant voltage value */ + struct regmap_field *const_volt_sel; + /* Constant current value */ + struct regmap_field *const_curr_sel; + /* Charger status */ + struct regmap_field *status; + /* Charging ended flag */ + struct regmap_field *chg_end; + /* Timeout flags (CV, charge, trickle) */ + struct regmap_field *timeout; + /* Overvoltage limit */ + struct regmap_field *vin_overvolt; + } charger; + struct { + /* Boost converter enable */ + struct regmap_field *enable; + struct { + /* Light load shutdown enable */ + struct regmap_field *enable; + /* Light load shutdown current limit */ + struct regmap_field *i_limit; + } light_load_shutdown; + /* Automatic powerup on increased load */ + struct regmap_field *load_powerup_en; + /* Automatic powerup on VIN pull-out */ + struct regmap_field *vin_pullout_en; + /* Undervoltage limit */ + struct regmap_field *undervolt_limit; + /* Light load status flag */ + struct regmap_field *light_load_status; + } boost; + struct { + /* NTC disable */ + struct regmap_field *ntc_dis; + /* Battery voltage type */ + struct regmap_field *type; + /* Battery voltage autoset from Vset pin */ + struct regmap_field *vset_en; + struct { + /* Battery measurement registers */ + struct ip5xxx_battery_adc_regs { + struct regmap_field *low; + struct regmap_field *high; + } volt, curr, open_volt; + } adc; + } battery; + struct { + /* Double/long press shutdown enable */ + struct regmap_field *shdn_enable; + /* WLED activation: double press or long press */ + struct regmap_field *wled_mode; + /* Shutdown activation: double press or long press */ + struct regmap_field *shdn_mode; + /* Long press time */ + struct regmap_field *long_press_time; + /* Button pressed */ + struct regmap_field *pressed; + /* Button long-pressed */ + struct regmap_field *long_pressed; + /* Button short-pressed */ + struct regmap_field *short_pressed; + } btn; + struct { + /* WLED enable */ + struct regmap_field *enable; + /* WLED detect */ + struct regmap_field *detect_en; + /* WLED present */ + struct regmap_field *present; + } wled; + } regs; + + /* Maximum supported battery voltage (via regs.battery.type) */ + int vbat_max; + /* Scaling constants for regs.boost.undervolt_limit */ + struct { + int setpoint; + int microvolts_per_bit; + } boost_undervolt; + /* Scaling constants for regs.charger.const_curr_sel */ + struct { + int setpoint; + } const_curr; + /* Whether regs.charger.chg_end is inverted */ + u8 chg_end_inverted; +}; + +#define REG_FIELD_UNSUPPORTED { .lsb = 1 } +/* Register fields layout. Unsupported registers marked as { .lsb = 1 } */ +struct ip5xxx_regfield_config { + const struct reg_field charger_enable; + const struct reg_field charger_const_volt_sel; + const struct reg_field charger_const_curr_sel; + const struct reg_field charger_status; + const struct reg_field charger_chg_end; + const struct reg_field charger_timeout; + const struct reg_field charger_vin_overvolt; + const struct reg_field boost_enable; + const struct reg_field boost_llshdn_enable; + const struct reg_field boost_llshdn_i_limit; + const struct reg_field boost_load_powerup_en; + const struct reg_field boost_vin_pullout_en; + const struct reg_field boost_undervolt_limit; + const struct reg_field boost_light_load_status; + const struct reg_field battery_ntc_dis; + const struct reg_field battery_type; + const struct reg_field battery_vset_en; + const struct reg_field battery_adc_volt_low; + const struct reg_field battery_adc_volt_high; + const struct reg_field battery_adc_curr_low; + const struct reg_field battery_adc_curr_high; + const struct reg_field battery_adc_ovolt_low; + const struct reg_field battery_adc_ovolt_high; + const struct reg_field btn_shdn_enable; + const struct reg_field btn_wled_mode; + const struct reg_field btn_shdn_mode; + const struct reg_field btn_long_press_time; + const struct reg_field btn_pressed; + const struct reg_field btn_long_pressed; + const struct reg_field btn_short_pressed; + const struct reg_field wled_enable; + const struct reg_field wled_detect_en; + const struct reg_field wled_present; + + int vbat_max; + int boost_undervolt_setpoint; + int boost_undervolt_uv_per_bit; + int const_curr_setpoint; + u8 chg_end_inverted; }; /* @@ -87,24 +165,30 @@ struct ip5xxx { * 2) Attempt the initialization sequence on each subsequent register access * until it succeeds. */ -static int ip5xxx_read(struct ip5xxx *ip5xxx, unsigned int reg, +static int ip5xxx_read(struct ip5xxx *ip5xxx, struct regmap_field *field, unsigned int *val) { int ret; - ret = regmap_read(ip5xxx->regmap, reg, val); + if (!field) + return -EOPNOTSUPP; + + ret = regmap_field_read(field, val); if (ret) ip5xxx->initialized = false; return ret; } -static int ip5xxx_update_bits(struct ip5xxx *ip5xxx, unsigned int reg, - unsigned int mask, unsigned int val) +static int ip5xxx_write(struct ip5xxx *ip5xxx, struct regmap_field *field, + unsigned int val) { int ret; - ret = regmap_update_bits(ip5xxx->regmap, reg, mask, val); + if (!field) + return -EOPNOTSUPP; + + ret = regmap_field_write(field, val); if (ret) ip5xxx->initialized = false; @@ -123,28 +207,26 @@ static int ip5xxx_initialize(struct power_supply *psy) * Disable shutdown under light load. * Enable power on when under load. */ - ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL1, - IP5XXX_SYS_CTL1_LIGHT_SHDN_EN | - IP5XXX_SYS_CTL1_LOAD_PWRUP_EN, - IP5XXX_SYS_CTL1_LOAD_PWRUP_EN); + if (ip5xxx->regs.boost.light_load_shutdown.enable) { + ret = ip5xxx_write(ip5xxx, ip5xxx->regs.boost.light_load_shutdown.enable, 0); + if (ret) + return ret; + } + ret = ip5xxx_write(ip5xxx, ip5xxx->regs.boost.load_powerup_en, 1); if (ret) return ret; /* * Enable shutdown after a long button press (as configured below). */ - ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL3, - IP5XXX_SYS_CTL3_BTN_SHDN_EN, - IP5XXX_SYS_CTL3_BTN_SHDN_EN); + ret = ip5xxx_write(ip5xxx, ip5xxx->regs.btn.shdn_enable, 1); if (ret) return ret; /* * Power on automatically when VIN is removed. */ - ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL4, - IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN, - IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN); + ret = ip5xxx_write(ip5xxx, ip5xxx->regs.boost.vin_pullout_en, 1); if (ret) return ret; @@ -152,12 +234,15 @@ static int ip5xxx_initialize(struct power_supply *psy) * Enable the NTC. * Configure the button for two presses => LED, long press => shutdown. */ - ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL5, - IP5XXX_SYS_CTL5_NTC_DIS | - IP5XXX_SYS_CTL5_WLED_MODE_SEL | - IP5XXX_SYS_CTL5_BTN_SHDN_SEL, - IP5XXX_SYS_CTL5_WLED_MODE_SEL | - IP5XXX_SYS_CTL5_BTN_SHDN_SEL); + if (ip5xxx->regs.battery.ntc_dis) { + ret = ip5xxx_write(ip5xxx, ip5xxx->regs.battery.ntc_dis, 0); + if (ret) + return ret; + } + ret = ip5xxx_write(ip5xxx, ip5xxx->regs.btn.wled_mode, 1); + if (ret) + return ret; + ret = ip5xxx_write(ip5xxx, ip5xxx->regs.btn.shdn_mode, 1); if (ret) return ret; @@ -186,24 +271,37 @@ static int ip5xxx_battery_get_status(struct ip5xxx *ip5xxx, int *val) unsigned int rval; int ret; - ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval); + if (!ip5xxx->regs.charger.status) { + // Fall-back to Charging Ended bit + ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.chg_end, &rval); + if (ret) + return ret; + + if (rval == ip5xxx->chg_end_inverted) + *val = POWER_SUPPLY_STATUS_CHARGING; + else + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + return 0; + } + + ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.status, &rval); if (ret) return ret; - switch (rval & IP5XXX_READ0_CHG_STAT) { - case IP5XXX_READ0_CHG_STAT_IDLE: + switch (rval) { + case IP5XXX_CHG_STAT_IDLE: *val = POWER_SUPPLY_STATUS_DISCHARGING; break; - case IP5XXX_READ0_CHG_STAT_TRICKLE: - case IP5XXX_READ0_CHG_STAT_CONST_CUR: - case IP5XXX_READ0_CHG_STAT_CONST_VOLT: + case IP5XXX_CHG_STAT_TRICKLE: + case IP5XXX_CHG_STAT_CONST_CUR: + case IP5XXX_CHG_STAT_CONST_VOLT: *val = POWER_SUPPLY_STATUS_CHARGING; break; - case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP: - case IP5XXX_READ0_CHG_STAT_FULL: + case IP5XXX_CHG_STAT_CONST_VOLT_STOP: + case IP5XXX_CHG_STAT_FULL: *val = POWER_SUPPLY_STATUS_FULL; break; - case IP5XXX_READ0_CHG_STAT_TIMEOUT: + case IP5XXX_CHG_STAT_TIMEOUT: *val = POWER_SUPPLY_STATUS_NOT_CHARGING; break; default: @@ -218,22 +316,22 @@ static int ip5xxx_battery_get_charge_type(struct ip5xxx *ip5xxx, int *val) unsigned int rval; int ret; - ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval); + ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.status, &rval); if (ret) return ret; - switch (rval & IP5XXX_READ0_CHG_STAT) { - case IP5XXX_READ0_CHG_STAT_IDLE: - case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP: - case IP5XXX_READ0_CHG_STAT_FULL: - case IP5XXX_READ0_CHG_STAT_TIMEOUT: + switch (rval) { + case IP5XXX_CHG_STAT_IDLE: + case IP5XXX_CHG_STAT_CONST_VOLT_STOP: + case IP5XXX_CHG_STAT_FULL: + case IP5XXX_CHG_STAT_TIMEOUT: *val = POWER_SUPPLY_CHARGE_TYPE_NONE; break; - case IP5XXX_READ0_CHG_STAT_TRICKLE: + case IP5XXX_CHG_STAT_TRICKLE: *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; break; - case IP5XXX_READ0_CHG_STAT_CONST_CUR: - case IP5XXX_READ0_CHG_STAT_CONST_VOLT: + case IP5XXX_CHG_STAT_CONST_CUR: + case IP5XXX_CHG_STAT_CONST_VOLT: *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD; break; default: @@ -248,11 +346,11 @@ static int ip5xxx_battery_get_health(struct ip5xxx *ip5xxx, int *val) unsigned int rval; int ret; - ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval); + ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.timeout, &rval); if (ret) return ret; - if (rval & IP5XXX_READ0_TIMEOUT) + if (rval) *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; else *val = POWER_SUPPLY_HEALTH_GOOD; @@ -265,7 +363,7 @@ static int ip5xxx_battery_get_voltage_max(struct ip5xxx *ip5xxx, int *val) unsigned int rval; int ret; - ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval); + ret = ip5xxx_read(ip5xxx, ip5xxx->regs.battery.type, &rval); if (ret) return ret; @@ -273,16 +371,19 @@ static int ip5xxx_battery_get_voltage_max(struct ip5xxx *ip5xxx, int *val) * It is not clear what this will return if * IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN is not set... */ - switch (rval & IP5XXX_CHG_CTL2_BAT_TYPE_SEL) { - case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V: + switch (rval) { + case IP5XXX_BAT_TYPE_4_2V: *val = 4200000; break; - case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V: + case IP5XXX_BAT_TYPE_4_3V: *val = 4300000; break; - case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V: + case IP5XXX_BAT_TYPE_4_35V: *val = 4350000; break; + case IP5XXX_BAT_TYPE_4_4V: + *val = 4400000; + break; default: return -EINVAL; } @@ -291,16 +392,16 @@ static int ip5xxx_battery_get_voltage_max(struct ip5xxx *ip5xxx, int *val) } static int ip5xxx_battery_read_adc(struct ip5xxx *ip5xxx, - u8 lo_reg, u8 hi_reg, int *val) + struct ip5xxx_battery_adc_regs *regs, int *val) { unsigned int hi, lo; int ret; - ret = ip5xxx_read(ip5xxx, lo_reg, &lo); + ret = ip5xxx_read(ip5xxx, regs->low, &lo); if (ret) return ret; - ret = ip5xxx_read(ip5xxx, hi_reg, &hi); + ret = ip5xxx_read(ip5xxx, regs->high, &hi); if (ret) return ret; @@ -335,33 +436,35 @@ static int ip5xxx_battery_get_property(struct power_supply *psy, return ip5xxx_battery_get_voltage_max(ip5xxx, &val->intval); case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATVADC_DAT0, - IP5XXX_BATVADC_DAT1, &raw); + ret = ip5xxx_battery_read_adc(ip5xxx, &ip5xxx->regs.battery.adc.volt, &raw); + if (ret) + return ret; val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100); return 0; case POWER_SUPPLY_PROP_VOLTAGE_OCV: - ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATOCV_DAT0, - IP5XXX_BATOCV_DAT1, &raw); + ret = ip5xxx_battery_read_adc(ip5xxx, &ip5xxx->regs.battery.adc.open_volt, &raw); + if (ret) + return ret; val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100); return 0; case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATIADC_DAT0, - IP5XXX_BATIADC_DAT1, &raw); + ret = ip5xxx_battery_read_adc(ip5xxx, &ip5xxx->regs.battery.adc.curr, &raw); + if (ret) + return ret; val->intval = DIV_ROUND_CLOSEST(raw * 149197, 200); return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL4A, &rval); + ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.const_curr_sel, &rval); if (ret) return ret; - rval &= IP5XXX_CHG_CTL4A_CONST_CUR_SEL; - val->intval = 100000 * rval; + val->intval = ip5xxx->const_curr.setpoint + 100000 * rval; return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: @@ -373,12 +476,11 @@ static int ip5xxx_battery_get_property(struct power_supply *psy, if (ret) return ret; - ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval); + ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.const_volt_sel, &rval); if (ret) return ret; - rval &= IP5XXX_CHG_CTL2_CONST_VOLT_SEL; - val->intval = vmax + 14000 * (rval >> 1); + val->intval = vmax + 14000 * rval; return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: @@ -399,30 +501,36 @@ static int ip5xxx_battery_set_voltage_max(struct ip5xxx *ip5xxx, int val) unsigned int rval; int ret; + if (val > ip5xxx->vbat_max) + return -EINVAL; + switch (val) { case 4200000: - rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V; + rval = IP5XXX_BAT_TYPE_4_2V; break; case 4300000: - rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V; + rval = IP5XXX_BAT_TYPE_4_3V; break; case 4350000: - rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V; + rval = IP5XXX_BAT_TYPE_4_35V; + break; + case 4400000: + rval = IP5XXX_BAT_TYPE_4_4V; break; default: return -EINVAL; } - ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2, - IP5XXX_CHG_CTL2_BAT_TYPE_SEL, rval); + ret = ip5xxx_write(ip5xxx, ip5xxx->regs.battery.type, rval); if (ret) return ret; - ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4, - IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN, - IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN); - if (ret) - return ret; + /* Don't try to auto-detect battery type, even if the IC could */ + if (ip5xxx->regs.battery.vset_en) { + ret = ip5xxx_write(ip5xxx, ip5xxx->regs.battery.vset_en, 1); + if (ret) + return ret; + } return 0; } @@ -443,7 +551,7 @@ static int ip5xxx_battery_set_property(struct power_supply *psy, case POWER_SUPPLY_PROP_STATUS: switch (val->intval) { case POWER_SUPPLY_STATUS_CHARGING: - rval = IP5XXX_SYS_CTL0_CHARGER_EN; + rval = 1; break; case POWER_SUPPLY_STATUS_DISCHARGING: case POWER_SUPPLY_STATUS_NOT_CHARGING: @@ -452,25 +560,22 @@ static int ip5xxx_battery_set_property(struct power_supply *psy, default: return -EINVAL; } - return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0, - IP5XXX_SYS_CTL0_CHARGER_EN, rval); + return ip5xxx_write(ip5xxx, ip5xxx->regs.charger.enable, rval); case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: return ip5xxx_battery_set_voltage_max(ip5xxx, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - rval = val->intval / 100000; - return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4A, - IP5XXX_CHG_CTL4A_CONST_CUR_SEL, rval); + rval = (val->intval - ip5xxx->const_curr.setpoint) / 100000; + return ip5xxx_write(ip5xxx, ip5xxx->regs.charger.const_curr_sel, rval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax); if (ret) return ret; - rval = ((val->intval - vmax) / 14000) << 1; - return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2, - IP5XXX_CHG_CTL2_CONST_VOLT_SEL, rval); + rval = (val->intval - vmax) / 14000; + return ip5xxx_write(ip5xxx, ip5xxx->regs.charger.const_volt_sel, rval); default: return -EINVAL; @@ -515,20 +620,20 @@ static int ip5xxx_boost_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_ONLINE: - ret = ip5xxx_read(ip5xxx, IP5XXX_SYS_CTL0, &rval); + ret = ip5xxx_read(ip5xxx, ip5xxx->regs.boost.enable, &rval); if (ret) return ret; - val->intval = !!(rval & IP5XXX_SYS_CTL0_BOOST_EN); + val->intval = !!rval; return 0; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL1, &rval); + ret = ip5xxx_read(ip5xxx, ip5xxx->regs.boost.undervolt_limit, &rval); if (ret) return ret; - rval &= IP5XXX_CHG_CTL1_BOOST_UVP_SEL; - val->intval = 4530000 + 100000 * (rval >> 2); + val->intval = ip5xxx->boost_undervolt.setpoint + + ip5xxx->boost_undervolt.microvolts_per_bit * rval; return 0; default: @@ -550,14 +655,12 @@ static int ip5xxx_boost_set_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_ONLINE: - rval = val->intval ? IP5XXX_SYS_CTL0_BOOST_EN : 0; - return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0, - IP5XXX_SYS_CTL0_BOOST_EN, rval); + return ip5xxx_write(ip5xxx, ip5xxx->regs.boost.enable, !!val->intval); case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - rval = ((val->intval - 4530000) / 100000) << 2; - return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL1, - IP5XXX_CHG_CTL1_BOOST_UVP_SEL, rval); + rval = (val->intval - ip5xxx->boost_undervolt.setpoint) / + ip5xxx->boost_undervolt.microvolts_per_bit; + return ip5xxx_write(ip5xxx, ip5xxx->regs.boost.undervolt_limit, rval); default: return -EINVAL; @@ -583,11 +686,149 @@ static const struct power_supply_desc ip5xxx_boost_desc = { static const struct regmap_config ip5xxx_regmap_config = { .reg_bits = 8, .val_bits = 8, - .max_register = IP5XXX_BATOCV_DAT1, + .max_register = 0xa9, +}; + +static struct ip5xxx_regfield_config ip51xx_fields = { + .charger_enable = REG_FIELD(0x01, 1, 1), + .charger_const_volt_sel = REG_FIELD(0x24, 1, 2), + .charger_const_curr_sel = REG_FIELD(0x25, 0, 4), + .charger_status = REG_FIELD(0x71, 5, 7), + .charger_chg_end = REG_FIELD(0x71, 3, 3), + .charger_timeout = REG_FIELD(0x71, 0, 2), + .charger_vin_overvolt = REG_FIELD(0x72, 5, 5), + .boost_enable = REG_FIELD(0x01, 2, 2), + .boost_llshdn_enable = REG_FIELD(0x02, 1, 1), + .boost_llshdn_i_limit = REG_FIELD(0x0c, 3, 7), + .boost_load_powerup_en = REG_FIELD(0x02, 0, 0), + .boost_vin_pullout_en = REG_FIELD(0x04, 5, 5), + .boost_undervolt_limit = REG_FIELD(0x22, 2, 3), + .boost_light_load_status = REG_FIELD(0x72, 6, 6), + .battery_ntc_dis = REG_FIELD(0x07, 6, 6), + .battery_type = REG_FIELD(0x24, 5, 6), + .battery_vset_en = REG_FIELD(0x26, 6, 6), + .battery_adc_volt_low = REG_FIELD(0xa2, 0, 7), + .battery_adc_volt_high = REG_FIELD(0xa3, 0, 5), + .battery_adc_curr_low = REG_FIELD(0xa4, 0, 7), + .battery_adc_curr_high = REG_FIELD(0xa5, 0, 5), + .battery_adc_ovolt_low = REG_FIELD(0xa8, 0, 7), + .battery_adc_ovolt_high = REG_FIELD(0xa9, 0, 5), + .btn_shdn_enable = REG_FIELD(0x03, 5, 5), + .btn_wled_mode = REG_FIELD(0x07, 1, 1), + .btn_shdn_mode = REG_FIELD(0x07, 0, 0), + .btn_long_press_time = REG_FIELD(0x03, 6, 7), + .btn_pressed = REG_FIELD(0x77, 3, 3), + .btn_long_pressed = REG_FIELD(0x77, 1, 1), + .btn_short_pressed = REG_FIELD(0x77, 0, 0), + .wled_enable = REG_FIELD(0x01, 3, 3), + .wled_detect_en = REG_FIELD(0x01, 4, 4), + .wled_present = REG_FIELD(0x72, 7, 7), + + .vbat_max = 4350000, + .boost_undervolt_setpoint = 4530000, + .boost_undervolt_uv_per_bit = 100000, +}; + +static struct ip5xxx_regfield_config ip5306_fields = { + .charger_enable = REG_FIELD(0x00, 4, 4), + .charger_const_volt_sel = REG_FIELD(0x22, 0, 1), + .charger_const_curr_sel = REG_FIELD(0x24, 0, 4), + .charger_status = REG_FIELD_UNSUPPORTED, // other bits... + .charger_chg_end = REG_FIELD(0x71, 3, 3), + .charger_timeout = REG_FIELD_UNSUPPORTED, + .charger_vin_overvolt = REG_FIELD_UNSUPPORTED, + .boost_enable = REG_FIELD(0x00, 5, 5), + .boost_llshdn_enable = REG_FIELD_UNSUPPORTED, + .boost_llshdn_i_limit = REG_FIELD_UNSUPPORTED, + .boost_load_powerup_en = REG_FIELD(0x00, 2, 2), + .boost_vin_pullout_en = REG_FIELD(0x01, 2, 2), + .boost_undervolt_limit = REG_FIELD(0x21, 2, 4), + .boost_light_load_status = REG_FIELD(0x72, 2, 2), + .battery_ntc_dis = REG_FIELD_UNSUPPORTED, + .battery_type = REG_FIELD(0x22, 2, 3), + .battery_vset_en = REG_FIELD_UNSUPPORTED, + .battery_adc_volt_low = REG_FIELD_UNSUPPORTED, + .battery_adc_volt_high = REG_FIELD_UNSUPPORTED, + .battery_adc_curr_low = REG_FIELD_UNSUPPORTED, + .battery_adc_curr_high = REG_FIELD_UNSUPPORTED, + .battery_adc_ovolt_low = REG_FIELD_UNSUPPORTED, + .battery_adc_ovolt_high = REG_FIELD_UNSUPPORTED, + .btn_shdn_enable = REG_FIELD(0x00, 0, 0), + .btn_wled_mode = REG_FIELD(0x01, 6, 6), + .btn_shdn_mode = REG_FIELD(0x01, 7, 7), + .btn_long_press_time = REG_FIELD(0x02, 4, 4), // +1s + .btn_pressed = REG_FIELD_UNSUPPORTED, + /* TODO: double press */ + .btn_long_pressed = REG_FIELD(0x77, 1, 1), + .btn_short_pressed = REG_FIELD(0x77, 0, 0), + .wled_enable = REG_FIELD_UNSUPPORTED, + .wled_detect_en = REG_FIELD_UNSUPPORTED, + .wled_present = REG_FIELD_UNSUPPORTED, + + .vbat_max = 4400000, + .boost_undervolt_setpoint = 4450000, + .boost_undervolt_uv_per_bit = 50000, + .const_curr_setpoint = 50000, + .chg_end_inverted = 1, }; +#define ip5xxx_setup_reg(_field, _reg) \ + do { \ + if (likely(cfg->_field.lsb <= cfg->_field.msb)) { \ + struct regmap_field *_tmp = devm_regmap_field_alloc(dev, \ + ip5xxx->regmap, cfg->_field); \ + if (!IS_ERR(_tmp)) \ + ip5xxx->regs._reg = _tmp; \ + } \ + } while (0) + +static void ip5xxx_setup_regs(struct device *dev, struct ip5xxx *ip5xxx, + const struct ip5xxx_regfield_config *cfg) +{ + ip5xxx_setup_reg(charger_enable, charger.enable); + ip5xxx_setup_reg(charger_const_volt_sel, charger.const_volt_sel); + ip5xxx_setup_reg(charger_const_curr_sel, charger.const_curr_sel); + ip5xxx_setup_reg(charger_status, charger.status); + ip5xxx_setup_reg(charger_chg_end, charger.chg_end); + ip5xxx_setup_reg(charger_timeout, charger.timeout); + ip5xxx_setup_reg(charger_vin_overvolt, charger.vin_overvolt); + ip5xxx_setup_reg(boost_enable, boost.enable); + ip5xxx_setup_reg(boost_llshdn_enable, boost.light_load_shutdown.enable); + ip5xxx_setup_reg(boost_llshdn_i_limit, boost.light_load_shutdown.i_limit); + ip5xxx_setup_reg(boost_load_powerup_en, boost.load_powerup_en); + ip5xxx_setup_reg(boost_vin_pullout_en, boost.vin_pullout_en); + ip5xxx_setup_reg(boost_undervolt_limit, boost.undervolt_limit); + ip5xxx_setup_reg(boost_light_load_status, boost.light_load_status); + ip5xxx_setup_reg(battery_ntc_dis, battery.ntc_dis); + ip5xxx_setup_reg(battery_type, battery.type); + ip5xxx_setup_reg(battery_vset_en, battery.vset_en); + ip5xxx_setup_reg(battery_adc_volt_low, battery.adc.volt.low); + ip5xxx_setup_reg(battery_adc_volt_high, battery.adc.volt.high); + ip5xxx_setup_reg(battery_adc_curr_low, battery.adc.curr.low); + ip5xxx_setup_reg(battery_adc_curr_high, battery.adc.curr.high); + ip5xxx_setup_reg(battery_adc_ovolt_low, battery.adc.open_volt.low); + ip5xxx_setup_reg(battery_adc_ovolt_high, battery.adc.open_volt.high); + ip5xxx_setup_reg(btn_shdn_enable, btn.shdn_enable); + ip5xxx_setup_reg(btn_wled_mode, btn.wled_mode); + ip5xxx_setup_reg(btn_shdn_mode, btn.shdn_mode); + ip5xxx_setup_reg(btn_long_press_time, btn.long_press_time); + ip5xxx_setup_reg(btn_pressed, btn.pressed); + ip5xxx_setup_reg(btn_long_pressed, btn.long_pressed); + ip5xxx_setup_reg(btn_short_pressed, btn.short_pressed); + ip5xxx_setup_reg(wled_enable, wled.enable); + ip5xxx_setup_reg(wled_detect_en, wled.detect_en); + ip5xxx_setup_reg(wled_present, wled.present); + + ip5xxx->vbat_max = cfg->vbat_max; + ip5xxx->boost_undervolt.setpoint = cfg->boost_undervolt_setpoint; + ip5xxx->boost_undervolt.microvolts_per_bit = cfg->boost_undervolt_uv_per_bit; + ip5xxx->const_curr.setpoint = cfg->const_curr_setpoint; + ip5xxx->chg_end_inverted = cfg->chg_end_inverted; +} + static int ip5xxx_power_probe(struct i2c_client *client) { + const struct ip5xxx_regfield_config *fields; struct power_supply_config psy_cfg = {}; struct device *dev = &client->dev; struct power_supply *psy; @@ -601,7 +842,10 @@ static int ip5xxx_power_probe(struct i2c_client *client) if (IS_ERR(ip5xxx->regmap)) return PTR_ERR(ip5xxx->regmap); - psy_cfg.of_node = dev->of_node; + fields = i2c_get_match_data(client) ?: &ip51xx_fields; + ip5xxx_setup_regs(dev, ip5xxx, fields); + + psy_cfg.fwnode = dev_fwnode(dev); psy_cfg.drv_data = ip5xxx; psy = devm_power_supply_register(dev, &ip5xxx_battery_desc, &psy_cfg); @@ -616,16 +860,17 @@ static int ip5xxx_power_probe(struct i2c_client *client) } static const struct of_device_id ip5xxx_power_of_match[] = { - { .compatible = "injoinic,ip5108" }, - { .compatible = "injoinic,ip5109" }, - { .compatible = "injoinic,ip5207" }, - { .compatible = "injoinic,ip5209" }, + { .compatible = "injoinic,ip5108", .data = &ip51xx_fields }, + { .compatible = "injoinic,ip5109", .data = &ip51xx_fields }, + { .compatible = "injoinic,ip5207", .data = &ip51xx_fields }, + { .compatible = "injoinic,ip5209", .data = &ip51xx_fields }, + { .compatible = "injoinic,ip5306", .data = &ip5306_fields }, { } }; MODULE_DEVICE_TABLE(of, ip5xxx_power_of_match); static struct i2c_driver ip5xxx_power_driver = { - .probe_new = ip5xxx_power_probe, + .probe = ip5xxx_power_probe, .driver = { .name = "ip5xxx-power", .of_match_table = ip5xxx_power_of_match, diff --git a/drivers/power/supply/ipaq_micro_battery.c b/drivers/power/supply/ipaq_micro_battery.c index 192d9db0fb00..ff8573a5ca6d 100644 --- a/drivers/power/supply/ipaq_micro_battery.c +++ b/drivers/power/supply/ipaq_micro_battery.c @@ -232,7 +232,8 @@ 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 = alloc_workqueue("ipaq-battery-wq", + WQ_MEM_RECLAIM | WQ_PERCPU, 0); if (!mb->wq) return -ENOMEM; @@ -265,7 +266,7 @@ batt_err: return ret; } -static int micro_batt_remove(struct platform_device *pdev) +static void micro_batt_remove(struct platform_device *pdev) { struct micro_battery *mb = platform_get_drvdata(pdev); @@ -274,8 +275,6 @@ static int micro_batt_remove(struct platform_device *pdev) power_supply_unregister(micro_batt_power); cancel_delayed_work_sync(&mb->update); destroy_workqueue(mb->wq); - - return 0; } static int __maybe_unused micro_batt_suspend(struct device *dev) diff --git a/drivers/power/supply/isp1704_charger.c b/drivers/power/supply/isp1704_charger.c index b6efc454e4f0..237912a92272 100644 --- a/drivers/power/supply/isp1704_charger.c +++ b/drivers/power/supply/isp1704_charger.c @@ -477,15 +477,13 @@ fail0: return ret; } -static int isp1704_charger_remove(struct platform_device *pdev) +static void isp1704_charger_remove(struct platform_device *pdev) { struct isp1704_charger *isp = platform_get_drvdata(pdev); usb_unregister_notifier(isp->phy, &isp->nb); power_supply_unregister(isp->psy); isp1704_charger_set_power(isp, 0); - - return 0; } #ifdef CONFIG_OF diff --git a/drivers/power/supply/lego_ev3_battery.c b/drivers/power/supply/lego_ev3_battery.c index ccb00be38e2c..28454de05761 100644 --- a/drivers/power/supply/lego_ev3_battery.c +++ b/drivers/power/supply/lego_ev3_battery.c @@ -20,9 +20,10 @@ #include <linux/iio/types.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_device.h> +#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/lenovo_yoga_c630_battery.c b/drivers/power/supply/lenovo_yoga_c630_battery.c new file mode 100644 index 000000000000..7a6c8af9e8c2 --- /dev/null +++ b/drivers/power/supply/lenovo_yoga_c630_battery.c @@ -0,0 +1,498 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022-2024, Linaro Ltd + * Authors: + * Bjorn Andersson + * Dmitry Baryshkov + */ +#include <linux/auxiliary_bus.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/power_supply.h> +#include <linux/platform_data/lenovo-yoga-c630.h> + +struct yoga_c630_psy { + struct yoga_c630_ec *ec; + struct device *dev; + struct fwnode_handle *fwnode; + struct notifier_block nb; + + /* guards all battery properties and registration of power supplies */ + struct mutex lock; + + struct power_supply *adp_psy; + struct power_supply *bat_psy; + + unsigned long last_status_update; + + bool adapter_online; + + bool unit_mA; + + bool bat_present; + unsigned int bat_status; + unsigned int design_capacity; + unsigned int design_voltage; + unsigned int full_charge_capacity; + + unsigned int capacity_now; + unsigned int voltage_now; + + int current_now; + int rate_now; +}; + +#define LENOVO_EC_CACHE_TIME (10 * HZ) + +#define LENOVO_EC_ADPT_STATUS 0xa3 +#define LENOVO_EC_ADPT_STATUS_PRESENT BIT(7) +#define LENOVO_EC_BAT_ATTRIBUTES 0xc0 +#define LENOVO_EC_BAT_ATTRIBUTES_UNIT_IS_MA BIT(1) +#define LENOVO_EC_BAT_STATUS 0xc1 +#define LENOVO_EC_BAT_STATUS_DISCHARGING BIT(0) +#define LENOVO_EC_BAT_STATUS_CHARGING BIT(1) +#define LENOVO_EC_BAT_REMAIN_CAPACITY 0xc2 +#define LENOVO_EC_BAT_VOLTAGE 0xc6 +#define LENOVO_EC_BAT_DESIGN_VOLTAGE 0xc8 +#define LENOVO_EC_BAT_DESIGN_CAPACITY 0xca +#define LENOVO_EC_BAT_FULL_CAPACITY 0xcc +#define LENOVO_EC_BAT_CURRENT 0xd2 +#define LENOVO_EC_BAT_FULL_FACTORY 0xd6 +#define LENOVO_EC_BAT_PRESENT 0xda +#define LENOVO_EC_BAT_PRESENT_IS_PRESENT BIT(0) +#define LENOVO_EC_BAT_FULL_REGISTER 0xdb +#define LENOVO_EC_BAT_FULL_REGISTER_IS_FACTORY BIT(0) + +static int yoga_c630_psy_update_bat_info(struct yoga_c630_psy *ecbat) +{ + struct yoga_c630_ec *ec = ecbat->ec; + int val; + + lockdep_assert_held(&ecbat->lock); + + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_PRESENT); + if (val < 0) + return val; + ecbat->bat_present = !!(val & LENOVO_EC_BAT_PRESENT_IS_PRESENT); + if (!ecbat->bat_present) + return val; + + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_ATTRIBUTES); + if (val < 0) + return val; + ecbat->unit_mA = val & LENOVO_EC_BAT_ATTRIBUTES_UNIT_IS_MA; + + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_DESIGN_CAPACITY); + if (val < 0) + return val; + ecbat->design_capacity = val * 1000; + + /* + * DSDT has delays after most of EC reads in these methods. + * Having no documentation for the EC we have to follow and sleep here. + */ + msleep(50); + + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_DESIGN_VOLTAGE); + if (val < 0) + return val; + ecbat->design_voltage = val; + + msleep(50); + + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_FULL_REGISTER); + if (val < 0) + return val; + val = yoga_c630_ec_read16(ec, + val & LENOVO_EC_BAT_FULL_REGISTER_IS_FACTORY ? + LENOVO_EC_BAT_FULL_FACTORY : + LENOVO_EC_BAT_FULL_CAPACITY); + if (val < 0) + return val; + + ecbat->full_charge_capacity = val * 1000; + + if (!ecbat->unit_mA) { + ecbat->design_capacity *= 10; + ecbat->full_charge_capacity *= 10; + } + + return 0; +} + +static int yoga_c630_psy_maybe_update_bat_status(struct yoga_c630_psy *ecbat) +{ + struct yoga_c630_ec *ec = ecbat->ec; + int current_mA; + int val; + + guard(mutex)(&ecbat->lock); + if (time_before(jiffies, ecbat->last_status_update + LENOVO_EC_CACHE_TIME)) + return 0; + + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_STATUS); + if (val < 0) + return val; + ecbat->bat_status = val; + + msleep(50); + + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_REMAIN_CAPACITY); + if (val < 0) + return val; + ecbat->capacity_now = val * 1000; + + msleep(50); + + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_VOLTAGE); + if (val < 0) + return val; + ecbat->voltage_now = val * 1000; + + msleep(50); + + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_CURRENT); + if (val < 0) + return val; + current_mA = sign_extend32(val, 15); + ecbat->current_now = current_mA * 1000; + ecbat->rate_now = current_mA * (ecbat->voltage_now / 1000); + + msleep(50); + + if (!ecbat->unit_mA) + ecbat->capacity_now *= 10; + + ecbat->last_status_update = jiffies; + + return 0; +} + +static int yoga_c630_psy_update_adapter_status(struct yoga_c630_psy *ecbat) +{ + struct yoga_c630_ec *ec = ecbat->ec; + int val; + + guard(mutex)(&ecbat->lock); + + val = yoga_c630_ec_read8(ec, LENOVO_EC_ADPT_STATUS); + if (val < 0) + return val; + + ecbat->adapter_online = !!(val & LENOVO_EC_ADPT_STATUS_PRESENT); + + return 0; +} + +static bool yoga_c630_psy_is_charged(struct yoga_c630_psy *ecbat) +{ + if (ecbat->bat_status != 0) + return false; + + if (ecbat->full_charge_capacity <= ecbat->capacity_now) + return true; + + if (ecbat->design_capacity <= ecbat->capacity_now) + return true; + + return false; +} + +static int yoga_c630_psy_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct yoga_c630_psy *ecbat = power_supply_get_drvdata(psy); + int rc = 0; + + if (!ecbat->bat_present && psp != POWER_SUPPLY_PROP_PRESENT) + return -ENODEV; + + rc = yoga_c630_psy_maybe_update_bat_status(ecbat); + if (rc) + return rc; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (ecbat->bat_status & LENOVO_EC_BAT_STATUS_DISCHARGING) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (ecbat->bat_status & LENOVO_EC_BAT_STATUS_CHARGING) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (yoga_c630_psy_is_charged(ecbat)) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = ecbat->bat_present; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = ecbat->design_voltage; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = ecbat->design_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = ecbat->full_charge_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_ENERGY_NOW: + val->intval = ecbat->capacity_now; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ecbat->current_now; + break; + case POWER_SUPPLY_PROP_POWER_NOW: + val->intval = ecbat->rate_now; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = ecbat->voltage_now; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "PABAS0241231"; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "Compal"; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static enum power_supply_property yoga_c630_psy_bat_mA_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SCOPE, +}; + +static enum power_supply_property yoga_c630_psy_bat_mWh_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SCOPE, +}; + +static const struct power_supply_desc yoga_c630_psy_bat_psy_desc_mA = { + .name = "yoga-c630-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = yoga_c630_psy_bat_mA_properties, + .num_properties = ARRAY_SIZE(yoga_c630_psy_bat_mA_properties), + .get_property = yoga_c630_psy_bat_get_property, +}; + +static const struct power_supply_desc yoga_c630_psy_bat_psy_desc_mWh = { + .name = "yoga-c630-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = yoga_c630_psy_bat_mWh_properties, + .num_properties = ARRAY_SIZE(yoga_c630_psy_bat_mWh_properties), + .get_property = yoga_c630_psy_bat_get_property, +}; + +static int yoga_c630_psy_adpt_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct yoga_c630_psy *ecbat = power_supply_get_drvdata(psy); + int ret = 0; + + ret = yoga_c630_psy_update_adapter_status(ecbat); + if (ret < 0) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ecbat->adapter_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 yoga_c630_psy_adpt_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_USB_TYPE, +}; + +static const struct power_supply_desc yoga_c630_psy_adpt_psy_desc = { + .name = "yoga-c630-adapter", + .type = POWER_SUPPLY_TYPE_USB, + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_C), + .properties = yoga_c630_psy_adpt_properties, + .num_properties = ARRAY_SIZE(yoga_c630_psy_adpt_properties), + .get_property = yoga_c630_psy_adpt_get_property, +}; + +static int yoga_c630_psy_register_bat_psy(struct yoga_c630_psy *ecbat) +{ + struct power_supply_config bat_cfg = {}; + + bat_cfg.drv_data = ecbat; + bat_cfg.fwnode = ecbat->fwnode; + bat_cfg.no_wakeup_source = true; + ecbat->bat_psy = power_supply_register(ecbat->dev, + ecbat->unit_mA ? + &yoga_c630_psy_bat_psy_desc_mA : + &yoga_c630_psy_bat_psy_desc_mWh, + &bat_cfg); + if (IS_ERR(ecbat->bat_psy)) { + dev_err(ecbat->dev, "failed to register battery supply\n"); + return PTR_ERR(ecbat->bat_psy); + } + + return 0; +} + +static void yoga_c630_ec_refresh_bat_info(struct yoga_c630_psy *ecbat) +{ + bool current_unit; + + guard(mutex)(&ecbat->lock); + + current_unit = ecbat->unit_mA; + + yoga_c630_psy_update_bat_info(ecbat); + + if (current_unit != ecbat->unit_mA) { + power_supply_unregister(ecbat->bat_psy); + yoga_c630_psy_register_bat_psy(ecbat); + } +} + +static int yoga_c630_psy_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct yoga_c630_psy *ecbat = container_of(nb, struct yoga_c630_psy, nb); + + switch (action) { + case LENOVO_EC_EVENT_BAT_INFO: + yoga_c630_ec_refresh_bat_info(ecbat); + break; + case LENOVO_EC_EVENT_BAT_ADPT_STATUS: + power_supply_changed(ecbat->adp_psy); + fallthrough; + case LENOVO_EC_EVENT_BAT_STATUS: + power_supply_changed(ecbat->bat_psy); + break; + } + + return NOTIFY_OK; +} + +static int yoga_c630_psy_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct yoga_c630_ec *ec = adev->dev.platform_data; + struct power_supply_config adp_cfg = {}; + struct device *dev = &adev->dev; + struct yoga_c630_psy *ecbat; + int ret; + + ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL); + if (!ecbat) + return -ENOMEM; + + ecbat->ec = ec; + ecbat->dev = dev; + mutex_init(&ecbat->lock); + ecbat->fwnode = adev->dev.parent->fwnode; + ecbat->nb.notifier_call = yoga_c630_psy_notify; + + auxiliary_set_drvdata(adev, ecbat); + + adp_cfg.drv_data = ecbat; + adp_cfg.fwnode = ecbat->fwnode; + adp_cfg.supplied_to = (char **)&yoga_c630_psy_bat_psy_desc_mA.name; + adp_cfg.num_supplicants = 1; + adp_cfg.no_wakeup_source = true; + ecbat->adp_psy = devm_power_supply_register(dev, &yoga_c630_psy_adpt_psy_desc, &adp_cfg); + if (IS_ERR(ecbat->adp_psy)) { + dev_err(dev, "failed to register AC adapter supply\n"); + return PTR_ERR(ecbat->adp_psy); + } + + scoped_guard(mutex, &ecbat->lock) { + ret = yoga_c630_psy_update_bat_info(ecbat); + if (ret) + goto err_unreg_bat; + + ret = yoga_c630_psy_register_bat_psy(ecbat); + if (ret) + goto err_unreg_bat; + } + + ret = yoga_c630_ec_register_notify(ecbat->ec, &ecbat->nb); + if (ret) + goto err_unreg_bat; + + return 0; + +err_unreg_bat: + power_supply_unregister(ecbat->bat_psy); + return ret; +} + +static void yoga_c630_psy_remove(struct auxiliary_device *adev) +{ + struct yoga_c630_psy *ecbat = auxiliary_get_drvdata(adev); + + yoga_c630_ec_unregister_notify(ecbat->ec, &ecbat->nb); + power_supply_unregister(ecbat->bat_psy); +} + +static const struct auxiliary_device_id yoga_c630_psy_id_table[] = { + { .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_PSY, }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, yoga_c630_psy_id_table); + +static struct auxiliary_driver yoga_c630_psy_driver = { + .name = YOGA_C630_DEV_PSY, + .id_table = yoga_c630_psy_id_table, + .probe = yoga_c630_psy_probe, + .remove = yoga_c630_psy_remove, +}; + +module_auxiliary_driver(yoga_c630_psy_driver); + +MODULE_DESCRIPTION("Lenovo Yoga C630 psy"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/lp8727_charger.c b/drivers/power/supply/lp8727_charger.c index e6c21377d53c..4186fcd37512 100644 --- a/drivers/power/supply/lp8727_charger.c +++ b/drivers/power/supply/lp8727_charger.c @@ -453,39 +453,20 @@ static int lp8727_register_psy(struct lp8727_chg *pchg) psy_cfg.supplied_to = battery_supplied_to; psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); - psy->ac = power_supply_register(pchg->dev, &lp8727_ac_desc, &psy_cfg); + psy->ac = devm_power_supply_register(pchg->dev, &lp8727_ac_desc, &psy_cfg); if (IS_ERR(psy->ac)) - goto err_psy_ac; + return -EPERM; - psy->usb = power_supply_register(pchg->dev, &lp8727_usb_desc, - &psy_cfg); + psy->usb = devm_power_supply_register(pchg->dev, &lp8727_usb_desc, + &psy_cfg); if (IS_ERR(psy->usb)) - goto err_psy_usb; + return -EPERM; - psy->batt = power_supply_register(pchg->dev, &lp8727_batt_desc, NULL); + psy->batt = devm_power_supply_register(pchg->dev, &lp8727_batt_desc, NULL); if (IS_ERR(psy->batt)) - goto err_psy_batt; + return -EPERM; return 0; - -err_psy_batt: - power_supply_unregister(psy->usb); -err_psy_usb: - power_supply_unregister(psy->ac); -err_psy_ac: - return -EPERM; -} - -static void lp8727_unregister_psy(struct lp8727_chg *pchg) -{ - struct lp8727_psy *psy = pchg->psy; - - if (!psy) - return; - - power_supply_unregister(psy->ac); - power_supply_unregister(psy->usb); - power_supply_unregister(psy->batt); } #ifdef CONFIG_OF @@ -583,7 +564,6 @@ static int lp8727_probe(struct i2c_client *cl) ret = lp8727_setup_irq(pchg); if (ret) { dev_err(pchg->dev, "irq handler err: %d", ret); - lp8727_unregister_psy(pchg); return ret; } @@ -595,17 +575,16 @@ static void lp8727_remove(struct i2c_client *cl) struct lp8727_chg *pchg = i2c_get_clientdata(cl); lp8727_release_irq(pchg); - lp8727_unregister_psy(pchg); } -static const struct of_device_id lp8727_dt_ids[] = { +static const struct of_device_id lp8727_dt_ids[] __maybe_unused = { { .compatible = "ti,lp8727", }, { } }; MODULE_DEVICE_TABLE(of, lp8727_dt_ids); static const struct i2c_device_id lp8727_ids[] = { - {"lp8727", 0}, + { "lp8727" }, { } }; MODULE_DEVICE_TABLE(i2c, lp8727_ids); @@ -615,7 +594,7 @@ static struct i2c_driver lp8727_driver = { .name = "lp8727", .of_match_table = of_match_ptr(lp8727_dt_ids), }, - .probe_new = lp8727_probe, + .probe = lp8727_probe, .remove = lp8727_remove, .id_table = lp8727_ids, }; diff --git a/drivers/power/supply/lp8788-charger.c b/drivers/power/supply/lp8788-charger.c index f5f47a0aa1e3..f0a680c155c4 100644 --- a/drivers/power/supply/lp8788-charger.c +++ b/drivers/power/supply/lp8788-charger.c @@ -406,12 +406,6 @@ static const struct power_supply_desc lp8788_psy_battery_desc = { .get_property = lp8788_battery_get_property, }; -static void lp8788_psy_unregister(struct lp8788_charger *pchg) -{ - power_supply_unregister(pchg->battery); - power_supply_unregister(pchg->charger); -} - static void lp8788_charger_event(struct work_struct *work) { struct lp8788_charger *pchg = @@ -602,7 +596,7 @@ static ssize_t lp8788_show_charger_status(struct device *dev, lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; - return scnprintf(buf, PAGE_SIZE, "%s\n", desc[state]); + return sysfs_emit(buf, "%s\n", desc[state]); } static ssize_t lp8788_show_eoc_time(struct device *dev, @@ -618,8 +612,7 @@ static ssize_t lp8788_show_eoc_time(struct device *dev, lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); val = (val & LP8788_CHG_EOC_TIME_M) >> LP8788_CHG_EOC_TIME_S; - return scnprintf(buf, PAGE_SIZE, "End Of Charge Time: %s\n", - stime[val]); + return sysfs_emit(buf, "End Of Charge Time: %s\n", stime[val]); } static ssize_t lp8788_show_eoc_level(struct device *dev, @@ -642,7 +635,7 @@ static ssize_t lp8788_show_eoc_level(struct device *dev, val = (val & LP8788_CHG_EOC_LEVEL_M) >> LP8788_CHG_EOC_LEVEL_S; level = mode ? abs_level[val] : relative_level[val]; - return scnprintf(buf, PAGE_SIZE, "End Of Charge Level: %s\n", level); + return sysfs_emit(buf, "End Of Charge Level: %s\n", level); } static DEVICE_ATTR(charger_status, S_IRUSR, lp8788_show_charger_status, NULL); @@ -667,18 +660,16 @@ static int lp8788_psy_register(struct platform_device *pdev, charger_cfg.supplied_to = battery_supplied_to; charger_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); - pchg->charger = power_supply_register(&pdev->dev, - &lp8788_psy_charger_desc, - &charger_cfg); + pchg->charger = devm_power_supply_register(&pdev->dev, + &lp8788_psy_charger_desc, + &charger_cfg); if (IS_ERR(pchg->charger)) return -EPERM; - pchg->battery = power_supply_register(&pdev->dev, - &lp8788_psy_battery_desc, NULL); - if (IS_ERR(pchg->battery)) { - power_supply_unregister(pchg->charger); + pchg->battery = devm_power_supply_register(&pdev->dev, + &lp8788_psy_battery_desc, NULL); + if (IS_ERR(pchg->battery)) return -EPERM; - } return 0; } @@ -715,15 +706,12 @@ static int lp8788_charger_probe(struct platform_device *pdev) return 0; } -static int lp8788_charger_remove(struct platform_device *pdev) +static void lp8788_charger_remove(struct platform_device *pdev) { struct lp8788_charger *pchg = platform_get_drvdata(pdev); flush_work(&pchg->charger_work); lp8788_irq_unregister(pdev, pchg); - lp8788_psy_unregister(pchg); - - return 0; } static struct platform_driver lp8788_charger_driver = { 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/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c index d3fb42825983..a1ddc4b060ce 100644 --- a/drivers/power/supply/ltc2941-battery-gauge.c +++ b/drivers/power/supply/ltc2941-battery-gauge.c @@ -11,7 +11,7 @@ #include <linux/devm-helpers.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/types.h> #include <linux/errno.h> #include <linux/swab.h> @@ -635,7 +635,7 @@ static struct i2c_driver ltc294x_driver = { .of_match_table = ltc294x_i2c_of_match, .pm = LTC294X_PM_OPS, }, - .probe_new = ltc294x_i2c_probe, + .probe = ltc294x_i2c_probe, .shutdown = ltc294x_i2c_shutdown, .id_table = ltc294x_i2c_id, }; diff --git a/drivers/power/supply/ltc4162-l-charger.c b/drivers/power/supply/ltc4162-l-charger.c index db2bb5233570..9dd74fa9552d 100644 --- a/drivers/power/supply/ltc4162-l-charger.c +++ b/drivers/power/supply/ltc4162-l-charger.c @@ -1,12 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Driver for Analog Devices (Linear Technology) LTC4162-L charger IC. + * Driver for Analog Devices (Linear Technology) + * LTC4162-L 35V/3.2A Multi-Cell Lithium-Ion Step-Down Battery Charger + * LTC4162-F 35V/3.2A Multi-Cell LiFePO4 Step-Down Battery Charger + * LTC4162-S 35V/3.2A Lead-Acid Step-Down Battery Charger + * LTC4015 35V/3.2A Multichemistry Buck Battery Charger Controller * Copyright (C) 2020, Topic Embedded Products */ +#include <linux/bitfield.h> #include <linux/module.h> #include <linux/delay.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/pm_runtime.h> #include <linux/power_supply.h> #include <linux/i2c.h> @@ -47,6 +52,20 @@ #define LTC4162L_VBAT_FILT 0x47 #define LTC4162L_INPUT_UNDERVOLTAGE_DAC 0x4B +#define LTC4162L_CHEM_MASK GENMASK(11, 8) + +enum ltc4162_chem { + ltc4162_lad, + ltc4162_l42, + ltc4162_l41, + ltc4162_l40, + ltc4162_fad, + ltc4162_ffs, + ltc4162_fst, + ltc4162_sst = 8, + ltc4162_sad, +}; + /* Enumeration as in datasheet. Individual bits are mutually exclusive. */ enum ltc4162l_state { battery_detection = 2048, @@ -75,10 +94,28 @@ enum ltc4162l_charge_status { /* Magic number to write to ARM_SHIP_MODE register */ #define LTC4162L_ARM_SHIP_MODE_MAGIC 21325 +struct ltc4162l_info; + +struct ltc4162l_chip_info { + const char *name; + int (*get_vbat)(struct ltc4162l_info *info, unsigned int reg, + union power_supply_propval *val); + int (*get_vcharge)(struct ltc4162l_info *info, unsigned int reg, + union power_supply_propval *val); + int (*set_vcharge)(struct ltc4162l_info *info, unsigned int reg, + unsigned int value); + int (*get_die_temp)(struct ltc4162l_info *info, + union power_supply_propval *val); + unsigned int ibat_resolution_pv; + unsigned int vin_resolution_uv; + u8 telemetry_mask; +}; + struct ltc4162l_info { struct i2c_client *client; struct regmap *regmap; struct power_supply *charger; + const struct ltc4162l_chip_info *chip_info; u32 rsnsb; /* Series resistor that sets charge current, microOhm */ u32 rsnsi; /* Series resistor to measure input current, microOhm */ u8 cell_count; /* Number of connected cells, 0 while unknown */ @@ -108,6 +145,18 @@ static u8 ltc4162l_get_cell_count(struct ltc4162l_info *info) return val; }; +static u8 ltc4162l_get_chem_type(struct ltc4162l_info *info) +{ + int ret; + unsigned int val; + + ret = regmap_read(info->regmap, LTC4162L_CHEM_CELLS_REG, &val); + if (ret) + return ret; + + return FIELD_GET(LTC4162L_CHEM_MASK, val); +}; + /* Convert enum value to POWER_SUPPLY_STATUS value */ static int ltc4162l_state_decode(enum ltc4162l_state value) { @@ -223,25 +272,83 @@ static int ltc4162l_get_vbat(struct ltc4162l_info *info, unsigned int reg, union power_supply_propval *val) { - unsigned int regval; + unsigned int regval, chem_type; int ret; ret = regmap_read(info->regmap, reg, ®val); if (ret) return ret; - /* cell_count × 192.4μV/LSB */ - regval *= 1924; - regval *= ltc4162l_get_cell_count(info); - regval /= 10; - val->intval = regval; + /* + * cell_count × scaling factor + * For ltc4162-s, it uses a cell_count value of 2 for each group of 3 + * physical (2V) cells, thus will return 2, 4, 6, 8 for 6V, 12V, 18V, + * and 24V respectively, and has to divide by 2 to multiply the scale + * factor by 1, 2, 3, or 4 to represent a 6V, 12V, 18V, or 24V battery + * respectively. + */ + chem_type = ltc4162l_get_chem_type(info); + switch (chem_type) { + case ltc4162_lad ... ltc4162_fst: + regval *= 1924; + regval *= ltc4162l_get_cell_count(info); + regval /= 10; + val->intval = regval; - return 0; + return 0; + case ltc4162_sst ... ltc4162_sad: + regval *= 3848; + regval *= ltc4162l_get_cell_count(info) / 2; + regval /= 10; + val->intval = regval; + + return 0; + default: + return -EINVAL; + } +} + +static int ltc4015_get_vbat(struct ltc4162l_info *info, + unsigned int reg, + union power_supply_propval *val) +{ + unsigned int regval, chem_type; + int ret; + + ret = regmap_read(info->regmap, reg, ®val); + if (ret) + return ret; + + /* + * cell count x scaling factor + * ltc4015 lead-acid fixed and lead-acid programmable corresponds to + * 0x7 and 0x8 chem respectively + */ + chem_type = ltc4162l_get_chem_type(info); + switch (chem_type) { + case ltc4162_lad ... ltc4162_fst: + regval *= 192264; + regval *= ltc4162l_get_cell_count(info); + regval /= 1000; + val->intval = regval; + + return 0; + case ltc4162_sst - 1 ... ltc4162_sad - 1: + regval *= 128176; + regval *= ltc4162l_get_cell_count(info); + regval /= 1000; + val->intval = regval; + + return 0; + default: + return -EINVAL; + } } static int ltc4162l_get_ibat(struct ltc4162l_info *info, union power_supply_propval *val) { + const struct ltc4162l_chip_info *chip_info = info->chip_info; unsigned int regval; int ret; @@ -249,9 +356,8 @@ static int ltc4162l_get_ibat(struct ltc4162l_info *info, if (ret) return ret; - /* Signed 16-bit number, 1.466μV / RSNSB amperes/LSB. */ ret = (s16)(regval & 0xFFFF); - val->intval = 100 * mult_frac(ret, 14660, (int)info->rsnsb); + val->intval = mult_frac(ret, chip_info->ibat_resolution_pv, info->rsnsb); return 0; } @@ -260,6 +366,7 @@ static int ltc4162l_get_ibat(struct ltc4162l_info *info, static int ltc4162l_get_input_voltage(struct ltc4162l_info *info, union power_supply_propval *val) { + const struct ltc4162l_chip_info *chip_info = info->chip_info; unsigned int regval; int ret; @@ -267,8 +374,7 @@ static int ltc4162l_get_input_voltage(struct ltc4162l_info *info, if (ret) return ret; - /* 1.649mV/LSB */ - val->intval = regval * 1694; + val->intval = regval * chip_info->vin_resolution_uv; return 0; } @@ -276,6 +382,7 @@ static int ltc4162l_get_input_voltage(struct ltc4162l_info *info, static int ltc4162l_get_input_current(struct ltc4162l_info *info, union power_supply_propval *val) { + const struct ltc4162l_chip_info *chip_info = info->chip_info; unsigned int regval; int ret; @@ -283,11 +390,9 @@ static int ltc4162l_get_input_current(struct ltc4162l_info *info, if (ret) return ret; - /* Signed 16-bit number, 1.466μV / RSNSI amperes/LSB. */ ret = (s16)(regval & 0xFFFF); - ret *= 14660; + ret *= chip_info->ibat_resolution_pv; ret /= info->rsnsi; - ret *= 100; val->intval = ret; @@ -305,7 +410,7 @@ static int ltc4162l_get_icharge(struct ltc4162l_info *info, if (ret) return ret; - regval &= BIT(6) - 1; /* Only the lower 5 bits */ + regval &= GENMASK(5, 0); /* The charge current servo level: (icharge_dac + 1) × 1mV/RSNSB */ ++regval; @@ -336,7 +441,7 @@ static int ltc4162l_get_vcharge(struct ltc4162l_info *info, unsigned int reg, union power_supply_propval *val) { - unsigned int regval; + unsigned int regval, chem_type; int ret; u32 voltage; @@ -344,41 +449,181 @@ static int ltc4162l_get_vcharge(struct ltc4162l_info *info, if (ret) return ret; - regval &= BIT(6) - 1; /* Only the lower 5 bits */ + regval &= GENMASK(5, 0); /* * charge voltage setting can be computed from - * cell_count × (vcharge_setting × 12.5mV + 3.8125V) - * where vcharge_setting ranges from 0 to 31 (4.2V max). + * cell_count × (vcharge_setting × a + b) + * where vcharge_setting ranges from 0 to c (d). + * for ltc4162l: a = 12.5mV , b = 3.8125V, c = 31, d = 4.2Vmax + * for ltc4162f: a = 12.5mV , b = 3.4125V, c = 31, d = 3.8Vmax + * + * for ltc4162s, the charge voltage setting can be computed from + * N x (vcharge_setting x 28.571mV + 6.0V) + * where N is 1, 2, 3, or 4 for 6V, 12V, 18V, or 24V battery respectively, + * and vcharge_setting ranges from 0 to 31 */ - voltage = 3812500 + (regval * 12500); - voltage *= ltc4162l_get_cell_count(info); - val->intval = voltage; + chem_type = ltc4162l_get_chem_type(info); + switch (chem_type) { + case ltc4162_lad ... ltc4162_l40: + voltage = 3812500 + (regval * 12500); + voltage *= ltc4162l_get_cell_count(info); + val->intval = voltage; - return 0; + return 0; + case ltc4162_fad ... ltc4162_fst: + voltage = 3412500 + (regval * 12500); + voltage *= ltc4162l_get_cell_count(info); + val->intval = voltage; + + return 0; + case ltc4162_sst ... ltc4162_sad: + voltage = 6000000 + (regval * 28571); + voltage *= ltc4162l_get_cell_count(info) / 2; + val->intval = voltage; + + return 0; + default: + return -EINVAL; + } } -static int ltc4162l_set_vcharge(struct ltc4162l_info *info, - unsigned int reg, - unsigned int value) +static int ltc4015_get_vcharge(struct ltc4162l_info *info, + unsigned int reg, + union power_supply_propval *val) { - u8 cell_count = ltc4162l_get_cell_count(info); + unsigned int regval, chem_type; + int ret; + u32 voltage; + + ret = regmap_read(info->regmap, reg, ®val); + if (ret) + return ret; + + regval &= GENMASK(5, 0); + + /* + * charge voltage setting can be computed from: + * cell_count × (vcharge_setting × a + b) + * where vcharge_setting ranges from 0 to c (d). + * Li-Ion: a = 1/80V, b = 3.8125V, c = 31, d = 4.2Vmax + * LiFePO4: a = 1/80V, b = 3.4125V, c = 31, d = 3.8Vmax + * Lead Acid: a = 1/105V, b = 2V, c = 35, d = 2.6Vmax + */ + chem_type = ltc4162l_get_chem_type(info); + switch (chem_type) { + case ltc4162_lad ... ltc4162_l40: + voltage = 3812500 + (regval * 12500); + voltage *= ltc4162l_get_cell_count(info); + val->intval = voltage; + + return 0; + case ltc4162_fad ... ltc4162_fst: + voltage = 3412500 + (regval * 12500); + voltage *= ltc4162l_get_cell_count(info); + val->intval = voltage; + + return 0; + case ltc4162_sst - 1 ... ltc4162_sad - 1: + voltage = 2000000 + mult_frac(regval, 1000000, 105); + voltage *= ltc4162l_get_cell_count(info); + val->intval = voltage; - if (!cell_count) - return -EBUSY; /* Not available yet, try again later */ + return 0; + default: + return -EINVAL; + } +} +static int ltc4162l_vcharge(unsigned int base_voltage, + unsigned int scale_factor, + unsigned int range, + unsigned int value, + u8 cell_count) +{ value /= cell_count; - if (value < 3812500) + if (value < base_voltage) return -EINVAL; - value -= 3812500; - value /= 12500; + value -= base_voltage; + value /= scale_factor; - if (value > 31) + if (value > range) return -EINVAL; - return regmap_write(info->regmap, reg, value); + return value; +} + +static int ltc4162l_set_vcharge(struct ltc4162l_info *info, + unsigned int reg, + unsigned int value) +{ + unsigned int chem_type; + u8 cell_count; + + chem_type = ltc4162l_get_chem_type(info); + switch (chem_type) { + case ltc4162_lad ... ltc4162_l40: + cell_count = ltc4162l_get_cell_count(info); + if (!cell_count) + return -EBUSY; + + value = ltc4162l_vcharge(3812500, 12500, 31, value, cell_count); + return regmap_write(info->regmap, reg, value); + case ltc4162_fad ... ltc4162_fst: + cell_count = ltc4162l_get_cell_count(info); + if (!cell_count) + return -EBUSY; + + value = ltc4162l_vcharge(3412500, 12500, 31, value, cell_count); + return regmap_write(info->regmap, reg, value); + case ltc4162_sst ... ltc4162_sad: + cell_count = ltc4162l_get_cell_count(info) / 2; + if (!cell_count) + return -EBUSY; + + value = ltc4162l_vcharge(6000000, 28571, 31, value, cell_count); + return regmap_write(info->regmap, reg, value); + default: + return -EINVAL; + } +} + +static int ltc4015_set_vcharge(struct ltc4162l_info *info, + unsigned int reg, + unsigned int value) +{ + unsigned int chem_type; + u8 cell_count; + + chem_type = ltc4162l_get_chem_type(info); + switch (chem_type) { + case ltc4162_lad ... ltc4162_l40: + cell_count = ltc4162l_get_cell_count(info); + if (!cell_count) + return -EBUSY; + + value = ltc4162l_vcharge(3812500, 12500, 31, value, cell_count); + return regmap_write(info->regmap, reg, value); + case ltc4162_fad ... ltc4162_fst: + cell_count = ltc4162l_get_cell_count(info); + if (!cell_count) + return -EBUSY; + + value = ltc4162l_vcharge(3412500, 12500, 31, value, cell_count); + return regmap_write(info->regmap, reg, value); + case ltc4162_sst - 1 ... ltc4162_sad - 1: + cell_count = ltc4162l_get_cell_count(info); + if (!cell_count) + return -EBUSY; + + value = ltc4162l_vcharge(2000000, 1000000 / 105, 35, + value, cell_count); + return regmap_write(info->regmap, reg, value); + default: + return -EINVAL; + } } static int ltc4162l_get_iin_limit_dac(struct ltc4162l_info *info, @@ -391,7 +636,7 @@ static int ltc4162l_get_iin_limit_dac(struct ltc4162l_info *info, if (ret) return ret; - regval &= BIT(6) - 1; /* Only 6 bits */ + regval &= GENMASK(5, 0); /* (iin_limit_dac + 1) × 500μV / RSNSI */ ++regval; @@ -437,9 +682,30 @@ static int ltc4162l_get_die_temp(struct ltc4162l_info *info, return 0; } +static int ltc4015_get_die_temp(struct ltc4162l_info *info, + union power_supply_propval *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(info->regmap, LTC4162L_DIE_TEMPERATURE, ®val); + if (ret) + return ret; + + /* (die_temp - 12010) / 45.6°C */ + ret = (s16)(regval & 0xFFFF); + ret -= 12010; + ret *= 1000; + ret /= 456; + val->intval = ret; + + return 0; +} + static int ltc4162l_get_term_current(struct ltc4162l_info *info, union power_supply_propval *val) { + const struct ltc4162l_chip_info *chip_info = info->chip_info; unsigned int regval; int ret; @@ -457,10 +723,9 @@ static int ltc4162l_get_term_current(struct ltc4162l_info *info, if (ret) return ret; - /* 1.466μV / RSNSB amperes/LSB */ - regval *= 14660u; + regval *= chip_info->ibat_resolution_pv; regval /= info->rsnsb; - val->intval = 100 * regval; + val->intval = regval; return 0; } @@ -525,7 +790,7 @@ static ssize_t charge_status_show(struct device *dev, } } - return sprintf(buf, "%s\n", result); + return sysfs_emit(buf, "%s\n", result); } static DEVICE_ATTR_RO(charge_status); @@ -534,14 +799,15 @@ static ssize_t vbat_show(struct device *dev, { struct power_supply *psy = to_power_supply(dev); struct ltc4162l_info *info = power_supply_get_drvdata(psy); + const struct ltc4162l_chip_info *chip_info = info->chip_info; union power_supply_propval val; int ret; - ret = ltc4162l_get_vbat(info, LTC4162L_VBAT, &val); + ret = chip_info->get_vbat(info, LTC4162L_VBAT, &val); if (ret) return ret; - return sprintf(buf, "%d\n", val.intval); + return sysfs_emit(buf, "%d\n", val.intval); } static DEVICE_ATTR_RO(vbat); @@ -550,14 +816,15 @@ static ssize_t vbat_avg_show(struct device *dev, { struct power_supply *psy = to_power_supply(dev); struct ltc4162l_info *info = power_supply_get_drvdata(psy); + const struct ltc4162l_chip_info *chip_info = info->chip_info; union power_supply_propval val; int ret; - ret = ltc4162l_get_vbat(info, LTC4162L_VBAT_FILT, &val); + ret = chip_info->get_vbat(info, LTC4162L_VBAT_FILT, &val); if (ret) return ret; - return sprintf(buf, "%d\n", val.intval); + return sysfs_emit(buf, "%d\n", val.intval); } static DEVICE_ATTR_RO(vbat_avg); @@ -573,7 +840,7 @@ static ssize_t ibat_show(struct device *dev, if (ret) return ret; - return sprintf(buf, "%d\n", val.intval); + return sysfs_emit(buf, "%d\n", val.intval); } static DEVICE_ATTR_RO(ibat); @@ -589,7 +856,8 @@ static ssize_t force_telemetry_show(struct device *dev, if (ret) return ret; - return sprintf(buf, "%u\n", regval & BIT(2) ? 1 : 0); + return sysfs_emit(buf, "%u\n", regval & + info->chip_info->telemetry_mask ? 1 : 0); } static ssize_t force_telemetry_store(struct device *dev, @@ -607,7 +875,8 @@ static ssize_t force_telemetry_store(struct device *dev, return ret; ret = regmap_update_bits(info->regmap, LTC4162L_CONFIG_BITS_REG, - BIT(2), value ? BIT(2) : 0); + info->chip_info->telemetry_mask, + value ? info->chip_info->telemetry_mask : 0); if (ret < 0) return ret; @@ -628,7 +897,7 @@ static ssize_t arm_ship_mode_show(struct device *dev, if (ret) return ret; - return sprintf(buf, "%u\n", + return sysfs_emit(buf, "%u\n", regval == LTC4162L_ARM_SHIP_MODE_MAGIC ? 1 : 0); } @@ -681,6 +950,7 @@ static int ltc4162l_get_property(struct power_supply *psy, union power_supply_propval *val) { struct ltc4162l_info *info = power_supply_get_drvdata(psy); + const struct ltc4162l_chip_info *chip_info = info->chip_info; switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -702,15 +972,13 @@ static int ltc4162l_get_property(struct power_supply *psy, return ltc4162l_get_icharge(info, LTC4162L_CHARGE_CURRENT_SETTING, val); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - return ltc4162l_get_vcharge(info, - LTC4162L_VCHARGE_DAC, val); + return chip_info->get_vcharge(info, LTC4162L_VCHARGE_DAC, val); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: - return ltc4162l_get_vcharge(info, - LTC4162L_VCHARGE_SETTING, val); + return chip_info->get_vcharge(info, LTC4162L_VCHARGE_SETTING, val); case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return ltc4162l_get_iin_limit_dac(info, val); case POWER_SUPPLY_PROP_TEMP: - return ltc4162l_get_die_temp(info, val); + return chip_info->get_die_temp(info, val); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return ltc4162l_get_term_current(info, val); default: @@ -772,7 +1040,6 @@ static enum power_supply_property ltc4162l_properties[] = { }; static const struct power_supply_desc ltc4162l_desc = { - .name = "ltc4162-l", .type = POWER_SUPPLY_TYPE_MAINS, .properties = ltc4162l_properties, .num_properties = ARRAY_SIZE(ltc4162l_properties), @@ -781,6 +1048,50 @@ static const struct power_supply_desc ltc4162l_desc = { .property_is_writeable = ltc4162l_property_is_writeable, }; +static const struct ltc4162l_chip_info ltc4162l_chip_info = { + .name = "ltc4162-l", + .get_vbat = ltc4162l_get_vbat, + .get_vcharge = ltc4162l_get_vcharge, + .set_vcharge = ltc4162l_set_vcharge, + .get_die_temp = ltc4162l_get_die_temp, + .ibat_resolution_pv = 1466000, + .vin_resolution_uv = 1649, + .telemetry_mask = BIT(2), +}; + +static const struct ltc4162l_chip_info ltc4162f_chip_info = { + .name = "ltc4162-f", + .get_vbat = ltc4162l_get_vbat, + .get_vcharge = ltc4162l_get_vcharge, + .set_vcharge = ltc4162l_set_vcharge, + .get_die_temp = ltc4162l_get_die_temp, + .ibat_resolution_pv = 1466000, + .vin_resolution_uv = 1649, + .telemetry_mask = BIT(2), +}; + +static const struct ltc4162l_chip_info ltc4162s_chip_info = { + .name = "ltc4162-s", + .get_vbat = ltc4162l_get_vbat, + .get_vcharge = ltc4162l_get_vcharge, + .set_vcharge = ltc4162l_set_vcharge, + .get_die_temp = ltc4162l_get_die_temp, + .ibat_resolution_pv = 1466000, + .vin_resolution_uv = 1649, + .telemetry_mask = BIT(2), +}; + +static const struct ltc4162l_chip_info ltc4015_chip_info = { + .name = "ltc4015", + .get_vbat = ltc4015_get_vbat, + .get_vcharge = ltc4015_get_vcharge, + .set_vcharge = ltc4015_set_vcharge, + .get_die_temp = ltc4015_get_die_temp, + .ibat_resolution_pv = 1464870, + .vin_resolution_uv = 1648, + .telemetry_mask = BIT(4), +}; + static bool ltc4162l_is_writeable_reg(struct device *dev, unsigned int reg) { /* all registers up to this one are writeable */ @@ -808,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) @@ -825,6 +1136,8 @@ static int ltc4162l_probe(struct i2c_client *client) struct device *dev = &client->dev; struct ltc4162l_info *info; struct power_supply_config ltc4162l_config = {}; + struct power_supply_desc *desc; + const struct ltc4162l_chip_info *chip_info; u32 value; int ret; @@ -839,6 +1152,12 @@ static int ltc4162l_probe(struct i2c_client *client) info->client = client; i2c_set_clientdata(client, info); + chip_info = i2c_get_match_data(client); + if (!chip_info) + return -ENODEV; + + info->chip_info = chip_info; + info->regmap = devm_regmap_init_i2c(client, <c4162l_regmap_config); if (IS_ERR(info->regmap)) { dev_err(dev, "Failed to initialize register map\n"); @@ -866,12 +1185,19 @@ 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; - info->charger = devm_power_supply_register(dev, <c4162l_desc, - <c4162l_config); + /* Duplicate the default descriptor to set name based on chip_info. */ + desc = devm_kmemdup(dev, <c4162l_desc, + sizeof(struct power_supply_desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + desc->name = chip_info->name; + + info->charger = devm_power_supply_register(dev, desc, <c4162l_config); if (IS_ERR(info->charger)) { dev_err(dev, "Failed to register charger\n"); return PTR_ERR(info->charger); @@ -903,19 +1229,25 @@ static void ltc4162l_alert(struct i2c_client *client, } static const struct i2c_device_id ltc4162l_i2c_id_table[] = { - { "ltc4162-l", 0 }, - { }, + { "ltc4015", (kernel_ulong_t)<c4015_chip_info }, + { "ltc4162-f", (kernel_ulong_t)<c4162f_chip_info }, + { "ltc4162-l", (kernel_ulong_t)<c4162l_chip_info }, + { "ltc4162-s", (kernel_ulong_t)<c4162s_chip_info }, + { } }; MODULE_DEVICE_TABLE(i2c, ltc4162l_i2c_id_table); -static const struct of_device_id ltc4162l_of_match[] = { - { .compatible = "lltc,ltc4162-l", }, - { }, +static const struct of_device_id ltc4162l_of_match[] __maybe_unused = { + { .compatible = "lltc,ltc4015", .data = <c4015_chip_info }, + { .compatible = "lltc,ltc4162-f", .data = <c4162f_chip_info }, + { .compatible = "lltc,ltc4162-l", .data = <c4162l_chip_info }, + { .compatible = "lltc,ltc4162-s", .data = <c4162s_chip_info }, + { } }; MODULE_DEVICE_TABLE(of, ltc4162l_of_match); static struct i2c_driver ltc4162l_driver = { - .probe_new = ltc4162l_probe, + .probe = ltc4162l_probe, .alert = ltc4162l_alert, .id_table = ltc4162l_i2c_id_table, .driver = { diff --git a/drivers/power/supply/max14577_charger.c b/drivers/power/supply/max14577_charger.c index f244cd902eb9..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 */ @@ -532,7 +532,7 @@ static ssize_t show_fast_charge_timer(struct device *dev, break; } - return scnprintf(buf, PAGE_SIZE, "%u\n", val); + return sysfs_emit(buf, "%u\n", val); } static ssize_t store_fast_charge_timer(struct device *dev, @@ -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); @@ -586,8 +586,9 @@ static int max14577_charger_probe(struct platform_device *pdev) } psy_cfg.drv_data = chg; - chg->charger = power_supply_register(&pdev->dev, &max14577_charger_desc, - &psy_cfg); + chg->charger = devm_power_supply_register(&pdev->dev, + &max14577_charger_desc, + &psy_cfg); if (IS_ERR(chg->charger)) { dev_err(&pdev->dev, "failed: power supply register\n"); ret = PTR_ERR(chg->charger); @@ -606,14 +607,9 @@ err: return ret; } -static int max14577_charger_remove(struct platform_device *pdev) +static void max14577_charger_remove(struct platform_device *pdev) { - struct max14577_charger *chg = platform_get_drvdata(pdev); - device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); - power_supply_unregister(chg->charger); - - return 0; } static const struct platform_device_id max14577_charger_id[] = { diff --git a/drivers/power/supply/max14656_charger_detector.c b/drivers/power/supply/max14656_charger_detector.c index 0d0180fcfa63..a5b42b42d134 100644 --- a/drivers/power/supply/max14656_charger_detector.c +++ b/drivers/power/supply/max14656_charger_detector.c @@ -14,8 +14,8 @@ #include <linux/delay.h> #include <linux/i2c.h> #include <linux/interrupt.h> +#include <linux/mod_devicetable.h> #include <linux/slab.h> -#include <linux/of_device.h> #include <linux/workqueue.h> #include <linux/power_supply.h> #include <linux/devm-helpers.h> @@ -300,7 +300,7 @@ static int max14656_probe(struct i2c_client *client) } static const struct i2c_device_id max14656_id[] = { - { "max14656", 0 }, + { "max14656" }, {} }; MODULE_DEVICE_TABLE(i2c, max14656_id); @@ -316,7 +316,7 @@ static struct i2c_driver max14656_i2c_driver = { .name = "max14656", .of_match_table = max14656_match_table, }, - .probe_new = max14656_probe, + .probe = max14656_probe, .id_table = max14656_id, }; module_i2c_driver(max14656_i2c_driver); diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index d1075959dd46..48453508688a 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -15,9 +15,10 @@ #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/power_supply.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/regmap.h> #include <linux/slab.h> +#include <linux/iio/consumer.h> #define MAX17040_VCELL 0x02 #define MAX17040_SOC 0x04 @@ -142,6 +143,7 @@ struct max17040_chip { struct delayed_work work; struct power_supply *battery; struct chip_data data; + struct iio_channel *channel_temp; /* battery capacity */ int soc; @@ -386,9 +388,11 @@ 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: + case POWER_SUPPLY_PROP_PRESENT: val->intval = max17040_get_online(chip); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: @@ -400,6 +404,20 @@ static int max17040_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: val->intval = chip->low_soc_alert; break; + case POWER_SUPPLY_PROP_STATUS: + power_supply_get_property_from_supplier(psy, psp, val); + break; + case POWER_SUPPLY_PROP_TEMP: + if (!chip->channel_temp) + return -ENODATA; + + 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; } @@ -415,9 +433,12 @@ static const struct regmap_config max17040_regmap = { static enum power_supply_property max17040_battery_props[] = { POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TEMP, }; static const struct power_supply_desc max17040_battery_desc = { @@ -463,6 +484,17 @@ static int max17040_probe(struct i2c_client *client) i2c_set_clientdata(client, chip); psy_cfg.drv_data = chip; + /* Switch to devm_iio_channel_get_optional when available */ + chip->channel_temp = devm_iio_channel_get(&client->dev, "temp"); + if (IS_ERR(chip->channel_temp)) { + ret = PTR_ERR(chip->channel_temp); + if (ret != -ENODEV) + return dev_err_probe(&client->dev, PTR_ERR(chip->channel_temp), + "failed to get temp\n"); + else + chip->channel_temp = NULL; + } + chip->battery = devm_power_supply_register(&client->dev, &max17040_battery_desc, &psy_cfg); if (IS_ERR(chip->battery)) { @@ -599,7 +631,7 @@ static struct i2c_driver max17040_i2c_driver = { .of_match_table = max17040_of_match, .pm = MAX17040_PM_OPS, }, - .probe_new = max17040_probe, + .probe = max17040_probe, .id_table = max17040_id, }; module_i2c_driver(max17040_i2c_driver); diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index 89cabe8ed3b0..acea176101fa 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -16,6 +16,7 @@ #include <linux/i2c.h> #include <linux/delay.h> #include <linux/interrupt.h> +#include <linux/platform_device.h> #include <linux/pm.h> #include <linux/mod_devicetable.h> #include <linux/power_supply.h> @@ -36,7 +37,7 @@ #define STATUS_BR_BIT (1 << 15) /* Interrupt mask bits */ -#define CONFIG_ALRT_BIT_ENBL (1 << 2) +#define CFG_ALRT_BIT_ENBL (1 << 2) #define VFSOC0_LOCK 0x0000 #define VFSOC0_UNLOCK 0x0080 @@ -52,13 +53,14 @@ #define MAX17042_VMAX_TOLERANCE 50 /* 50 mV */ struct max17042_chip { - struct i2c_client *client; + struct device *dev; struct regmap *regmap; struct power_supply *battery; enum max170xx_chip_type chip_type; struct max17042_platform_data *pdata; struct work_struct work; int init_complete; + int irq; }; static enum power_supply_property max17042_battery_props[] = { @@ -499,11 +501,6 @@ static int max17042_property_is_writeable(struct power_supply *psy, return ret; } -static void max17042_external_power_changed(struct power_supply *psy) -{ - power_supply_changed(psy); -} - static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value) { int retries = 8; @@ -578,11 +575,11 @@ static inline int max17042_model_data_compare(struct max17042_chip *chip, int i; if (memcmp(data1, data2, size)) { - dev_err(&chip->client->dev, "%s compare failed\n", __func__); + dev_err(chip->dev, "%s compare failed\n", __func__); for (i = 0; i < size; i++) - dev_info(&chip->client->dev, "0x%x, 0x%x", + dev_info(chip->dev, "0x%x, 0x%x", data1[i], data2[i]); - dev_info(&chip->client->dev, "\n"); + dev_info(chip->dev, "\n"); return -EINVAL; } return 0; @@ -817,14 +814,14 @@ static int max17042_init_chip(struct max17042_chip *chip) /* write cell characterization data */ ret = max17042_init_model(chip); if (ret) { - dev_err(&chip->client->dev, "%s init failed\n", + dev_err(chip->dev, "%s init failed\n", __func__); return -EIO; } ret = max17042_verify_model_lock(chip); if (ret) { - dev_err(&chip->client->dev, "%s lock verify failed\n", + dev_err(chip->dev, "%s lock verify failed\n", __func__); return -EIO; } @@ -858,7 +855,10 @@ static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off) /* program interrupt thresholds such that we should * get interrupt for every 'off' perc change in the soc */ - regmap_read(map, MAX17042_RepSOC, &soc); + if (chip->pdata->enable_current_sense) + regmap_read(map, MAX17042_RepSOC, &soc); + else + regmap_read(map, MAX17042_VFSOC, &soc); soc >>= 8; soc_tr = (soc + off) << 8; if (off < soc) @@ -877,7 +877,7 @@ static irqreturn_t max17042_thread_handler(int id, void *dev) return IRQ_HANDLED; if ((val & STATUS_SMN_BIT) || (val & STATUS_SMX_BIT)) { - dev_dbg(&chip->client->dev, "SOC threshold INTR\n"); + dev_dbg(chip->dev, "SOC threshold INTR\n"); max17042_set_soc_threshold(chip, 1); } @@ -909,7 +909,7 @@ static void max17042_init_worker(struct work_struct *work) static struct max17042_platform_data * max17042_get_of_pdata(struct max17042_chip *chip) { - struct device *dev = &chip->client->dev; + struct device *dev = chip->dev; struct device_node *np = dev->of_node; u32 prop; struct max17042_platform_data *pdata; @@ -951,7 +951,7 @@ static struct max17042_reg_data max17047_default_pdata_init_regs[] = { static struct max17042_platform_data * max17042_get_default_pdata(struct max17042_chip *chip) { - struct device *dev = &chip->client->dev; + struct device *dev = chip->dev; struct max17042_platform_data *pdata; int ret, misc_cfg; @@ -992,7 +992,7 @@ max17042_get_default_pdata(struct max17042_chip *chip) static struct max17042_platform_data * max17042_get_pdata(struct max17042_chip *chip) { - struct device *dev = &chip->client->dev; + struct device *dev = chip->dev; #ifdef CONFIG_OF if (dev->of_node) @@ -1005,6 +1005,7 @@ max17042_get_pdata(struct max17042_chip *chip) } static const struct regmap_config max17042_regmap_config = { + .name = "max17042", .reg_bits = 8, .val_bits = 16, .val_format_endian = REGMAP_ENDIAN_NATIVE, @@ -1016,7 +1017,7 @@ static const struct power_supply_desc max17042_psy_desc = { .get_property = max17042_get_property, .set_property = max17042_set_property, .property_is_writeable = max17042_property_is_writeable, - .external_power_changed = max17042_external_power_changed, + .external_power_changed = power_supply_changed, .properties = max17042_battery_props, .num_properties = ARRAY_SIZE(max17042_battery_props), }; @@ -1031,14 +1032,12 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = { .num_properties = ARRAY_SIZE(max17042_battery_props) - 2, }; -static int max17042_probe(struct i2c_client *client) +static int max17042_probe(struct i2c_client *client, struct device *dev, int irq, + enum max170xx_chip_type chip_type) { - const struct i2c_device_id *id = i2c_client_get_device_id(client); struct i2c_adapter *adapter = client->adapter; const struct power_supply_desc *max17042_desc = &max17042_psy_desc; struct power_supply_config psy_cfg = {}; - const struct acpi_device_id *acpi_id = NULL; - struct device *dev = &client->dev; struct max17042_chip *chip; int ret; int i; @@ -1047,35 +1046,27 @@ static int max17042_probe(struct i2c_client *client) if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) return -EIO; - chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; - chip->client = client; - if (id) { - chip->chip_type = id->driver_data; - } else { - acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev); - if (!acpi_id) - return -ENODEV; - - chip->chip_type = acpi_id->driver_data; - } + 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(&client->dev, "Failed to initialize regmap\n"); + dev_err(dev, "Failed to initialize regmap\n"); return -EINVAL; } chip->pdata = max17042_get_pdata(chip); if (!chip->pdata) { - dev_err(&client->dev, "no platform data provided\n"); + dev_err(dev, "no platform data provided\n"); return -EINVAL; } - i2c_set_clientdata(client, chip); + 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. */ @@ -1097,46 +1088,41 @@ static int max17042_probe(struct i2c_client *client) regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007); } - chip->battery = devm_power_supply_register(&client->dev, max17042_desc, + chip->battery = devm_power_supply_register(dev, max17042_desc, &psy_cfg); if (IS_ERR(chip->battery)) { - dev_err(&client->dev, "failed: power supply register\n"); + dev_err(dev, "failed: power supply register\n"); return PTR_ERR(chip->battery); } - if (client->irq) { - unsigned int flags = IRQF_ONESHOT; + if (irq) { + unsigned int flags = IRQF_ONESHOT | IRQF_SHARED | IRQF_PROBE_SHARED; - /* - * On ACPI systems the IRQ may be handled by ACPI-event code, - * so we need to share (if the ACPI code is willing to share). - */ - if (acpi_id) - flags |= IRQF_SHARED | IRQF_PROBE_SHARED; - - ret = devm_request_threaded_irq(&client->dev, client->irq, + ret = devm_request_threaded_irq(dev, irq, NULL, max17042_thread_handler, flags, chip->battery->desc->name, chip); if (!ret) { regmap_update_bits(chip->regmap, MAX17042_CONFIG, - CONFIG_ALRT_BIT_ENBL, - CONFIG_ALRT_BIT_ENBL); + CFG_ALRT_BIT_ENBL, + CFG_ALRT_BIT_ENBL); max17042_set_soc_threshold(chip, 1); } else { - client->irq = 0; + irq = 0; if (ret != -EBUSY) - dev_err(&client->dev, "Failed to get IRQ\n"); + dev_err(dev, "Failed to get IRQ\n"); } } /* Not able to update the charge threshold when exceeded? -> disable */ - if (!client->irq) + if (!irq) regmap_write(chip->regmap, MAX17042_SALRT_Th, 0xff00); + chip->irq = irq; + regmap_read(chip->regmap, MAX17042_STATUS, &val); if (val & STATUS_POR_BIT) { - ret = devm_work_autocancel(&client->dev, &chip->work, + ret = devm_work_autocancel(dev, &chip->work, max17042_init_worker); if (ret) return ret; @@ -1148,6 +1134,44 @@ static int max17042_probe(struct i2c_client *client) return 0; } +static int max17042_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + const struct acpi_device_id *acpi_id = NULL; + struct device *dev = &client->dev; + enum max170xx_chip_type chip_type; + + if (id) { + chip_type = id->driver_data; + } else { + acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!acpi_id) + return -ENODEV; + + chip_type = acpi_id->driver_data; + } + + return max17042_probe(client, dev, client->irq, chip_type); +} + +static int max17042_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct i2c_client *i2c; + const struct platform_device_id *id; + int irq; + + i2c = to_i2c_client(pdev->dev.parent); + if (!i2c) + return -EINVAL; + + dev->of_node = dev->parent->of_node; + id = platform_get_device_id(pdev); + irq = platform_get_irq(pdev, 0); + + return max17042_probe(i2c, dev, irq, id->driver_data); +} + #ifdef CONFIG_PM_SLEEP static int max17042_suspend(struct device *dev) { @@ -1157,9 +1181,9 @@ static int max17042_suspend(struct device *dev) * disable the irq and enable irq_wake * capability to the interrupt line. */ - if (chip->client->irq) { - disable_irq(chip->client->irq); - enable_irq_wake(chip->client->irq); + if (chip->irq) { + disable_irq(chip->irq); + enable_irq_wake(chip->irq); } return 0; @@ -1169,9 +1193,9 @@ static int max17042_resume(struct device *dev) { struct max17042_chip *chip = dev_get_drvdata(dev); - if (chip->client->irq) { - disable_irq_wake(chip->client->irq); - enable_irq(chip->client->irq); + if (chip->irq) { + disable_irq_wake(chip->irq); + enable_irq(chip->irq); /* re-program the SOC thresholds to 1% change */ max17042_set_soc_threshold(chip, 1); } @@ -1192,12 +1216,28 @@ MODULE_DEVICE_TABLE(acpi, max17042_acpi_match); #endif #ifdef CONFIG_OF -static const struct of_device_id max17042_dt_match[] = { - { .compatible = "maxim,max17042" }, - { .compatible = "maxim,max17047" }, - { .compatible = "maxim,max17050" }, - { .compatible = "maxim,max17055" }, - { .compatible = "maxim,max77849-battery" }, +/* + * Device may be instantiated through parent MFD device and device matching is done + * through platform_device_id. + * + * However if device's DT node contains proper clock compatible and driver is + * built as a module, then the *module* matching will be done trough DT aliases. + * This requires of_device_id table. In the same time this will not change the + * actual *device* matching so do not add .of_match_table. + */ +static const struct of_device_id max17042_dt_match[] __used = { + { .compatible = "maxim,max17042", + .data = (void *) MAXIM_DEVICE_TYPE_MAX17042 }, + { .compatible = "maxim,max17047", + .data = (void *) MAXIM_DEVICE_TYPE_MAX17047 }, + { .compatible = "maxim,max17050", + .data = (void *) MAXIM_DEVICE_TYPE_MAX17050 }, + { .compatible = "maxim,max17055", + .data = (void *) MAXIM_DEVICE_TYPE_MAX17055 }, + { .compatible = "maxim,max77705-battery", + .data = (void *) MAXIM_DEVICE_TYPE_MAX17047 }, + { .compatible = "maxim,max77849-battery", + .data = (void *) MAXIM_DEVICE_TYPE_MAX17047 }, { }, }; MODULE_DEVICE_TABLE(of, max17042_dt_match); @@ -1213,6 +1253,17 @@ static const struct i2c_device_id max17042_id[] = { }; MODULE_DEVICE_TABLE(i2c, max17042_id); +static const struct platform_device_id max17042_platform_id[] = { + { "max17042", MAXIM_DEVICE_TYPE_MAX17042 }, + { "max17047", MAXIM_DEVICE_TYPE_MAX17047 }, + { "max17050", MAXIM_DEVICE_TYPE_MAX17050 }, + { "max17055", MAXIM_DEVICE_TYPE_MAX17055 }, + { "max77705-battery", MAXIM_DEVICE_TYPE_MAX17047 }, + { "max77849-battery", MAXIM_DEVICE_TYPE_MAX17047 }, + { } +}; +MODULE_DEVICE_TABLE(platform, max17042_platform_id); + static struct i2c_driver max17042_i2c_driver = { .driver = { .name = "max17042", @@ -1220,10 +1271,44 @@ static struct i2c_driver max17042_i2c_driver = { .of_match_table = of_match_ptr(max17042_dt_match), .pm = &max17042_pm_ops, }, - .probe_new = max17042_probe, + .probe = max17042_i2c_probe, .id_table = max17042_id, }; -module_i2c_driver(max17042_i2c_driver); + +static struct platform_driver max17042_platform_driver = { + .driver = { + .name = "max17042", + .acpi_match_table = ACPI_PTR(max17042_acpi_match), + .pm = &max17042_pm_ops, + }, + .probe = max17042_platform_probe, + .id_table = max17042_platform_id, +}; + +static int __init max17042_init(void) +{ + int ret; + + ret = platform_driver_register(&max17042_platform_driver); + if (ret) + return ret; + + ret = i2c_add_driver(&max17042_i2c_driver); + if (ret) { + platform_driver_unregister(&max17042_platform_driver); + return ret; + } + + return 0; +} +module_init(max17042_init); + +static void __exit max17042_exit(void) +{ + i2c_del_driver(&max17042_i2c_driver); + platform_driver_unregister(&max17042_platform_driver); +} +module_exit(max17042_exit); MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); diff --git a/drivers/power/supply/max1720x_battery.c b/drivers/power/supply/max1720x_battery.c new file mode 100644 index 000000000000..e2bd54ee3970 --- /dev/null +++ b/drivers/power/supply/max1720x_battery.c @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Fuel gauge driver for Maxim 17201/17205 + * + * based on max1721x_battery.c + * + * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH + */ + +#include <linux/bitfield.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> + +#include <linux/unaligned.h> + +/* SBS compliant registers */ +#define MAX172XX_TEMP1 0x34 +#define MAX172XX_INT_TEMP 0x35 +#define MAX172XX_TEMP2 0x3B + +/* Nonvolatile registers */ +#define MAX1720X_NXTABLE0 0x80 +#define MAX1720X_NRSENSE 0xCF /* RSense in 10^-5 Ohm */ +#define MAX1720X_NDEVICE_NAME4 0xDF + +/* 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 */ +#define MAX172XX_CURRENT 0x0A /* Actual current */ +#define MAX172XX_AVG_CURRENT 0x0B /* Average current */ +#define MAX172XX_FULL_CAP 0x10 /* Calculated full capacity */ +#define MAX172XX_TTE 0x11 /* Time to empty */ +#define MAX172XX_AVG_TA 0x16 /* Average temperature */ +#define MAX172XX_CYCLES 0x17 +#define MAX172XX_DESIGN_CAP 0x18 /* Design capacity */ +#define MAX172XX_AVG_VCELL 0x19 +#define MAX172XX_TTF 0x20 /* Time to full */ +#define MAX172XX_DEV_NAME 0x21 /* Device name */ +#define MAX172XX_DEV_NAME_TYPE_MASK GENMASK(3, 0) +#define MAX172XX_DEV_NAME_TYPE_MAX17201 BIT(0) +#define MAX172XX_DEV_NAME_TYPE_MAX17205 (BIT(0) | BIT(2)) +#define MAX172XX_QR_TABLE10 0x22 +#define MAX172XX_BATT 0xDA /* Battery voltage */ +#define MAX172XX_ATAVCAP 0xDF + +static const char *const max1720x_manufacturer = "Maxim Integrated"; +static const char *const max17201_model = "MAX17201"; +static const char *const max17205_model = "MAX17205"; + +struct max1720x_device_info { + struct regmap *regmap; + struct regmap *regmap_nv; + struct i2c_client *ancillary; + int rsense; +}; + +/* + * Model Gauge M5 Algorithm output register + * Volatile data (must not be cached) + */ +static const struct regmap_range max1720x_volatile_allow[] = { + regmap_reg_range(MAX172XX_STATUS, MAX172XX_CYCLES), + regmap_reg_range(MAX172XX_AVG_VCELL, MAX172XX_TTF), + regmap_reg_range(MAX172XX_QR_TABLE10, MAX172XX_ATAVCAP), +}; + +static const struct regmap_range max1720x_readable_allow[] = { + regmap_reg_range(MAX172XX_STATUS, MAX172XX_ATAVCAP), +}; + +static const struct regmap_range max1720x_readable_deny[] = { + /* unused registers */ + regmap_reg_range(0x24, 0x26), + regmap_reg_range(0x30, 0x31), + regmap_reg_range(0x33, 0x34), + regmap_reg_range(0x37, 0x37), + regmap_reg_range(0x3B, 0x3C), + regmap_reg_range(0x40, 0x41), + regmap_reg_range(0x43, 0x44), + regmap_reg_range(0x47, 0x49), + regmap_reg_range(0x4B, 0x4C), + regmap_reg_range(0x4E, 0xAF), + regmap_reg_range(0xB1, 0xB3), + regmap_reg_range(0xB5, 0xB7), + regmap_reg_range(0xBF, 0xD0), + regmap_reg_range(0xDB, 0xDB), + regmap_reg_range(0xE0, 0xFF), +}; + +static const struct regmap_access_table max1720x_readable_regs = { + .yes_ranges = max1720x_readable_allow, + .n_yes_ranges = ARRAY_SIZE(max1720x_readable_allow), + .no_ranges = max1720x_readable_deny, + .n_no_ranges = ARRAY_SIZE(max1720x_readable_deny), +}; + +static const struct regmap_access_table max1720x_volatile_regs = { + .yes_ranges = max1720x_volatile_allow, + .n_yes_ranges = ARRAY_SIZE(max1720x_volatile_allow), + .no_ranges = max1720x_readable_deny, + .n_no_ranges = ARRAY_SIZE(max1720x_readable_deny), +}; + +static const struct regmap_config max1720x_regmap_cfg = { + .reg_bits = 8, + .val_bits = 16, + .max_register = MAX172XX_ATAVCAP, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .rd_table = &max1720x_readable_regs, + .volatile_table = &max1720x_volatile_regs, + .cache_type = REGCACHE_MAPLE, +}; + +static const struct regmap_range max1720x_nvmem_allow[] = { + regmap_reg_range(MAX172XX_TEMP1, MAX172XX_INT_TEMP), + regmap_reg_range(MAX172XX_TEMP2, MAX172XX_TEMP2), + regmap_reg_range(MAX1720X_NXTABLE0, MAX1720X_NDEVICE_NAME4), +}; + +static const struct regmap_range max1720x_nvmem_deny[] = { + regmap_reg_range(0x00, 0x33), + regmap_reg_range(0x36, 0x3A), + regmap_reg_range(0x3C, 0x7F), + regmap_reg_range(0xE0, 0xFF), +}; + +static const struct regmap_access_table max1720x_nvmem_regs = { + .yes_ranges = max1720x_nvmem_allow, + .n_yes_ranges = ARRAY_SIZE(max1720x_nvmem_allow), + .no_ranges = max1720x_nvmem_deny, + .n_no_ranges = ARRAY_SIZE(max1720x_nvmem_deny), +}; + +static const struct regmap_config max1720x_nvmem_regmap_cfg = { + .reg_bits = 8, + .val_bits = 16, + .max_register = MAX1720X_NDEVICE_NAME4, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .rd_table = &max1720x_nvmem_regs, +}; + +static const struct nvmem_cell_info max1720x_nvmem_cells[] = { + { .name = "nXTable0", .offset = 0, .bytes = 2, }, + { .name = "nXTable1", .offset = 2, .bytes = 2, }, + { .name = "nXTable2", .offset = 4, .bytes = 2, }, + { .name = "nXTable3", .offset = 6, .bytes = 2, }, + { .name = "nXTable4", .offset = 8, .bytes = 2, }, + { .name = "nXTable5", .offset = 10, .bytes = 2, }, + { .name = "nXTable6", .offset = 12, .bytes = 2, }, + { .name = "nXTable7", .offset = 14, .bytes = 2, }, + { .name = "nXTable8", .offset = 16, .bytes = 2, }, + { .name = "nXTable9", .offset = 18, .bytes = 2, }, + { .name = "nXTable10", .offset = 20, .bytes = 2, }, + { .name = "nXTable11", .offset = 22, .bytes = 2, }, + { .name = "nUser18C", .offset = 24, .bytes = 2, }, + { .name = "nUser18D", .offset = 26, .bytes = 2, }, + { .name = "nODSCTh", .offset = 28, .bytes = 2, }, + { .name = "nODSCCfg", .offset = 30, .bytes = 2, }, + + { .name = "nOCVTable0", .offset = 32, .bytes = 2, }, + { .name = "nOCVTable1", .offset = 34, .bytes = 2, }, + { .name = "nOCVTable2", .offset = 36, .bytes = 2, }, + { .name = "nOCVTable3", .offset = 38, .bytes = 2, }, + { .name = "nOCVTable4", .offset = 40, .bytes = 2, }, + { .name = "nOCVTable5", .offset = 42, .bytes = 2, }, + { .name = "nOCVTable6", .offset = 44, .bytes = 2, }, + { .name = "nOCVTable7", .offset = 46, .bytes = 2, }, + { .name = "nOCVTable8", .offset = 48, .bytes = 2, }, + { .name = "nOCVTable9", .offset = 50, .bytes = 2, }, + { .name = "nOCVTable10", .offset = 52, .bytes = 2, }, + { .name = "nOCVTable11", .offset = 54, .bytes = 2, }, + { .name = "nIChgTerm", .offset = 56, .bytes = 2, }, + { .name = "nFilterCfg", .offset = 58, .bytes = 2, }, + { .name = "nVEmpty", .offset = 60, .bytes = 2, }, + { .name = "nLearnCfg", .offset = 62, .bytes = 2, }, + + { .name = "nQRTable00", .offset = 64, .bytes = 2, }, + { .name = "nQRTable10", .offset = 66, .bytes = 2, }, + { .name = "nQRTable20", .offset = 68, .bytes = 2, }, + { .name = "nQRTable30", .offset = 70, .bytes = 2, }, + { .name = "nCycles", .offset = 72, .bytes = 2, }, + { .name = "nFullCapNom", .offset = 74, .bytes = 2, }, + { .name = "nRComp0", .offset = 76, .bytes = 2, }, + { .name = "nTempCo", .offset = 78, .bytes = 2, }, + { .name = "nIAvgEmpty", .offset = 80, .bytes = 2, }, + { .name = "nFullCapRep", .offset = 82, .bytes = 2, }, + { .name = "nVoltTemp", .offset = 84, .bytes = 2, }, + { .name = "nMaxMinCurr", .offset = 86, .bytes = 2, }, + { .name = "nMaxMinVolt", .offset = 88, .bytes = 2, }, + { .name = "nMaxMinTemp", .offset = 90, .bytes = 2, }, + { .name = "nSOC", .offset = 92, .bytes = 2, }, + { .name = "nTimerH", .offset = 94, .bytes = 2, }, + + { .name = "nConfig", .offset = 96, .bytes = 2, }, + { .name = "nRippleCfg", .offset = 98, .bytes = 2, }, + { .name = "nMiscCfg", .offset = 100, .bytes = 2, }, + { .name = "nDesignCap", .offset = 102, .bytes = 2, }, + { .name = "nHibCfg", .offset = 104, .bytes = 2, }, + { .name = "nPackCfg", .offset = 106, .bytes = 2, }, + { .name = "nRelaxCfg", .offset = 108, .bytes = 2, }, + { .name = "nConvgCfg", .offset = 110, .bytes = 2, }, + { .name = "nNVCfg0", .offset = 112, .bytes = 2, }, + { .name = "nNVCfg1", .offset = 114, .bytes = 2, }, + { .name = "nNVCfg2", .offset = 116, .bytes = 2, }, + { .name = "nSBSCfg", .offset = 118, .bytes = 2, }, + { .name = "nROMID0", .offset = 120, .bytes = 2, }, + { .name = "nROMID1", .offset = 122, .bytes = 2, }, + { .name = "nROMID2", .offset = 124, .bytes = 2, }, + { .name = "nROMID3", .offset = 126, .bytes = 2, }, + + { .name = "nVAlrtTh", .offset = 128, .bytes = 2, }, + { .name = "nTAlrtTh", .offset = 130, .bytes = 2, }, + { .name = "nSAlrtTh", .offset = 132, .bytes = 2, }, + { .name = "nIAlrtTh", .offset = 134, .bytes = 2, }, + { .name = "nUser1C4", .offset = 136, .bytes = 2, }, + { .name = "nUser1C5", .offset = 138, .bytes = 2, }, + { .name = "nFullSOCThr", .offset = 140, .bytes = 2, }, + { .name = "nTTFCfg", .offset = 142, .bytes = 2, }, + { .name = "nCGain", .offset = 144, .bytes = 2, }, + { .name = "nTCurve", .offset = 146, .bytes = 2, }, + { .name = "nTGain", .offset = 148, .bytes = 2, }, + { .name = "nTOff", .offset = 150, .bytes = 2, }, + { .name = "nManfctrName0", .offset = 152, .bytes = 2, }, + { .name = "nManfctrName1", .offset = 154, .bytes = 2, }, + { .name = "nManfctrName2", .offset = 156, .bytes = 2, }, + { .name = "nRSense", .offset = 158, .bytes = 2, }, + + { .name = "nUser1D0", .offset = 160, .bytes = 2, }, + { .name = "nUser1D1", .offset = 162, .bytes = 2, }, + { .name = "nAgeFcCfg", .offset = 164, .bytes = 2, }, + { .name = "nDesignVoltage", .offset = 166, .bytes = 2, }, + { .name = "nUser1D4", .offset = 168, .bytes = 2, }, + { .name = "nRFastVShdn", .offset = 170, .bytes = 2, }, + { .name = "nManfctrDate", .offset = 172, .bytes = 2, }, + { .name = "nFirstUsed", .offset = 174, .bytes = 2, }, + { .name = "nSerialNumber0", .offset = 176, .bytes = 2, }, + { .name = "nSerialNumber1", .offset = 178, .bytes = 2, }, + { .name = "nSerialNumber2", .offset = 180, .bytes = 2, }, + { .name = "nDeviceName0", .offset = 182, .bytes = 2, }, + { .name = "nDeviceName1", .offset = 184, .bytes = 2, }, + { .name = "nDeviceName2", .offset = 186, .bytes = 2, }, + { .name = "nDeviceName3", .offset = 188, .bytes = 2, }, + { .name = "nDeviceName4", .offset = 190, .bytes = 2, }, +}; + +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, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_AVG, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +/* Convert regs value to power_supply units */ + +static int max172xx_time_to_ps(unsigned int reg) +{ + return reg * 5625 / 1000; /* in sec. */ +} + +static int max172xx_percent_to_ps(unsigned int reg) +{ + return reg / 256; /* in percent from 0 to 100 */ +} + +static int max172xx_voltage_to_ps(unsigned int reg) +{ + return reg * 1250; /* in uV */ +} + +static int max172xx_capacity_to_ps(unsigned int reg, + struct max1720x_device_info *info) +{ + return reg * (500000 / info->rsense); /* in uAh */ +} + +/* + * Current and temperature is signed values, so unsigned regs + * value must be converted to signed type + */ + +static int max172xx_temperature_to_ps(unsigned int reg) +{ + int val = (int16_t)reg; + + return val * 10 / 256; /* in tenths of deg. C */ +} + +/* + * Calculating current registers resolution: + * + * 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 + */ +static int max172xx_current_to_voltage(unsigned int reg) +{ + int val = (int16_t)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) +{ + struct max1720x_device_info *info = power_supply_get_drvdata(psy); + unsigned int reg_val; + 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 + * sysfs interface. Value return 0 if battery not + * present or unaccesable via I2c. + */ + ret = regmap_read(info->regmap, MAX172XX_STATUS, ®_val); + if (ret < 0) { + val->intval = 0; + return 0; + } + + val->intval = !FIELD_GET(MAX172XX_STATUS_BAT_ABSENT, reg_val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = regmap_read(info->regmap, MAX172XX_REPSOC, ®_val); + val->intval = max172xx_percent_to_ps(reg_val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = regmap_read(info->regmap, MAX172XX_BATT, ®_val); + val->intval = max172xx_voltage_to_ps(reg_val); + 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, info); + break; + case POWER_SUPPLY_PROP_CHARGE_AVG: + ret = regmap_read(info->regmap, MAX172XX_REPCAP, ®_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); + val->intval = max172xx_time_to_ps(reg_val); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + ret = regmap_read(info->regmap, MAX172XX_TTF, ®_val); + val->intval = max172xx_time_to_ps(reg_val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = regmap_read(info->regmap, MAX172XX_TEMP, ®_val); + val->intval = max172xx_temperature_to_ps(reg_val); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = regmap_read(info->regmap, MAX172XX_CURRENT, ®_val); + val->intval = max172xx_current_to_voltage(reg_val) / info->rsense; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = regmap_read(info->regmap, MAX172XX_AVG_CURRENT, ®_val); + val->intval = max172xx_current_to_voltage(reg_val) / info->rsense; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = regmap_read(info->regmap, MAX172XX_FULL_CAP, ®_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; + else if (reg_val == MAX172XX_DEV_NAME_TYPE_MAX17205) + val->strval = max17205_model; + else + return -ENODEV; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = max1720x_manufacturer; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int max1720x_read_temp(struct device *dev, u8 reg, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max1720x_device_info *info = power_supply_get_drvdata(psy); + unsigned int val; + int ret; + + ret = regmap_read(info->regmap_nv, reg, &val); + if (ret < 0) + return ret; + + /* + * Temperature in degrees Celsius starting at absolute zero, -273C or + * 0K with an LSb of 0.1C + */ + return sysfs_emit(buf, "%d\n", val - 2730); +} + +static ssize_t temp_ain1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return max1720x_read_temp(dev, MAX172XX_TEMP1, buf); +} + +static ssize_t temp_ain2_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return max1720x_read_temp(dev, MAX172XX_TEMP2, buf); +} + +static ssize_t temp_int_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return max1720x_read_temp(dev, MAX172XX_INT_TEMP, buf); +} + +static DEVICE_ATTR_RO(temp_ain1); +static DEVICE_ATTR_RO(temp_ain2); +static DEVICE_ATTR_RO(temp_int); + +static struct attribute *max1720x_attrs[] = { + &dev_attr_temp_ain1.attr, + &dev_attr_temp_ain2.attr, + &dev_attr_temp_int.attr, + NULL +}; +ATTRIBUTE_GROUPS(max1720x); + +static +int max1720x_nvmem_reg_read(void *priv, unsigned int off, void *val, size_t len) +{ + struct max1720x_device_info *info = priv; + unsigned int reg = MAX1720X_NXTABLE0 + (off / 2); + + return regmap_bulk_read(info->regmap_nv, reg, val, len / 2); +} + +static void max1720x_unregister_ancillary(void *data) +{ + struct max1720x_device_info *info = data; + + i2c_unregister_device(info->ancillary); +} + +static int max1720x_probe_nvmem(struct i2c_client *client, + struct max1720x_device_info *info) +{ + struct device *dev = &client->dev; + struct nvmem_config nvmem_config = { + .dev = dev, + .name = "max1720x_nvmem", + .cells = max1720x_nvmem_cells, + .ncells = ARRAY_SIZE(max1720x_nvmem_cells), + .read_only = true, + .root_only = true, + .reg_read = max1720x_nvmem_reg_read, + .size = ARRAY_SIZE(max1720x_nvmem_cells) * 2, + .word_size = 2, + .stride = 2, + .priv = info, + }; + struct nvmem_device *nvmem; + unsigned int val; + int ret; + + info->ancillary = i2c_new_ancillary_device(client, "nvmem", 0xb); + if (IS_ERR(info->ancillary)) { + dev_err(dev, "Failed to initialize ancillary i2c device\n"); + return PTR_ERR(info->ancillary); + } + + ret = devm_add_action_or_reset(dev, max1720x_unregister_ancillary, info); + if (ret) { + dev_err(dev, "Failed to add unregister callback\n"); + return ret; + } + + info->regmap_nv = devm_regmap_init_i2c(info->ancillary, + &max1720x_nvmem_regmap_cfg); + if (IS_ERR(info->regmap_nv)) { + dev_err(dev, "regmap initialization of nvmem failed\n"); + return PTR_ERR(info->regmap_nv); + } + + ret = regmap_read(info->regmap_nv, MAX1720X_NRSENSE, &val); + if (ret < 0) { + dev_err(dev, "Failed to read sense resistor value\n"); + return ret; + } + + info->rsense = val; + if (!info->rsense) { + dev_warn(dev, "RSense not calibrated, set 10 mOhms!\n"); + info->rsense = 1000; /* in regs in 10^-5 */ + } + + nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(nvmem)) { + dev_err(dev, "Could not register nvmem!"); + return PTR_ERR(nvmem); + } + + return 0; +} + +static const struct power_supply_desc max1720x_bat_desc = { + .name = "max1720x", + .no_thermal = true, + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = max1720x_battery_props, + .num_properties = ARRAY_SIZE(max1720x_battery_props), + .get_property = max1720x_battery_get_property, +}; + +static int max1720x_probe(struct i2c_client *client) +{ + struct power_supply_config psy_cfg = {}; + struct device *dev = &client->dev; + struct max1720x_device_info *info; + struct power_supply *bat; + int ret; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + psy_cfg.drv_data = info; + psy_cfg.fwnode = dev_fwnode(dev); + psy_cfg.attr_grp = max1720x_groups; + i2c_set_clientdata(client, info); + info->regmap = devm_regmap_init_i2c(client, &max1720x_regmap_cfg); + if (IS_ERR(info->regmap)) + return dev_err_probe(dev, PTR_ERR(info->regmap), + "regmap initialization failed\n"); + + ret = max1720x_probe_nvmem(client, info); + if (ret) + return dev_err_probe(dev, ret, "Failed to probe nvmem\n"); + + bat = devm_power_supply_register(dev, &max1720x_bat_desc, &psy_cfg); + if (IS_ERR(bat)) + return dev_err_probe(dev, PTR_ERR(bat), + "Failed to register power supply\n"); + + return 0; +} + +static const struct of_device_id max1720x_of_match[] = { + { .compatible = "maxim,max17201" }, + {} +}; +MODULE_DEVICE_TABLE(of, max1720x_of_match); + +static struct i2c_driver max1720x_i2c_driver = { + .driver = { + .name = "max1720x", + .of_match_table = max1720x_of_match, + }, + .probe = max1720x_probe, +}; +module_i2c_driver(max1720x_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dimitri Fedrau <dima.fedrau@gmail.com>"); +MODULE_DESCRIPTION("Maxim MAX17201/MAX17205 Fuel Gauge IC driver"); diff --git a/drivers/power/supply/max1721x_battery.c b/drivers/power/supply/max1721x_battery.c index d8d52e09da7b..bac43ab9e97c 100644 --- a/drivers/power/supply/max1721x_battery.c +++ b/drivers/power/supply/max1721x_battery.c @@ -384,7 +384,7 @@ static int devm_w1_max1721x_add_device(struct w1_slave *sl) } if (!info->ManufacturerName[0]) - strncpy(info->ManufacturerName, DEF_MFG_NAME, + strscpy(info->ManufacturerName, DEF_MFG_NAME, 2 * MAX1721X_REG_MFG_NUMB); if (get_string(info, MAX1721X_REG_DEV_STR, @@ -403,15 +403,15 @@ static int devm_w1_max1721x_add_device(struct w1_slave *sl) switch (dev_name & MAX172XX_DEV_MASK) { case MAX172X1_DEV: - strncpy(info->DeviceName, DEF_DEV_NAME_MAX17211, + strscpy(info->DeviceName, DEF_DEV_NAME_MAX17211, 2 * MAX1721X_REG_DEV_NUMB); break; case MAX172X5_DEV: - strncpy(info->DeviceName, DEF_DEV_NAME_MAX17215, + strscpy(info->DeviceName, DEF_DEV_NAME_MAX17215, 2 * MAX1721X_REG_DEV_NUMB); break; default: - strncpy(info->DeviceName, DEF_DEV_NAME_UNKNOWN, + strscpy(info->DeviceName, DEF_DEV_NAME_UNKNOWN, 2 * MAX1721X_REG_DEV_NUMB); } } diff --git a/drivers/power/supply/max77650-charger.c b/drivers/power/supply/max77650-charger.c index d913428bedc0..4ae43668992e 100644 --- a/drivers/power/supply/max77650-charger.c +++ b/drivers/power/supply/max77650-charger.c @@ -141,7 +141,7 @@ static int max77650_charger_enable(struct max77650_charger_data *chg) return rv; } -static int max77650_charger_disable(struct max77650_charger_data *chg) +static void max77650_charger_disable(struct max77650_charger_data *chg) { int rv; @@ -151,8 +151,6 @@ static int max77650_charger_disable(struct max77650_charger_data *chg) MAX77650_CHARGER_DISABLED); if (rv) dev_err(chg->dev, "unable to disable the charger: %d\n", rv); - - return rv; } static irqreturn_t max77650_charger_check_status(int irq, void *data) @@ -300,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"); @@ -347,11 +345,11 @@ static int max77650_charger_probe(struct platform_device *pdev) return max77650_charger_enable(chg); } -static int max77650_charger_remove(struct platform_device *pdev) +static void max77650_charger_remove(struct platform_device *pdev) { struct max77650_charger_data *chg = platform_get_drvdata(pdev); - return max77650_charger_disable(chg); + max77650_charger_disable(chg); } static const struct of_device_id max77650_charger_of_match[] = { diff --git a/drivers/power/supply/max77693_charger.c b/drivers/power/supply/max77693_charger.c index a2c5c9858639..027d6a539b65 100644 --- a/drivers/power/supply/max77693_charger.c +++ b/drivers/power/supply/max77693_charger.c @@ -197,12 +197,58 @@ static int max77693_get_online(struct regmap *regmap, int *val) return 0; } +/* + * There are *two* current limit registers: + * - CHGIN limit, which limits the input current from the external charger; + * - Fast charge current limit, which limits the current going to the battery. + */ + +static int max77693_get_input_current_limit(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_CNFG_09, &data); + if (ret < 0) + return ret; + + data &= CHG_CNFG_09_CHGIN_ILIM_MASK; + data >>= CHG_CNFG_09_CHGIN_ILIM_SHIFT; + + if (data <= 0x03) + /* The first four values (0x00..0x03) are 60mA */ + *val = 60000; + else + *val = data * 20000; /* 20mA steps */ + + return 0; +} + +static int max77693_get_fast_charge_current(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_CNFG_02, &data); + if (ret < 0) + return ret; + + data &= CHG_CNFG_02_CC_MASK; + data >>= CHG_CNFG_02_CC_SHIFT; + + *val = data * 33300; /* 33.3mA steps */ + + return 0; +} + static enum power_supply_property max77693_charger_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, }; @@ -231,6 +277,12 @@ static int max77693_charger_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_ONLINE: ret = max77693_get_online(regmap, &val->intval); break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = max77693_get_input_current_limit(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = max77693_get_fast_charge_current(regmap, &val->intval); + break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = max77693_charger_model; break; @@ -296,7 +348,7 @@ static ssize_t fast_charge_timer_show(struct device *dev, break; } - return scnprintf(buf, PAGE_SIZE, "%u\n", val); + return sysfs_emit(buf, "%u\n", val); } static int max77693_set_fast_charge_timer(struct max77693_charger *chg, @@ -357,7 +409,7 @@ static ssize_t top_off_threshold_current_show(struct device *dev, else val = data * 50000; - return scnprintf(buf, PAGE_SIZE, "%u\n", val); + return sysfs_emit(buf, "%u\n", val); } static int max77693_set_top_off_threshold_current(struct max77693_charger *chg, @@ -405,7 +457,7 @@ static ssize_t top_off_timer_show(struct device *dev, val = data * 10; - return scnprintf(buf, PAGE_SIZE, "%u\n", val); + return sysfs_emit(buf, "%u\n", val); } static int max77693_set_top_off_timer(struct max77693_charger *chg, @@ -556,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"); @@ -709,9 +761,9 @@ static int max77693_charger_probe(struct platform_device *pdev) goto err; } - chg->charger = power_supply_register(&pdev->dev, - &max77693_charger_desc, - &psy_cfg); + chg->charger = devm_power_supply_register(&pdev->dev, + &max77693_charger_desc, + &psy_cfg); if (IS_ERR(chg->charger)) { dev_err(&pdev->dev, "failed: power supply register\n"); ret = PTR_ERR(chg->charger); @@ -728,17 +780,11 @@ err: return ret; } -static int max77693_charger_remove(struct platform_device *pdev) +static void max77693_charger_remove(struct platform_device *pdev) { - struct max77693_charger *chg = platform_get_drvdata(pdev); - device_remove_file(&pdev->dev, &dev_attr_top_off_timer); device_remove_file(&pdev->dev, &dev_attr_top_off_threshold_current); device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); - - power_supply_unregister(chg->charger); - - return 0; } static const struct platform_device_id max77693_charger_id[] = { diff --git a/drivers/power/supply/max77705_charger.c b/drivers/power/supply/max77705_charger.c new file mode 100644 index 000000000000..5dd02f658f5b --- /dev/null +++ b/drivers/power/supply/max77705_charger.c @@ -0,0 +1,713 @@ +// 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 = create_singlethread_workqueue(dev_name(dev)); + if (!chg->wqueue) + return -ENOMEM; + + ret = devm_work_autocancel(dev, &chg->chgin_work, max77705_chgin_isr_work); + if (ret) { + dev_err_probe(dev, ret, "failed to initialize interrupt work\n"); + goto destroy_wq; + } + + ret = max77705_charger_initialize(chg); + if (ret) { + dev_err_probe(dev, ret, "failed to initialize charger IC\n"); + goto destroy_wq; + } + + 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) { + dev_err_probe(dev, ret, "Failed to Request chgin IRQ\n"); + goto destroy_wq; + } + + 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) { + dev_err_probe(dev, ret, "Failed to Request aicl IRQ\n"); + goto destroy_wq; + } + + ret = max77705_charger_enable(chg); + if (ret) { + dev_err_probe(dev, ret, "failed to enable charge\n"); + goto destroy_wq; + } + + return devm_add_action_or_reset(dev, max77705_charger_disable, chg); + +destroy_wq: + destroy_workqueue(chg->wqueue); + return ret; +} + +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/max77976_charger.c b/drivers/power/supply/max77976_charger.c index 4fed74511931..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, @@ -452,6 +452,7 @@ static int max77976_probe(struct i2c_client *client) i2c_set_clientdata(client, chg); psy_cfg.drv_data = chg; + psy_cfg.no_wakeup_source = true; chg->client = client; chg->regmap = devm_regmap_init_i2c(client, &max77976_regmap_config); @@ -475,7 +476,7 @@ static int max77976_probe(struct i2c_client *client) if (err) return err; - psy = devm_power_supply_register_no_ws(dev, &max77976_psy_desc, &psy_cfg); + psy = devm_power_supply_register(dev, &max77976_psy_desc, &psy_cfg); if (IS_ERR(psy)) return dev_err_probe(dev, PTR_ERR(psy), "cannot register\n"); @@ -483,8 +484,8 @@ static int max77976_probe(struct i2c_client *client) } static const struct i2c_device_id max77976_i2c_id[] = { - { MAX77976_DRIVER_NAME, 0 }, - { }, + { MAX77976_DRIVER_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, max77976_i2c_id); @@ -499,7 +500,7 @@ static struct i2c_driver max77976_driver = { .name = MAX77976_DRIVER_NAME, .of_match_table = max77976_of_id, }, - .probe_new = max77976_probe, + .probe = max77976_probe, .id_table = max77976_i2c_id, }; module_i2c_driver(max77976_driver); diff --git a/drivers/power/supply/max8903_charger.c b/drivers/power/supply/max8903_charger.c index 54d50b55fbae..45fbaad6c647 100644 --- a/drivers/power/supply/max8903_charger.c +++ b/drivers/power/supply/max8903_charger.c @@ -10,7 +10,6 @@ #include <linux/interrupt.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/slab.h> #include <linux/power_supply.h> #include <linux/platform_device.h> @@ -350,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/max8925_power.c b/drivers/power/supply/max8925_power.c index 8878f9131184..d753145de3bb 100644 --- a/drivers/power/supply/max8925_power.c +++ b/drivers/power/supply/max8925_power.c @@ -73,7 +73,7 @@ struct max8925_power_info { unsigned usb_online:1; unsigned bat_online:1; unsigned chg_mode:2; - unsigned batt_detect:1; /* detecing MB by ID pin */ + unsigned batt_detect:1; /* detecting MB by ID pin */ unsigned topoff_threshold:2; unsigned fast_charge:3; unsigned no_temp_support:1; @@ -507,7 +507,6 @@ static int max8925_power_probe(struct platform_device *pdev) struct power_supply_config psy_cfg = {}; /* Only for ac and usb */ struct max8925_power_pdata *pdata = NULL; struct max8925_power_info *info; - int ret; pdata = max8925_power_dt_init(pdev); if (!pdata) { @@ -528,25 +527,19 @@ static int max8925_power_probe(struct platform_device *pdev) psy_cfg.supplied_to = pdata->supplied_to; psy_cfg.num_supplicants = pdata->num_supplicants; - info->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg); - if (IS_ERR(info->ac)) { - ret = PTR_ERR(info->ac); - goto out; - } + info->ac = devm_power_supply_register(&pdev->dev, &ac_desc, &psy_cfg); + if (IS_ERR(info->ac)) + return PTR_ERR(info->ac); info->ac->dev.parent = &pdev->dev; - info->usb = power_supply_register(&pdev->dev, &usb_desc, &psy_cfg); - if (IS_ERR(info->usb)) { - ret = PTR_ERR(info->usb); - goto out_unregister_ac; - } + info->usb = devm_power_supply_register(&pdev->dev, &usb_desc, &psy_cfg); + if (IS_ERR(info->usb)) + return PTR_ERR(info->usb); info->usb->dev.parent = &pdev->dev; - info->battery = power_supply_register(&pdev->dev, &battery_desc, NULL); - if (IS_ERR(info->battery)) { - ret = PTR_ERR(info->battery); - goto out_unregister_usb; - } + info->battery = devm_power_supply_register(&pdev->dev, &battery_desc, NULL); + if (IS_ERR(info->battery)) + return PTR_ERR(info->battery); info->battery->dev.parent = &pdev->dev; info->batt_detect = pdata->batt_detect; @@ -558,25 +551,14 @@ static int max8925_power_probe(struct platform_device *pdev) max8925_init_charger(chip, info); return 0; -out_unregister_usb: - power_supply_unregister(info->usb); -out_unregister_ac: - power_supply_unregister(info->ac); -out: - return ret; } -static int max8925_power_remove(struct platform_device *pdev) +static void max8925_power_remove(struct platform_device *pdev) { struct max8925_power_info *info = platform_get_drvdata(pdev); - if (info) { - power_supply_unregister(info->ac); - power_supply_unregister(info->usb); - power_supply_unregister(info->battery); + if (info) max8925_deinit_charger(info); - } - return 0; } static struct platform_driver max8925_power_driver = { 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/max8998_charger.c b/drivers/power/supply/max8998_charger.c index c26023b19f26..418b882b163d 100644 --- a/drivers/power/supply/max8998_charger.c +++ b/drivers/power/supply/max8998_charger.c @@ -191,6 +191,7 @@ static const struct platform_device_id max8998_battery_id[] = { { "max8998-battery", TYPE_MAX8998 }, { } }; +MODULE_DEVICE_TABLE(platform, max8998_battery_id); static struct platform_driver max8998_battery_driver = { .driver = { diff --git a/drivers/power/supply/mm8013.c b/drivers/power/supply/mm8013.c new file mode 100644 index 000000000000..93c50cff31bc --- /dev/null +++ b/drivers/power/supply/mm8013.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2019 The Linux Foundation. All rights reserved. + * Copyright (c) 2023, Linaro Limited + */ +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> + +#define REG_BATID 0x00 /* This one is very unclear */ + #define BATID_101 0x0101 /* 107kOhm */ + #define BATID_102 0x0102 /* 10kOhm */ +#define REG_TEMPERATURE 0x06 +#define REG_VOLTAGE 0x08 +#define REG_FLAGS 0x0a + #define MM8013_FLAG_OTC BIT(15) + #define MM8013_FLAG_OTD BIT(14) + #define MM8013_FLAG_BATHI BIT(13) + #define MM8013_FLAG_BATLOW BIT(12) + #define MM8013_FLAG_CHG_INH BIT(11) + #define MM8013_FLAG_FC BIT(9) + #define MM8013_FLAG_CHG BIT(8) + #define MM8013_FLAG_OCC BIT(6) + #define MM8013_FLAG_ODC BIT(5) + #define MM8013_FLAG_OT BIT(4) + #define MM8013_FLAG_UT BIT(3) + #define MM8013_FLAG_DSG BIT(0) +#define REG_FULL_CHARGE_CAPACITY 0x0e +#define REG_NOMINAL_CHARGE_CAPACITY 0x0c +#define REG_AVERAGE_CURRENT 0x14 +#define REG_AVERAGE_TIME_TO_EMPTY 0x16 +#define REG_AVERAGE_TIME_TO_FULL 0x18 +#define REG_MAX_LOAD_CURRENT 0x1e +#define REG_CYCLE_COUNT 0x2a +#define REG_STATE_OF_CHARGE 0x2c +#define REG_DESIGN_CAPACITY 0x3c +/* TODO: 0x62-0x68 seem to contain 'MM8013C' in a length-prefixed, non-terminated string */ + +#define DECIKELVIN_TO_DECIDEGC(t) (t - 2731) + +struct mm8013_chip { + struct i2c_client *client; + struct regmap *regmap; +}; + +static int mm8013_checkdevice(struct mm8013_chip *chip) +{ + int battery_id, ret; + u32 val; + + ret = regmap_write(chip->regmap, REG_BATID, 0x0008); + if (ret < 0) + return ret; + + ret = regmap_read(chip->regmap, REG_BATID, &val); + if (ret < 0) + return ret; + + if (val == BATID_102) + battery_id = 2; + else if (val == BATID_101) + battery_id = 1; + else + return -EINVAL; + + dev_dbg(&chip->client->dev, "battery_id: %d\n", battery_id); + + return 0; +} + +static enum power_supply_property mm8013_battery_props[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static int mm8013_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mm8013_chip *chip = power_supply_get_drvdata(psy); + int ret = 0; + u32 regval; + + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + ret = regmap_read(chip->regmap, REG_STATE_OF_CHARGE, ®val); + if (ret < 0) + return ret; + + val->intval = regval; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = regmap_read(chip->regmap, REG_FULL_CHARGE_CAPACITY, ®val); + if (ret < 0) + return ret; + + val->intval = 1000 * regval; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = regmap_read(chip->regmap, REG_DESIGN_CAPACITY, ®val); + if (ret < 0) + return ret; + + val->intval = 1000 * regval; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = regmap_read(chip->regmap, REG_NOMINAL_CHARGE_CAPACITY, ®val); + if (ret < 0) + return ret; + + val->intval = 1000 * regval; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + ret = regmap_read(chip->regmap, REG_MAX_LOAD_CURRENT, ®val); + if (ret < 0) + return ret; + + val->intval = -1000 * (s16)regval; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = regmap_read(chip->regmap, REG_AVERAGE_CURRENT, ®val); + if (ret < 0) + return ret; + + val->intval = -1000 * (s16)regval; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = regmap_read(chip->regmap, REG_CYCLE_COUNT, ®val); + if (ret < 0) + return ret; + + val->intval = regval; + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = regmap_read(chip->regmap, REG_FLAGS, ®val); + if (ret < 0) + return ret; + + if (regval & MM8013_FLAG_UT) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else if (regval & (MM8013_FLAG_ODC | MM8013_FLAG_OCC)) + val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT; + else if (regval & (MM8013_FLAG_BATLOW)) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (regval & MM8013_FLAG_BATHI) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (regval & (MM8013_FLAG_OT | MM8013_FLAG_OTD | MM8013_FLAG_OTC)) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = regmap_read(chip->regmap, REG_TEMPERATURE, ®val); + if (ret < 0) + return ret; + + val->intval = ((s16)regval > 0); + break; + case POWER_SUPPLY_PROP_STATUS: + ret = regmap_read(chip->regmap, REG_FLAGS, ®val); + if (ret < 0) + return ret; + + if (regval & MM8013_FLAG_DSG) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (regval & MM8013_FLAG_CHG_INH) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (regval & MM8013_FLAG_CHG) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (regval & MM8013_FLAG_FC) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = regmap_read(chip->regmap, REG_TEMPERATURE, ®val); + if (ret < 0) + return ret; + + val->intval = DECIKELVIN_TO_DECIDEGC(regval); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + ret = regmap_read(chip->regmap, REG_AVERAGE_TIME_TO_EMPTY, ®val); + if (ret < 0) + return ret; + + /* The estimation is not yet ready */ + if (regval == U16_MAX) + return -ENODATA; + + val->intval = regval; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + ret = regmap_read(chip->regmap, REG_AVERAGE_TIME_TO_FULL, ®val); + if (ret < 0) + return ret; + + /* The estimation is not yet ready */ + if (regval == U16_MAX) + return -ENODATA; + + val->intval = regval; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = regmap_read(chip->regmap, REG_VOLTAGE, ®val); + if (ret < 0) + return ret; + + val->intval = 1000 * regval; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct power_supply_desc mm8013_desc = { + .name = "mm8013", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = mm8013_battery_props, + .num_properties = ARRAY_SIZE(mm8013_battery_props), + .get_property = mm8013_get_property, +}; + +static const struct regmap_config mm8013_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = 0x68, + .use_single_read = true, + .use_single_write = true, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int mm8013_probe(struct i2c_client *client) +{ + struct power_supply_config psy_cfg = {}; + struct device *dev = &client->dev; + struct power_supply *psy; + struct mm8013_chip *chip; + int ret = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return dev_err_probe(dev, -EIO, + "I2C_FUNC_SMBUS_WORD_DATA not supported\n"); + + chip = devm_kzalloc(dev, sizeof(struct mm8013_chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + + chip->regmap = devm_regmap_init_i2c(client, &mm8013_regmap_config); + if (IS_ERR(chip->regmap)) { + ret = PTR_ERR(chip->regmap); + return dev_err_probe(dev, ret, "Couldn't initialize regmap\n"); + } + + ret = mm8013_checkdevice(chip); + if (ret) + return dev_err_probe(dev, ret, "MM8013 not found\n"); + + psy_cfg.drv_data = chip; + psy_cfg.fwnode = dev_fwnode(dev); + + psy = devm_power_supply_register(dev, &mm8013_desc, &psy_cfg); + if (IS_ERR(psy)) + return PTR_ERR(psy); + + return 0; +} + +static const struct i2c_device_id mm8013_id_table[] = { + { "mm8013" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, mm8013_id_table); + +static const struct of_device_id mm8013_match_table[] = { + { .compatible = "mitsumi,mm8013" }, + {} +}; + +static struct i2c_driver mm8013_i2c_driver = { + .probe = mm8013_probe, + .id_table = mm8013_id_table, + .driver = { + .name = "mm8013", + .of_match_table = mm8013_match_table, + }, +}; +module_i2c_driver(mm8013_i2c_driver); + +MODULE_DESCRIPTION("MM8013 fuel gauge driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/mp2629_charger.c b/drivers/power/supply/mp2629_charger.c index bf9c27b463a8..d281c1059629 100644 --- a/drivers/power/supply/mp2629_charger.c +++ b/drivers/power/supply/mp2629_charger.c @@ -94,14 +94,6 @@ struct mp2629_prop { int shift; }; -static enum power_supply_usb_type mp2629_usb_types[] = { - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_PD_DRP, - POWER_SUPPLY_USB_TYPE_UNKNOWN -}; - static enum power_supply_property mp2629_charger_usb_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_USB_TYPE, @@ -487,8 +479,11 @@ unlock: static const struct power_supply_desc mp2629_usb_desc = { .name = "mp2629_usb", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = mp2629_usb_types, - .num_usb_types = ARRAY_SIZE(mp2629_usb_types), + .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_PD_DRP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = mp2629_charger_usb_props, .num_properties = ARRAY_SIZE(mp2629_charger_usb_props), .get_property = mp2629_charger_usb_get_prop, @@ -519,7 +514,7 @@ static ssize_t batt_impedance_compensation_show(struct device *dev, return ret; rval = (rval >> 4) * 10; - return sprintf(buf, "%d mohm\n", rval); + return sysfs_emit(buf, "%d mohm\n", rval); } static ssize_t batt_impedance_compensation_store(struct device *dev, diff --git a/drivers/power/supply/mt6360_charger.c b/drivers/power/supply/mt6360_charger.c index 92e48e3a4853..77747eb51667 100644 --- a/drivers/power/supply/mt6360_charger.c +++ b/drivers/power/supply/mt6360_charger.c @@ -154,13 +154,6 @@ enum mt6360_pmu_chg_type { MT6360_CHG_TYPE_MAX, }; -static enum power_supply_usb_type mt6360_charger_usb_types[] = { - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, -}; - static int mt6360_get_chrdet_ext_stat(struct mt6360_chg_info *mci, bool *pwr_rdy) { @@ -574,8 +567,10 @@ static const struct power_supply_desc mt6360_charger_desc = { .get_property = mt6360_charger_get_property, .set_property = mt6360_charger_set_property, .property_is_writeable = mt6360_charger_property_is_writeable, - .usb_types = mt6360_charger_usb_types, - .num_usb_types = ARRAY_SIZE(mt6360_charger_usb_types), + .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), }; static const struct regulator_ops mt6360_chg_otg_ops = { @@ -588,7 +583,7 @@ static const struct regulator_ops mt6360_chg_otg_ops = { }; static const struct regulator_desc mt6360_otg_rdesc = { - .of_match = "usb-otg-vbus", + .of_match = "usb-otg-vbus-regulator", .name = "usb-otg-vbus", .ops = &mt6360_chg_otg_ops, .owner = THIS_MODULE, @@ -796,7 +791,9 @@ static int mt6360_charger_probe(struct platform_device *pdev) mci->vinovp = 6500000; mutex_init(&mci->chgdet_lock); platform_set_drvdata(pdev, mci); - devm_work_autocancel(&pdev->dev, &mci->chrdet_work, mt6360_chrdet_work); + ret = devm_work_autocancel(&pdev->dev, &mci->chrdet_work, mt6360_chrdet_work); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to set delayed work\n"); ret = device_property_read_u32(&pdev->dev, "richtek,vinovp-microvolt", &mci->vinovp); if (ret) @@ -813,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 f27dae5043f5..e6db961d5818 100644 --- a/drivers/power/supply/mt6370-charger.c +++ b/drivers/power/supply/mt6370-charger.c @@ -324,7 +324,7 @@ static int mt6370_chg_toggle_cfo(struct mt6370_priv *priv) if (fl_strobe) { dev_err(priv->dev, "Flash led is still in strobe mode\n"); - return ret; + return -EINVAL; } /* cfo off */ @@ -624,13 +624,6 @@ static enum power_supply_property mt6370_chg_properties[] = { POWER_SUPPLY_PROP_USB_TYPE, }; -static enum power_supply_usb_type mt6370_chg_usb_types[] = { - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_DCP, -}; - static const struct power_supply_desc mt6370_chg_psy_desc = { .name = "mt6370-charger", .type = POWER_SUPPLY_TYPE_USB, @@ -639,8 +632,10 @@ static const struct power_supply_desc mt6370_chg_psy_desc = { .get_property = mt6370_chg_get_property, .set_property = mt6370_chg_set_property, .property_is_writeable = mt6370_chg_property_is_writeable, - .usb_types = mt6370_chg_usb_types, - .num_usb_types = ARRAY_SIZE(mt6370_chg_usb_types), + .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), }; static const struct regulator_ops mt6370_chg_otg_ops = { @@ -757,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, @@ -766,18 +761,10 @@ 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); } @@ -849,9 +836,7 @@ static int mt6370_chg_init_irq(struct mt6370_priv *priv) ret = platform_get_irq_byname(to_platform_device(priv->dev), mt6370_chg_irqs[i].name); if (ret < 0) - return dev_err_probe(priv->dev, ret, - "Failed to get irq %s\n", - mt6370_chg_irqs[i].name); + return ret; priv->irq_nums[i] = ret; ret = devm_request_threaded_irq(priv->dev, ret, NULL, @@ -902,22 +887,19 @@ 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)); if (!priv->wq) - return dev_err_probe(dev, -ENOMEM, - "Failed to create workqueue\n"); + return -ENOMEM; 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 ret; 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 a5da20ffd685..202c4fa9b903 100644 --- a/drivers/power/supply/olpc_battery.c +++ b/drivers/power/supply/olpc_battery.c @@ -527,7 +527,7 @@ static enum power_supply_property olpc_xo15_bat_props[] = { #define EEPROM_SIZE (EEPROM_END - EEPROM_START) static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, loff_t off, size_t count) + const struct bin_attribute *attr, char *buf, loff_t off, size_t count) { uint8_t ec_byte; int ret; @@ -547,7 +547,7 @@ static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj, return count; } -static struct bin_attribute olpc_bat_eeprom = { +static const struct bin_attribute olpc_bat_eeprom = { .attr = { .name = "eeprom", .mode = S_IRUGO, @@ -568,7 +568,7 @@ static ssize_t olpc_bat_error_read(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%d\n", ec_byte); + return sysfs_emit(buf, "%d\n", ec_byte); } static struct device_attribute olpc_bat_error = { @@ -584,7 +584,7 @@ static struct attribute *olpc_bat_sysfs_attrs[] = { NULL }; -static struct bin_attribute *olpc_bat_sysfs_bin_attrs[] = { +static const struct bin_attribute *const olpc_bat_sysfs_bin_attrs[] = { &olpc_bat_eeprom, NULL }; @@ -592,7 +592,6 @@ static struct bin_attribute *olpc_bat_sysfs_bin_attrs[] = { static const struct attribute_group olpc_bat_sysfs_group = { .attrs = olpc_bat_sysfs_attrs, .bin_attrs = olpc_bat_sysfs_bin_attrs, - }; static const struct attribute_group *olpc_bat_sysfs_groups[] = { @@ -675,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, @@ -693,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 8c5d892f6350..000000000000 --- a/drivers/power/supply/pcf50633-charger.c +++ /dev/null @@ -1,473 +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 sprintf(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 sprintf(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 sprintf(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 = 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 = 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"); - power_supply_unregister(mbc->adapter); - return PTR_ERR(mbc->usb); - } - - mbc->ac = 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"); - power_supply_unregister(mbc->adapter); - power_supply_unregister(mbc->usb); - 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 int 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]); - - power_supply_unregister(mbc->usb); - power_supply_unregister(mbc->adapter); - power_supply_unregister(mbc->ac); - - return 0; -} - -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/pda_power.c b/drivers/power/supply/pda_power.c deleted file mode 100644 index 03a37fd6be27..000000000000 --- a/drivers/power/supply/pda_power.c +++ /dev/null @@ -1,520 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Common power driver for PDAs and phones with one or two external - * power supplies (AC/USB) connected to main and backup batteries, - * and optional builtin charger. - * - * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> - */ - -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/err.h> -#include <linux/interrupt.h> -#include <linux/notifier.h> -#include <linux/power_supply.h> -#include <linux/pda_power.h> -#include <linux/regulator/consumer.h> -#include <linux/timer.h> -#include <linux/jiffies.h> -#include <linux/usb/otg.h> - -static inline unsigned int get_irq_flags(struct resource *res) -{ - return IRQF_SHARED | (res->flags & IRQF_TRIGGER_MASK); -} - -static struct device *dev; -static struct pda_power_pdata *pdata; -static struct resource *ac_irq, *usb_irq; -static struct delayed_work charger_work; -static struct delayed_work polling_work; -static struct delayed_work supply_work; -static int polling; -static struct power_supply *pda_psy_ac, *pda_psy_usb; - -#if IS_ENABLED(CONFIG_USB_PHY) -static struct usb_phy *transceiver; -static struct notifier_block otg_nb; -#endif - -static struct regulator *ac_draw; - -enum { - PDA_PSY_OFFLINE = 0, - PDA_PSY_ONLINE = 1, - PDA_PSY_TO_CHANGE, -}; -static int new_ac_status = -1; -static int new_usb_status = -1; -static int ac_status = -1; -static int usb_status = -1; - -static int pda_power_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - if (psy->desc->type == POWER_SUPPLY_TYPE_MAINS) - val->intval = pdata->is_ac_online ? - pdata->is_ac_online() : 0; - else - val->intval = pdata->is_usb_online ? - pdata->is_usb_online() : 0; - break; - default: - return -EINVAL; - } - return 0; -} - -static enum power_supply_property pda_power_props[] = { - POWER_SUPPLY_PROP_ONLINE, -}; - -static char *pda_power_supplied_to[] = { - "main-battery", - "backup-battery", -}; - -static const struct power_supply_desc pda_psy_ac_desc = { - .name = "ac", - .type = POWER_SUPPLY_TYPE_MAINS, - .properties = pda_power_props, - .num_properties = ARRAY_SIZE(pda_power_props), - .get_property = pda_power_get_property, -}; - -static const struct power_supply_desc pda_psy_usb_desc = { - .name = "usb", - .type = POWER_SUPPLY_TYPE_USB, - .properties = pda_power_props, - .num_properties = ARRAY_SIZE(pda_power_props), - .get_property = pda_power_get_property, -}; - -static void update_status(void) -{ - if (pdata->is_ac_online) - new_ac_status = !!pdata->is_ac_online(); - - if (pdata->is_usb_online) - new_usb_status = !!pdata->is_usb_online(); -} - -static void update_charger(void) -{ - static int regulator_enabled; - int max_uA = pdata->ac_max_uA; - - if (pdata->set_charge) { - if (new_ac_status > 0) { - dev_dbg(dev, "charger on (AC)\n"); - pdata->set_charge(PDA_POWER_CHARGE_AC); - } else if (new_usb_status > 0) { - dev_dbg(dev, "charger on (USB)\n"); - pdata->set_charge(PDA_POWER_CHARGE_USB); - } else { - dev_dbg(dev, "charger off\n"); - pdata->set_charge(0); - } - } else if (ac_draw) { - if (new_ac_status > 0) { - regulator_set_current_limit(ac_draw, max_uA, max_uA); - if (!regulator_enabled) { - dev_dbg(dev, "charger on (AC)\n"); - WARN_ON(regulator_enable(ac_draw)); - regulator_enabled = 1; - } - } else { - if (regulator_enabled) { - dev_dbg(dev, "charger off\n"); - WARN_ON(regulator_disable(ac_draw)); - regulator_enabled = 0; - } - } - } -} - -static void supply_work_func(struct work_struct *work) -{ - if (ac_status == PDA_PSY_TO_CHANGE) { - ac_status = new_ac_status; - power_supply_changed(pda_psy_ac); - } - - if (usb_status == PDA_PSY_TO_CHANGE) { - usb_status = new_usb_status; - power_supply_changed(pda_psy_usb); - } -} - -static void psy_changed(void) -{ - update_charger(); - - /* - * Okay, charger set. Now wait a bit before notifying supplicants, - * charge power should stabilize. - */ - cancel_delayed_work(&supply_work); - schedule_delayed_work(&supply_work, - msecs_to_jiffies(pdata->wait_for_charger)); -} - -static void charger_work_func(struct work_struct *work) -{ - update_status(); - psy_changed(); -} - -static irqreturn_t power_changed_isr(int irq, void *power_supply) -{ - if (power_supply == pda_psy_ac) - ac_status = PDA_PSY_TO_CHANGE; - else if (power_supply == pda_psy_usb) - usb_status = PDA_PSY_TO_CHANGE; - else - return IRQ_NONE; - - /* - * Wait a bit before reading ac/usb line status and setting charger, - * because ac/usb status readings may lag from irq. - */ - cancel_delayed_work(&charger_work); - schedule_delayed_work(&charger_work, - msecs_to_jiffies(pdata->wait_for_status)); - - return IRQ_HANDLED; -} - -static void polling_work_func(struct work_struct *work) -{ - int changed = 0; - - dev_dbg(dev, "polling...\n"); - - update_status(); - - if (!ac_irq && new_ac_status != ac_status) { - ac_status = PDA_PSY_TO_CHANGE; - changed = 1; - } - - if (!usb_irq && new_usb_status != usb_status) { - usb_status = PDA_PSY_TO_CHANGE; - changed = 1; - } - - if (changed) - psy_changed(); - - cancel_delayed_work(&polling_work); - schedule_delayed_work(&polling_work, - msecs_to_jiffies(pdata->polling_interval)); -} - -#if IS_ENABLED(CONFIG_USB_PHY) -static int otg_is_usb_online(void) -{ - return (transceiver->last_event == USB_EVENT_VBUS || - transceiver->last_event == USB_EVENT_ENUMERATED); -} - -static int otg_is_ac_online(void) -{ - return (transceiver->last_event == USB_EVENT_CHARGER); -} - -static int otg_handle_notification(struct notifier_block *nb, - unsigned long event, void *unused) -{ - switch (event) { - case USB_EVENT_CHARGER: - ac_status = PDA_PSY_TO_CHANGE; - break; - case USB_EVENT_VBUS: - case USB_EVENT_ENUMERATED: - usb_status = PDA_PSY_TO_CHANGE; - break; - case USB_EVENT_NONE: - ac_status = PDA_PSY_TO_CHANGE; - usb_status = PDA_PSY_TO_CHANGE; - break; - default: - return NOTIFY_OK; - } - - /* - * Wait a bit before reading ac/usb line status and setting charger, - * because ac/usb status readings may lag from irq. - */ - cancel_delayed_work(&charger_work); - schedule_delayed_work(&charger_work, - msecs_to_jiffies(pdata->wait_for_status)); - - return NOTIFY_OK; -} -#endif - -static int pda_power_probe(struct platform_device *pdev) -{ - struct power_supply_config psy_cfg = {}; - int ret = 0; - - dev = &pdev->dev; - - if (pdev->id != -1) { - dev_err(dev, "it's meaningless to register several " - "pda_powers; use id = -1\n"); - ret = -EINVAL; - goto wrongid; - } - - pdata = pdev->dev.platform_data; - - if (pdata->init) { - ret = pdata->init(dev); - if (ret < 0) - goto init_failed; - } - - ac_draw = regulator_get(dev, "ac_draw"); - if (IS_ERR(ac_draw)) { - dev_dbg(dev, "couldn't get ac_draw regulator\n"); - ac_draw = NULL; - } - - update_status(); - update_charger(); - - if (!pdata->wait_for_status) - pdata->wait_for_status = 500; - - if (!pdata->wait_for_charger) - pdata->wait_for_charger = 500; - - if (!pdata->polling_interval) - pdata->polling_interval = 2000; - - if (!pdata->ac_max_uA) - pdata->ac_max_uA = 500000; - - INIT_DELAYED_WORK(&charger_work, charger_work_func); - INIT_DELAYED_WORK(&supply_work, supply_work_func); - - ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac"); - usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb"); - - if (pdata->supplied_to) { - psy_cfg.supplied_to = pdata->supplied_to; - psy_cfg.num_supplicants = pdata->num_supplicants; - } else { - psy_cfg.supplied_to = pda_power_supplied_to; - psy_cfg.num_supplicants = ARRAY_SIZE(pda_power_supplied_to); - } - -#if IS_ENABLED(CONFIG_USB_PHY) - transceiver = usb_get_phy(USB_PHY_TYPE_USB2); - if (!IS_ERR_OR_NULL(transceiver)) { - if (!pdata->is_usb_online) - pdata->is_usb_online = otg_is_usb_online; - if (!pdata->is_ac_online) - pdata->is_ac_online = otg_is_ac_online; - } -#endif - - if (pdata->is_ac_online) { - pda_psy_ac = power_supply_register(&pdev->dev, - &pda_psy_ac_desc, &psy_cfg); - if (IS_ERR(pda_psy_ac)) { - dev_err(dev, "failed to register %s power supply\n", - pda_psy_ac_desc.name); - ret = PTR_ERR(pda_psy_ac); - goto ac_supply_failed; - } - - if (ac_irq) { - ret = request_irq(ac_irq->start, power_changed_isr, - get_irq_flags(ac_irq), ac_irq->name, - pda_psy_ac); - if (ret) { - dev_err(dev, "request ac irq failed\n"); - goto ac_irq_failed; - } - } else { - polling = 1; - } - } - - if (pdata->is_usb_online) { - pda_psy_usb = power_supply_register(&pdev->dev, - &pda_psy_usb_desc, - &psy_cfg); - if (IS_ERR(pda_psy_usb)) { - dev_err(dev, "failed to register %s power supply\n", - pda_psy_usb_desc.name); - ret = PTR_ERR(pda_psy_usb); - goto usb_supply_failed; - } - - if (usb_irq) { - ret = request_irq(usb_irq->start, power_changed_isr, - get_irq_flags(usb_irq), - usb_irq->name, pda_psy_usb); - if (ret) { - dev_err(dev, "request usb irq failed\n"); - goto usb_irq_failed; - } - } else { - polling = 1; - } - } - -#if IS_ENABLED(CONFIG_USB_PHY) - if (!IS_ERR_OR_NULL(transceiver) && pdata->use_otg_notifier) { - otg_nb.notifier_call = otg_handle_notification; - ret = usb_register_notifier(transceiver, &otg_nb); - if (ret) { - dev_err(dev, "failure to register otg notifier\n"); - goto otg_reg_notifier_failed; - } - polling = 0; - } -#endif - - if (polling) { - dev_dbg(dev, "will poll for status\n"); - INIT_DELAYED_WORK(&polling_work, polling_work_func); - cancel_delayed_work(&polling_work); - schedule_delayed_work(&polling_work, - msecs_to_jiffies(pdata->polling_interval)); - } - - if (ac_irq || usb_irq) - device_init_wakeup(&pdev->dev, 1); - - return 0; - -#if IS_ENABLED(CONFIG_USB_PHY) -otg_reg_notifier_failed: - if (pdata->is_usb_online && usb_irq) - free_irq(usb_irq->start, pda_psy_usb); -#endif -usb_irq_failed: - if (pdata->is_usb_online) - power_supply_unregister(pda_psy_usb); -usb_supply_failed: - if (pdata->is_ac_online && ac_irq) - free_irq(ac_irq->start, pda_psy_ac); -#if IS_ENABLED(CONFIG_USB_PHY) - if (!IS_ERR_OR_NULL(transceiver)) - usb_put_phy(transceiver); -#endif -ac_irq_failed: - if (pdata->is_ac_online) - power_supply_unregister(pda_psy_ac); -ac_supply_failed: - if (ac_draw) { - regulator_put(ac_draw); - ac_draw = NULL; - } - if (pdata->exit) - pdata->exit(dev); -init_failed: -wrongid: - return ret; -} - -static int pda_power_remove(struct platform_device *pdev) -{ -#if IS_ENABLED(CONFIG_USB_PHY) - if (!IS_ERR_OR_NULL(transceiver) && pdata->use_otg_notifier) - usb_unregister_notifier(transceiver, &otg_nb); -#endif - if (pdata->is_usb_online && usb_irq) - free_irq(usb_irq->start, pda_psy_usb); - if (pdata->is_ac_online && ac_irq) - free_irq(ac_irq->start, pda_psy_ac); - - if (polling) - cancel_delayed_work_sync(&polling_work); - cancel_delayed_work_sync(&charger_work); - cancel_delayed_work_sync(&supply_work); - - if (pdata->is_usb_online) - power_supply_unregister(pda_psy_usb); - if (pdata->is_ac_online) - power_supply_unregister(pda_psy_ac); -#if IS_ENABLED(CONFIG_USB_PHY) - if (!IS_ERR_OR_NULL(transceiver)) - usb_put_phy(transceiver); -#endif - if (ac_draw) { - regulator_put(ac_draw); - ac_draw = NULL; - } - if (pdata->exit) - pdata->exit(dev); - - return 0; -} - -#ifdef CONFIG_PM -static int ac_wakeup_enabled; -static int usb_wakeup_enabled; - -static int pda_power_suspend(struct platform_device *pdev, pm_message_t state) -{ - if (pdata->suspend) { - int ret = pdata->suspend(state); - - if (ret) - return ret; - } - - if (device_may_wakeup(&pdev->dev)) { - if (ac_irq) - ac_wakeup_enabled = !enable_irq_wake(ac_irq->start); - if (usb_irq) - usb_wakeup_enabled = !enable_irq_wake(usb_irq->start); - } - - return 0; -} - -static int pda_power_resume(struct platform_device *pdev) -{ - if (device_may_wakeup(&pdev->dev)) { - if (usb_irq && usb_wakeup_enabled) - disable_irq_wake(usb_irq->start); - if (ac_irq && ac_wakeup_enabled) - disable_irq_wake(ac_irq->start); - } - - if (pdata->resume) - return pdata->resume(); - - return 0; -} -#else -#define pda_power_suspend NULL -#define pda_power_resume NULL -#endif /* CONFIG_PM */ - -static struct platform_driver pda_power_pdrv = { - .driver = { - .name = "pda-power", - }, - .probe = pda_power_probe, - .remove = pda_power_remove, - .suspend = pda_power_suspend, - .resume = pda_power_resume, -}; - -module_platform_driver(pda_power_pdrv); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>"); -MODULE_ALIAS("platform:pda-power"); diff --git a/drivers/power/supply/pf1550-charger.c b/drivers/power/supply/pf1550-charger.c new file mode 100644 index 000000000000..98f1ee8eca3b --- /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"); + + 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"); + } + + 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"); + + 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 new file mode 100644 index 000000000000..5120be086e6f --- /dev/null +++ b/drivers/power/supply/pm8916_bms_vm.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, Nikita Travkin <nikita@trvn.ru> + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/timekeeping.h> +#include <linux/mod_devicetable.h> + +#define PM8916_PERPH_TYPE 0x04 +#define PM8916_BMS_VM_TYPE 0x020D + +#define PM8916_SEC_ACCESS 0xD0 +#define PM8916_SEC_MAGIC 0xA5 + +#define PM8916_BMS_VM_STATUS1 0x08 +#define PM8916_BMS_VM_FSM_STATE(x) (((x) & 0b00111000) >> 3) +#define PM8916_BMS_VM_FSM_STATE_S2 0x2 + +#define PM8916_BMS_VM_MODE_CTL 0x40 +#define PM8916_BMS_VM_MODE_FORCE_S3 (BIT(0) | BIT(1)) +#define PM8916_BMS_VM_MODE_NORMAL (BIT(1) | BIT(3)) + +#define PM8916_BMS_VM_EN_CTL 0x46 +#define PM8916_BMS_ENABLED BIT(7) + +#define PM8916_BMS_VM_FIFO_LENGTH_CTL 0x47 +#define PM8916_BMS_VM_S1_SAMPLE_INTERVAL_CTL 0x55 +#define PM8916_BMS_VM_S2_SAMPLE_INTERVAL_CTL 0x56 +#define PM8916_BMS_VM_S3_S7_OCV_DATA0 0x6A +#define PM8916_BMS_VM_BMS_FIFO_REG_0_LSB 0xC0 + +/* Using only 1 fifo is broken in hardware */ +#define PM8916_BMS_VM_FIFO_COUNT 2 /* 2 .. 8 */ + +#define PM8916_BMS_VM_S1_SAMPLE_INTERVAL 10 +#define PM8916_BMS_VM_S2_SAMPLE_INTERVAL 10 + +struct pm8916_bms_vm_battery { + struct device *dev; + struct power_supply *battery; + struct power_supply_battery_info *info; + struct regmap *regmap; + unsigned int reg; + unsigned int last_ocv; + time64_t last_ocv_time; + unsigned int vbat_now; +}; + +static int pm8916_bms_vm_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pm8916_bms_vm_battery *bat = power_supply_get_drvdata(psy); + struct power_supply_battery_info *info = bat->info; + int supplied; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + supplied = power_supply_am_i_supplied(psy); + + if (supplied < 0 && supplied != -ENODEV) + return supplied; + else if (supplied && supplied != -ENODEV) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + + case POWER_SUPPLY_PROP_HEALTH: + if (bat->vbat_now < info->voltage_min_design_uv) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (bat->vbat_now > info->voltage_max_design_uv) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + return 0; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = bat->vbat_now; + return 0; + + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + /* + * Hardware only reliably measures OCV when the system is off or suspended. + * We expose the last known OCV value on boot, invalidating it after 180 seconds. + */ + if (ktime_get_seconds() - bat->last_ocv_time > 180) + return -ENODATA; + + val->intval = bat->last_ocv; + return 0; + + default: + return -EINVAL; + } +} + +static enum power_supply_property pm8916_bms_vm_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_HEALTH, +}; + +static irqreturn_t pm8916_bms_vm_fifo_update_done_irq(int irq, void *data) +{ + struct pm8916_bms_vm_battery *bat = data; + u16 vbat_data[PM8916_BMS_VM_FIFO_COUNT]; + int ret; + + ret = regmap_bulk_read(bat->regmap, bat->reg + PM8916_BMS_VM_BMS_FIFO_REG_0_LSB, + &vbat_data, PM8916_BMS_VM_FIFO_COUNT * 2); + if (ret) + return IRQ_HANDLED; + + /* + * The VM-BMS hardware only collects voltage data and the software + * has to process it to calculate the OCV and SoC. Hardware provides + * up to 8 averaged measurements for software to take in account. + * + * Just use the last measured value for now to report the current + * battery voltage. + */ + bat->vbat_now = vbat_data[PM8916_BMS_VM_FIFO_COUNT - 1] * 300; + + power_supply_changed(bat->battery); + + return IRQ_HANDLED; +} + +static const struct power_supply_desc pm8916_bms_vm_battery_psy_desc = { + .name = "pm8916-bms-vm", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = pm8916_bms_vm_battery_properties, + .num_properties = ARRAY_SIZE(pm8916_bms_vm_battery_properties), + .get_property = pm8916_bms_vm_battery_get_property, +}; + +static int pm8916_bms_vm_battery_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pm8916_bms_vm_battery *bat; + struct power_supply_config psy_cfg = {}; + int ret, irq; + unsigned int tmp; + + bat = devm_kzalloc(dev, sizeof(*bat), GFP_KERNEL); + if (!bat) + return -ENOMEM; + + bat->dev = dev; + + bat->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!bat->regmap) + return -ENODEV; + + ret = device_property_read_u32(dev, "reg", &bat->reg); + 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; + + if (tmp != PM8916_BMS_VM_TYPE) + return dev_err_probe(dev, -ENODEV, "Device reported wrong type: 0x%X\n", tmp); + + ret = regmap_write(bat->regmap, bat->reg + PM8916_BMS_VM_S1_SAMPLE_INTERVAL_CTL, + PM8916_BMS_VM_S1_SAMPLE_INTERVAL); + if (ret) + goto comm_error; + ret = regmap_write(bat->regmap, bat->reg + PM8916_BMS_VM_S2_SAMPLE_INTERVAL_CTL, + PM8916_BMS_VM_S2_SAMPLE_INTERVAL); + if (ret) + goto comm_error; + ret = regmap_write(bat->regmap, bat->reg + PM8916_BMS_VM_FIFO_LENGTH_CTL, + PM8916_BMS_VM_FIFO_COUNT << 4 | PM8916_BMS_VM_FIFO_COUNT); + if (ret) + goto comm_error; + ret = regmap_write(bat->regmap, + bat->reg + PM8916_BMS_VM_EN_CTL, PM8916_BMS_ENABLED); + if (ret) + goto comm_error; + + ret = regmap_bulk_read(bat->regmap, + bat->reg + PM8916_BMS_VM_S3_S7_OCV_DATA0, &tmp, 2); + if (ret) + goto comm_error; + + bat->last_ocv_time = ktime_get_seconds(); + bat->last_ocv = tmp * 300; + bat->vbat_now = bat->last_ocv; + + psy_cfg.drv_data = bat; + 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)) + return dev_err_probe(dev, PTR_ERR(bat->battery), "Unable to register battery\n"); + + ret = power_supply_get_battery_info(bat->battery, &bat->info); + if (ret) + return dev_err_probe(dev, ret, "Unable to get battery info\n"); + + platform_set_drvdata(pdev, bat); + + return 0; + +comm_error: + return dev_err_probe(dev, ret, "Unable to communicate with device\n"); +} + +static int pm8916_bms_vm_battery_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct pm8916_bms_vm_battery *bat = platform_get_drvdata(pdev); + int ret; + + /* + * Due to a hardware quirk the FSM doesn't switch states normally. + * Instead we unlock the debug registers and force S3 (Measure OCV/Sleep) + * mode every time we suspend. + */ + + ret = regmap_write(bat->regmap, + bat->reg + PM8916_SEC_ACCESS, PM8916_SEC_MAGIC); + if (ret) + goto error; + ret = regmap_write(bat->regmap, + bat->reg + PM8916_BMS_VM_MODE_CTL, PM8916_BMS_VM_MODE_FORCE_S3); + if (ret) + goto error; + + return 0; + +error: + dev_err(bat->dev, "Failed to force S3 mode: %pe\n", ERR_PTR(ret)); + return ret; +} + +static int pm8916_bms_vm_battery_resume(struct platform_device *pdev) +{ + struct pm8916_bms_vm_battery *bat = platform_get_drvdata(pdev); + int ret; + unsigned int tmp; + + ret = regmap_bulk_read(bat->regmap, + bat->reg + PM8916_BMS_VM_S3_S7_OCV_DATA0, &tmp, 2); + + bat->last_ocv_time = ktime_get_seconds(); + bat->last_ocv = tmp * 300; + + ret = regmap_write(bat->regmap, + bat->reg + PM8916_SEC_ACCESS, PM8916_SEC_MAGIC); + if (ret) + goto error; + ret = regmap_write(bat->regmap, + bat->reg + PM8916_BMS_VM_MODE_CTL, PM8916_BMS_VM_MODE_NORMAL); + if (ret) + goto error; + + return 0; + +error: + dev_err(bat->dev, "Failed to return normal mode: %pe\n", ERR_PTR(ret)); + return ret; +} + +static const struct of_device_id pm8916_bms_vm_battery_of_match[] = { + { .compatible = "qcom,pm8916-bms-vm", }, + {} +}; +MODULE_DEVICE_TABLE(of, pm8916_bms_vm_battery_of_match); + +static struct platform_driver pm8916_bms_vm_battery_driver = { + .driver = { + .name = "pm8916-bms-vm", + .of_match_table = pm8916_bms_vm_battery_of_match, + }, + .probe = pm8916_bms_vm_battery_probe, + .suspend = pm8916_bms_vm_battery_suspend, + .resume = pm8916_bms_vm_battery_resume, +}; +module_platform_driver(pm8916_bms_vm_battery_driver); + +MODULE_DESCRIPTION("pm8916 BMS-VM driver"); +MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/pm8916_lbc.c b/drivers/power/supply/pm8916_lbc.c new file mode 100644 index 000000000000..c74b75b1b267 --- /dev/null +++ b/drivers/power/supply/pm8916_lbc.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, Nikita Travkin <nikita@trvn.ru> + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/extcon-provider.h> +#include <linux/mod_devicetable.h> + +/* Two bytes: type + subtype */ +#define PM8916_PERPH_TYPE 0x04 +#define PM8916_LBC_CHGR_TYPE 0x1502 +#define PM8916_LBC_BAT_IF_TYPE 0x1602 +#define PM8916_LBC_USB_TYPE 0x1702 +#define PM8916_LBC_MISC_TYPE 0x1802 + +#define PM8916_LBC_CHGR_CHG_OPTION 0x08 +#define PM8916_LBC_CHGR_PMIC_CHARGER BIT(7) + +#define PM8916_LBC_CHGR_CHG_STATUS 0x09 + +#define PM8916_INT_RT_STS 0x10 + +#define PM8916_LBC_USB_USBIN_VALID BIT(1) + +#define PM8916_LBC_CHGR_VDD_MAX 0x40 +#define PM8916_LBC_CHGR_VDD_SAFE 0x41 +#define PM8916_LBC_CHGR_IBAT_MAX 0x44 +#define PM8916_LBC_CHGR_IBAT_SAFE 0x45 + +#define PM8916_LBC_CHGR_TCHG_MAX_EN 0x60 +#define PM8916_LBC_CHGR_TCHG_MAX_ENABLED BIT(7) +#define PM8916_LBC_CHGR_TCHG_MAX 0x61 + +#define PM8916_LBC_CHGR_CHG_CTRL 0x49 +#define PM8916_LBC_CHGR_CHG_EN BIT(7) +#define PM8916_LBC_CHGR_PSTG_EN BIT(5) + +#define PM8916_LBC_CHGR_MIN_CURRENT 90000 +#define PM8916_LBC_CHGR_MAX_CURRENT 1440000 + +#define PM8916_LBC_CHGR_MIN_VOLTAGE 4000000 +#define PM8916_LBC_CHGR_MAX_VOLTAGE 4775000 +#define PM8916_LBC_CHGR_VOLTAGE_STEP 25000 + +#define PM8916_LBC_CHGR_MIN_TIME 4 +#define PM8916_LBC_CHGR_MAX_TIME 256 + +struct pm8916_lbc_charger { + struct device *dev; + struct extcon_dev *edev; + struct power_supply *charger; + struct power_supply_battery_info *info; + struct regmap *regmap; + unsigned int reg[4]; + bool online; + unsigned int charge_voltage_max; + unsigned int charge_voltage_safe; + unsigned int charge_current_max; + unsigned int charge_current_safe; +}; + +static const unsigned int pm8916_lbc_charger_cable[] = { + EXTCON_USB, + EXTCON_NONE, +}; + +enum { + LBC_CHGR = 0, + LBC_BAT_IF, + LBC_USB, + LBC_MISC, +}; + +static int pm8916_lbc_charger_configure(struct pm8916_lbc_charger *chg) +{ + int ret = 0; + unsigned int tmp; + + chg->charge_voltage_max = clamp_t(u32, chg->charge_voltage_max, + PM8916_LBC_CHGR_MIN_VOLTAGE, chg->charge_voltage_safe); + + tmp = chg->charge_voltage_max - PM8916_LBC_CHGR_MIN_VOLTAGE; + tmp /= PM8916_LBC_CHGR_VOLTAGE_STEP; + chg->charge_voltage_max = PM8916_LBC_CHGR_MIN_VOLTAGE + tmp * PM8916_LBC_CHGR_VOLTAGE_STEP; + + ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_MAX, tmp); + if (ret) + goto error; + + chg->charge_current_max = min(chg->charge_current_max, chg->charge_current_safe); + + tmp = clamp_t(u32, chg->charge_current_max, + PM8916_LBC_CHGR_MIN_CURRENT, PM8916_LBC_CHGR_MAX_CURRENT); + + tmp = chg->charge_current_max / PM8916_LBC_CHGR_MIN_CURRENT - 1; + chg->charge_current_max = (tmp + 1) * PM8916_LBC_CHGR_MIN_CURRENT; + + ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_MAX, tmp); + if (ret) + goto error; + + ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_CTRL, + PM8916_LBC_CHGR_CHG_EN | PM8916_LBC_CHGR_PSTG_EN); + if (ret) + goto error; + + return ret; + +error: + dev_err(chg->dev, "Failed to configure charging: %pe\n", ERR_PTR(ret)); + return ret; +} + +static int pm8916_lbc_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pm8916_lbc_charger *chg = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chg->online; + return 0; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = chg->charge_voltage_max; + return 0; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = chg->charge_current_max; + return 0; + + default: + return -EINVAL; + }; +} + +static int pm8916_lbc_charger_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct pm8916_lbc_charger *chg = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + chg->charge_current_max = val->intval; + return pm8916_lbc_charger_configure(chg); + default: + return -EINVAL; + } +} + +static int pm8916_lbc_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return true; + default: + return false; + } +} + +static enum power_supply_property pm8916_lbc_charger_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, +}; + +static irqreturn_t pm8916_lbc_charger_state_changed_irq(int irq, void *data) +{ + struct pm8916_lbc_charger *chg = data; + unsigned int tmp; + int ret; + + ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp); + if (ret) + return IRQ_HANDLED; + + chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID); + extcon_set_state_sync(chg->edev, EXTCON_USB, chg->online); + + power_supply_changed(chg->charger); + + return IRQ_HANDLED; +} + +static int pm8916_lbc_charger_probe_dt(struct pm8916_lbc_charger *chg) +{ + struct device *dev = chg->dev; + int ret = 0; + unsigned int tmp; + + ret = device_property_read_u32(dev, "qcom,fast-charge-safe-voltage", &chg->charge_voltage_safe); + if (ret) + return ret; + if (chg->charge_voltage_safe < PM8916_LBC_CHGR_MIN_VOLTAGE) + return -EINVAL; + + chg->charge_voltage_safe = clamp_t(u32, chg->charge_voltage_safe, + PM8916_LBC_CHGR_MIN_VOLTAGE, PM8916_LBC_CHGR_MAX_VOLTAGE); + + tmp = chg->charge_voltage_safe - PM8916_LBC_CHGR_MIN_VOLTAGE; + tmp /= PM8916_LBC_CHGR_VOLTAGE_STEP; + ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_SAFE, tmp); + if (ret) + return ret; + + ret = device_property_read_u32(dev, "qcom,fast-charge-safe-current", &chg->charge_current_safe); + if (ret) + return ret; + if (chg->charge_current_safe < PM8916_LBC_CHGR_MIN_CURRENT) + return -EINVAL; + + chg->charge_current_safe = clamp_t(u32, chg->charge_current_safe, + PM8916_LBC_CHGR_MIN_CURRENT, PM8916_LBC_CHGR_MAX_CURRENT); + + chg->charge_current_max = chg->charge_current_safe; + + tmp = chg->charge_current_safe / PM8916_LBC_CHGR_MIN_CURRENT - 1; + ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_SAFE, tmp); + if (ret) + return ret; + + /* Disable charger timeout. */ + ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, 0x00); + if (ret) + return ret; + + return ret; +} + +static const struct power_supply_desc pm8916_lbc_charger_psy_desc = { + .name = "pm8916-lbc-chgr", + .type = POWER_SUPPLY_TYPE_USB, + .properties = pm8916_lbc_charger_properties, + .num_properties = ARRAY_SIZE(pm8916_lbc_charger_properties), + .get_property = pm8916_lbc_charger_get_property, + .set_property = pm8916_lbc_charger_set_property, + .property_is_writeable = pm8916_lbc_charger_property_is_writeable, +}; + +static int pm8916_lbc_charger_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pm8916_lbc_charger *chg; + struct power_supply_config psy_cfg = {}; + int ret, len, irq; + unsigned int tmp; + + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + chg->dev = dev; + + chg->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chg->regmap) + return -ENODEV; + + len = device_property_count_u32(dev, "reg"); + if (len < 0) + return len; + if (len != 4) + 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; + + ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_PERPH_TYPE, &tmp, 2); + if (ret) + goto comm_error; + if (tmp != PM8916_LBC_CHGR_TYPE) + goto type_error; + + ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_BAT_IF] + PM8916_PERPH_TYPE, &tmp, 2); + if (ret) + goto comm_error; + if (tmp != PM8916_LBC_BAT_IF_TYPE) + goto type_error; + + ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_USB] + PM8916_PERPH_TYPE, &tmp, 2); + if (ret) + goto comm_error; + if (tmp != PM8916_LBC_USB_TYPE) + goto type_error; + + ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_MISC] + PM8916_PERPH_TYPE, &tmp, 2); + if (ret) + goto comm_error; + if (tmp != PM8916_LBC_MISC_TYPE) + goto type_error; + + ret = regmap_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_OPTION, &tmp); + if (ret) + goto comm_error; + if (tmp != PM8916_LBC_CHGR_PMIC_CHARGER) + dev_err_probe(dev, -ENODEV, "The system is using an external charger\n"); + + ret = pm8916_lbc_charger_probe_dt(chg); + if (ret) + dev_err_probe(dev, ret, "Error while parsing device tree\n"); + + psy_cfg.drv_data = chg; + 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)) + return dev_err_probe(dev, PTR_ERR(chg->charger), "Unable to register charger\n"); + + ret = power_supply_get_battery_info(chg->charger, &chg->info); + if (ret) + return dev_err_probe(dev, ret, "Unable to get battery info\n"); + + chg->edev = devm_extcon_dev_allocate(dev, pm8916_lbc_charger_cable); + if (IS_ERR(chg->edev)) + return PTR_ERR(chg->edev); + + ret = devm_extcon_dev_register(dev, chg->edev); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to register extcon device\n"); + + ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp); + if (ret) + goto comm_error; + + chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID); + extcon_set_state_sync(chg->edev, EXTCON_USB, chg->online); + + chg->charge_voltage_max = chg->info->voltage_max_design_uv; + ret = pm8916_lbc_charger_configure(chg); + if (ret) + return ret; + + return 0; + +comm_error: + return dev_err_probe(dev, ret, "Unable to communicate with device\n"); + +type_error: + return dev_err_probe(dev, -ENODEV, "Device reported wrong type: 0x%X\n", tmp); +} + +static const struct of_device_id pm8916_lbc_charger_of_match[] = { + { .compatible = "qcom,pm8916-lbc", }, + {} +}; +MODULE_DEVICE_TABLE(of, pm8916_lbc_charger_of_match); + +static struct platform_driver pm8916_lbc_charger_driver = { + .driver = { + .name = "pm8916-lbc", + .of_match_table = pm8916_lbc_charger_of_match, + }, + .probe = pm8916_lbc_charger_probe, +}; +module_platform_driver(pm8916_lbc_charger_driver); + +MODULE_DESCRIPTION("pm8916 LBC driver"); +MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/pmu_battery.c b/drivers/power/supply/pmu_battery.c index eaab7500d99b..ed83c5e05ca3 100644 --- a/drivers/power/supply/pmu_battery.c +++ b/drivers/power/supply/pmu_battery.c @@ -170,6 +170,7 @@ static int __init pmu_bat_init(void) pbat->bat_desc.properties = pmu_bat_props; pbat->bat_desc.num_properties = ARRAY_SIZE(pmu_bat_props); pbat->bat_desc.get_property = pmu_bat_get_property; + pbat->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; pbat->pbi = &pmu_batteries[i]; psy_cfg.drv_data = pbat; diff --git a/drivers/power/supply/power_supply.h b/drivers/power/supply/power_supply.h index 645eee4d6b6a..8f6a2d44b996 100644 --- a/drivers/power/supply/power_supply.h +++ b/drivers/power/supply/power_supply.h @@ -9,19 +9,55 @@ * Modified: 2004, Oct Szabolcs Gyurko */ +#include <linux/lockdep.h> + struct device; struct device_type; struct power_supply; +extern int power_supply_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp); +extern bool power_supply_has_property(struct power_supply *psy, + enum power_supply_property psp); +extern bool power_supply_ext_has_property(const struct power_supply_ext *ext, + enum power_supply_property psp); + +struct power_supply_ext_registration { + struct list_head list_head; + const struct power_supply_ext *ext; + struct device *dev; + void *data; +}; + +/* Make sure that the macro is a single expression */ +#define power_supply_for_each_extension(pos, psy) \ + if ( ({ lockdep_assert_held(&(psy)->extensions_sem); 0; }) ) \ + ; \ + else \ + list_for_each_entry(pos, &(psy)->extensions, list_head) \ + #ifdef CONFIG_SYSFS -extern void power_supply_init_attrs(struct device_type *dev_type); +extern void __init power_supply_init_attrs(void); extern int power_supply_uevent(const struct device *dev, struct kobj_uevent_env *env); +extern const struct attribute_group *power_supply_attr_groups[]; +extern int power_supply_sysfs_add_extension(struct power_supply *psy, + const struct power_supply_ext *ext, + struct device *dev); +extern void power_supply_sysfs_remove_extension(struct power_supply *psy, + const struct power_supply_ext *ext); #else -static inline void power_supply_init_attrs(struct device_type *dev_type) {} +static inline void power_supply_init_attrs(void) {} +#define power_supply_attr_groups NULL #define power_supply_uevent NULL +static inline int power_supply_sysfs_add_extension(struct power_supply *psy, + const struct power_supply_ext *ext, + struct device *dev) +{ return 0; } +static inline void power_supply_sysfs_remove_extension(struct power_supply *psy, + const struct power_supply_ext *ext) {} #endif /* CONFIG_SYSFS */ @@ -39,3 +75,20 @@ static inline int power_supply_create_triggers(struct power_supply *psy) static inline void power_supply_remove_triggers(struct power_supply *psy) {} #endif /* CONFIG_LEDS_TRIGGERS */ + +#ifdef CONFIG_POWER_SUPPLY_HWMON + +int power_supply_add_hwmon_sysfs(struct power_supply *psy); +void power_supply_remove_hwmon_sysfs(struct power_supply *psy); + +#else + +static inline int power_supply_add_hwmon_sysfs(struct power_supply *psy) +{ + return 0; +} + +static inline +void power_supply_remove_hwmon_sysfs(struct power_supply *psy) {} + +#endif /* CONFIG_POWER_SUPPLY_HWMON */ diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 7c790c41e2fe..9a28381e2607 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -9,6 +9,7 @@ * Modified: 2004, Oct Szabolcs Gyurko */ +#include <linux/cleanup.h> #include <linux/module.h> #include <linux/types.h> #include <linux/init.h> @@ -17,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> @@ -25,14 +25,17 @@ #include "power_supply.h" #include "samsung-sdi-battery.h" -/* exported for the APM Power driver, APM emulation */ -struct class *power_supply_class; -EXPORT_SYMBOL_GPL(power_supply_class); +static const struct class power_supply_class = { + .name = "power_supply", + .dev_uevent = power_supply_uevent, +}; -ATOMIC_NOTIFIER_HEAD(power_supply_notifier); -EXPORT_SYMBOL_GPL(power_supply_notifier); +static BLOCKING_NOTIFIER_HEAD(power_supply_notifier); -static struct device_type power_supply_dev_type; +static const struct device_type power_supply_dev_type = { + .name = "power_supply", + .groups = power_supply_attr_groups, +}; #define POWER_SUPPLY_DEFERRED_REGISTER_TIME msecs_to_jiffies(10) @@ -62,21 +65,19 @@ static bool __power_supply_is_supplied_by(struct power_supply *supplier, return false; } -static int __power_supply_changed_work(struct device *dev, void *data) +static int __power_supply_changed_work(struct power_supply *pst, void *data) { struct power_supply *psy = data; - struct power_supply *pst = dev_get_drvdata(dev); - if (__power_supply_is_supplied_by(psy, pst)) { - if (pst->desc->external_power_changed) - pst->desc->external_power_changed(pst); - } + if (__power_supply_is_supplied_by(psy, pst)) + power_supply_external_power_changed(pst); return 0; } static void power_supply_changed_work(struct work_struct *work) { + int ret; unsigned long flags; struct power_supply *psy = container_of(work, struct power_supply, changed_work); @@ -84,6 +85,16 @@ static void power_supply_changed_work(struct work_struct *work) dev_dbg(&psy->dev, "%s\n", __func__); spin_lock_irqsave(&psy->changed_lock, flags); + + if (unlikely(psy->update_groups)) { + psy->update_groups = false; + spin_unlock_irqrestore(&psy->changed_lock, flags); + ret = sysfs_update_groups(&psy->dev.kobj, power_supply_dev_type.groups); + if (ret) + dev_warn(&psy->dev, "failed to update sysfs groups: %pe\n", ERR_PTR(ret)); + spin_lock_irqsave(&psy->changed_lock, flags); + } + /* * Check 'changed' here to avoid issues due to race between * power_supply_changed() and this routine. In worst case @@ -94,10 +105,9 @@ static void power_supply_changed_work(struct work_struct *work) if (likely(psy->changed)) { psy->changed = false; spin_unlock_irqrestore(&psy->changed_lock, flags); - class_for_each_device(power_supply_class, NULL, psy, - __power_supply_changed_work); + power_supply_for_each_psy(psy, __power_supply_changed_work); power_supply_update_leds(psy); - atomic_notifier_call_chain(&power_supply_notifier, + blocking_notifier_call_chain(&power_supply_notifier, PSY_EVENT_PROP_CHANGED, psy); kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE); spin_lock_irqsave(&psy->changed_lock, flags); @@ -113,6 +123,30 @@ static void power_supply_changed_work(struct work_struct *work) spin_unlock_irqrestore(&psy->changed_lock, flags); } +struct psy_for_each_psy_cb_data { + int (*fn)(struct power_supply *psy, void *data); + void *data; +}; + +static int psy_for_each_psy_cb(struct device *dev, void *data) +{ + struct psy_for_each_psy_cb_data *cb_data = data; + struct power_supply *psy = dev_to_psy(dev); + + return cb_data->fn(psy, cb_data->data); +} + +int power_supply_for_each_psy(void *data, int (*fn)(struct power_supply *psy, void *data)) +{ + struct psy_for_each_psy_cb_data cb_data = { + .fn = fn, + .data = data, + }; + + return class_for_each_device(&power_supply_class, NULL, &cb_data, psy_for_each_psy_cb); +} +EXPORT_SYMBOL_GPL(power_supply_for_each_psy); + void power_supply_changed(struct power_supply *psy) { unsigned long flags; @@ -143,7 +177,7 @@ static void power_supply_deferred_register_work(struct work_struct *work) deferred_register_work.work); if (psy->dev.parent) { - while (!mutex_trylock(&psy->dev.parent->mutex)) { + while (!device_trylock(psy->dev.parent)) { if (psy->removing) return; msleep(10); @@ -153,33 +187,32 @@ static void power_supply_deferred_register_work(struct work_struct *work) power_supply_changed(psy); if (psy->dev.parent) - mutex_unlock(&psy->dev.parent->mutex); + device_unlock(psy->dev.parent); } #ifdef CONFIG_OF -static int __power_supply_populate_supplied_from(struct device *dev, +static int __power_supply_populate_supplied_from(struct power_supply *epsy, void *data) { struct power_supply *psy = data; - struct power_supply *epsy = dev_get_drvdata(dev); - 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; } @@ -188,50 +221,47 @@ static int power_supply_populate_supplied_from(struct power_supply *psy) { int error; - error = class_for_each_device(power_supply_class, NULL, psy, - __power_supply_populate_supplied_from); + error = power_supply_for_each_psy(psy, __power_supply_populate_supplied_from); dev_dbg(&psy->dev, "%s %d\n", __func__, error); return error; } -static int __power_supply_find_supply_from_node(struct device *dev, +static int __power_supply_find_supply_from_node(struct power_supply *epsy, void *data) { - struct device_node *np = data; - struct power_supply *epsy = dev_get_drvdata(dev); + struct fwnode_handle *fwnode = data; - /* returning non-zero breaks out of class_for_each_device loop */ - if (epsy->of_node == np) + /* returning non-zero breaks out of power_supply_for_each_psy loop */ + 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; /* - * class_for_each_device() either returns its own errors or values + * 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 class_for_each_device() returned 1, -EPROBE_DEFER if + * We return 0 if power_supply_for_each_psy() returned 1, -EPROBE_DEFER if * it returned 0, or error as returned by it. */ - error = class_for_each_device(power_supply_class, NULL, supply_node, - __power_supply_find_supply_from_node); + error = power_supply_for_each_psy(supply_node, __power_supply_find_supply_from_node); return error ? (error == 1 ? 0 : error) : -EPROBE_DEFER; } 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 */ @@ -239,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) @@ -309,10 +339,9 @@ struct psy_am_i_supplied_data { unsigned int count; }; -static int __power_supply_am_i_supplied(struct device *dev, void *_data) +static int __power_supply_am_i_supplied(struct power_supply *epsy, void *_data) { union power_supply_propval ret = {0,}; - struct power_supply *epsy = dev_get_drvdata(dev); struct psy_am_i_supplied_data *data = _data; if (__power_supply_is_supplied_by(epsy, data->psy)) { @@ -330,8 +359,7 @@ int power_supply_am_i_supplied(struct power_supply *psy) struct psy_am_i_supplied_data data = { psy, 0 }; int error; - error = class_for_each_device(power_supply_class, NULL, &data, - __power_supply_am_i_supplied); + error = power_supply_for_each_psy(&data, __power_supply_am_i_supplied); dev_dbg(&psy->dev, "%s count %u err %d\n", __func__, data.count, error); @@ -342,12 +370,15 @@ int power_supply_am_i_supplied(struct power_supply *psy) } EXPORT_SYMBOL_GPL(power_supply_am_i_supplied); -static int __power_supply_is_system_supplied(struct device *dev, void *data) +static int __power_supply_is_system_supplied(struct power_supply *psy, void *data) { union power_supply_propval ret = {0,}; - struct power_supply *psy = dev_get_drvdata(dev); unsigned int *count = data; + if (!psy->desc->get_property(psy, POWER_SUPPLY_PROP_SCOPE, &ret)) + if (ret.intval == POWER_SUPPLY_SCOPE_DEVICE) + return 0; + (*count)++; if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY) if (!psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE, @@ -362,12 +393,11 @@ int power_supply_is_system_supplied(void) int error; unsigned int count = 0; - error = class_for_each_device(power_supply_class, NULL, &count, - __power_supply_is_system_supplied); + error = power_supply_for_each_psy(&count, __power_supply_is_system_supplied); /* - * If no power class device was found at all, most probably we are - * running on a desktop system, so assume we are on mains power. + * If no system scope power class device was found at all, most probably we + * are running on a desktop system, so assume we are on mains power. */ if (count == 0) return 1; @@ -382,13 +412,12 @@ struct psy_get_supplier_prop_data { union power_supply_propval *val; }; -static int __power_supply_get_supplier_property(struct device *dev, void *_data) +static int __power_supply_get_supplier_property(struct power_supply *epsy, void *_data) { - struct power_supply *epsy = dev_get_drvdata(dev); struct psy_get_supplier_prop_data *data = _data; if (__power_supply_is_supplied_by(epsy, data->psy)) - if (!epsy->desc->get_property(epsy, data->psp, data->val)) + if (!power_supply_get_property(epsy, data->psp, data->val)) return 1; /* Success */ return 0; /* Continue iterating */ @@ -409,8 +438,7 @@ int power_supply_get_property_from_supplier(struct power_supply *psy, * This function is not intended for use with a supply with multiple * suppliers, we simply pick the first supply to report the psp. */ - ret = class_for_each_device(power_supply_class, NULL, &data, - __power_supply_get_supplier_property); + ret = power_supply_for_each_psy(&data, __power_supply_get_supplier_property); if (ret < 0) return ret; if (ret == 0) @@ -420,23 +448,10 @@ 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; - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); return strcmp(psy->desc->name, name) == 0; } @@ -455,11 +470,11 @@ static int power_supply_match_device_by_name(struct device *dev, const void *dat struct power_supply *power_supply_get_by_name(const char *name) { struct power_supply *psy = NULL; - struct device *dev = class_find_device(power_supply_class, NULL, name, - power_supply_match_device_by_name); + struct device *dev = class_find_device(&power_supply_class, NULL, name, + power_supply_match_device_by_name); if (dev) { - psy = dev_get_drvdata(dev); + psy = dev_to_psy(dev); atomic_inc(&psy->use_cnt); } @@ -476,22 +491,19 @@ EXPORT_SYMBOL_GPL(power_supply_get_by_name); */ void power_supply_put(struct power_supply *psy) { - might_sleep(); - atomic_dec(&psy->use_cnt); put_device(&psy->dev); } 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 @@ -501,30 +513,30 @@ 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_get_drvdata(dev); + psy = dev_to_psy(dev); atomic_inc(&psy->use_cnt); } 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) { @@ -534,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 { @@ -563,37 +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; + 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; + srcnode = dev_fwnode(&psy->dev); + if (!srcnode && psy->dev.parent) + srcnode = dev_fwnode(psy->dev.parent); - fwnode = fwnode_handle_get(of_fwnode_handle(battery_np)); - } else { - err = fwnode_property_get_reference_args( - dev_fwnode(psy->dev.parent), - "monitored-battery", NULL, 0, 0, &args); - if (err) - return err; - - fwnode = args.fwnode; - } + 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) @@ -723,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; @@ -740,35 +733,46 @@ 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; - char *propname; - int i, tab_len, size; + int i, tab_len; - propname = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", index); + char *propname __free(kfree) = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", + index); if (!propname) { power_supply_put_battery_info(psy, info); 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); - kfree(propname); power_supply_put_battery_info(psy, info); err = -EINVAL; goto out_put_node; } - kfree(propname); - 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; - table = info->ocv_table[index] = + info->ocv_table[index] = table = devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL); if (!info->ocv_table[index]) { power_supply_put_battery_info(psy, info); @@ -777,19 +781,37 @@ 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; + } + + propdata = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL); + if (!propdata) { + power_supply_put_battery_info(psy, info); + err = -ENOMEM; + goto out_put_node; + } - info->resist_table_size = len / (2 * sizeof(__be32)); - resist_table = info->resist_table = devm_kcalloc(&psy->dev, + 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), GFP_KERNEL); @@ -800,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: @@ -810,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); @@ -832,6 +853,133 @@ void power_supply_put_battery_info(struct power_supply *psy, } EXPORT_SYMBOL_GPL(power_supply_put_battery_info); +const enum power_supply_property power_supply_battery_info_properties[] = { + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_PRECHARGE_CURRENT, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN, + POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX, + POWER_SUPPLY_PROP_TEMP_ALERT_MIN, + POWER_SUPPLY_PROP_TEMP_ALERT_MAX, + POWER_SUPPLY_PROP_TEMP_MIN, + POWER_SUPPLY_PROP_TEMP_MAX, +}; +EXPORT_SYMBOL_GPL(power_supply_battery_info_properties); + +const size_t power_supply_battery_info_properties_size = ARRAY_SIZE(power_supply_battery_info_properties); +EXPORT_SYMBOL_GPL(power_supply_battery_info_properties_size); + +bool power_supply_battery_info_has_prop(struct power_supply_battery_info *info, + enum power_supply_property psp) +{ + if (!info) + return false; + + switch (psp) { + case POWER_SUPPLY_PROP_TECHNOLOGY: + return info->technology != POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + return info->energy_full_design_uwh >= 0; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + return info->charge_full_design_uah >= 0; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + return info->voltage_min_design_uv >= 0; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + return info->voltage_max_design_uv >= 0; + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + return info->precharge_current_ua >= 0; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return info->charge_term_current_ua >= 0; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return info->constant_charge_current_max_ua >= 0; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return info->constant_charge_voltage_max_uv >= 0; + case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN: + return info->temp_ambient_alert_min > INT_MIN; + case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX: + return info->temp_ambient_alert_max < INT_MAX; + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + return info->temp_alert_min > INT_MIN; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + return info->temp_alert_max < INT_MAX; + case POWER_SUPPLY_PROP_TEMP_MIN: + return info->temp_min > INT_MIN; + case POWER_SUPPLY_PROP_TEMP_MAX: + return info->temp_max < INT_MAX; + default: + return false; + } +} +EXPORT_SYMBOL_GPL(power_supply_battery_info_has_prop); + +int power_supply_battery_info_get_prop(struct power_supply_battery_info *info, + enum power_supply_property psp, + union power_supply_propval *val) +{ + if (!info) + return -EINVAL; + + if (!power_supply_battery_info_has_prop(info, psp)) + return -EINVAL; + + switch (psp) { + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = info->technology; + return 0; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = info->energy_full_design_uwh; + return 0; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = info->charge_full_design_uah; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = info->voltage_min_design_uv; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = info->voltage_max_design_uv; + return 0; + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + val->intval = info->precharge_current_ua; + return 0; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + val->intval = info->charge_term_current_ua; + return 0; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = info->constant_charge_current_max_ua; + return 0; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = info->constant_charge_voltage_max_uv; + return 0; + case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN: + val->intval = info->temp_ambient_alert_min; + return 0; + case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX: + val->intval = info->temp_ambient_alert_max; + return 0; + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + val->intval = info->temp_alert_min; + return 0; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + val->intval = info->temp_alert_max; + return 0; + case POWER_SUPPLY_PROP_TEMP_MIN: + val->intval = info->temp_min; + return 0; + case POWER_SUPPLY_PROP_TEMP_MAX: + val->intval = info->temp_max; + return 0; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(power_supply_battery_info_get_prop); + /** * power_supply_temp2resist_simple() - find the battery internal resistance * percent from temperature @@ -846,7 +994,7 @@ EXPORT_SYMBOL_GPL(power_supply_put_battery_info); * * Return: the battery internal resistance percent */ -int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table, +int power_supply_temp2resist_simple(const struct power_supply_resistance_temp_table *table, int table_len, int temp) { int i, high, low; @@ -887,7 +1035,7 @@ EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple); int power_supply_vbat2ri(struct power_supply_battery_info *info, int vbat_uv, bool charging) { - struct power_supply_vbat_ri_table *vbat2ri; + const struct power_supply_vbat_ri_table *vbat2ri; int table_len; int i, high, low; @@ -935,7 +1083,7 @@ int power_supply_vbat2ri(struct power_supply_battery_info *info, } EXPORT_SYMBOL_GPL(power_supply_vbat2ri); -struct power_supply_maintenance_charge_table * +const struct power_supply_maintenance_charge_table * power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, int index) { @@ -957,7 +1105,7 @@ EXPORT_SYMBOL_GPL(power_supply_get_maintenance_charging_setting); * * Return: the battery capacity. */ -int power_supply_ocv2cap_simple(struct power_supply_battery_ocv_table *table, +int power_supply_ocv2cap_simple(const struct power_supply_battery_ocv_table *table, int table_len, int ocv) { int i, high, low; @@ -982,7 +1130,7 @@ int power_supply_ocv2cap_simple(struct power_supply_battery_ocv_table *table, } EXPORT_SYMBOL_GPL(power_supply_ocv2cap_simple); -struct power_supply_battery_ocv_table * +const struct power_supply_battery_ocv_table * power_supply_find_ocv2cap_table(struct power_supply_battery_info *info, int temp, int *table_len) { @@ -1013,7 +1161,7 @@ EXPORT_SYMBOL_GPL(power_supply_find_ocv2cap_table); int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info, int ocv, int temp) { - struct power_supply_battery_ocv_table *table; + const struct power_supply_battery_ocv_table *table; int table_len; table = power_supply_find_ocv2cap_table(info, temp, &table_len); @@ -1046,41 +1194,187 @@ bool power_supply_battery_bti_in_range(struct power_supply_battery_info *info, } EXPORT_SYMBOL_GPL(power_supply_battery_bti_in_range); -int power_supply_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) +static bool psy_desc_has_property(const struct power_supply_desc *psy_desc, + enum power_supply_property psp) { + bool found = false; + int i; + + for (i = 0; i < psy_desc->num_properties; i++) { + if (psy_desc->properties[i] == psp) { + found = true; + break; + } + } + + return found; +} + +bool power_supply_ext_has_property(const struct power_supply_ext *psy_ext, + enum power_supply_property psp) +{ + int i; + + for (i = 0; i < psy_ext->num_properties; i++) + if (psy_ext->properties[i] == psp) + return true; + + return false; +} + +bool power_supply_has_property(struct power_supply *psy, + enum power_supply_property psp) +{ + struct power_supply_ext_registration *reg; + + if (psy_desc_has_property(psy->desc, psp)) + return true; + + if (power_supply_battery_info_has_prop(psy->battery_info, psp)) + return true; + + power_supply_for_each_extension(reg, psy) { + if (power_supply_ext_has_property(reg->ext, psp)) + return true; + } + + return false; +} + +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; + if (atomic_read(&psy->use_cnt) <= 0) { if (!psy->initialized) return -EAGAIN; return -ENODEV; } - return psy->desc->get_property(psy, psp, val); + 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); + } + } + } + + if (psy_desc_has_property(psy->desc, psp)) + return psy->desc->get_property(psy, psp, val); + else if (power_supply_battery_info_has_prop(psy->battery_info, psp)) + return power_supply_battery_info_get_prop(psy->battery_info, psp, val); + 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) { - if (atomic_read(&psy->use_cnt) <= 0 || !psy->desc->set_property) + struct power_supply_ext_registration *reg; + + if (atomic_read(&psy->use_cnt) <= 0) + return -ENODEV; + + 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); + else + return -ENODEV; + } + } + } + + if (!psy->desc->set_property) return -ENODEV; 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) { - if (atomic_read(&psy->use_cnt) <= 0 || - !psy->desc->property_is_writeable) - return -ENODEV; + struct power_supply_ext_registration *reg; + + power_supply_for_each_extension(reg, psy) { + if (power_supply_ext_has_property(reg->ext, psp)) { + if (reg->ext->property_is_writeable) + return reg->ext->property_is_writeable(psy, reg->ext, + reg->data, psp); + else + return 0; + } + } + + if (!psy->desc->property_is_writeable) + return 0; return psy->desc->property_is_writeable(psy, psp); } -EXPORT_SYMBOL_GPL(power_supply_property_is_writeable); void power_supply_external_power_changed(struct power_supply *psy) { @@ -1098,41 +1392,108 @@ int power_supply_powers(struct power_supply *psy, struct device *dev) } EXPORT_SYMBOL_GPL(power_supply_powers); +static int power_supply_update_sysfs_and_hwmon(struct power_supply *psy) +{ + unsigned long flags; + + spin_lock_irqsave(&psy->changed_lock, flags); + psy->update_groups = true; + spin_unlock_irqrestore(&psy->changed_lock, flags); + + power_supply_changed(psy); + + power_supply_remove_hwmon_sysfs(psy); + return power_supply_add_hwmon_sysfs(psy); +} + +int power_supply_register_extension(struct power_supply *psy, const struct power_supply_ext *ext, + struct device *dev, void *data) +{ + struct power_supply_ext_registration *reg; + size_t i; + int ret; + + if (!psy || !dev || !ext || !ext->name || !ext->properties || !ext->num_properties) + return -EINVAL; + + guard(rwsem_write)(&psy->extensions_sem); + + power_supply_for_each_extension(reg, psy) + if (strcmp(ext->name, reg->ext->name) == 0) + return -EEXIST; + + for (i = 0; i < ext->num_properties; i++) + if (power_supply_has_property(psy, ext->properties[i])) + return -EEXIST; + + reg = kmalloc(sizeof(*reg), GFP_KERNEL); + if (!reg) + return -ENOMEM; + + reg->ext = ext; + reg->dev = dev; + reg->data = data; + list_add(®->list_head, &psy->extensions); + + ret = power_supply_sysfs_add_extension(psy, ext, dev); + if (ret) + goto sysfs_add_failed; + + ret = power_supply_update_sysfs_and_hwmon(psy); + if (ret) + goto sysfs_hwmon_failed; + + return 0; + +sysfs_hwmon_failed: + power_supply_sysfs_remove_extension(psy, ext); +sysfs_add_failed: + list_del(®->list_head); + kfree(reg); + return ret; +} +EXPORT_SYMBOL_GPL(power_supply_register_extension); + +void power_supply_unregister_extension(struct power_supply *psy, const struct power_supply_ext *ext) +{ + struct power_supply_ext_registration *reg; + + guard(rwsem_write)(&psy->extensions_sem); + + power_supply_for_each_extension(reg, psy) { + if (reg->ext == ext) { + list_del(®->list_head); + power_supply_sysfs_remove_extension(psy, ext); + kfree(reg); + power_supply_update_sysfs_and_hwmon(psy); + return; + } + } + + dev_warn(&psy->dev, "Trying to unregister invalid extension"); +} +EXPORT_SYMBOL_GPL(power_supply_unregister_extension); + static void power_supply_dev_release(struct device *dev) { struct power_supply *psy = to_power_supply(dev); + dev_dbg(dev, "%s\n", __func__); kfree(psy); } int power_supply_reg_notifier(struct notifier_block *nb) { - return atomic_notifier_chain_register(&power_supply_notifier, nb); + return blocking_notifier_chain_register(&power_supply_notifier, nb); } EXPORT_SYMBOL_GPL(power_supply_reg_notifier); void power_supply_unreg_notifier(struct notifier_block *nb) { - atomic_notifier_chain_unregister(&power_supply_notifier, nb); + blocking_notifier_chain_unregister(&power_supply_notifier, nb); } EXPORT_SYMBOL_GPL(power_supply_unreg_notifier); -static bool psy_has_property(const struct power_supply_desc *psy_desc, - enum power_supply_property psp) -{ - bool found = false; - int i; - - for (i = 0; i < psy_desc->num_properties; i++) { - if (psy_desc->properties[i] == psp) { - found = true; - break; - } - } - - return found; -} - #ifdef CONFIG_THERMAL static int power_supply_read_temp(struct thermal_zone_device *tzd, int *temp) @@ -1142,7 +1503,7 @@ static int power_supply_read_temp(struct thermal_zone_device *tzd, int ret; WARN_ON(tzd == NULL); - psy = tzd->devdata; + psy = thermal_zone_device_priv(tzd); ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &val); if (ret) return ret; @@ -1153,7 +1514,7 @@ static int power_supply_read_temp(struct thermal_zone_device *tzd, return ret; } -static struct thermal_zone_device_ops psy_tzd_ops = { +static const struct thermal_zone_device_ops psy_tzd_ops = { .get_temp = power_supply_read_temp, }; @@ -1165,9 +1526,13 @@ static int psy_register_thermal(struct power_supply *psy) return 0; /* Register battery zone device psy reports temperature */ - if (psy_has_property(psy->desc, POWER_SUPPLY_PROP_TEMP)) { - psy->tzd = thermal_zone_device_register(psy->desc->name, - 0, 0, psy, &psy_tzd_ops, NULL, 0, 0); + if (psy_desc_has_property(psy->desc, POWER_SUPPLY_PROP_TEMP)) { + /* Prefer our hwmon device and avoid duplicates */ + struct thermal_zone_params tzp = { + .no_hwmon = IS_ENABLED(CONFIG_POWER_SUPPLY_HWMON) + }; + psy->tzd = thermal_tripless_zone_device_register(psy->desc->name, + psy, &psy_tzd_ops, &tzp); if (IS_ERR(psy->tzd)) return PTR_ERR(psy->tzd); ret = thermal_zone_device_enable(psy->tzd); @@ -1186,83 +1551,6 @@ static void psy_unregister_thermal(struct power_supply *psy) thermal_zone_device_unregister(psy->tzd); } -/* thermal cooling device callbacks */ -static int ps_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd, - unsigned long *state) -{ - struct power_supply *psy; - union power_supply_propval val; - int ret; - - psy = tcd->devdata; - ret = power_supply_get_property(psy, - POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, &val); - if (ret) - return ret; - - *state = val.intval; - - return ret; -} - -static int ps_get_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, - unsigned long *state) -{ - struct power_supply *psy; - union power_supply_propval val; - int ret; - - psy = tcd->devdata; - ret = power_supply_get_property(psy, - POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val); - if (ret) - return ret; - - *state = val.intval; - - return ret; -} - -static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, - unsigned long state) -{ - struct power_supply *psy; - union power_supply_propval val; - int ret; - - psy = tcd->devdata; - val.intval = state; - ret = psy->desc->set_property(psy, - POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val); - - return ret; -} - -static const struct thermal_cooling_device_ops psy_tcd_ops = { - .get_max_state = ps_get_max_charge_cntl_limit, - .get_cur_state = ps_get_cur_charge_cntl_limit, - .set_cur_state = ps_set_cur_charge_cntl_limit, -}; - -static int psy_register_cooler(struct power_supply *psy) -{ - /* Register for cooling device if psy can control charging */ - if (psy_has_property(psy->desc, POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT)) { - psy->tcd = thermal_cooling_device_register( - (char *)psy->desc->name, - psy, &psy_tcd_ops); - return PTR_ERR_OR_ZERO(psy->tcd); - } - - return 0; -} - -static void psy_unregister_cooler(struct power_supply *psy) -{ - if (IS_ERR_OR_NULL(psy->tcd)) - return; - thermal_cooling_device_unregister(psy->tcd); -} #else static int psy_register_thermal(struct power_supply *psy) { @@ -1272,37 +1560,23 @@ static int psy_register_thermal(struct power_supply *psy) static void psy_unregister_thermal(struct power_supply *psy) { } - -static int psy_register_cooler(struct power_supply *psy) -{ - return 0; -} - -static void psy_unregister_cooler(struct power_supply *psy) -{ -} #endif static struct power_supply *__must_check __power_supply_register(struct device *parent, const struct power_supply_desc *desc, - const struct power_supply_config *cfg, - bool ws) + const struct power_supply_config *cfg) { struct device *dev; struct power_supply *psy; int rc; - if (!parent) - pr_warn("%s: Expected proper parent device for '%s'\n", - __func__, desc->name); - if (!desc || !desc->name || !desc->properties || !desc->num_properties) return ERR_PTR(-EINVAL); - if (psy_has_property(desc, POWER_SUPPLY_PROP_USB_TYPE) && - (!desc->usb_types || !desc->num_usb_types)) - return ERR_PTR(-EINVAL); + if (!parent) + pr_warn("%s: Expected proper parent device for '%s'\n", + __func__, desc->name); psy = kzalloc(sizeof(*psy), GFP_KERNEL); if (!psy) @@ -1312,17 +1586,16 @@ __power_supply_register(struct device *parent, device_initialize(dev); - dev->class = power_supply_class; + dev->class = &power_supply_class; dev->type = &power_supply_dev_type; dev->parent = parent; dev->release = power_supply_dev_release; 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; psy->supplied_to = cfg->supplied_to; psy->num_supplicants = cfg->num_supplicants; } @@ -1341,12 +1614,26 @@ __power_supply_register(struct device *parent, goto check_supplies_failed; } + /* + * Expose constant battery info, if it is available. While there are + * some chargers accessing constant battery data, we only want to + * expose battery data to userspace for battery devices. + */ + if (desc->type == POWER_SUPPLY_TYPE_BATTERY) { + rc = power_supply_get_battery_info(psy, &psy->battery_info); + if (rc && rc != -ENODEV && rc != -ENOENT) + goto check_supplies_failed; + } + spin_lock_init(&psy->changed_lock); + init_rwsem(&psy->extensions_sem); + INIT_LIST_HEAD(&psy->extensions); + rc = device_add(dev); if (rc) goto device_add_failed; - rc = device_init_wakeup(dev, ws); + rc = device_init_wakeup(dev, cfg ? !cfg->no_wakeup_source : true); if (rc) goto wakeup_init_failed; @@ -1354,17 +1641,15 @@ __power_supply_register(struct device *parent, if (rc) goto register_thermal_failed; - rc = psy_register_cooler(psy); - if (rc) - goto register_cooler_failed; - rc = power_supply_create_triggers(psy); if (rc) goto create_triggers_failed; - rc = power_supply_add_hwmon_sysfs(psy); - if (rc) - goto add_hwmon_sysfs_failed; + scoped_guard(rwsem_read, &psy->extensions_sem) { + rc = power_supply_add_hwmon_sysfs(psy); + if (rc) + goto add_hwmon_sysfs_failed; + } /* * Update use_cnt after any uevents (most notably from device_add()). @@ -1387,8 +1672,6 @@ __power_supply_register(struct device *parent, add_hwmon_sysfs_failed: power_supply_remove_triggers(psy); create_triggers_failed: - psy_unregister_cooler(psy); -register_cooler_failed: psy_unregister_thermal(psy); register_thermal_failed: wakeup_init_failed: @@ -1418,33 +1701,10 @@ struct power_supply *__must_check power_supply_register(struct device *parent, const struct power_supply_desc *desc, const struct power_supply_config *cfg) { - return __power_supply_register(parent, desc, cfg, true); + return __power_supply_register(parent, desc, cfg); } EXPORT_SYMBOL_GPL(power_supply_register); -/** - * power_supply_register_no_ws() - Register new non-waking-source power supply - * @parent: Device to be a parent of power supply's device, usually - * the device which probe function calls this - * @desc: Description of power supply, must be valid through whole - * lifetime of this power supply - * @cfg: Run-time specific configuration accessed during registering, - * may be NULL - * - * Return: A pointer to newly allocated power_supply on success - * or ERR_PTR otherwise. - * Use power_supply_unregister() on returned power_supply pointer to release - * resources. - */ -struct power_supply *__must_check -power_supply_register_no_ws(struct device *parent, - const struct power_supply_desc *desc, - const struct power_supply_config *cfg) -{ - return __power_supply_register(parent, desc, cfg, false); -} -EXPORT_SYMBOL_GPL(power_supply_register_no_ws); - static void devm_power_supply_release(struct device *dev, void *res) { struct power_supply **psy = res; @@ -1477,7 +1737,7 @@ devm_power_supply_register(struct device *parent, if (!ptr) return ERR_PTR(-ENOMEM); - psy = __power_supply_register(parent, desc, cfg, true); + psy = __power_supply_register(parent, desc, cfg); if (IS_ERR(psy)) { devres_free(ptr); } else { @@ -1489,42 +1749,6 @@ devm_power_supply_register(struct device *parent, EXPORT_SYMBOL_GPL(devm_power_supply_register); /** - * devm_power_supply_register_no_ws() - Register managed non-waking-source power supply - * @parent: Device to be a parent of power supply's device, usually - * the device which probe function calls this - * @desc: Description of power supply, must be valid through whole - * lifetime of this power supply - * @cfg: Run-time specific configuration accessed during registering, - * may be NULL - * - * Return: A pointer to newly allocated power_supply on success - * or ERR_PTR otherwise. - * The returned power_supply pointer will be automatically unregistered - * on driver detach. - */ -struct power_supply *__must_check -devm_power_supply_register_no_ws(struct device *parent, - const struct power_supply_desc *desc, - const struct power_supply_config *cfg) -{ - struct power_supply **ptr, *psy; - - ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL); - - if (!ptr) - return ERR_PTR(-ENOMEM); - psy = __power_supply_register(parent, desc, cfg, false); - if (IS_ERR(psy)) { - devres_free(ptr); - } else { - *ptr = psy; - devres_add(parent, ptr); - } - return psy; -} -EXPORT_SYMBOL_GPL(devm_power_supply_register_no_ws); - -/** * power_supply_unregister() - Remove this power supply from system * @psy: Pointer to power supply to unregister * @@ -1540,7 +1764,6 @@ void power_supply_unregister(struct power_supply *psy) sysfs_remove_link(&psy->dev.kobj, "powers"); power_supply_remove_hwmon_sysfs(psy); power_supply_remove_triggers(psy); - psy_unregister_cooler(psy); psy_unregister_thermal(psy); device_init_wakeup(&psy->dev, false); device_unregister(&psy->dev); @@ -1555,27 +1778,19 @@ EXPORT_SYMBOL_GPL(power_supply_get_drvdata); static int __init power_supply_class_init(void) { - power_supply_class = class_create(THIS_MODULE, "power_supply"); - - if (IS_ERR(power_supply_class)) - return PTR_ERR(power_supply_class); - - power_supply_class->dev_uevent = power_supply_uevent; - power_supply_init_attrs(&power_supply_dev_type); - - return 0; + power_supply_init_attrs(); + return class_register(&power_supply_class); } static void __exit power_supply_class_exit(void) { - class_destroy(power_supply_class); + class_unregister(&power_supply_class); } subsys_initcall(power_supply_class_init); module_exit(power_supply_class_exit); MODULE_DESCRIPTION("Universal power supply monitor class"); -MODULE_AUTHOR("Ian Molton <spyro@f2s.com>, " - "Szabolcs Gyurko, " - "Anton Vorontsov <cbou@mail.ru>"); -MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ian Molton <spyro@f2s.com>"); +MODULE_AUTHOR("Szabolcs Gyurko"); +MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>"); diff --git a/drivers/power/supply/power_supply_hwmon.c b/drivers/power/supply/power_supply_hwmon.c index a48aa4afb828..95245e6a6baa 100644 --- a/drivers/power/supply/power_supply_hwmon.c +++ b/drivers/power/supply/power_supply_hwmon.c @@ -7,6 +7,7 @@ #include <linux/hwmon.h> #include <linux/power_supply.h> #include <linux/slab.h> +#include "power_supply.h" struct power_supply_hwmon { struct power_supply *psy; @@ -48,6 +49,18 @@ static int power_supply_hwmon_curr_to_property(u32 attr) } } +static int power_supply_hwmon_power_to_property(u32 attr) +{ + switch (attr) { + case hwmon_power_input: + return POWER_SUPPLY_PROP_POWER_NOW; + case hwmon_power_average: + return POWER_SUPPLY_PROP_POWER_AVG; + default: + return -EINVAL; + } +} + static int power_supply_hwmon_temp_to_property(u32 attr, int channel) { if (channel) { @@ -90,6 +103,8 @@ power_supply_hwmon_to_property(enum hwmon_sensor_types type, return power_supply_hwmon_in_to_property(attr); case hwmon_curr: return power_supply_hwmon_curr_to_property(attr); + case hwmon_power: + return power_supply_hwmon_power_to_property(attr); case hwmon_temp: return power_supply_hwmon_temp_to_property(attr, channel); default: @@ -229,6 +244,11 @@ power_supply_hwmon_read(struct device *dev, enum hwmon_sensor_types type, case hwmon_in: pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 1000); break; + case hwmon_power: + /* + * Power properties are already in microwatts. + */ + break; /* * Temp needs to be converted from 1/10 C to milli-C */ @@ -293,13 +313,14 @@ static const struct hwmon_ops power_supply_hwmon_ops = { .read_string = power_supply_hwmon_read_string, }; -static const struct hwmon_channel_info *power_supply_hwmon_info[] = { +static const struct hwmon_channel_info * const power_supply_hwmon_info[] = { HWMON_CHANNEL_INFO(temp, HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | - HWMON_T_MIN_ALARM, + HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM, HWMON_T_LABEL | HWMON_T_INPUT | @@ -311,6 +332,10 @@ static const struct hwmon_channel_info *power_supply_hwmon_info[] = { HWMON_C_MAX | HWMON_C_INPUT), + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | + HWMON_P_AVERAGE), + HWMON_CHANNEL_INFO(in, HWMON_I_AVERAGE | HWMON_I_MIN | @@ -324,9 +349,28 @@ static const struct hwmon_chip_info power_supply_hwmon_chip_info = { .info = power_supply_hwmon_info, }; +static const enum power_supply_property power_supply_hwmon_props[] = { + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_MAX, + POWER_SUPPLY_PROP_TEMP_MIN, + POWER_SUPPLY_PROP_TEMP_ALERT_MIN, + POWER_SUPPLY_PROP_TEMP_ALERT_MAX, + POWER_SUPPLY_PROP_TEMP_AMBIENT, + POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN, + POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + int power_supply_add_hwmon_sysfs(struct power_supply *psy) { - const struct power_supply_desc *desc = psy->desc; struct power_supply_hwmon *psyhw; struct device *dev = &psy->dev; struct device *hwmon; @@ -352,30 +396,11 @@ int power_supply_add_hwmon_sysfs(struct power_supply *psy) goto error; } - for (i = 0; i < desc->num_properties; i++) { - const enum power_supply_property prop = desc->properties[i]; - - switch (prop) { - case POWER_SUPPLY_PROP_CURRENT_AVG: - case POWER_SUPPLY_PROP_CURRENT_MAX: - case POWER_SUPPLY_PROP_CURRENT_NOW: - case POWER_SUPPLY_PROP_TEMP: - case POWER_SUPPLY_PROP_TEMP_MAX: - case POWER_SUPPLY_PROP_TEMP_MIN: - case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: - case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: - case POWER_SUPPLY_PROP_TEMP_AMBIENT: - case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN: - case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX: - case POWER_SUPPLY_PROP_VOLTAGE_AVG: - case POWER_SUPPLY_PROP_VOLTAGE_MIN: - case POWER_SUPPLY_PROP_VOLTAGE_MAX: - case POWER_SUPPLY_PROP_VOLTAGE_NOW: + for (i = 0; i < ARRAY_SIZE(power_supply_hwmon_props); i++) { + const enum power_supply_property prop = power_supply_hwmon_props[i]; + + if (power_supply_has_property(psy, prop)) set_bit(prop, psyhw->props); - break; - default: - break; - } } name = psy->desc->name; diff --git a/drivers/power/supply/power_supply_leds.c b/drivers/power/supply/power_supply_leds.c index d69880cc3593..f4a7e566bea1 100644 --- a/drivers/power/supply/power_supply_leds.c +++ b/drivers/power/supply/power_supply_leds.c @@ -13,16 +13,87 @@ #include <linux/device.h> #include <linux/power_supply.h> #include <linux/slab.h> +#include <linux/leds.h> #include "power_supply.h" /* Battery specific LEDs triggers. */ +struct power_supply_led_trigger { + struct led_trigger trig; + struct power_supply *psy; +}; + +#define trigger_to_psy_trigger(trigger) \ + container_of(trigger, struct power_supply_led_trigger, trig) + +static int power_supply_led_trigger_activate(struct led_classdev *led_cdev) +{ + struct power_supply_led_trigger *psy_trig = + trigger_to_psy_trigger(led_cdev->trigger); + + /* Sync current power-supply state to LED being activated */ + power_supply_update_leds(psy_trig->psy); + return 0; +} + +static int power_supply_register_led_trigger(struct power_supply *psy, + const char *name_template, + struct led_trigger **tp, int *err) +{ + struct power_supply_led_trigger *psy_trig; + int ret = -ENOMEM; + + /* Bail on previous errors */ + if (err && *err) + return *err; + + psy_trig = kzalloc(sizeof(*psy_trig), GFP_KERNEL); + if (!psy_trig) + goto err_free_trigger; + + psy_trig->trig.name = kasprintf(GFP_KERNEL, name_template, psy->desc->name); + if (!psy_trig->trig.name) + goto err_free_trigger; + + psy_trig->trig.activate = power_supply_led_trigger_activate; + psy_trig->psy = psy; + + ret = led_trigger_register(&psy_trig->trig); + if (ret) + goto err_free_name; + + *tp = &psy_trig->trig; + return 0; + +err_free_name: + kfree(psy_trig->trig.name); +err_free_trigger: + kfree(psy_trig); + if (err) + *err = ret; + + return ret; +} + +static void power_supply_unregister_led_trigger(struct led_trigger *trig) +{ + struct power_supply_led_trigger *psy_trig; + + if (!trig) + return; + + psy_trig = trigger_to_psy_trigger(trig); + led_trigger_unregister(&psy_trig->trig); + kfree(psy_trig->trig.name); + kfree(psy_trig); +} + static void power_supply_update_bat_leds(struct power_supply *psy) { union power_supply_propval status; - unsigned long delay_on = 0; - unsigned long delay_off = 0; + unsigned int intensity_green[3] = { 0, 255, 0 }; + unsigned int intensity_orange[3] = { 255, 128, 0 }; if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status)) return; @@ -31,81 +102,66 @@ static void power_supply_update_bat_leds(struct power_supply *psy) switch (status.intval) { case POWER_SUPPLY_STATUS_FULL: - led_trigger_event(psy->charging_full_trig, LED_FULL); + led_trigger_event(psy->trig, LED_FULL); led_trigger_event(psy->charging_trig, LED_OFF); led_trigger_event(psy->full_trig, LED_FULL); - led_trigger_event(psy->charging_blink_full_solid_trig, - LED_FULL); + /* Going from blink to LED on requires a LED_OFF event to stop blink */ + led_trigger_event(psy->charging_blink_full_solid_trig, LED_OFF); + led_trigger_event(psy->charging_blink_full_solid_trig, LED_FULL); + led_mc_trigger_event(psy->charging_orange_full_green_trig, + intensity_green, + ARRAY_SIZE(intensity_green), + LED_FULL); break; case POWER_SUPPLY_STATUS_CHARGING: - led_trigger_event(psy->charging_full_trig, LED_FULL); + led_trigger_event(psy->trig, LED_FULL); led_trigger_event(psy->charging_trig, LED_FULL); led_trigger_event(psy->full_trig, LED_OFF); - led_trigger_blink(psy->charging_blink_full_solid_trig, - &delay_on, &delay_off); + led_trigger_blink(psy->charging_blink_full_solid_trig, 0, 0); + led_mc_trigger_event(psy->charging_orange_full_green_trig, + intensity_orange, + ARRAY_SIZE(intensity_orange), + LED_FULL); break; default: - led_trigger_event(psy->charging_full_trig, LED_OFF); + led_trigger_event(psy->trig, LED_OFF); led_trigger_event(psy->charging_trig, LED_OFF); led_trigger_event(psy->full_trig, LED_OFF); led_trigger_event(psy->charging_blink_full_solid_trig, LED_OFF); + led_trigger_event(psy->charging_orange_full_green_trig, + LED_OFF); break; } } -static int power_supply_create_bat_triggers(struct power_supply *psy) +static void power_supply_remove_bat_triggers(struct power_supply *psy) { - psy->charging_full_trig_name = kasprintf(GFP_KERNEL, - "%s-charging-or-full", psy->desc->name); - if (!psy->charging_full_trig_name) - goto charging_full_failed; - - psy->charging_trig_name = kasprintf(GFP_KERNEL, - "%s-charging", psy->desc->name); - if (!psy->charging_trig_name) - goto charging_failed; - - psy->full_trig_name = kasprintf(GFP_KERNEL, "%s-full", psy->desc->name); - if (!psy->full_trig_name) - goto full_failed; - - psy->charging_blink_full_solid_trig_name = kasprintf(GFP_KERNEL, - "%s-charging-blink-full-solid", psy->desc->name); - if (!psy->charging_blink_full_solid_trig_name) - goto charging_blink_full_solid_failed; - - led_trigger_register_simple(psy->charging_full_trig_name, - &psy->charging_full_trig); - led_trigger_register_simple(psy->charging_trig_name, - &psy->charging_trig); - led_trigger_register_simple(psy->full_trig_name, - &psy->full_trig); - led_trigger_register_simple(psy->charging_blink_full_solid_trig_name, - &psy->charging_blink_full_solid_trig); - - return 0; - -charging_blink_full_solid_failed: - kfree(psy->full_trig_name); -full_failed: - kfree(psy->charging_trig_name); -charging_failed: - kfree(psy->charging_full_trig_name); -charging_full_failed: - return -ENOMEM; + power_supply_unregister_led_trigger(psy->trig); + power_supply_unregister_led_trigger(psy->charging_trig); + power_supply_unregister_led_trigger(psy->full_trig); + power_supply_unregister_led_trigger(psy->charging_blink_full_solid_trig); + power_supply_unregister_led_trigger(psy->charging_orange_full_green_trig); } -static void power_supply_remove_bat_triggers(struct power_supply *psy) +static int power_supply_create_bat_triggers(struct power_supply *psy) { - led_trigger_unregister_simple(psy->charging_full_trig); - led_trigger_unregister_simple(psy->charging_trig); - led_trigger_unregister_simple(psy->full_trig); - led_trigger_unregister_simple(psy->charging_blink_full_solid_trig); - kfree(psy->charging_blink_full_solid_trig_name); - kfree(psy->full_trig_name); - kfree(psy->charging_trig_name); - kfree(psy->charging_full_trig_name); + int err = 0; + + power_supply_register_led_trigger(psy, "%s-charging-or-full", + &psy->trig, &err); + power_supply_register_led_trigger(psy, "%s-charging", + &psy->charging_trig, &err); + power_supply_register_led_trigger(psy, "%s-full", + &psy->full_trig, &err); + power_supply_register_led_trigger(psy, "%s-charging-blink-full-solid", + &psy->charging_blink_full_solid_trig, &err); + power_supply_register_led_trigger(psy, "%s-charging-orange-full-green", + &psy->charging_orange_full_green_trig, &err); + if (err) + power_supply_remove_bat_triggers(psy); + + return err; } /* Generated power specific LEDs triggers. */ @@ -120,27 +176,19 @@ static void power_supply_update_gen_leds(struct power_supply *psy) dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval); if (online.intval) - led_trigger_event(psy->online_trig, LED_FULL); + led_trigger_event(psy->trig, LED_FULL); else - led_trigger_event(psy->online_trig, LED_OFF); + led_trigger_event(psy->trig, LED_OFF); } static int power_supply_create_gen_triggers(struct power_supply *psy) { - psy->online_trig_name = kasprintf(GFP_KERNEL, "%s-online", - psy->desc->name); - if (!psy->online_trig_name) - return -ENOMEM; - - led_trigger_register_simple(psy->online_trig_name, &psy->online_trig); - - return 0; + return power_supply_register_led_trigger(psy, "%s-online", &psy->trig, NULL); } static void power_supply_remove_gen_triggers(struct power_supply *psy) { - led_trigger_unregister_simple(psy->online_trig); - kfree(psy->online_trig_name); + power_supply_unregister_led_trigger(psy->trig); } /* Choice what triggers to create&update. */ diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 6ca7d3985a40..198405f7126f 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -15,6 +15,7 @@ #include <linux/power_supply.h> #include <linux/slab.h> #include <linux/stat.h> +#include <linux/string_helpers.h> #include "power_supply.h" @@ -32,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, \ } @@ -98,6 +99,7 @@ static const char * const POWER_SUPPLY_HEALTH_TEXT[] = { [POWER_SUPPLY_HEALTH_OVERHEAT] = "Overheat", [POWER_SUPPLY_HEALTH_DEAD] = "Dead", [POWER_SUPPLY_HEALTH_OVERVOLTAGE] = "Over voltage", + [POWER_SUPPLY_HEALTH_UNDERVOLTAGE] = "Under voltage", [POWER_SUPPLY_HEALTH_UNSPEC_FAILURE] = "Unspecified failure", [POWER_SUPPLY_HEALTH_COLD] = "Cold", [POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE] = "Watchdog timer expire", @@ -108,6 +110,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[] = { @@ -136,12 +140,13 @@ 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[] = { +static struct power_supply_attr power_supply_attrs[] __ro_after_init = { /* Properties of type `int' */ POWER_SUPPLY_ENUM_ATTR(STATUS), POWER_SUPPLY_ENUM_ATTR(CHARGE_TYPE), @@ -181,6 +186,8 @@ static struct power_supply_attr power_supply_attrs[] = { POWER_SUPPLY_ATTR(CHARGE_CONTROL_START_THRESHOLD), POWER_SUPPLY_ATTR(CHARGE_CONTROL_END_THRESHOLD), POWER_SUPPLY_ENUM_ATTR(CHARGE_BEHAVIOUR), + /* Same enum value texts as "charge_type" without the 's' at the end */ + _POWER_SUPPLY_ENUM_ATTR(CHARGE_TYPES, POWER_SUPPLY_CHARGE_TYPE_TEXT), POWER_SUPPLY_ATTR(INPUT_CURRENT_LIMIT), POWER_SUPPLY_ATTR(INPUT_VOLTAGE_LIMIT), POWER_SUPPLY_ATTR(INPUT_POWER_LIMIT), @@ -208,7 +215,7 @@ static struct power_supply_attr power_supply_attrs[] = { POWER_SUPPLY_ATTR(TIME_TO_FULL_NOW), POWER_SUPPLY_ATTR(TIME_TO_FULL_AVG), POWER_SUPPLY_ENUM_ATTR(TYPE), - POWER_SUPPLY_ATTR(USB_TYPE), + POWER_SUPPLY_ENUM_ATTR(USB_TYPE), POWER_SUPPLY_ENUM_ATTR(SCOPE), POWER_SUPPLY_ATTR(PRECHARGE_CURRENT), POWER_SUPPLY_ATTR(CHARGE_TERM_CURRENT), @@ -216,16 +223,19 @@ static struct power_supply_attr power_supply_attrs[] = { POWER_SUPPLY_ATTR(MANUFACTURE_YEAR), POWER_SUPPLY_ATTR(MANUFACTURE_MONTH), POWER_SUPPLY_ATTR(MANUFACTURE_DAY), + POWER_SUPPLY_ATTR(INTERNAL_RESISTANCE), + POWER_SUPPLY_ATTR(STATE_OF_HEALTH), /* Properties of type `const char *' */ POWER_SUPPLY_ATTR(MODEL_NAME), POWER_SUPPLY_ATTR(MANUFACTURER), POWER_SUPPLY_ATTR(SERIAL_NUMBER), }; +#define POWER_SUPPLY_ATTR_CNT ARRAY_SIZE(power_supply_attrs) static struct attribute * -__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1]; +__power_supply_attrs[POWER_SUPPLY_ATTR_CNT + 1] __ro_after_init; -static struct power_supply_attr *to_ps_attr(struct device_attribute *attr) +static const struct power_supply_attr *to_ps_attr(struct device_attribute *attr) { return container_of(attr, struct power_supply_attr, dev_attr); } @@ -235,31 +245,57 @@ static enum power_supply_property dev_attr_psp(struct device_attribute *attr) return to_ps_attr(attr) - power_supply_attrs; } -static ssize_t power_supply_show_usb_type(struct device *dev, - const struct power_supply_desc *desc, - union power_supply_propval *value, - char *buf) +static void power_supply_escape_spaces(const char *str, char *buf, size_t bufsize) +{ + strscpy(buf, str, bufsize); + strreplace(buf, ' ', '_'); +} + +static int power_supply_match_string(const char * const *array, size_t n, const char *s) { - enum power_supply_usb_type usb_type; + int ret; + + /* First try an exact match */ + ret = __sysfs_match_string(array, n, s); + if (ret >= 0) + return ret; + + /* Second round, try matching with spaces replaced by '_' */ + for (size_t i = 0; i < n; i++) { + char buf[32]; + + power_supply_escape_spaces(array[i], buf, sizeof(buf)); + if (sysfs_streq(buf, s)) + return i; + } + + return -EINVAL; +} + +static ssize_t power_supply_show_enum_with_available( + struct device *dev, const char * const labels[], int label_count, + unsigned int available_values, int value, char *buf) +{ + bool match = false, available, active; + char escaped_label[32]; ssize_t count = 0; - bool match = false; int i; - for (i = 0; i < desc->num_usb_types; ++i) { - usb_type = desc->usb_types[i]; + for (i = 0; i < label_count; i++) { + available = available_values & BIT(i); + active = i == value; + power_supply_escape_spaces(labels[i], escaped_label, sizeof(escaped_label)); - if (value->intval == usb_type) { - count += sprintf(buf + count, "[%s] ", - POWER_SUPPLY_USB_TYPE_TEXT[usb_type]); + if (available && active) { + count += sysfs_emit_at(buf, count, "[%s] ", escaped_label); match = true; - } else { - count += sprintf(buf + count, "%s ", - POWER_SUPPLY_USB_TYPE_TEXT[usb_type]); + } else if (available) { + count += sysfs_emit_at(buf, count, "%s ", escaped_label); } } if (!match) { - dev_warn(dev, "driver reporting unsupported connected type\n"); + dev_warn(dev, "driver reporting unavailable enum value %d\n", value); return -EINVAL; } @@ -269,12 +305,56 @@ static ssize_t power_supply_show_usb_type(struct device *dev, return count; } -static ssize_t power_supply_show_property(struct device *dev, - struct device_attribute *attr, - char *buf) { +static ssize_t power_supply_show_charge_behaviour(struct device *dev, + struct power_supply *psy, + union power_supply_propval *value, + 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_BEHAVIOUR)) + return power_supply_charge_behaviour_show(dev, + reg->ext->charge_behaviours, + value->intval, buf); + } + } + + return power_supply_charge_behaviour_show(dev, psy->desc->charge_behaviours, + 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, + char *buf) +{ ssize_t ret; - struct power_supply *psy = dev_get_drvdata(dev); - struct power_supply_attr *ps_attr = to_ps_attr(attr); + struct power_supply *psy = dev_to_psy(dev); + const struct power_supply_attr *ps_attr = to_ps_attr(attr); enum power_supply_property psp = dev_attr_psp(attr); union power_supply_propval value; @@ -285,9 +365,10 @@ static ssize_t power_supply_show_property(struct device *dev, if (ret < 0) { if (ret == -ENODATA) - dev_dbg(dev, "driver has no data for `%s' property\n", + dev_dbg_ratelimited(dev, + "driver has no data for `%s' property\n", attr->attr.name); - else if (ret != -ENODEV && ret != -EAGAIN) + else if (ret != -ENODEV && ret != -EAGAIN && ret != -EINVAL) dev_err_ratelimited(dev, "driver failed to report `%s' property: %zd\n", attr->attr.name, ret); @@ -295,39 +376,60 @@ static ssize_t power_supply_show_property(struct device *dev, } } - if (ps_attr->text_values_len > 0 && - value.intval < ps_attr->text_values_len && value.intval >= 0) { - return sprintf(buf, "%s\n", ps_attr->text_values[value.intval]); - } - switch (psp) { case POWER_SUPPLY_PROP_USB_TYPE: - ret = power_supply_show_usb_type(dev, psy->desc, - &value, buf); + ret = power_supply_show_enum_with_available( + dev, POWER_SUPPLY_USB_TYPE_TEXT, + ARRAY_SIZE(POWER_SUPPLY_USB_TYPE_TEXT), + psy->desc->usb_types, value.intval, buf); + break; + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + if (uevent) /* no possible values in uevents */ + goto default_format; + ret = power_supply_show_charge_behaviour(dev, psy, &value, buf); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPES: + if (uevent) /* no possible values in uevents */ + goto default_format; + ret = power_supply_show_charge_types(dev, psy, + value.intval, buf); break; case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER: - ret = sprintf(buf, "%s\n", value.strval); + ret = sysfs_emit(buf, "%s\n", value.strval); break; default: - ret = sprintf(buf, "%d\n", value.intval); +default_format: + if (ps_attr->text_values_len > 0 && + value.intval < ps_attr->text_values_len && value.intval >= 0) { + ret = sysfs_emit(buf, "%s\n", ps_attr->text_values[value.intval]); + } else { + ret = sysfs_emit(buf, "%d\n", value.intval); + } } return ret; } +static ssize_t power_supply_show_property(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return power_supply_format_property(dev, false, attr, buf); +} + static ssize_t power_supply_store_property(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { ssize_t ret; - struct power_supply *psy = dev_get_drvdata(dev); - struct power_supply_attr *ps_attr = to_ps_attr(attr); + struct power_supply *psy = dev_to_psy(dev); + const struct power_supply_attr *ps_attr = to_ps_attr(attr); enum power_supply_property psp = dev_attr_psp(attr); union power_supply_propval value; ret = -EINVAL; if (ps_attr->text_values_len > 0) { - ret = __sysfs_match_string(ps_attr->text_values, - ps_attr->text_values_len, buf); + ret = power_supply_match_string(ps_attr->text_values, + ps_attr->text_values_len, buf); } /* @@ -358,9 +460,8 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj, int attrno) { struct device *dev = kobj_to_dev(kobj); - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); umode_t mode = S_IRUSR | S_IRGRP | S_IROTH; - int i; if (!power_supply_attrs[attrno].prop_name) return 0; @@ -368,16 +469,12 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj, if (attrno == POWER_SUPPLY_PROP_TYPE) return mode; - for (i = 0; i < psy->desc->num_properties; i++) { - int property = psy->desc->properties[i]; - - if (property == attrno) { - if (psy->desc->property_is_writeable && - psy->desc->property_is_writeable(psy, property) > 0) - mode |= S_IWUSR; + guard(rwsem_read)(&psy->extensions_sem); - return mode; - } + if (power_supply_has_property(psy, attrno)) { + if (power_supply_property_is_writeable(psy, attrno) > 0) + mode |= S_IWUSR; + return mode; } return 0; @@ -388,25 +485,25 @@ static const struct attribute_group power_supply_attr_group = { .is_visible = power_supply_attr_is_visible, }; -static const struct attribute_group *power_supply_attr_groups[] = { - &power_supply_attr_group, - NULL, +static struct attribute *power_supply_extension_attrs[] = { + NULL }; -static void str_to_lower(char *str) -{ - while (*str) { - *str = tolower(*str); - str++; - } -} +static const struct attribute_group power_supply_extension_group = { + .name = "extensions", + .attrs = power_supply_extension_attrs, +}; + +const struct attribute_group *power_supply_attr_groups[] = { + &power_supply_attr_group, + &power_supply_extension_group, + NULL +}; -void power_supply_init_attrs(struct device_type *dev_type) +void __init power_supply_init_attrs(void) { int i; - dev_type->groups = power_supply_attr_groups; - for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++) { struct device_attribute *attr; @@ -415,7 +512,8 @@ void power_supply_init_attrs(struct device_type *dev_type) __func__, i); sprintf(power_supply_attrs[i].attr_name, "_err_%d", i); } else { - str_to_lower(power_supply_attrs[i].attr_name); + string_lower(power_supply_attrs[i].attr_name, + power_supply_attrs[i].attr_name); } attr = &power_supply_attrs[i].dev_attr; @@ -438,8 +536,8 @@ static int add_prop_uevent(const struct device *dev, struct kobj_uevent_env *env pwr_attr = &power_supply_attrs[prop]; dev_attr = &pwr_attr->dev_attr; - ret = power_supply_show_property((struct device *)dev, dev_attr, prop_buf); - if (ret == -ENODEV || ret == -ENODATA) { + ret = power_supply_format_property((struct device *)dev, true, dev_attr, prop_buf); + if (ret == -ENODEV || ret == -ENODATA || ret == -EINVAL) { /* * When a battery is absent, we expect -ENODEV. Don't abort; * send the uevent with at least the PRESENT=0 property @@ -460,7 +558,7 @@ static int add_prop_uevent(const struct device *dev, struct kobj_uevent_env *env int power_supply_uevent(const struct device *dev, struct kobj_uevent_env *env) { - const struct power_supply *psy = dev_get_drvdata(dev); + const struct power_supply *psy = dev_to_psy(dev); int ret = 0, j; char *prop_buf; @@ -473,6 +571,13 @@ int power_supply_uevent(const struct device *dev, struct kobj_uevent_env *env) if (ret) return ret; + /* + * Kernel generates KOBJ_REMOVE uevent in device removal path, after + * resources have been freed. Exit early to avoid use-after-free. + */ + if (psy->removing) + return 0; + prop_buf = (char *)get_zeroed_page(GFP_KERNEL); if (!prop_buf) return -ENOMEM; @@ -481,9 +586,8 @@ int power_supply_uevent(const struct device *dev, struct kobj_uevent_env *env) if (ret) goto out; - for (j = 0; j < psy->desc->num_properties; j++) { - ret = add_prop_uevent(dev, env, psy->desc->properties[j], - prop_buf); + for (j = 0; j < POWER_SUPPLY_ATTR_CNT; j++) { + ret = add_prop_uevent(dev, env, j, prop_buf); if (ret) goto out; } @@ -499,33 +603,10 @@ ssize_t power_supply_charge_behaviour_show(struct device *dev, enum power_supply_charge_behaviour current_behaviour, char *buf) { - bool match = false, available, active; - ssize_t count = 0; - int i; - - for (i = 0; i < ARRAY_SIZE(POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT); i++) { - available = available_behaviours & BIT(i); - active = i == current_behaviour; - - if (available && active) { - count += sysfs_emit_at(buf, count, "[%s] ", - POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[i]); - match = true; - } else if (available) { - count += sysfs_emit_at(buf, count, "%s ", - POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[i]); - } - } - - if (!match) { - dev_warn(dev, "driver reporting unsupported charge behaviour\n"); - return -EINVAL; - } - - if (count) - buf[count - 1] = '\n'; - - return count; + return power_supply_show_enum_with_available( + dev, POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT, + ARRAY_SIZE(POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT), + available_behaviours, current_behaviour, buf); } EXPORT_SYMBOL_GPL(power_supply_charge_behaviour_show); @@ -542,3 +623,44 @@ int power_supply_charge_behaviour_parse(unsigned int available_behaviours, const return -EINVAL; } EXPORT_SYMBOL_GPL(power_supply_charge_behaviour_parse); + +ssize_t power_supply_charge_types_show(struct device *dev, + unsigned int available_types, + enum power_supply_charge_type current_type, + char *buf) +{ + return power_supply_show_enum_with_available( + dev, POWER_SUPPLY_CHARGE_TYPE_TEXT, + ARRAY_SIZE(POWER_SUPPLY_CHARGE_TYPE_TEXT), + available_types, current_type, buf); +} +EXPORT_SYMBOL_GPL(power_supply_charge_types_show); + +int power_supply_charge_types_parse(unsigned int available_types, const char *buf) +{ + int i = power_supply_match_string(POWER_SUPPLY_CHARGE_TYPE_TEXT, + ARRAY_SIZE(POWER_SUPPLY_CHARGE_TYPE_TEXT), + buf); + + if (i < 0) + return i; + + if (available_types & BIT(i)) + return i; + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(power_supply_charge_types_parse); + +int power_supply_sysfs_add_extension(struct power_supply *psy, const struct power_supply_ext *ext, + struct device *dev) +{ + return sysfs_add_link_to_group(&psy->dev.kobj, power_supply_extension_group.name, + &dev->kobj, ext->name); +} + +void power_supply_sysfs_remove_extension(struct power_supply *psy, + const struct power_supply_ext *ext) +{ + sysfs_remove_link_from_group(&psy->dev.kobj, power_supply_extension_group.name, ext->name); +} diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c new file mode 100644 index 000000000000..c8028606bba0 --- /dev/null +++ b/drivers/power/supply/qcom_battmgr.c @@ -0,0 +1,1738 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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> +#include <linux/units.h> + +#define BATTMGR_CHEMISTRY_LEN 4 +#define BATTMGR_STRING_LEN 128 + +enum qcom_battmgr_variant { + QCOM_BATTMGR_SC8280XP, + QCOM_BATTMGR_SM8350, + QCOM_BATTMGR_SM8550, + QCOM_BATTMGR_X1E80100, +}; + +#define BATTMGR_BAT_STATUS 0x1 + +#define BATTMGR_REQUEST_NOTIFICATION 0x4 + +#define BATTMGR_NOTIFICATION 0x7 +#define NOTIF_BAT_PROPERTY 0x30 +#define NOTIF_USB_PROPERTY 0x32 +#define NOTIF_WLS_PROPERTY 0x34 +#define NOTIF_BAT_STATUS 0x80 +#define NOTIF_BAT_INFO 0x81 +#define NOTIF_BAT_CHARGING_STATE 0x83 + +#define BATTMGR_BAT_INFO 0x9 + +#define BATTMGR_BAT_DISCHARGE_TIME 0xc + +#define BATTMGR_BAT_CHARGE_TIME 0xd + +#define BATTMGR_BAT_PROPERTY_GET 0x30 +#define BATTMGR_BAT_PROPERTY_SET 0x31 +#define BATT_STATUS 0 +#define BATT_HEALTH 1 +#define BATT_PRESENT 2 +#define BATT_CHG_TYPE 3 +#define BATT_CAPACITY 4 +#define BATT_SOH 5 +#define BATT_VOLT_OCV 6 +#define BATT_VOLT_NOW 7 +#define BATT_VOLT_MAX 8 +#define BATT_CURR_NOW 9 +#define BATT_CHG_CTRL_LIM 10 +#define BATT_CHG_CTRL_LIM_MAX 11 +#define BATT_TEMP 12 +#define BATT_TECHNOLOGY 13 +#define BATT_CHG_COUNTER 14 +#define BATT_CYCLE_COUNT 15 +#define BATT_CHG_FULL_DESIGN 16 +#define BATT_CHG_FULL 17 +#define BATT_MODEL_NAME 18 +#define BATT_TTF_AVG 19 +#define BATT_TTE_AVG 20 +#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 +#define USB_ONLINE 0 +#define USB_VOLT_NOW 1 +#define USB_VOLT_MAX 2 +#define USB_CURR_NOW 3 +#define USB_CURR_MAX 4 +#define USB_INPUT_CURR_LIMIT 5 +#define USB_TYPE 6 +#define USB_ADAP_TYPE 7 +#define USB_MOISTURE_DET_EN 8 +#define USB_MOISTURE_DET_STS 9 + +#define BATTMGR_WLS_PROPERTY_GET 0x34 +#define BATTMGR_WLS_PROPERTY_SET 0x35 +#define WLS_ONLINE 0 +#define WLS_VOLT_NOW 1 +#define WLS_VOLT_MAX 2 +#define WLS_CURR_NOW 3 +#define WLS_CURR_MAX 4 +#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; + __le32 power_state; + __le32 low_capacity; + __le32 high_capacity; +}; + +struct qcom_battmgr_property_request { + struct pmic_glink_hdr hdr; + __le32 battery; + __le32 property; + __le32 value; +}; + +struct qcom_battmgr_update_request { + struct pmic_glink_hdr hdr; + __le32 battery_id; +}; + +struct qcom_battmgr_charge_time_request { + struct pmic_glink_hdr hdr; + __le32 battery_id; + __le32 percent; + __le32 reserved; +}; + +struct qcom_battmgr_discharge_time_request { + struct pmic_glink_hdr hdr; + __le32 battery_id; + __le32 rate; /* 0 for current rate */ + __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 { + struct { + __le32 property; + __le32 value; + __le32 result; + } intval; + struct { + __le32 property; + char model[BATTMGR_STRING_LEN]; + } strval; + struct { + /* + * 0: mWh + * 1: mAh + */ + __le32 power_unit; + __le32 design_capacity; + __le32 last_full_capacity; + /* + * 0 nonrechargable + * 1 rechargable + */ + __le32 battery_tech; + __le32 design_voltage; /* mV */ + __le32 capacity_low; + __le32 capacity_warning; + __le32 cycle_count; + /* thousandth of percent */ + __le32 accuracy; + __le32 max_sample_time_ms; + __le32 min_sample_time_ms; + __le32 max_average_interval_ms; + __le32 min_average_interval_ms; + /* granularity between low and warning */ + __le32 capacity_granularity1; + /* granularity between warning and full */ + __le32 capacity_granularity2; + /* + * 0: no + * 1: cold + * 2: hot + */ + __le32 swappable; + __le32 capabilities; + char model_number[BATTMGR_STRING_LEN]; + char serial_number[BATTMGR_STRING_LEN]; + char battery_type[BATTMGR_STRING_LEN]; + char oem_info[BATTMGR_STRING_LEN]; + char battery_chemistry[BATTMGR_CHEMISTRY_LEN]; + char uid[BATTMGR_STRING_LEN]; + __le32 critical_bias; + u8 day; + u8 month; + __le16 year; + __le32 battery_id; + } info; + struct { + /* + * BIT(0) discharging + * BIT(1) charging + * BIT(2) critical low + */ + __le32 battery_state; + /* mWh or mAh, based on info->power_unit */ + __le32 capacity; + __le32 rate; + /* mv */ + __le32 battery_voltage; + /* + * BIT(0) power online + * BIT(1) discharging + * BIT(2) charging + * BIT(3) battery critical + */ + __le32 power_state; + /* + * 1: AC + * 2: USB + * 3: Wireless + */ + __le32 charging_source; + __le32 temperature; + } status; + __le32 time; + __le32 notification; + }; +}; + +#define BATTMGR_CHARGING_SOURCE_AC 1 +#define BATTMGR_CHARGING_SOURCE_USB 2 +#define BATTMGR_CHARGING_SOURCE_WIRELESS 3 + +enum qcom_battmgr_unit { + QCOM_BATTMGR_UNIT_mWh = 0, + QCOM_BATTMGR_UNIT_mAh = 1 +}; + +struct qcom_battmgr_info { + bool valid; + + bool present; + unsigned int charge_type; + unsigned int design_capacity; + unsigned int last_full_capacity; + unsigned int voltage_max_design; + unsigned int voltage_max; + unsigned int capacity_low; + 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]; + unsigned char technology; + unsigned char day; + unsigned char month; + unsigned short year; +}; + +struct qcom_battmgr_status { + unsigned int status; + unsigned int health; + unsigned int capacity; + unsigned int percent; + int current_now; + int power_now; + 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; +}; + +struct qcom_battmgr_ac { + bool online; +}; + +struct qcom_battmgr_usb { + bool online; + unsigned int voltage_now; + unsigned int voltage_max; + unsigned int current_now; + unsigned int current_max; + unsigned int current_limit; + unsigned int usb_type; +}; + +struct qcom_battmgr_wireless { + bool online; + unsigned int voltage_now; + unsigned int voltage_max; + unsigned int current_now; + unsigned int current_max; +}; + +struct qcom_battmgr { + struct device *dev; + struct pmic_glink_client *client; + + enum qcom_battmgr_variant variant; + + struct power_supply *ac_psy; + struct power_supply *bat_psy; + struct power_supply *usb_psy; + struct power_supply *wls_psy; + + enum qcom_battmgr_unit unit; + + int error; + struct completion ack; + + bool service_up; + + struct qcom_battmgr_info info; + struct qcom_battmgr_status status; + struct qcom_battmgr_ac ac; + struct qcom_battmgr_usb usb; + struct qcom_battmgr_wireless wireless; + + struct work_struct enable_work; + + /* + * @lock is used to prevent concurrent power supply requests to the + * firmware, as it then stops responding. + */ + struct mutex lock; +}; + +static int qcom_battmgr_request(struct qcom_battmgr *battmgr, void *data, size_t len) +{ + unsigned long left; + int ret; + + reinit_completion(&battmgr->ack); + + battmgr->error = 0; + + ret = pmic_glink_send(battmgr->client, data, len); + if (ret < 0) + return ret; + + left = wait_for_completion_timeout(&battmgr->ack, HZ); + if (!left) + return -ETIMEDOUT; + + return battmgr->error; +} + +static int qcom_battmgr_request_property(struct qcom_battmgr *battmgr, int opcode, + int property, u32 value) +{ + struct qcom_battmgr_property_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(opcode), + .battery = cpu_to_le32(0), + .property = cpu_to_le32(property), + .value = cpu_to_le32(value), + }; + + return qcom_battmgr_request(battmgr, &request, sizeof(request)); +} + +static int qcom_battmgr_update_status(struct qcom_battmgr *battmgr) +{ + struct qcom_battmgr_update_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_BAT_STATUS), + .battery_id = cpu_to_le32(0), + }; + + return qcom_battmgr_request(battmgr, &request, sizeof(request)); +} + +static int qcom_battmgr_update_info(struct qcom_battmgr *battmgr) +{ + struct qcom_battmgr_update_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_BAT_INFO), + .battery_id = cpu_to_le32(0), + }; + + return qcom_battmgr_request(battmgr, &request, sizeof(request)); +} + +static int qcom_battmgr_update_charge_time(struct qcom_battmgr *battmgr) +{ + struct qcom_battmgr_charge_time_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_BAT_CHARGE_TIME), + .battery_id = cpu_to_le32(0), + .percent = cpu_to_le32(100), + }; + + return qcom_battmgr_request(battmgr, &request, sizeof(request)); +} + +static int qcom_battmgr_update_discharge_time(struct qcom_battmgr *battmgr) +{ + struct qcom_battmgr_discharge_time_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_BAT_DISCHARGE_TIME), + .battery_id = cpu_to_le32(0), + .rate = cpu_to_le32(0), + }; + + return qcom_battmgr_request(battmgr, &request, sizeof(request)); +} + +static const u8 sm8350_bat_prop_map[] = { + [POWER_SUPPLY_PROP_STATUS] = BATT_STATUS, + [POWER_SUPPLY_PROP_HEALTH] = BATT_HEALTH, + [POWER_SUPPLY_PROP_PRESENT] = BATT_PRESENT, + [POWER_SUPPLY_PROP_CHARGE_TYPE] = BATT_CHG_TYPE, + [POWER_SUPPLY_PROP_CAPACITY] = BATT_CAPACITY, + [POWER_SUPPLY_PROP_VOLTAGE_OCV] = BATT_VOLT_OCV, + [POWER_SUPPLY_PROP_VOLTAGE_NOW] = BATT_VOLT_NOW, + [POWER_SUPPLY_PROP_VOLTAGE_MAX] = BATT_VOLT_MAX, + [POWER_SUPPLY_PROP_CURRENT_NOW] = BATT_CURR_NOW, + [POWER_SUPPLY_PROP_TEMP] = BATT_TEMP, + [POWER_SUPPLY_PROP_TECHNOLOGY] = BATT_TECHNOLOGY, + [POWER_SUPPLY_PROP_CHARGE_COUNTER] = BATT_CHG_COUNTER, + [POWER_SUPPLY_PROP_CYCLE_COUNT] = BATT_CYCLE_COUNT, + [POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN] = BATT_CHG_FULL_DESIGN, + [POWER_SUPPLY_PROP_CHARGE_FULL] = BATT_CHG_FULL, + [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, + enum power_supply_property psp) +{ + unsigned int prop; + int ret; + + if (psp >= ARRAY_SIZE(sm8350_bat_prop_map)) + return -EINVAL; + + prop = sm8350_bat_prop_map[psp]; + + mutex_lock(&battmgr->lock); + ret = qcom_battmgr_request_property(battmgr, BATTMGR_BAT_PROPERTY_GET, prop, 0); + mutex_unlock(&battmgr->lock); + + return ret; +} + +static int qcom_battmgr_bat_sc8280xp_update(struct qcom_battmgr *battmgr, + enum power_supply_property psp) +{ + int ret; + + mutex_lock(&battmgr->lock); + + if (!battmgr->info.valid) { + ret = qcom_battmgr_update_info(battmgr); + if (ret < 0) + goto out_unlock; + battmgr->info.valid = true; + } + + ret = qcom_battmgr_update_status(battmgr); + if (ret < 0) + goto out_unlock; + + if (psp == POWER_SUPPLY_PROP_TIME_TO_FULL_AVG) { + ret = qcom_battmgr_update_charge_time(battmgr); + if (ret < 0) { + ret = -ENODATA; + goto out_unlock; + } + } + + if (psp == POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG) { + ret = qcom_battmgr_update_discharge_time(battmgr); + if (ret < 0) { + ret = -ENODATA; + goto out_unlock; + } + } + +out_unlock: + mutex_unlock(&battmgr->lock); + return ret; +} + +static int qcom_battmgr_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy); + enum qcom_battmgr_unit unit = battmgr->unit; + int ret; + + if (!battmgr->service_up) + return -EAGAIN; + + 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); + if (ret < 0) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = battmgr->status.status; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = battmgr->info.charge_type; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = battmgr->status.health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = battmgr->info.present; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = battmgr->info.technology; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + val->intval = battmgr->info.cycle_count; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = battmgr->info.voltage_max_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = battmgr->info.voltage_max; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = battmgr->status.voltage_now; + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = battmgr->status.voltage_ocv; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = battmgr->status.current_now; + break; + case POWER_SUPPLY_PROP_POWER_NOW: + val->intval = battmgr->status.power_now; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + if (unit != QCOM_BATTMGR_UNIT_mAh) + return -ENODATA; + val->intval = battmgr->info.design_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (unit != QCOM_BATTMGR_UNIT_mAh) + return -ENODATA; + val->intval = battmgr->info.last_full_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_EMPTY: + if (unit != QCOM_BATTMGR_UNIT_mAh) + return -ENODATA; + val->intval = battmgr->info.capacity_low; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (unit != QCOM_BATTMGR_UNIT_mAh) + return -ENODATA; + val->intval = battmgr->status.capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = battmgr->info.charge_count; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + if (unit != QCOM_BATTMGR_UNIT_mWh) + return -ENODATA; + val->intval = battmgr->info.design_capacity; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + if (unit != QCOM_BATTMGR_UNIT_mWh) + return -ENODATA; + val->intval = battmgr->info.last_full_capacity; + break; + case POWER_SUPPLY_PROP_ENERGY_EMPTY: + if (unit != QCOM_BATTMGR_UNIT_mWh) + return -ENODATA; + val->intval = battmgr->info.capacity_low; + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + if (unit != QCOM_BATTMGR_UNIT_mWh) + return -ENODATA; + 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; + case POWER_SUPPLY_PROP_MANUFACTURE_MONTH: + val->intval = battmgr->info.month; + break; + case POWER_SUPPLY_PROP_MANUFACTURE_DAY: + val->intval = battmgr->info.day; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = battmgr->info.model_number; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = battmgr->info.oem_info; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = battmgr->info.serial_number; + break; + default: + return -EINVAL; + } + + 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, + 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, +}; + +static const struct power_supply_desc sc8280xp_bat_psy_desc = { + .name = "qcom-battmgr-bat", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = sc8280xp_bat_props, + .num_properties = ARRAY_SIZE(sc8280xp_bat_props), + .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, + 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, +}; + +static const struct power_supply_desc sm8350_bat_psy_desc = { + .name = "qcom-battmgr-bat", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = sm8350_bat_props, + .num_properties = ARRAY_SIZE(sm8350_bat_props), + .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) +{ + struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy); + int ret; + + if (!battmgr->service_up) + return -EAGAIN; + + ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = battmgr->ac.online; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const enum power_supply_property sc8280xp_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct power_supply_desc sc8280xp_ac_psy_desc = { + .name = "qcom-battmgr-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = sc8280xp_ac_props, + .num_properties = ARRAY_SIZE(sc8280xp_ac_props), + .get_property = qcom_battmgr_ac_get_property, +}; + +static const u8 sm8350_usb_prop_map[] = { + [POWER_SUPPLY_PROP_ONLINE] = USB_ONLINE, + [POWER_SUPPLY_PROP_VOLTAGE_NOW] = USB_VOLT_NOW, + [POWER_SUPPLY_PROP_VOLTAGE_MAX] = USB_VOLT_MAX, + [POWER_SUPPLY_PROP_CURRENT_NOW] = USB_CURR_NOW, + [POWER_SUPPLY_PROP_CURRENT_MAX] = USB_CURR_MAX, + [POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT] = USB_INPUT_CURR_LIMIT, + [POWER_SUPPLY_PROP_USB_TYPE] = USB_TYPE, +}; + +static int qcom_battmgr_usb_sm8350_update(struct qcom_battmgr *battmgr, + enum power_supply_property psp) +{ + unsigned int prop; + int ret; + + if (psp >= ARRAY_SIZE(sm8350_usb_prop_map)) + return -EINVAL; + + prop = sm8350_usb_prop_map[psp]; + + mutex_lock(&battmgr->lock); + ret = qcom_battmgr_request_property(battmgr, BATTMGR_USB_PROPERTY_GET, prop, 0); + mutex_unlock(&battmgr->lock); + + return ret; +} + +static int qcom_battmgr_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy); + int ret; + + if (!battmgr->service_up) + return -EAGAIN; + + 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); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = battmgr->usb.online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = battmgr->usb.voltage_now; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = battmgr->usb.voltage_max; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = battmgr->usb.current_now; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = battmgr->usb.current_max; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + val->intval = battmgr->usb.current_limit; + break; + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = battmgr->usb.usb_type; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const enum power_supply_property sc8280xp_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct power_supply_desc sc8280xp_usb_psy_desc = { + .name = "qcom-battmgr-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = sc8280xp_usb_props, + .num_properties = ARRAY_SIZE(sc8280xp_usb_props), + .get_property = qcom_battmgr_usb_get_property, + .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) | + 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_APPLE_BRICK_ID), +}; + +static const enum power_supply_property sm8350_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_USB_TYPE, +}; + +static const struct power_supply_desc sm8350_usb_psy_desc = { + .name = "qcom-battmgr-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = sm8350_usb_props, + .num_properties = ARRAY_SIZE(sm8350_usb_props), + .get_property = qcom_battmgr_usb_get_property, + .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) | + 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_APPLE_BRICK_ID), +}; + +static const u8 sm8350_wls_prop_map[] = { + [POWER_SUPPLY_PROP_ONLINE] = WLS_ONLINE, + [POWER_SUPPLY_PROP_VOLTAGE_NOW] = WLS_VOLT_NOW, + [POWER_SUPPLY_PROP_VOLTAGE_MAX] = WLS_VOLT_MAX, + [POWER_SUPPLY_PROP_CURRENT_NOW] = WLS_CURR_NOW, + [POWER_SUPPLY_PROP_CURRENT_MAX] = WLS_CURR_MAX, +}; + +static int qcom_battmgr_wls_sm8350_update(struct qcom_battmgr *battmgr, + enum power_supply_property psp) +{ + unsigned int prop; + int ret; + + if (psp >= ARRAY_SIZE(sm8350_wls_prop_map)) + return -EINVAL; + + prop = sm8350_wls_prop_map[psp]; + + mutex_lock(&battmgr->lock); + ret = qcom_battmgr_request_property(battmgr, BATTMGR_WLS_PROPERTY_GET, prop, 0); + mutex_unlock(&battmgr->lock); + + return ret; +} + +static int qcom_battmgr_wls_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy); + int ret; + + if (!battmgr->service_up) + return -EAGAIN; + + 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); + if (ret < 0) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = battmgr->wireless.online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = battmgr->wireless.voltage_now; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = battmgr->wireless.voltage_max; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = battmgr->wireless.current_now; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = battmgr->wireless.current_max; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const enum power_supply_property sc8280xp_wls_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct power_supply_desc sc8280xp_wls_psy_desc = { + .name = "qcom-battmgr-wls", + .type = POWER_SUPPLY_TYPE_WIRELESS, + .properties = sc8280xp_wls_props, + .num_properties = ARRAY_SIZE(sc8280xp_wls_props), + .get_property = qcom_battmgr_wls_get_property, +}; + +static const enum power_supply_property sm8350_wls_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static const struct power_supply_desc sm8350_wls_psy_desc = { + .name = "qcom-battmgr-wls", + .type = POWER_SUPPLY_TYPE_WIRELESS, + .properties = sm8350_wls_props, + .num_properties = ARRAY_SIZE(sm8350_wls_props), + .get_property = qcom_battmgr_wls_get_property, +}; + +static void qcom_battmgr_notification(struct qcom_battmgr *battmgr, + const struct qcom_battmgr_message *msg, + int len) +{ + size_t payload_len = len - sizeof(struct pmic_glink_hdr); + unsigned int notification; + + if (payload_len != sizeof(msg->notification)) { + dev_warn(battmgr->dev, "ignoring notification with invalid length\n"); + return; + } + + 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: + power_supply_changed(battmgr->usb_psy); + break; + case NOTIF_WLS_PROPERTY: + power_supply_changed(battmgr->wls_psy); + break; + default: + dev_err(battmgr->dev, "unknown notification: %#x\n", notification); + break; + } +} + +static void qcom_battmgr_sc8280xp_strcpy(char *dest, const char *src) +{ + size_t len = src[0]; + + /* Some firmware versions return Pascal-style strings */ + if (len < BATTMGR_STRING_LEN && len == strnlen(src + 1, BATTMGR_STRING_LEN - 1)) { + memcpy(dest, src + 1, len); + dest[len] = '\0'; + } else { + memcpy(dest, src, BATTMGR_STRING_LEN); + } +} + +static unsigned int qcom_battmgr_sc8280xp_parse_technology(const char *chemistry) +{ + 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)) + return POWER_SUPPLY_TECHNOLOGY_LIPO; + + pr_err("Unknown battery technology '%s'\n", chemistry); + return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; +} + +static unsigned int qcom_battmgr_sc8280xp_convert_temp(unsigned int temperature) +{ + return DIV_ROUND_CLOSEST(temperature, 10); +} + +static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr, + const struct qcom_battmgr_message *resp, + size_t len) +{ + unsigned int opcode = le32_to_cpu(resp->hdr.opcode); + unsigned int source; + unsigned int state; + size_t payload_len = len - sizeof(struct pmic_glink_hdr); + + if (payload_len < sizeof(__le32)) { + dev_warn(battmgr->dev, "invalid payload length for %#x: %zd\n", + opcode, len); + return; + } + + switch (opcode) { + case BATTMGR_REQUEST_NOTIFICATION: + battmgr->error = 0; + break; + case BATTMGR_BAT_INFO: + /* some firmware versions report an extra __le32 at the end of the payload */ + if (payload_len != sizeof(resp->info) && + payload_len != (sizeof(resp->info) + sizeof(__le32))) { + dev_warn(battmgr->dev, + "invalid payload length for battery information request: %zd\n", + payload_len); + battmgr->error = -ENODATA; + return; + } + + battmgr->unit = le32_to_cpu(resp->info.power_unit); + + battmgr->info.present = true; + battmgr->info.design_capacity = le32_to_cpu(resp->info.design_capacity) * 1000; + battmgr->info.last_full_capacity = le32_to_cpu(resp->info.last_full_capacity) * 1000; + battmgr->info.voltage_max_design = le32_to_cpu(resp->info.design_voltage) * 1000; + battmgr->info.capacity_low = le32_to_cpu(resp->info.capacity_low) * 1000; + battmgr->info.cycle_count = le32_to_cpu(resp->info.cycle_count); + qcom_battmgr_sc8280xp_strcpy(battmgr->info.model_number, resp->info.model_number); + qcom_battmgr_sc8280xp_strcpy(battmgr->info.serial_number, resp->info.serial_number); + battmgr->info.technology = qcom_battmgr_sc8280xp_parse_technology(resp->info.battery_chemistry); + qcom_battmgr_sc8280xp_strcpy(battmgr->info.oem_info, resp->info.oem_info); + battmgr->info.day = resp->info.day; + battmgr->info.month = resp->info.month; + battmgr->info.year = le16_to_cpu(resp->info.year); + break; + case BATTMGR_BAT_STATUS: + if (payload_len != sizeof(resp->status)) { + dev_warn(battmgr->dev, + "invalid payload length for battery status request: %zd\n", + payload_len); + battmgr->error = -ENODATA; + return; + } + + state = le32_to_cpu(resp->status.battery_state); + if (state & BIT(0)) + battmgr->status.status = POWER_SUPPLY_STATUS_DISCHARGING; + else if (state & BIT(1)) + battmgr->status.status = POWER_SUPPLY_STATUS_CHARGING; + else + battmgr->status.status = POWER_SUPPLY_STATUS_NOT_CHARGING; + + battmgr->status.capacity = le32_to_cpu(resp->status.capacity) * 1000; + battmgr->status.power_now = le32_to_cpu(resp->status.rate) * 1000; + battmgr->status.voltage_now = le32_to_cpu(resp->status.battery_voltage) * 1000; + battmgr->status.temperature = qcom_battmgr_sc8280xp_convert_temp(le32_to_cpu(resp->status.temperature)); + + source = le32_to_cpu(resp->status.charging_source); + 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); + break; + 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; + } + + complete(&battmgr->ack); +} + +static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr, + const struct qcom_battmgr_message *resp, + size_t len) +{ + unsigned int property; + unsigned int opcode = le32_to_cpu(resp->hdr.opcode); + size_t payload_len = len - sizeof(struct pmic_glink_hdr); + unsigned int val; + + if (payload_len < sizeof(__le32)) { + dev_warn(battmgr->dev, "invalid payload length for %#x: %zd\n", + opcode, len); + return; + } + + switch (opcode) { + case BATTMGR_BAT_PROPERTY_GET: + property = le32_to_cpu(resp->intval.property); + if (property == BATT_MODEL_NAME) { + if (payload_len != sizeof(resp->strval)) { + dev_warn(battmgr->dev, + "invalid payload length for BATT_MODEL_NAME request: %zd\n", + payload_len); + battmgr->error = -ENODATA; + return; + } + } else { + if (payload_len != sizeof(resp->intval)) { + dev_warn(battmgr->dev, + "invalid payload length for %#x request: %zd\n", + property, payload_len); + battmgr->error = -ENODATA; + return; + } + + battmgr->error = le32_to_cpu(resp->intval.result); + if (battmgr->error) + goto out_complete; + } + + switch (property) { + case BATT_STATUS: + battmgr->status.status = le32_to_cpu(resp->intval.value); + break; + case BATT_HEALTH: + battmgr->status.health = le32_to_cpu(resp->intval.value); + break; + case BATT_PRESENT: + battmgr->info.present = le32_to_cpu(resp->intval.value); + break; + case BATT_CHG_TYPE: + battmgr->info.charge_type = le32_to_cpu(resp->intval.value); + break; + 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; + case BATT_VOLT_NOW: + battmgr->status.voltage_now = le32_to_cpu(resp->intval.value); + break; + case BATT_VOLT_MAX: + battmgr->info.voltage_max = le32_to_cpu(resp->intval.value); + break; + case BATT_CURR_NOW: + battmgr->status.current_now = le32_to_cpu(resp->intval.value); + break; + case BATT_TEMP: + val = le32_to_cpu(resp->intval.value); + battmgr->status.temperature = DIV_ROUND_CLOSEST(val, 10); + break; + case BATT_TECHNOLOGY: + battmgr->info.technology = le32_to_cpu(resp->intval.value); + break; + case BATT_CHG_COUNTER: + battmgr->info.charge_count = le32_to_cpu(resp->intval.value); + break; + case BATT_CYCLE_COUNT: + battmgr->info.cycle_count = le32_to_cpu(resp->intval.value); + break; + case BATT_CHG_FULL_DESIGN: + battmgr->info.design_capacity = le32_to_cpu(resp->intval.value); + break; + case BATT_CHG_FULL: + battmgr->info.last_full_capacity = le32_to_cpu(resp->intval.value); + break; + case BATT_MODEL_NAME: + strscpy(battmgr->info.model_number, resp->strval.model, BATTMGR_STRING_LEN); + break; + case BATT_TTF_AVG: + battmgr->status.charge_time = le32_to_cpu(resp->intval.value); + break; + 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; + } + break; + case BATTMGR_USB_PROPERTY_GET: + property = le32_to_cpu(resp->intval.property); + if (payload_len != sizeof(resp->intval)) { + dev_warn(battmgr->dev, + "invalid payload length for %#x request: %zd\n", + property, payload_len); + battmgr->error = -ENODATA; + return; + } + + battmgr->error = le32_to_cpu(resp->intval.result); + if (battmgr->error) + goto out_complete; + + switch (property) { + case USB_ONLINE: + battmgr->usb.online = le32_to_cpu(resp->intval.value); + break; + case USB_VOLT_NOW: + battmgr->usb.voltage_now = le32_to_cpu(resp->intval.value); + break; + case USB_VOLT_MAX: + battmgr->usb.voltage_max = le32_to_cpu(resp->intval.value); + break; + case USB_CURR_NOW: + battmgr->usb.current_now = le32_to_cpu(resp->intval.value); + break; + case USB_CURR_MAX: + battmgr->usb.current_max = le32_to_cpu(resp->intval.value); + break; + case USB_INPUT_CURR_LIMIT: + battmgr->usb.current_limit = le32_to_cpu(resp->intval.value); + break; + case USB_TYPE: + battmgr->usb.usb_type = le32_to_cpu(resp->intval.value); + break; + default: + dev_warn(battmgr->dev, "unknown property %#x\n", property); + break; + } + break; + case BATTMGR_WLS_PROPERTY_GET: + property = le32_to_cpu(resp->intval.property); + if (payload_len != sizeof(resp->intval)) { + dev_warn(battmgr->dev, + "invalid payload length for %#x request: %zd\n", + property, payload_len); + battmgr->error = -ENODATA; + return; + } + + battmgr->error = le32_to_cpu(resp->intval.result); + if (battmgr->error) + goto out_complete; + + switch (property) { + case WLS_ONLINE: + battmgr->wireless.online = le32_to_cpu(resp->intval.value); + break; + case WLS_VOLT_NOW: + battmgr->wireless.voltage_now = le32_to_cpu(resp->intval.value); + break; + case WLS_VOLT_MAX: + battmgr->wireless.voltage_max = le32_to_cpu(resp->intval.value); + break; + case WLS_CURR_NOW: + battmgr->wireless.current_now = le32_to_cpu(resp->intval.value); + break; + case WLS_CURR_MAX: + battmgr->wireless.current_max = le32_to_cpu(resp->intval.value); + break; + default: + dev_warn(battmgr->dev, "unknown property %#x\n", property); + break; + } + break; + case BATTMGR_REQUEST_NOTIFICATION: + case BATTMGR_CHG_CTRL_LIMIT_EN: + battmgr->error = 0; + break; + default: + dev_warn(battmgr->dev, "unknown message %#x\n", opcode); + break; + } + +out_complete: + complete(&battmgr->ack); +} + +static void qcom_battmgr_callback(const void *data, size_t len, void *priv) +{ + const struct pmic_glink_hdr *hdr = data; + struct qcom_battmgr *battmgr = priv; + unsigned int opcode = le32_to_cpu(hdr->opcode); + + if (opcode == BATTMGR_NOTIFICATION) + qcom_battmgr_notification(battmgr, data, len); + 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); +} + +static void qcom_battmgr_enable_worker(struct work_struct *work) +{ + struct qcom_battmgr *battmgr = container_of(work, struct qcom_battmgr, enable_work); + struct qcom_battmgr_enable_request req = { + .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_BATTMGR), + .hdr.type = cpu_to_le32(PMIC_GLINK_NOTIFY), + .hdr.opcode = cpu_to_le32(BATTMGR_REQUEST_NOTIFICATION), + }; + int ret; + + ret = qcom_battmgr_request(battmgr, &req, sizeof(req)); + if (ret) + dev_err(battmgr->dev, "failed to request power notifications\n"); +} + +static void qcom_battmgr_pdr_notify(void *priv, int state) +{ + struct qcom_battmgr *battmgr = priv; + + if (state == SERVREG_SERVICE_STATE_UP) { + battmgr->service_up = true; + schedule_work(&battmgr->enable_work); + } else { + battmgr->service_up = false; + } +} + +static const struct of_device_id qcom_battmgr_of_variants[] = { + { .compatible = "qcom,sc8180x-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP }, + { .compatible = "qcom,sc8280xp-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 */ + {} +}; + +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) + return -ENOMEM; + + battmgr->dev = dev; + + psy_cfg.drv_data = battmgr; + psy_cfg.fwnode = dev_fwnode(&adev->dev); + + psy_cfg_supply.drv_data = battmgr; + psy_cfg_supply.fwnode = dev_fwnode(&adev->dev); + psy_cfg_supply.supplied_to = qcom_battmgr_battery; + psy_cfg_supply.num_supplicants = 1; + + INIT_WORK(&battmgr->enable_work, qcom_battmgr_enable_worker); + mutex_init(&battmgr->lock); + init_completion(&battmgr->ack); + + match = of_match_device(qcom_battmgr_of_variants, dev->parent); + if (match) + battmgr->variant = (unsigned long)match->data; + else + battmgr->variant = QCOM_BATTMGR_SM8350; + + 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"); + + battmgr->ac_psy = devm_power_supply_register(dev, &sc8280xp_ac_psy_desc, &psy_cfg_supply); + if (IS_ERR(battmgr->ac_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->ac_psy), + "failed to register AC power supply\n"); + + battmgr->usb_psy = devm_power_supply_register(dev, &sc8280xp_usb_psy_desc, &psy_cfg_supply); + if (IS_ERR(battmgr->usb_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->usb_psy), + "failed to register USB power supply\n"); + + battmgr->wls_psy = devm_power_supply_register(dev, &sc8280xp_wls_psy_desc, &psy_cfg_supply); + if (IS_ERR(battmgr->wls_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->wls_psy), + "failed to register wireless charing power supply\n"); + } else { + 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"); + + battmgr->usb_psy = devm_power_supply_register(dev, &sm8350_usb_psy_desc, &psy_cfg_supply); + if (IS_ERR(battmgr->usb_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->usb_psy), + "failed to register USB power supply\n"); + + battmgr->wls_psy = devm_power_supply_register(dev, &sm8350_wls_psy_desc, &psy_cfg_supply); + if (IS_ERR(battmgr->wls_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->wls_psy), + "failed to register wireless charing power supply\n"); + } + + battmgr->client = devm_pmic_glink_client_alloc(dev, PMIC_GLINK_OWNER_BATTMGR, + qcom_battmgr_callback, + qcom_battmgr_pdr_notify, + battmgr); + if (IS_ERR(battmgr->client)) + return PTR_ERR(battmgr->client); + + pmic_glink_client_register(battmgr->client); + + return 0; +} + +static const struct auxiliary_device_id qcom_battmgr_id_table[] = { + { .name = "pmic_glink.power-supply", }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, qcom_battmgr_id_table); + +static struct auxiliary_driver qcom_battmgr_driver = { + .name = "pmic_glink_power_supply", + .probe = qcom_battmgr_probe, + .id_table = qcom_battmgr_id_table, +}; + +module_auxiliary_driver(qcom_battmgr_driver); + +MODULE_DESCRIPTION("Qualcomm PMIC GLINK battery manager driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/qcom_smbb.c b/drivers/power/supply/qcom_smbb.c index bd50124eef9f..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); @@ -1000,15 +1000,13 @@ static int smbb_charger_probe(struct platform_device *pdev) return 0; } -static int smbb_charger_remove(struct platform_device *pdev) +static void smbb_charger_remove(struct platform_device *pdev) { struct smbb_charger *chg; chg = platform_get_drvdata(pdev); regmap_update_bits(chg->regmap, chg->addr + SMBB_CHG_CTRL, CTRL_EN, 0); - - return 0; } static const struct of_device_id smbb_charger_id_table[] = { @@ -1019,10 +1017,10 @@ static const struct of_device_id smbb_charger_id_table[] = { MODULE_DEVICE_TABLE(of, smbb_charger_id_table); static struct platform_driver smbb_charger_driver = { - .probe = smbb_charger_probe, - .remove = smbb_charger_remove, - .driver = { - .name = "qcom-smbb", + .probe = smbb_charger_probe, + .remove = smbb_charger_remove, + .driver = { + .name = "qcom-smbb", .of_match_table = smbb_charger_id_table, }, }; diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c new file mode 100644 index 000000000000..b1cb925581ec --- /dev/null +++ b/drivers/power/supply/qcom_smbx.c @@ -0,0 +1,1052 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2019 The Linux Foundation. All rights reserved. + * Copyright (c) 2023, Linaro Ltd. + * 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. + */ + +#include <linux/bits.h> +#include <linux/devm-helpers.h> +#include <linux/iio/consumer.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_wakeirq.h> +#include <linux/of.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +/* clang-format off */ +#define BATTERY_CHARGER_STATUS_1 0x06 +#define BVR_INITIAL_RAMP_BIT BIT(7) +#define CC_SOFT_TERMINATE_BIT BIT(6) +#define STEP_CHARGING_STATUS_SHIFT 3 +#define STEP_CHARGING_STATUS_MASK GENMASK(5, 3) +#define BATTERY_CHARGER_STATUS_MASK GENMASK(2, 0) + +#define BATTERY_CHARGER_STATUS_2 0x07 +#define INPUT_CURRENT_LIMITED_BIT BIT(7) +#define CHARGER_ERROR_STATUS_SFT_EXPIRE_BIT BIT(6) +#define CHARGER_ERROR_STATUS_BAT_OV_BIT BIT(5) +#define CHARGER_ERROR_STATUS_BAT_TERM_MISSING_BIT BIT(4) +#define BAT_TEMP_STATUS_MASK GENMASK(3, 0) +#define BAT_TEMP_STATUS_SOFT_LIMIT_MASK GENMASK(3, 2) +#define BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT BIT(3) +#define BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT BIT(2) +#define BAT_TEMP_STATUS_TOO_HOT_BIT BIT(1) +#define BAT_TEMP_STATUS_TOO_COLD_BIT BIT(0) + +#define BATTERY_CHARGER_STATUS_4 0x0A +#define CHARGE_CURRENT_POST_JEITA_MASK GENMASK(7, 0) + +#define BATTERY_CHARGER_STATUS_7 0x0D +#define ENABLE_TRICKLE_BIT BIT(7) +#define ENABLE_PRE_CHARGING_BIT BIT(6) +#define ENABLE_FAST_CHARGING_BIT BIT(5) +#define ENABLE_FULLON_MODE_BIT BIT(4) +#define TOO_COLD_ADC_BIT BIT(3) +#define TOO_HOT_ADC_BIT BIT(2) +#define HOT_SL_ADC_BIT BIT(1) +#define COLD_SL_ADC_BIT BIT(0) + +#define CHARGING_ENABLE_CMD 0x42 +#define CHARGING_ENABLE_CMD_BIT BIT(0) + +#define CHGR_CFG2 0x51 +#define CHG_EN_SRC_BIT BIT(7) +#define CHG_EN_POLARITY_BIT BIT(6) +#define PRETOFAST_TRANSITION_CFG_BIT BIT(5) +#define BAT_OV_ECC_BIT BIT(4) +#define I_TERM_BIT BIT(3) +#define AUTO_RECHG_BIT BIT(2) +#define EN_ANALOG_DROP_IN_VBATT_BIT BIT(1) +#define CHARGER_INHIBIT_BIT BIT(0) + +#define PRE_CHARGE_CURRENT_CFG 0x60 +#define PRE_CHARGE_CURRENT_SETTING_MASK GENMASK(5, 0) + +#define FAST_CHARGE_CURRENT_CFG 0x61 +#define FAST_CHARGE_CURRENT_SETTING_MASK GENMASK(7, 0) + +#define FLOAT_VOLTAGE_CFG 0x70 +#define FLOAT_VOLTAGE_SETTING_MASK GENMASK(7, 0) + +#define FG_UPDATE_CFG_2_SEL 0x7D +#define SOC_LT_OTG_THRESH_SEL_BIT BIT(3) +#define SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT BIT(2) +#define VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT BIT(1) +#define IBT_LT_CHG_TERM_THRESH_SEL_BIT BIT(0) + +#define JEITA_EN_CFG 0x90 +#define JEITA_EN_HARDLIMIT_BIT BIT(4) +#define JEITA_EN_HOT_SL_FCV_BIT BIT(3) +#define JEITA_EN_COLD_SL_FCV_BIT BIT(2) +#define JEITA_EN_HOT_SL_CCC_BIT BIT(1) +#define JEITA_EN_COLD_SL_CCC_BIT BIT(0) + +#define INT_RT_STS 0x310 +#define TYPE_C_CHANGE_RT_STS_BIT BIT(7) +#define USBIN_ICL_CHANGE_RT_STS_BIT BIT(6) +#define USBIN_SOURCE_CHANGE_RT_STS_BIT BIT(5) +#define USBIN_PLUGIN_RT_STS_BIT BIT(4) +#define USBIN_OV_RT_STS_BIT BIT(3) +#define USBIN_UV_RT_STS_BIT BIT(2) +#define USBIN_LT_3P6V_RT_STS_BIT BIT(1) +#define USBIN_COLLAPSE_RT_STS_BIT BIT(0) + +#define OTG_CFG 0x153 +#define OTG_RESERVED_MASK GENMASK(7, 6) +#define DIS_OTG_ON_TLIM_BIT BIT(5) +#define QUICKSTART_OTG_FASTROLESWAP_BIT BIT(4) +#define INCREASE_DFP_TIME_BIT BIT(3) +#define ENABLE_OTG_IN_DEBUG_MODE_BIT BIT(2) +#define OTG_EN_SRC_CFG_BIT BIT(1) +#define CONCURRENT_MODE_CFG_BIT BIT(0) + +#define OTG_ENG_OTG_CFG 0x1C0 +#define ENG_BUCKBOOST_HALT1_8_MODE_BIT BIT(0) + +#define APSD_STATUS 0x307 +#define APSD_STATUS_7_BIT BIT(7) +#define HVDCP_CHECK_TIMEOUT_BIT BIT(6) +#define SLOW_PLUGIN_TIMEOUT_BIT BIT(5) +#define ENUMERATION_DONE_BIT BIT(4) +#define VADP_CHANGE_DONE_AFTER_AUTH_BIT BIT(3) +#define QC_AUTH_DONE_STATUS_BIT BIT(2) +#define QC_CHARGER_BIT BIT(1) +#define APSD_DTC_STATUS_DONE_BIT BIT(0) + +#define APSD_RESULT_STATUS 0x308 +#define ICL_OVERRIDE_LATCH_BIT BIT(7) +#define APSD_RESULT_STATUS_MASK GENMASK(6, 0) +#define QC_3P0_BIT BIT(6) +#define QC_2P0_BIT BIT(5) +#define FLOAT_CHARGER_BIT BIT(4) +#define DCP_CHARGER_BIT BIT(3) +#define CDP_CHARGER_BIT BIT(2) +#define OCP_CHARGER_BIT BIT(1) +#define SDP_CHARGER_BIT BIT(0) + +#define TYPE_C_STATUS_1 0x30B +#define UFP_TYPEC_MASK GENMASK(7, 5) +#define UFP_TYPEC_RDSTD_BIT BIT(7) +#define UFP_TYPEC_RD1P5_BIT BIT(6) +#define UFP_TYPEC_RD3P0_BIT BIT(5) +#define UFP_TYPEC_FMB_255K_BIT BIT(4) +#define UFP_TYPEC_FMB_301K_BIT BIT(3) +#define UFP_TYPEC_FMB_523K_BIT BIT(2) +#define UFP_TYPEC_FMB_619K_BIT BIT(1) +#define UFP_TYPEC_OPEN_OPEN_BIT BIT(0) + +#define TYPE_C_STATUS_2 0x30C +#define DFP_RA_OPEN_BIT BIT(7) +#define TIMER_STAGE_BIT BIT(6) +#define EXIT_UFP_MODE_BIT BIT(5) +#define EXIT_DFP_MODE_BIT BIT(4) +#define DFP_TYPEC_MASK GENMASK(3, 0) +#define DFP_RD_OPEN_BIT BIT(3) +#define DFP_RD_RA_VCONN_BIT BIT(2) +#define DFP_RD_RD_BIT BIT(1) +#define DFP_RA_RA_BIT BIT(0) + +#define TYPE_C_STATUS_3 0x30D +#define ENABLE_BANDGAP_BIT BIT(7) +#define U_USB_GND_NOVBUS_BIT BIT(6) +#define U_USB_FLOAT_NOVBUS_BIT BIT(5) +#define U_USB_GND_BIT BIT(4) +#define U_USB_FMB1_BIT BIT(3) +#define U_USB_FLOAT1_BIT BIT(2) +#define U_USB_FMB2_BIT BIT(1) +#define U_USB_FLOAT2_BIT BIT(0) + +#define TYPE_C_STATUS_4 0x30E +#define UFP_DFP_MODE_STATUS_BIT BIT(7) +#define TYPEC_VBUS_STATUS_BIT BIT(6) +#define TYPEC_VBUS_ERROR_STATUS_BIT BIT(5) +#define TYPEC_DEBOUNCE_DONE_STATUS_BIT BIT(4) +#define TYPEC_UFP_AUDIO_ADAPT_STATUS_BIT BIT(3) +#define TYPEC_VCONN_OVERCURR_STATUS_BIT BIT(2) +#define CC_ORIENTATION_BIT BIT(1) +#define CC_ATTACHED_BIT BIT(0) + +#define TYPE_C_STATUS_5 0x30F +#define TRY_SOURCE_FAILED_BIT BIT(6) +#define TRY_SINK_FAILED_BIT BIT(5) +#define TIMER_STAGE_2_BIT BIT(4) +#define TYPEC_LEGACY_CABLE_STATUS_BIT BIT(3) +#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT BIT(2) +#define TYPEC_TRYSOURCE_DETECT_STATUS_BIT BIT(1) +#define TYPEC_TRYSINK_DETECT_STATUS_BIT BIT(0) + +#define CMD_APSD 0x341 +#define ICL_OVERRIDE_BIT BIT(1) +#define APSD_RERUN_BIT BIT(0) + +#define TYPE_C_CFG 0x358 +#define APSD_START_ON_CC_BIT BIT(7) +#define WAIT_FOR_APSD_BIT BIT(6) +#define FACTORY_MODE_DETECTION_EN_BIT BIT(5) +#define FACTORY_MODE_ICL_3A_4A_BIT BIT(4) +#define FACTORY_MODE_DIS_CHGING_CFG_BIT BIT(3) +#define SUSPEND_NON_COMPLIANT_CFG_BIT BIT(2) +#define VCONN_OC_CFG_BIT BIT(1) +#define TYPE_C_OR_U_USB_BIT BIT(0) + +#define TYPE_C_CFG_2 0x359 +#define TYPE_C_DFP_CURRSRC_MODE_BIT BIT(7) +#define DFP_CC_1P4V_OR_1P6V_BIT BIT(6) +#define VCONN_SOFTSTART_CFG_MASK GENMASK(5, 4) +#define EN_TRY_SOURCE_MODE_BIT BIT(3) +#define USB_FACTORY_MODE_ENABLE_BIT BIT(2) +#define TYPE_C_UFP_MODE_BIT BIT(1) +#define EN_80UA_180UA_CUR_SOURCE_BIT BIT(0) + +#define TYPE_C_CFG_3 0x35A +#define TVBUS_DEBOUNCE_BIT BIT(7) +#define TYPEC_LEGACY_CABLE_INT_EN_BIT BIT(6) +#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN_B BIT(5) +#define TYPEC_TRYSOURCE_DETECT_INT_EN_BIT BIT(4) +#define TYPEC_TRYSINK_DETECT_INT_EN_BIT BIT(3) +#define EN_TRYSINK_MODE_BIT BIT(2) +#define EN_LEGACY_CABLE_DETECTION_BIT BIT(1) +#define ALLOW_PD_DRING_UFP_TCCDB_BIT BIT(0) + +#define USBIN_OPTIONS_1_CFG 0x362 +#define CABLE_R_SEL_BIT BIT(7) +#define HVDCP_AUTH_ALG_EN_CFG_BIT BIT(6) +#define HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT BIT(5) +#define INPUT_PRIORITY_BIT BIT(4) +#define AUTO_SRC_DETECT_BIT BIT(3) +#define HVDCP_EN_BIT BIT(2) +#define VADP_INCREMENT_VOLTAGE_LIMIT_BIT BIT(1) +#define VADP_TAPER_TIMER_EN_BIT BIT(0) + +#define USBIN_OPTIONS_2_CFG 0x363 +#define WIPWR_RST_EUD_CFG_BIT BIT(7) +#define SWITCHER_START_CFG_BIT BIT(6) +#define DCD_TIMEOUT_SEL_BIT BIT(5) +#define OCD_CURRENT_SEL_BIT BIT(4) +#define SLOW_PLUGIN_TIMER_EN_CFG_BIT BIT(3) +#define FLOAT_OPTIONS_MASK GENMASK(2, 0) +#define FLOAT_DIS_CHGING_CFG_BIT BIT(2) +#define SUSPEND_FLOAT_CFG_BIT BIT(1) +#define FORCE_FLOAT_SDP_CFG_BIT BIT(0) + +#define TAPER_TIMER_SEL_CFG 0x364 +#define TYPEC_SPARE_CFG_BIT BIT(7) +#define TYPEC_DRP_DFP_TIME_CFG_BIT BIT(5) +#define TAPER_TIMER_SEL_MASK GENMASK(1, 0) + +#define USBIN_LOAD_CFG 0x365 +#define USBIN_OV_CH_LOAD_OPTION_BIT BIT(7) +#define ICL_OVERRIDE_AFTER_APSD_BIT BIT(4) + +#define USBIN_ICL_OPTIONS 0x366 +#define CFG_USB3P0_SEL_BIT BIT(2) +#define USB51_MODE_BIT BIT(1) +#define USBIN_MODE_CHG_BIT BIT(0) + +#define TYPE_C_INTRPT_ENB_SOFTWARE_CTRL 0x368 +#define EXIT_SNK_BASED_ON_CC_BIT BIT(7) +#define VCONN_EN_ORIENTATION_BIT BIT(6) +#define TYPEC_VCONN_OVERCURR_INT_EN_BIT BIT(5) +#define VCONN_EN_SRC_BIT BIT(4) +#define VCONN_EN_VALUE_BIT BIT(3) +#define TYPEC_POWER_ROLE_CMD_MASK GENMASK(2, 0) +#define UFP_EN_CMD_BIT BIT(2) +#define DFP_EN_CMD_BIT BIT(1) +#define TYPEC_DISABLE_CMD_BIT BIT(0) + +#define USBIN_CURRENT_LIMIT_CFG 0x370 +#define USBIN_CURRENT_LIMIT_MASK GENMASK(7, 0) + +#define USBIN_AICL_OPTIONS_CFG 0x380 +#define SUSPEND_ON_COLLAPSE_USBIN_BIT BIT(7) +#define USBIN_AICL_HDC_EN_BIT BIT(6) +#define USBIN_AICL_START_AT_MAX_BIT BIT(5) +#define USBIN_AICL_RERUN_EN_BIT BIT(4) +#define USBIN_AICL_ADC_EN_BIT BIT(3) +#define USBIN_AICL_EN_BIT BIT(2) +#define USBIN_HV_COLLAPSE_RESPONSE_BIT BIT(1) +#define USBIN_LV_COLLAPSE_RESPONSE_BIT BIT(0) + +#define USBIN_5V_AICL_THRESHOLD_CFG 0x381 +#define USBIN_5V_AICL_THRESHOLD_CFG_MASK GENMASK(2, 0) + +#define USBIN_CONT_AICL_THRESHOLD_CFG 0x384 +#define USBIN_CONT_AICL_THRESHOLD_CFG_MASK GENMASK(5, 0) + +#define DC_ENG_SSUPPLY_CFG2 0x4C1 +#define ENG_SSUPPLY_IVREF_OTG_SS_MASK GENMASK(2, 0) +#define OTG_SS_SLOW 0x3 + +#define DCIN_AICL_REF_SEL_CFG 0x481 +#define DCIN_CONT_AICL_THRESHOLD_CFG_MASK GENMASK(5, 0) + +#define WI_PWR_OPTIONS 0x495 +#define CHG_OK_BIT BIT(7) +#define WIPWR_UVLO_IRQ_OPT_BIT BIT(6) +#define BUCK_HOLDOFF_ENABLE_BIT BIT(5) +#define CHG_OK_HW_SW_SELECT_BIT BIT(4) +#define WIPWR_RST_ENABLE_BIT BIT(3) +#define DCIN_WIPWR_IRQ_SELECT_BIT BIT(2) +#define AICL_SWITCH_ENABLE_BIT BIT(1) +#define ZIN_ICL_ENABLE_BIT BIT(0) + +#define ICL_STATUS 0x607 +#define INPUT_CURRENT_LIMIT_MASK GENMASK(7, 0) + +#define POWER_PATH_STATUS 0x60B +#define P_PATH_INPUT_SS_DONE_BIT BIT(7) +#define P_PATH_USBIN_SUSPEND_STS_BIT BIT(6) +#define P_PATH_DCIN_SUSPEND_STS_BIT BIT(5) +#define P_PATH_USE_USBIN_BIT BIT(4) +#define P_PATH_USE_DCIN_BIT BIT(3) +#define P_PATH_POWER_PATH_MASK GENMASK(2, 1) +#define P_PATH_VALID_INPUT_POWER_SOURCE_STS_BIT BIT(0) + +#define BARK_BITE_WDOG_PET 0x643 +#define BARK_BITE_WDOG_PET_BIT BIT(0) + +#define WD_CFG 0x651 +#define WATCHDOG_TRIGGER_AFP_EN_BIT BIT(7) +#define BARK_WDOG_INT_EN_BIT BIT(6) +#define BITE_WDOG_INT_EN_BIT BIT(5) +#define SFT_AFTER_WDOG_IRQ_MASK GENMASK(4, 3) +#define WDOG_IRQ_SFT_BIT BIT(2) +#define WDOG_TIMER_EN_ON_PLUGIN_BIT BIT(1) +#define WDOG_TIMER_EN_BIT BIT(0) + +#define SNARL_BARK_BITE_WD_CFG 0x653 +#define BITE_WDOG_DISABLE_CHARGING_CFG_BIT BIT(7) +#define SNARL_WDOG_TIMEOUT_MASK GENMASK(6, 4) +#define BARK_WDOG_TIMEOUT_MASK GENMASK(3, 2) +#define BITE_WDOG_TIMEOUT_MASK GENMASK(1, 0) + +#define AICL_RERUN_TIME_CFG 0x661 +#define AICL_RERUN_TIME_MASK GENMASK(1, 0) + +#define STAT_CFG 0x690 +#define STAT_SW_OVERRIDE_VALUE_BIT BIT(7) +#define STAT_SW_OVERRIDE_CFG_BIT BIT(6) +#define STAT_PARALLEL_OFF_DG_CFG_MASK GENMASK(5, 4) +#define STAT_POLARITY_CFG_BIT BIT(3) +#define STAT_PARALLEL_CFG_BIT BIT(2) +#define STAT_FUNCTION_CFG_BIT BIT(1) +#define STAT_IRQ_PULSING_EN_BIT BIT(0) + +#define SDP_CURRENT_UA 500000 +#define CDP_CURRENT_UA 1500000 +#define DCP_CURRENT_UA 1500000 +#define CURRENT_MAX_UA DCP_CURRENT_UA + +/* pmi8998 registers represent current in increments of 1/40th of an amp */ +#define CURRENT_SCALE_FACTOR 25000 +/* clang-format on */ + +enum charger_status { + TRICKLE_CHARGE = 0, + PRE_CHARGE, + FAST_CHARGE, + FULLON_CHARGE, + TAPER_CHARGE, + TERMINATE_CHARGE, + INHIBIT_CHARGE, + DISABLE_CHARGE, +}; + +struct smb_init_register { + u16 addr; + u8 mask; + u8 val; +}; + +/** + * struct smb_chip - smb chip structure + * @dev: Device reference for power_supply + * @name: The platform device name + * @base: Base address for smb registers + * @regmap: Register map + * @batt_info: Battery data from DT + * @status_change_work: Worker to handle plug/unplug events + * @cable_irq: USB plugin IRQ + * @wakeup_enabled: If the cable IRQ will cause a wakeup + * @usb_in_i_chan: USB_IN current measurement channel + * @usb_in_v_chan: USB_IN voltage measurement channel + * @chg_psy: Charger power supply instance + */ +struct smb_chip { + struct device *dev; + const char *name; + unsigned int base; + struct regmap *regmap; + struct power_supply_battery_info *batt_info; + + struct delayed_work status_change_work; + int cable_irq; + bool wakeup_enabled; + + struct iio_channel *usb_in_i_chan; + struct iio_channel *usb_in_v_chan; + + struct power_supply *chg_psy; +}; + +static enum power_supply_property smb_properties[] = { + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_USB_TYPE, +}; + +static int smb_get_prop_usb_online(struct smb_chip *chip, int *val) +{ + unsigned int stat; + int rc; + + rc = regmap_read(chip->regmap, chip->base + POWER_PATH_STATUS, &stat); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read power path status: %d\n", rc); + return rc; + } + + *val = (stat & P_PATH_USE_USBIN_BIT) && + (stat & P_PATH_VALID_INPUT_POWER_SOURCE_STS_BIT); + return 0; +} + +/* + * Qualcomm "automatic power source detection" aka APSD + * tells us what type of charger we're connected to. + */ +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 = smb_get_prop_usb_online(chip, &usb_online); + if (!usb_online) { + *val = POWER_SUPPLY_USB_TYPE_UNKNOWN; + return rc; + } + + rc = regmap_read(chip->regmap, chip->base + APSD_STATUS, &apsd_stat); + if (rc < 0) { + dev_err(chip->dev, "Failed to read apsd status, rc = %d", rc); + return rc; + } + if (!(apsd_stat & APSD_DTC_STATUS_DONE_BIT)) { + dev_dbg(chip->dev, "Apsd not ready"); + return -EAGAIN; + } + + rc = regmap_read(chip->regmap, chip->base + APSD_RESULT_STATUS, &stat); + if (rc < 0) { + dev_err(chip->dev, "Failed to read apsd result, rc = %d", rc); + return rc; + } + + stat &= APSD_RESULT_STATUS_MASK; + + if (stat & CDP_CHARGER_BIT) + *val = POWER_SUPPLY_USB_TYPE_CDP; + else if (stat & (DCP_CHARGER_BIT | OCP_CHARGER_BIT | FLOAT_CHARGER_BIT)) + *val = POWER_SUPPLY_USB_TYPE_DCP; + else /* SDP_CHARGER_BIT (or others) */ + *val = POWER_SUPPLY_USB_TYPE_SDP; + + return 0; +} + +static int smb_get_prop_status(struct smb_chip *chip, int *val) +{ + unsigned char stat[2]; + int usb_online = 0; + int rc; + + rc = smb_get_prop_usb_online(chip, &usb_online); + if (!usb_online) { + *val = POWER_SUPPLY_STATUS_DISCHARGING; + return rc; + } + + rc = regmap_bulk_read(chip->regmap, + chip->base + BATTERY_CHARGER_STATUS_1, &stat, 2); + if (rc < 0) { + dev_err(chip->dev, "Failed to read charging status ret=%d\n", + rc); + return rc; + } + + if (stat[1] & CHARGER_ERROR_STATUS_BAT_OV_BIT) { + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + return 0; + } + + stat[0] = stat[0] & BATTERY_CHARGER_STATUS_MASK; + + switch (stat[0]) { + case TRICKLE_CHARGE: + case PRE_CHARGE: + case FAST_CHARGE: + case FULLON_CHARGE: + case TAPER_CHARGE: + *val = POWER_SUPPLY_STATUS_CHARGING; + return rc; + case DISABLE_CHARGE: + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + return rc; + case TERMINATE_CHARGE: + case INHIBIT_CHARGE: + *val = POWER_SUPPLY_STATUS_FULL; + return rc; + default: + *val = POWER_SUPPLY_STATUS_UNKNOWN; + return rc; + } +} + +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); + + if (rc >= 0) + *val *= CURRENT_SCALE_FACTOR; + return rc; +} + +static int smb_set_current_limit(struct smb_chip *chip, unsigned int val) +{ + unsigned char val_raw; + + if (val > 4800000) { + dev_err(chip->dev, + "Can't set current limit higher than 4800000uA"); + return -EINVAL; + } + val_raw = val / CURRENT_SCALE_FACTOR; + + return regmap_write(chip->regmap, chip->base + USBIN_CURRENT_LIMIT_CFG, + val_raw); +} + +static void smb_status_change_work(struct work_struct *work) +{ + unsigned int charger_type, current_ua; + int usb_online = 0; + int count, rc; + struct smb_chip *chip; + + chip = container_of(work, struct smb_chip, status_change_work.work); + + 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 = smb_apsd_get_charger_type(chip, &charger_type); + if (rc != -EAGAIN) + break; + msleep(100); + } + + if (rc < 0 && rc != -EAGAIN) { + dev_err(chip->dev, "get charger type failed: %d\n", rc); + return; + } + + if (rc < 0) { + rc = regmap_update_bits(chip->regmap, chip->base + CMD_APSD, + APSD_RERUN_BIT, APSD_RERUN_BIT); + schedule_delayed_work(&chip->status_change_work, + msecs_to_jiffies(1000)); + dev_dbg(chip->dev, "get charger type failed, rerun apsd\n"); + return; + } + + switch (charger_type) { + case POWER_SUPPLY_USB_TYPE_CDP: + current_ua = CDP_CURRENT_UA; + break; + case POWER_SUPPLY_USB_TYPE_DCP: + current_ua = DCP_CURRENT_UA; + break; + case POWER_SUPPLY_USB_TYPE_SDP: + default: + current_ua = SDP_CURRENT_UA; + break; + } + + smb_set_current_limit(chip, current_ua); + power_supply_changed(chip->chg_psy); +} + +static int smb_get_iio_chan(struct smb_chip *chip, struct iio_channel *chan, + int *val) +{ + int rc; + union power_supply_propval status; + + rc = power_supply_get_property(chip->chg_psy, POWER_SUPPLY_PROP_STATUS, + &status); + if (rc < 0 || status.intval != POWER_SUPPLY_STATUS_CHARGING) { + *val = 0; + return 0; + } + + if (IS_ERR(chan)) { + dev_err(chip->dev, "Failed to chan, err = %li", PTR_ERR(chan)); + return PTR_ERR(chan); + } + + return iio_read_channel_processed(chan, val); +} + +static int smb_get_prop_health(struct smb_chip *chip, int *val) +{ + int rc; + unsigned int stat; + + rc = regmap_read(chip->regmap, chip->base + BATTERY_CHARGER_STATUS_2, + &stat); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read charger status rc=%d\n", rc); + return rc; + } + + switch (stat) { + case CHARGER_ERROR_STATUS_BAT_OV_BIT: + *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + return 0; + case BAT_TEMP_STATUS_TOO_COLD_BIT: + *val = POWER_SUPPLY_HEALTH_COLD; + return 0; + case BAT_TEMP_STATUS_TOO_HOT_BIT: + *val = POWER_SUPPLY_HEALTH_OVERHEAT; + return 0; + case BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT: + *val = POWER_SUPPLY_HEALTH_COOL; + return 0; + case BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT: + *val = POWER_SUPPLY_HEALTH_WARM; + return 0; + default: + *val = POWER_SUPPLY_HEALTH_GOOD; + return 0; + } +} + +static int smb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "Qualcomm"; + return 0; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = chip->name; + return 0; + case POWER_SUPPLY_PROP_CURRENT_MAX: + return smb_get_current_limit(chip, &val->intval); + case POWER_SUPPLY_PROP_CURRENT_NOW: + return smb_get_iio_chan(chip, chip->usb_in_i_chan, + &val->intval); + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return smb_get_iio_chan(chip, chip->usb_in_v_chan, + &val->intval); + case POWER_SUPPLY_PROP_ONLINE: + return smb_get_prop_usb_online(chip, &val->intval); + case POWER_SUPPLY_PROP_STATUS: + return smb_get_prop_status(chip, &val->intval); + case POWER_SUPPLY_PROP_HEALTH: + return smb_get_prop_health(chip, &val->intval); + case POWER_SUPPLY_PROP_USB_TYPE: + return smb_apsd_get_charger_type(chip, &val->intval); + default: + dev_err(chip->dev, "invalid property: %d\n", psp); + return -EINVAL; + } +} + +static int smb_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + return smb_set_current_limit(chip, val->intval); + default: + dev_err(chip->dev, "No setter for property: %d\n", psp); + return -EINVAL; + } +} + +static int smb_property_is_writable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + return 1; + default: + return 0; + } +} + +static irqreturn_t smb_handle_batt_overvoltage(int irq, void *data) +{ + struct smb_chip *chip = data; + unsigned int status; + + regmap_read(chip->regmap, chip->base + BATTERY_CHARGER_STATUS_2, + &status); + + if (status & CHARGER_ERROR_STATUS_BAT_OV_BIT) { + /* The hardware stops charging automatically */ + dev_err(chip->dev, "battery overvoltage detected\n"); + power_supply_changed(chip->chg_psy); + } + + return IRQ_HANDLED; +} + +static irqreturn_t smb_handle_usb_plugin(int irq, void *data) +{ + struct smb_chip *chip = data; + + power_supply_changed(chip->chg_psy); + + schedule_delayed_work(&chip->status_change_work, + msecs_to_jiffies(1500)); + + return IRQ_HANDLED; +} + +static irqreturn_t smb_handle_usb_icl_change(int irq, void *data) +{ + struct smb_chip *chip = data; + + power_supply_changed(chip->chg_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t smb_handle_wdog_bark(int irq, void *data) +{ + struct smb_chip *chip = data; + int rc; + + power_supply_changed(chip->chg_psy); + + rc = regmap_write(chip->regmap, BARK_BITE_WDOG_PET, + BARK_BITE_WDOG_PET_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't pet the dog rc=%d\n", rc); + + return IRQ_HANDLED; +} + +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 = 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 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 + * FIXME: This will be handled by the type-c driver + */ + { .addr = TYPE_C_INTRPT_ENB_SOFTWARE_CTRL, + .mask = TYPEC_POWER_ROLE_CMD_MASK | VCONN_EN_SRC_BIT | + VCONN_EN_VALUE_BIT, + .val = VCONN_EN_SRC_BIT }, + /* + * Disable Type-C factory mode and stay in Attached.SRC state when VCONN + * over-current happens + */ + { .addr = TYPE_C_CFG, + .mask = FACTORY_MODE_DETECTION_EN_BIT | VCONN_OC_CFG_BIT, + .val = 0 }, + /* Configure VBUS for software control */ + { .addr = OTG_CFG, .mask = OTG_EN_SRC_CFG_BIT, .val = 0 }, + /* + * Use VBAT to determine the recharge threshold when battery is full + * rather than the state of charge. + */ + { .addr = FG_UPDATE_CFG_2_SEL, + .mask = SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT | + VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT, + .val = VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT }, + /* Enable charging */ + { .addr = USBIN_OPTIONS_1_CFG, .mask = HVDCP_EN_BIT, .val = 0 }, + { .addr = CHARGING_ENABLE_CMD, + .mask = CHARGING_ENABLE_CMD_BIT, + .val = CHARGING_ENABLE_CMD_BIT }, + /* + * Match downstream defaults + * CHG_EN_SRC_BIT - charger enable is controlled by software + * CHG_EN_POLARITY_BIT - polarity of charge enable pin when in HW control + * pulled low on OnePlus 6 and SHIFT6mq + * PRETOFAST_TRANSITION_CFG_BIT - + * BAT_OV_ECC_BIT - + * I_TERM_BIT - Current termination ?? 0 = enabled + * AUTO_RECHG_BIT - Enable automatic recharge when battery is full + * 0 = enabled + * EN_ANALOG_DROP_IN_VBATT_BIT + * CHARGER_INHIBIT_BIT - Inhibit charging based on battery voltage + * instead of ?? + */ + { .addr = CHGR_CFG2, + .mask = CHG_EN_SRC_BIT | CHG_EN_POLARITY_BIT | + PRETOFAST_TRANSITION_CFG_BIT | BAT_OV_ECC_BIT | I_TERM_BIT | + AUTO_RECHG_BIT | EN_ANALOG_DROP_IN_VBATT_BIT | + CHARGER_INHIBIT_BIT, + .val = CHARGER_INHIBIT_BIT }, + /* STAT pin software override, match downstream. Parallel charging? */ + { .addr = STAT_CFG, + .mask = STAT_SW_OVERRIDE_CFG_BIT, + .val = STAT_SW_OVERRIDE_CFG_BIT }, + /* Set the default SDP charger type to a 500ma USB 2.0 port */ + { .addr = USBIN_ICL_OPTIONS, + .mask = USB51_MODE_BIT | USBIN_MODE_CHG_BIT, + .val = USB51_MODE_BIT }, + /* Disable watchdog */ + { .addr = SNARL_BARK_BITE_WD_CFG, .mask = 0xff, .val = 0 }, + { .addr = WD_CFG, + .mask = WATCHDOG_TRIGGER_AFP_EN_BIT | WDOG_TIMER_EN_ON_PLUGIN_BIT | + BARK_WDOG_INT_EN_BIT, + .val = 0 }, + /* These bits aren't documented anywhere */ + { .addr = USBIN_5V_AICL_THRESHOLD_CFG, + .mask = USBIN_5V_AICL_THRESHOLD_CFG_MASK, + .val = 0x3 }, + { .addr = USBIN_CONT_AICL_THRESHOLD_CFG, + .mask = USBIN_CONT_AICL_THRESHOLD_CFG_MASK, + .val = 0x3 }, + /* + * Enable Automatic Input Current Limit, this will slowly ramp up the current + * When connected to a wall charger, and automatically stop when it detects + * the charger current limit (voltage drop?) or it reaches the programmed limit. + */ + { .addr = USBIN_AICL_OPTIONS_CFG, + .mask = USBIN_AICL_START_AT_MAX_BIT | USBIN_AICL_ADC_EN_BIT | + USBIN_AICL_EN_BIT | SUSPEND_ON_COLLAPSE_USBIN_BIT | + USBIN_HV_COLLAPSE_RESPONSE_BIT | + USBIN_LV_COLLAPSE_RESPONSE_BIT, + .val = USBIN_HV_COLLAPSE_RESPONSE_BIT | + USBIN_LV_COLLAPSE_RESPONSE_BIT | USBIN_AICL_EN_BIT }, + /* + * Set pre charge current to default, the OnePlus 6 bootloader + * sets this very conservatively. + */ + { .addr = PRE_CHARGE_CURRENT_CFG, + .mask = PRE_CHARGE_CURRENT_SETTING_MASK, + .val = 500000 / CURRENT_SCALE_FACTOR }, + /* + * This overrides all of the current limit options exposed to userspace + * and prevents the device from pulling more than ~1A. This is done + * to minimise potential fire hazard risks. + */ + { .addr = FAST_CHARGE_CURRENT_CFG, + .mask = FAST_CHARGE_CURRENT_SETTING_MASK, + .val = 1000000 / CURRENT_SCALE_FACTOR }, +}; + +static int smb_init_hw(struct smb_chip *chip) +{ + int rc, i; + + for (i = 0; i < ARRAY_SIZE(smb_init_seq); i++) { + dev_dbg(chip->dev, "%d: Writing 0x%02x to 0x%02x\n", i, + smb_init_seq[i].val, smb_init_seq[i].addr); + rc = regmap_update_bits(chip->regmap, + 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", + __func__, i); + } + + return 0; +} + +static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name, + irqreturn_t (*handler)(int irq, void *data)) +{ + int irqnum; + int rc; + + irqnum = platform_get_irq_byname(to_platform_device(chip->dev), name); + if (irqnum < 0) + return irqnum; + + rc = devm_request_threaded_irq(chip->dev, irqnum, NULL, handler, + IRQF_ONESHOT, name, chip); + if (rc < 0) + return dev_err_probe(chip->dev, rc, "Couldn't request irq %s\n", + name); + + if (irq) + *irq = irqnum; + + return 0; +} + +static int smb_probe(struct platform_device *pdev) +{ + struct power_supply_config supply_config = {}; + struct power_supply_desc *desc; + struct smb_chip *chip; + int rc, irq; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &pdev->dev; + chip->name = pdev->name; + + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) + return dev_err_probe(chip->dev, -ENODEV, + "failed to locate the regmap\n"); + + rc = device_property_read_u32(chip->dev, "reg", &chip->base); + if (rc < 0) + return dev_err_probe(chip->dev, rc, + "Couldn't read base address\n"); + + chip->usb_in_v_chan = devm_iio_channel_get(chip->dev, "usbin_v"); + if (IS_ERR(chip->usb_in_v_chan)) + return dev_err_probe(chip->dev, PTR_ERR(chip->usb_in_v_chan), + "Couldn't get usbin_v IIO channel\n"); + + chip->usb_in_i_chan = devm_iio_channel_get(chip->dev, "usbin_i"); + if (IS_ERR(chip->usb_in_i_chan)) { + return dev_err_probe(chip->dev, PTR_ERR(chip->usb_in_i_chan), + "Couldn't get usbin_i IIO channel\n"); + } + + rc = smb_init_hw(chip); + if (rc < 0) + return rc; + + supply_config.drv_data = chip; + supply_config.fwnode = dev_fwnode(&pdev->dev); + + desc = devm_kzalloc(chip->dev, sizeof(smb_psy_desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + 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)); + if (!desc->name) + return -ENOMEM; + + chip->chg_psy = + devm_power_supply_register(chip->dev, desc, &supply_config); + if (IS_ERR(chip->chg_psy)) + return dev_err_probe(chip->dev, PTR_ERR(chip->chg_psy), + "failed to register power supply\n"); + + rc = power_supply_get_battery_info(chip->chg_psy, &chip->batt_info); + if (rc) + return dev_err_probe(chip->dev, rc, + "Failed to get battery info\n"); + + rc = devm_delayed_work_autocancel(chip->dev, &chip->status_change_work, + smb_status_change_work); + if (rc) + return dev_err_probe(chip->dev, rc, + "Failed to init status change work\n"); + + rc = (chip->batt_info->voltage_max_design_uv - 3487500) / 7500 + 1; + rc = regmap_update_bits(chip->regmap, chip->base + FLOAT_VOLTAGE_CFG, + FLOAT_VOLTAGE_SETTING_MASK, rc); + if (rc < 0) + return dev_err_probe(chip->dev, rc, "Couldn't set vbat max\n"); + + rc = smb_init_irq(chip, &irq, "bat-ov", smb_handle_batt_overvoltage); + if (rc < 0) + return rc; + + rc = smb_init_irq(chip, &chip->cable_irq, "usb-plugin", + smb_handle_usb_plugin); + if (rc < 0) + return rc; + + rc = smb_init_irq(chip, &irq, "usbin-icl-change", + smb_handle_usb_icl_change); + if (rc < 0) + return rc; + rc = smb_init_irq(chip, &irq, "wdog-bark", smb_handle_wdog_bark); + if (rc < 0) + return rc; + + 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"); + + platform_set_drvdata(pdev, chip); + + /* Initialise charger state */ + schedule_delayed_work(&chip->status_change_work, 0); + + return 0; +} + +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, smb_match_id_table); + +static struct platform_driver qcom_spmi_smb = { + .probe = smb_probe, + .driver = { + .name = "qcom-smbx-charger", + .of_match_table = smb_match_id_table, + }, +}; + +module_platform_driver(qcom_spmi_smb); + +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 4f9c1c417916..9436c6bbf51f 100644 --- a/drivers/power/supply/rk817_charger.c +++ b/drivers/power/supply/rk817_charger.c @@ -8,7 +8,7 @@ * Chris Morgan <macromorgan@hotmail.com> */ -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/devm-helpers.h> #include <linux/mfd/rk808.h> #include <linux/irq.h> @@ -240,9 +240,32 @@ static int rk817_record_battery_nvram_values(struct rk817_charger *charger) static int rk817_bat_calib_cap(struct rk817_charger *charger) { struct rk808 *rk808 = charger->rk808; - int tmp, charge_now, charge_now_adc, volt_avg; + int charge_now, charge_now_adc; u8 bulk_reg[4]; + /* Don't do anything if there's no battery. */ + if (!charger->battery_present) + return 0; + + /* + * When resuming from suspend, sometimes the voltage value would be + * incorrect. BSP would simply wait two seconds and try reading the + * values again. Do not do any sort of calibration activity when the + * reported value is incorrect. The next scheduled update of battery + * vaules should then return valid data and the driver can continue. + * Use 2.7v as the sanity value because per the datasheet the PMIC + * can in no way support a battery voltage lower than this. BSP only + * checked for values too low, but I'm adding in a check for values + * too high just in case; again the PMIC can in no way support + * voltages above 4.45v, so this seems like a good value. + */ + if ((charger->volt_avg_uv < 2700000) || (charger->volt_avg_uv > 4450000)) { + dev_dbg(charger->dev, + "Battery voltage of %d is invalid, ignoring.\n", + charger->volt_avg_uv); + return -EINVAL; + } + /* Calibrate the soc and fcc on a fully charged battery */ if (charger->charge_status == CHARGE_FINISH && (!charger->soc_cal)) { @@ -304,37 +327,6 @@ static int rk817_bat_calib_cap(struct rk817_charger *charger) } } - /* - * Calibrate the fully charged capacity when we previously had a full - * battery (soc_cal = 1) and are now empty (at or below minimum design - * voltage). If our columb counter is still positive, subtract that - * from our fcc value to get a calibrated fcc, and if our columb - * counter is negative add that to our fcc (but not to exceed our - * design capacity). - */ - regmap_bulk_read(charger->rk808->regmap, RK817_GAS_GAUGE_BAT_VOL_H, - bulk_reg, 2); - tmp = get_unaligned_be16(bulk_reg); - volt_avg = (charger->voltage_k * tmp) + 1000 * charger->voltage_b; - if (volt_avg <= charger->bat_voltage_min_design_uv && - charger->soc_cal) { - regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_Q_PRES_H3, - bulk_reg, 4); - charge_now_adc = get_unaligned_be32(bulk_reg); - charge_now = ADC_TO_CHARGE_UAH(charge_now_adc, - charger->res_div); - /* - * Note, if charge_now is negative this will add it (what we - * want) and if it's positive this will subtract (also what - * we want). - */ - charger->fcc_mah = charger->fcc_mah - (charge_now / 1000); - - dev_dbg(charger->dev, - "Recalibrating full charge capacity to %d uah\n", - charger->fcc_mah * 1000); - } - rk817_record_battery_nvram_values(charger); return 0; @@ -634,6 +626,24 @@ static irqreturn_t rk817_plug_out_isr(int irq, void *cg) return IRQ_HANDLED; } +static int rk817_bat_set_prop(struct power_supply *ps, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct rk817_charger *charger = power_supply_get_drvdata(ps); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_FULL: + if ((val->intval < 500000) || + (val->intval > charger->bat_charge_full_design_uah)) + return -EINVAL; + charger->fcc_mah = val->intval / 1000; + return rk817_bat_calib_cap(charger); + default: + return -EINVAL; + } +} + static enum power_supply_property rk817_bat_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_STATUS, @@ -659,24 +669,32 @@ static enum power_supply_property rk817_chg_props[] = { POWER_SUPPLY_PROP_VOLTAGE_AVG, }; -static enum power_supply_usb_type rk817_usb_type[] = { - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_UNKNOWN, -}; +static int rk817_bat_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_FULL: + return 1; + default: + return 0; + } +} static const struct power_supply_desc rk817_bat_desc = { .name = "rk817-battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = rk817_bat_props, + .property_is_writeable = rk817_bat_prop_writeable, .num_properties = ARRAY_SIZE(rk817_bat_props), .get_property = rk817_bat_get_prop, + .set_property = rk817_bat_set_prop, }; static const struct power_supply_desc rk817_chg_desc = { .name = "rk817-charger", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = rk817_usb_type, - .num_usb_types = ARRAY_SIZE(rk817_usb_type), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = rk817_chg_props, .num_properties = ARRAY_SIZE(rk817_chg_props), .get_property = rk817_chg_get_prop, @@ -710,9 +728,10 @@ static int rk817_read_battery_nvram_values(struct rk817_charger *charger) /* * Read the nvram for state of charge. Sanity check for values greater - * than 100 (10000). If the value is off it should get corrected - * automatically when the voltage drops to the min (soc is 0) or when - * the battery is full (soc is 100). + * than 100 (10000) or less than 0, because other things (BSP kernels, + * U-Boot, or even i2cset) can write to this register. If the value is + * off it should get corrected automatically when the voltage drops to + * the min (soc is 0) or when the battery is full (soc is 100). */ ret = regmap_bulk_read(charger->rk808->regmap, RK817_GAS_GAUGE_BAT_R1, bulk_reg, 3); @@ -721,6 +740,8 @@ static int rk817_read_battery_nvram_values(struct rk817_charger *charger) charger->soc = get_unaligned_le24(bulk_reg); if (charger->soc > 10000) charger->soc = 10000; + if (charger->soc < 0) + charger->soc = 0; return 0; } @@ -731,8 +752,8 @@ rk817_read_or_set_full_charge_on_boot(struct rk817_charger *charger, { struct rk808 *rk808 = charger->rk808; u8 bulk_reg[4]; - u32 boot_voltage, boot_charge_mah, tmp; - int ret, reg, off_time; + u32 boot_voltage, boot_charge_mah; + int ret, reg, off_time, tmp; bool first_boot; /* @@ -790,7 +811,7 @@ rk817_read_or_set_full_charge_on_boot(struct rk817_charger *charger, boot_charge_mah = ADC_TO_CHARGE_UAH(tmp, charger->res_div) / 1000; /* - * Check if the columb counter has been off for more than 300 + * Check if the columb counter has been off for more than 30 * minutes as it tends to drift downward. If so, re-init soc * with the boot voltage instead. Note the unit values for the * OFF_CNT register appear to be in decaminutes and stops @@ -801,7 +822,7 @@ rk817_read_or_set_full_charge_on_boot(struct rk817_charger *charger, * than 0 on a reboot anyway. */ regmap_read(rk808->regmap, RK817_GAS_GAUGE_OFF_CNT, &off_time); - if (off_time >= 30) { + if (off_time >= 3) { regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_PWRON_VOL_H, bulk_reg, 2); @@ -818,21 +839,6 @@ rk817_read_or_set_full_charge_on_boot(struct rk817_charger *charger, } } - regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_PWRON_VOL_H, - bulk_reg, 2); - tmp = get_unaligned_be16(bulk_reg); - boot_voltage = (charger->voltage_k * tmp) + 1000 * charger->voltage_b; - regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_Q_PRES_H3, - bulk_reg, 4); - tmp = get_unaligned_be32(bulk_reg); - if (tmp < 0) - tmp = 0; - boot_charge_mah = ADC_TO_CHARGE_UAH(tmp, charger->res_div) / 1000; - regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_OCV_VOL_H, - bulk_reg, 2); - tmp = get_unaligned_be16(bulk_reg); - boot_voltage = (charger->voltage_k * tmp) + 1000 * charger->voltage_b; - /* * Now we have our full charge capacity and soc, init the columb * counter. @@ -1040,7 +1046,14 @@ 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) +{ + struct device_node *node = data; + + of_node_put(node); } static int rk817_charger_probe(struct platform_device *pdev) @@ -1059,11 +1072,13 @@ static int rk817_charger_probe(struct platform_device *pdev) if (!node) return -ENODEV; + ret = devm_add_action_or_reset(&pdev->dev, rk817_cleanup_node, node); + if (ret) + return ret; + charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); - if (!charger) { - of_node_put(node); + if (!charger) return -ENOMEM; - } charger->rk808 = rk808; @@ -1073,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 @@ -1132,7 +1147,7 @@ static int rk817_charger_probe(struct platform_device *pdev) &bat_info); if (ret) { return dev_err_probe(dev, ret, - "Unable to get battery info: %d\n", ret); + "Unable to get battery info\n"); } if ((bat_info->charge_full_design_uah <= 0) || @@ -1191,16 +1206,38 @@ 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; +} + +static int __maybe_unused rk817_suspend(struct device *dev) +{ + struct rk817_charger *charger = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&charger->work); + + return 0; +} + +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_percpu_wq, &charger->work, 0); return 0; } +static SIMPLE_DEV_PM_OPS(rk817_charger_pm, rk817_suspend, rk817_resume); static struct platform_driver rk817_charger_driver = { .probe = rk817_charger_probe, .driver = { .name = "rk817-charger", + .pm = &rk817_charger_pm, }, }; module_platform_driver(rk817_charger_driver); @@ -1209,3 +1246,4 @@ MODULE_DESCRIPTION("Battery power supply driver for RK817 PMIC"); MODULE_AUTHOR("Maya Matuszczyk <maccraft123mc@gmail.com>"); MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rk817-charger"); diff --git a/drivers/power/supply/rn5t618_power.c b/drivers/power/supply/rn5t618_power.c index a5e09ac78a50..40dec55a9f73 100644 --- a/drivers/power/supply/rn5t618_power.c +++ b/drivers/power/supply/rn5t618_power.c @@ -14,7 +14,6 @@ #include <linux/interrupt.h> #include <linux/module.h> #include <linux/mfd/rn5t618.h> -#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/regmap.h> @@ -71,13 +70,6 @@ struct rn5t618_power_info { int irq; }; -static enum power_supply_usb_type rn5t618_usb_types[] = { - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_UNKNOWN -}; - static enum power_supply_property rn5t618_usb_props[] = { /* input current limit is not very accurate */ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, @@ -682,8 +674,10 @@ static const struct power_supply_desc rn5t618_adp_desc = { static const struct power_supply_desc rn5t618_usb_desc = { .name = "rn5t618-usb", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = rn5t618_usb_types, - .num_usb_types = ARRAY_SIZE(rn5t618_usb_types), + .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 = rn5t618_usb_props, .num_properties = ARRAY_SIZE(rn5t618_usb_props), .get_property = rn5t618_usb_get_property, diff --git a/drivers/power/supply/rt5033_battery.c b/drivers/power/supply/rt5033_battery.c index 5c04cf305219..b2674adfa30b 100644 --- a/drivers/power/supply/rt5033_battery.c +++ b/drivers/power/supply/rt5033_battery.c @@ -6,11 +6,33 @@ * Author: Beomho Seo <beomho.seo@samsung.com> */ +#include <linux/i2c.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/power_supply.h> +#include <linux/regmap.h> #include <linux/mfd/rt5033-private.h> -#include <linux/mfd/rt5033.h> + +struct rt5033_battery { + struct i2c_client *client; + struct regmap *regmap; + struct power_supply *psy; +}; + +static int rt5033_battery_get_status(struct i2c_client *client) +{ + struct rt5033_battery *battery = i2c_get_clientdata(client); + union power_supply_propval val; + int ret; + + ret = power_supply_get_property_from_supplier(battery->psy, + POWER_SUPPLY_PROP_STATUS, + &val); + if (ret) + val.intval = POWER_SUPPLY_STATUS_UNKNOWN; + + return val.intval; +} static int rt5033_battery_get_capacity(struct i2c_client *client) { @@ -84,6 +106,9 @@ static int rt5033_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CAPACITY: val->intval = rt5033_battery_get_capacity(battery->client); break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = rt5033_battery_get_status(battery->client); + break; default: return -EINVAL; } @@ -96,6 +121,7 @@ static enum power_supply_property rt5033_battery_props[] = { POWER_SUPPLY_PROP_VOLTAGE_OCV, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_STATUS, }; static const struct regmap_config rt5033_battery_regmap_config = { @@ -117,7 +143,6 @@ static int rt5033_battery_probe(struct i2c_client *client) struct i2c_adapter *adapter = client->adapter; struct power_supply_config psy_cfg = {}; struct rt5033_battery *battery; - u32 ret; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) return -EIO; @@ -135,26 +160,19 @@ static int rt5033_battery_probe(struct i2c_client *client) } i2c_set_clientdata(client, battery); + psy_cfg.fwnode = dev_fwnode(&client->dev); psy_cfg.drv_data = battery; - battery->psy = power_supply_register(&client->dev, - &rt5033_battery_desc, &psy_cfg); - if (IS_ERR(battery->psy)) { - dev_err(&client->dev, "Failed to register power supply\n"); - ret = PTR_ERR(battery->psy); - return ret; - } + battery->psy = devm_power_supply_register(&client->dev, + &rt5033_battery_desc, + &psy_cfg); + if (IS_ERR(battery->psy)) + return dev_err_probe(&client->dev, PTR_ERR(battery->psy), + "Failed to register power supply\n"); return 0; } -static void rt5033_battery_remove(struct i2c_client *client) -{ - struct rt5033_battery *battery = i2c_get_clientdata(client); - - power_supply_unregister(battery->psy); -} - static const struct i2c_device_id rt5033_battery_id[] = { { "rt5033-battery", }, { } @@ -172,8 +190,7 @@ static struct i2c_driver rt5033_battery_driver = { .name = "rt5033-battery", .of_match_table = rt5033_battery_of_match, }, - .probe_new = rt5033_battery_probe, - .remove = rt5033_battery_remove, + .probe = rt5033_battery_probe, .id_table = rt5033_battery_id, }; module_i2c_driver(rt5033_battery_driver); diff --git a/drivers/power/supply/rt5033_charger.c b/drivers/power/supply/rt5033_charger.c new file mode 100644 index 000000000000..de724f23e453 --- /dev/null +++ b/drivers/power/supply/rt5033_charger.c @@ -0,0 +1,753 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Battery charger driver for RT5033 + * + * Copyright (C) 2014 Samsung Electronics, Co., Ltd. + * Author: Beomho Seo <beomho.seo@samsung.com> + */ + +#include <linux/devm-helpers.h> +#include <linux/extcon.h> +#include <linux/mod_devicetable.h> +#include <linux/module.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/mfd/rt5033-private.h> +#include <linux/property.h> + +struct rt5033_charger_data { + unsigned int pre_uamp; + unsigned int pre_uvolt; + unsigned int const_uvolt; + unsigned int eoc_uamp; + unsigned int fast_uamp; +}; + +struct rt5033_charger { + struct device *dev; + struct regmap *regmap; + struct power_supply *psy; + struct rt5033_charger_data chg; + struct extcon_dev *edev; + struct notifier_block extcon_nb; + struct work_struct extcon_work; + struct mutex lock; + bool online; + bool otg; + bool mivr_enabled; + u8 cv_regval; +}; + +static int rt5033_get_charger_state(struct rt5033_charger *charger) +{ + struct regmap *regmap = charger->regmap; + unsigned int reg_data; + int state; + + if (!regmap) + return POWER_SUPPLY_STATUS_UNKNOWN; + + regmap_read(regmap, RT5033_REG_CHG_STAT, ®_data); + + switch (reg_data & RT5033_CHG_STAT_MASK) { + case RT5033_CHG_STAT_DISCHARGING: + state = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case RT5033_CHG_STAT_CHARGING: + state = POWER_SUPPLY_STATUS_CHARGING; + break; + case RT5033_CHG_STAT_FULL: + state = POWER_SUPPLY_STATUS_FULL; + break; + case RT5033_CHG_STAT_NOT_CHARGING: + state = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + state = POWER_SUPPLY_STATUS_UNKNOWN; + } + + /* For OTG mode, RT5033 would still report "charging" */ + if (charger->otg) + state = POWER_SUPPLY_STATUS_DISCHARGING; + + return state; +} + +static int rt5033_get_charger_type(struct rt5033_charger *charger) +{ + struct regmap *regmap = charger->regmap; + unsigned int reg_data; + int state; + + regmap_read(regmap, RT5033_REG_CHG_STAT, ®_data); + + switch (reg_data & RT5033_CHG_STAT_TYPE_MASK) { + case RT5033_CHG_STAT_TYPE_FAST: + state = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case RT5033_CHG_STAT_TYPE_PRE: + state = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + default: + state = POWER_SUPPLY_CHARGE_TYPE_NONE; + } + + return state; +} + +static int rt5033_get_charger_current_limit(struct rt5033_charger *charger) +{ + struct regmap *regmap = charger->regmap; + unsigned int state, reg_data, data; + + regmap_read(regmap, RT5033_REG_CHG_CTRL5, ®_data); + + state = (reg_data & RT5033_CHGCTRL5_ICHG_MASK) + >> RT5033_CHGCTRL5_ICHG_SHIFT; + + data = RT5033_CHARGER_FAST_CURRENT_MIN + + RT5033_CHARGER_FAST_CURRENT_STEP_NUM * state; + + return data; +} + +static int rt5033_get_charger_const_voltage(struct rt5033_charger *charger) +{ + struct regmap *regmap = charger->regmap; + unsigned int state, reg_data, data; + + regmap_read(regmap, RT5033_REG_CHG_CTRL2, ®_data); + + state = (reg_data & RT5033_CHGCTRL2_CV_MASK) + >> RT5033_CHGCTRL2_CV_SHIFT; + + data = RT5033_CHARGER_CONST_VOLTAGE_LIMIT_MIN + + RT5033_CHARGER_CONST_VOLTAGE_STEP_NUM * state; + + return data; +} + +static inline int rt5033_init_const_charge(struct rt5033_charger *charger) +{ + struct rt5033_charger_data *chg = &charger->chg; + int ret; + unsigned int val; + u8 reg_data; + + /* Set constant voltage mode */ + if (chg->const_uvolt < RT5033_CHARGER_CONST_VOLTAGE_LIMIT_MIN || + chg->const_uvolt > RT5033_CHARGER_CONST_VOLTAGE_LIMIT_MAX) { + dev_err(charger->dev, + "Value 'constant-charge-voltage-max-microvolt' out of range\n"); + return -EINVAL; + } + + if (chg->const_uvolt == RT5033_CHARGER_CONST_VOLTAGE_LIMIT_MIN) + reg_data = 0x00; + else if (chg->const_uvolt == RT5033_CHARGER_CONST_VOLTAGE_LIMIT_MAX) + reg_data = RT5033_CV_MAX_VOLTAGE; + else { + val = chg->const_uvolt; + val -= RT5033_CHARGER_CONST_VOLTAGE_LIMIT_MIN; + val /= RT5033_CHARGER_CONST_VOLTAGE_STEP_NUM; + reg_data = val; + } + + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL2, + RT5033_CHGCTRL2_CV_MASK, + reg_data << RT5033_CHGCTRL2_CV_SHIFT); + if (ret) { + dev_err(charger->dev, "Failed regmap update\n"); + return -EINVAL; + } + + /* Store that value for later usage */ + charger->cv_regval = reg_data; + + /* Set end of charge current */ + if (chg->eoc_uamp < RT5033_CHARGER_EOC_MIN || + chg->eoc_uamp > RT5033_CHARGER_EOC_MAX) { + dev_err(charger->dev, + "Value 'charge-term-current-microamp' out of range\n"); + return -EINVAL; + } + + if (chg->eoc_uamp == RT5033_CHARGER_EOC_MIN) + reg_data = 0x01; + else if (chg->eoc_uamp == RT5033_CHARGER_EOC_MAX) + reg_data = 0x07; + else { + val = chg->eoc_uamp; + if (val < RT5033_CHARGER_EOC_REF) { + val -= RT5033_CHARGER_EOC_MIN; + val /= RT5033_CHARGER_EOC_STEP_NUM1; + reg_data = 0x01 + val; + } else if (val > RT5033_CHARGER_EOC_REF) { + val -= RT5033_CHARGER_EOC_REF; + val /= RT5033_CHARGER_EOC_STEP_NUM2; + reg_data = 0x04 + val; + } else { + reg_data = 0x04; + } + } + + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL4, + RT5033_CHGCTRL4_EOC_MASK, reg_data); + if (ret) { + dev_err(charger->dev, "Failed regmap update\n"); + return -EINVAL; + } + + return 0; +} + +static inline int rt5033_init_fast_charge(struct rt5033_charger *charger) +{ + struct rt5033_charger_data *chg = &charger->chg; + int ret; + unsigned int val; + u8 reg_data; + + /* Set limit input current */ + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL1, + RT5033_CHGCTRL1_IAICR_MASK, RT5033_AICR_2000_MODE); + if (ret) { + dev_err(charger->dev, "Failed regmap update\n"); + return -EINVAL; + } + + /* Set fast-charge mode charging current */ + if (chg->fast_uamp < RT5033_CHARGER_FAST_CURRENT_MIN || + chg->fast_uamp > RT5033_CHARGER_FAST_CURRENT_MAX) { + dev_err(charger->dev, + "Value 'constant-charge-current-max-microamp' out of range\n"); + return -EINVAL; + } + + if (chg->fast_uamp == RT5033_CHARGER_FAST_CURRENT_MIN) + reg_data = 0x00; + else if (chg->fast_uamp == RT5033_CHARGER_FAST_CURRENT_MAX) + reg_data = RT5033_CHG_MAX_CURRENT; + else { + val = chg->fast_uamp; + val -= RT5033_CHARGER_FAST_CURRENT_MIN; + val /= RT5033_CHARGER_FAST_CURRENT_STEP_NUM; + reg_data = val; + } + + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL5, + RT5033_CHGCTRL5_ICHG_MASK, + reg_data << RT5033_CHGCTRL5_ICHG_SHIFT); + if (ret) { + dev_err(charger->dev, "Failed regmap update\n"); + return -EINVAL; + } + + return 0; +} + +static inline int rt5033_init_pre_charge(struct rt5033_charger *charger) +{ + struct rt5033_charger_data *chg = &charger->chg; + int ret; + unsigned int val; + u8 reg_data; + + /* Set pre-charge threshold voltage */ + if (chg->pre_uvolt < RT5033_CHARGER_PRE_THRESHOLD_LIMIT_MIN || + chg->pre_uvolt > RT5033_CHARGER_PRE_THRESHOLD_LIMIT_MAX) { + dev_err(charger->dev, + "Value 'precharge-upper-limit-microvolt' out of range\n"); + return -EINVAL; + } + + if (chg->pre_uvolt == RT5033_CHARGER_PRE_THRESHOLD_LIMIT_MIN) + reg_data = 0x00; + else if (chg->pre_uvolt == RT5033_CHARGER_PRE_THRESHOLD_LIMIT_MAX) + reg_data = 0x0f; + else { + val = chg->pre_uvolt; + val -= RT5033_CHARGER_PRE_THRESHOLD_LIMIT_MIN; + val /= RT5033_CHARGER_PRE_THRESHOLD_STEP_NUM; + reg_data = val; + } + + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL5, + RT5033_CHGCTRL5_VPREC_MASK, reg_data); + if (ret) { + dev_err(charger->dev, "Failed regmap update\n"); + return -EINVAL; + } + + /* Set pre-charge mode charging current */ + if (chg->pre_uamp < RT5033_CHARGER_PRE_CURRENT_LIMIT_MIN || + chg->pre_uamp > RT5033_CHARGER_PRE_CURRENT_LIMIT_MAX) { + dev_err(charger->dev, + "Value 'precharge-current-microamp' out of range\n"); + return -EINVAL; + } + + if (chg->pre_uamp == RT5033_CHARGER_PRE_CURRENT_LIMIT_MIN) + reg_data = 0x00; + else if (chg->pre_uamp == RT5033_CHARGER_PRE_CURRENT_LIMIT_MAX) + reg_data = RT5033_CHG_MAX_PRE_CURRENT; + else { + val = chg->pre_uamp; + val -= RT5033_CHARGER_PRE_CURRENT_LIMIT_MIN; + val /= RT5033_CHARGER_PRE_CURRENT_STEP_NUM; + reg_data = val; + } + + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL4, + RT5033_CHGCTRL4_IPREC_MASK, + reg_data << RT5033_CHGCTRL4_IPREC_SHIFT); + if (ret) { + dev_err(charger->dev, "Failed regmap update\n"); + return -EINVAL; + } + + return 0; +} + +static int rt5033_charger_reg_init(struct rt5033_charger *charger) +{ + int ret = 0; + + /* Enable charging termination */ + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL1, + RT5033_CHGCTRL1_TE_EN_MASK, RT5033_TE_ENABLE); + if (ret) { + dev_err(charger->dev, "Failed to enable charging termination.\n"); + return -EINVAL; + } + + /* + * Disable minimum input voltage regulation (MIVR), this improves + * the charging performance. + */ + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL4, + RT5033_CHGCTRL4_MIVR_MASK, RT5033_CHARGER_MIVR_DISABLE); + if (ret) { + dev_err(charger->dev, "Failed to disable MIVR.\n"); + return -EINVAL; + } + + ret = rt5033_init_pre_charge(charger); + if (ret) + return ret; + + ret = rt5033_init_fast_charge(charger); + if (ret) + return ret; + + ret = rt5033_init_const_charge(charger); + if (ret) + return ret; + + return 0; +} + +static int rt5033_charger_set_otg(struct rt5033_charger *charger) +{ + int ret; + + mutex_lock(&charger->lock); + + /* Set OTG boost v_out to 5 volts */ + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL2, + RT5033_CHGCTRL2_CV_MASK, + 0x37 << RT5033_CHGCTRL2_CV_SHIFT); + if (ret) { + dev_err(charger->dev, "Failed set OTG boost v_out\n"); + ret = -EINVAL; + goto out_unlock; + } + + /* Set operation mode to OTG */ + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL1, + RT5033_CHGCTRL1_MODE_MASK, RT5033_BOOST_MODE); + if (ret) { + dev_err(charger->dev, "Failed to update OTG mode.\n"); + ret = -EINVAL; + goto out_unlock; + } + + /* In case someone switched from charging to OTG directly */ + if (charger->online) + charger->online = false; + + charger->otg = true; + +out_unlock: + mutex_unlock(&charger->lock); + + return ret; +} + +static int rt5033_charger_unset_otg(struct rt5033_charger *charger) +{ + int ret; + u8 data; + + /* Restore constant voltage for charging */ + data = charger->cv_regval; + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL2, + RT5033_CHGCTRL2_CV_MASK, + data << RT5033_CHGCTRL2_CV_SHIFT); + if (ret) { + dev_err(charger->dev, "Failed to restore constant voltage\n"); + return -EINVAL; + } + + /* Set operation mode to charging */ + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL1, + RT5033_CHGCTRL1_MODE_MASK, RT5033_CHARGER_MODE); + if (ret) { + dev_err(charger->dev, "Failed to update charger mode.\n"); + return -EINVAL; + } + + charger->otg = false; + + return 0; +} + +static int rt5033_charger_set_charging(struct rt5033_charger *charger) +{ + int ret; + + mutex_lock(&charger->lock); + + /* In case someone switched from OTG to charging directly */ + if (charger->otg) { + ret = rt5033_charger_unset_otg(charger); + if (ret) { + mutex_unlock(&charger->lock); + return -EINVAL; + } + } + + charger->online = true; + + mutex_unlock(&charger->lock); + + return 0; +} + +static int rt5033_charger_set_mivr(struct rt5033_charger *charger) +{ + int ret; + + mutex_lock(&charger->lock); + + /* + * When connected via USB connector type SDP (Standard Downstream Port), + * the minimum input voltage regulation (MIVR) should be enabled. It + * prevents an input voltage drop due to insufficient current provided + * by the adapter or USB input. As a downside, it may reduces the + * charging current and thus slows the charging. + */ + ret = regmap_update_bits(charger->regmap, RT5033_REG_CHG_CTRL4, + RT5033_CHGCTRL4_MIVR_MASK, RT5033_CHARGER_MIVR_4600MV); + if (ret) { + dev_err(charger->dev, "Failed to set MIVR level.\n"); + mutex_unlock(&charger->lock); + return -EINVAL; + } + + charger->mivr_enabled = true; + + mutex_unlock(&charger->lock); + + /* Beyond this, do the same steps like setting charging */ + rt5033_charger_set_charging(charger); + + return 0; +} + +static int rt5033_charger_set_disconnect(struct rt5033_charger *charger) +{ + int ret = 0; + + mutex_lock(&charger->lock); + + /* Disable MIVR if enabled */ + if (charger->mivr_enabled) { + ret = regmap_update_bits(charger->regmap, + RT5033_REG_CHG_CTRL4, + RT5033_CHGCTRL4_MIVR_MASK, + RT5033_CHARGER_MIVR_DISABLE); + if (ret) { + dev_err(charger->dev, "Failed to disable MIVR.\n"); + ret = -EINVAL; + goto out_unlock; + } + + charger->mivr_enabled = false; + } + + if (charger->otg) { + ret = rt5033_charger_unset_otg(charger); + if (ret) { + ret = -EINVAL; + goto out_unlock; + } + } + + if (charger->online) + charger->online = false; + +out_unlock: + mutex_unlock(&charger->lock); + + return ret; +} + +static enum power_supply_property rt5033_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_ONLINE, +}; + +static int rt5033_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt5033_charger *charger = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = rt5033_get_charger_state(charger); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = rt5033_get_charger_type(charger); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = rt5033_get_charger_current_limit(charger); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + val->intval = rt5033_get_charger_const_voltage(charger); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = RT5033_CHARGER_MODEL; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = RT5033_MANUFACTURER; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = charger->online; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rt5033_charger_dt_init(struct rt5033_charger *charger) +{ + struct rt5033_charger_data *chg = &charger->chg; + struct power_supply_battery_info *info; + int ret; + + ret = power_supply_get_battery_info(charger->psy, &info); + if (ret) + return dev_err_probe(charger->dev, -EINVAL, + "missing battery info\n"); + + /* Assign data. Validity will be checked in the init functions. */ + chg->pre_uamp = info->precharge_current_ua; + chg->fast_uamp = info->constant_charge_current_max_ua; + chg->eoc_uamp = info->charge_term_current_ua; + chg->pre_uvolt = info->precharge_voltage_max_uv; + chg->const_uvolt = info->constant_charge_voltage_max_uv; + + return 0; +} + +static void rt5033_charger_extcon_work(struct work_struct *work) +{ + struct rt5033_charger *charger = + container_of(work, struct rt5033_charger, extcon_work); + struct extcon_dev *edev = charger->edev; + int connector, state; + int ret; + + for (connector = EXTCON_USB_HOST; connector <= EXTCON_CHG_USB_PD; + connector++) { + state = extcon_get_state(edev, connector); + if (state == 1) + break; + } + + /* + * Adding a delay between extcon notification and extcon action. This + * makes extcon action execution more reliable. Without the delay the + * execution sometimes fails, possibly because the chip is busy or not + * ready. + */ + msleep(100); + + switch (connector) { + case EXTCON_CHG_USB_SDP: + ret = rt5033_charger_set_mivr(charger); + if (ret) { + dev_err(charger->dev, "failed to set USB mode\n"); + break; + } + dev_info(charger->dev, "USB mode. connector type: %d\n", + connector); + break; + case EXTCON_CHG_USB_DCP: + case EXTCON_CHG_USB_CDP: + case EXTCON_CHG_USB_ACA: + case EXTCON_CHG_USB_FAST: + case EXTCON_CHG_USB_SLOW: + case EXTCON_CHG_WPT: + case EXTCON_CHG_USB_PD: + ret = rt5033_charger_set_charging(charger); + if (ret) { + dev_err(charger->dev, "failed to set charging\n"); + break; + } + dev_info(charger->dev, "charging. connector type: %d\n", + connector); + break; + case EXTCON_USB_HOST: + ret = rt5033_charger_set_otg(charger); + if (ret) { + dev_err(charger->dev, "failed to set OTG\n"); + break; + } + dev_info(charger->dev, "OTG enabled\n"); + break; + default: + ret = rt5033_charger_set_disconnect(charger); + if (ret) { + dev_err(charger->dev, "failed to set disconnect\n"); + break; + } + dev_info(charger->dev, "disconnected\n"); + break; + } + + power_supply_changed(charger->psy); +} + +static int rt5033_charger_extcon_notifier(struct notifier_block *nb, + unsigned long event, void *param) +{ + struct rt5033_charger *charger = + container_of(nb, struct rt5033_charger, extcon_nb); + + schedule_work(&charger->extcon_work); + + return NOTIFY_OK; +} + +static const struct power_supply_desc rt5033_charger_desc = { + .name = "rt5033-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = rt5033_charger_props, + .num_properties = ARRAY_SIZE(rt5033_charger_props), + .get_property = rt5033_charger_get_property, +}; + +static int rt5033_charger_probe(struct platform_device *pdev) +{ + struct rt5033_charger *charger; + struct power_supply_config psy_cfg = {}; + struct device_node *np_conn, *np_edev; + int ret; + + charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + platform_set_drvdata(pdev, charger); + charger->dev = &pdev->dev; + charger->regmap = dev_get_regmap(pdev->dev.parent, NULL); + mutex_init(&charger->lock); + + psy_cfg.fwnode = dev_fwnode(&pdev->dev); + psy_cfg.drv_data = charger; + + charger->psy = devm_power_supply_register(charger->dev, + &rt5033_charger_desc, + &psy_cfg); + if (IS_ERR(charger->psy)) + return dev_err_probe(charger->dev, PTR_ERR(charger->psy), + "Failed to register power supply\n"); + + ret = rt5033_charger_dt_init(charger); + if (ret) + return ret; + + ret = rt5033_charger_reg_init(charger); + if (ret) + return ret; + + /* + * Extcon support is not vital for the charger to work. If no extcon + * is available, just emit a warning and leave the probe function. + */ + 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; + } + + ret = devm_work_autocancel(charger->dev, &charger->extcon_work, + rt5033_charger_extcon_work); + if (ret) { + dev_err(charger->dev, "failed to initialize extcon work\n"); + return ret; + } + + charger->extcon_nb.notifier_call = rt5033_charger_extcon_notifier; + ret = devm_extcon_register_notifier_all(charger->dev, charger->edev, + &charger->extcon_nb); + if (ret) { + dev_err(charger->dev, "failed to register extcon notifier\n"); + return ret; + } +out: + return 0; +} + +static const struct platform_device_id rt5033_charger_id[] = { + { "rt5033-charger", }, + { } +}; +MODULE_DEVICE_TABLE(platform, rt5033_charger_id); + +static const struct of_device_id rt5033_charger_of_match[] = { + { .compatible = "richtek,rt5033-charger", }, + { } +}; +MODULE_DEVICE_TABLE(of, rt5033_charger_of_match); + +static struct platform_driver rt5033_charger_driver = { + .driver = { + .name = "rt5033-charger", + .of_match_table = rt5033_charger_of_match, + }, + .probe = rt5033_charger_probe, + .id_table = rt5033_charger_id, +}; +module_platform_driver(rt5033_charger_driver); + +MODULE_DESCRIPTION("Richtek RT5033 charger driver"); +MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c index 31fb6526a1fd..1ffe7f02932f 100644 --- a/drivers/power/supply/rt9455_charger.c +++ b/drivers/power/supply/rt9455_charger.c @@ -8,8 +8,7 @@ #include <linux/module.h> #include <linux/interrupt.h> #include <linux/delay.h> -#include <linux/of_irq.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/pm_runtime.h> #include <linux/power_supply.h> #include <linux/i2c.h> @@ -193,6 +192,7 @@ static const int rt9455_voreg_values[] = { 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000 }; +#if IS_ENABLED(CONFIG_USB_PHY) /* * When the charger is in boost mode, REG02[7:2] represent boost output * voltage. @@ -208,6 +208,7 @@ static const int rt9455_boost_voltage_values[] = { 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, }; +#endif /* REG07[3:0] (VMREG) in uV */ static const int rt9455_vmreg_values[] = { @@ -1578,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) @@ -1657,7 +1658,7 @@ 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 = @@ -1717,12 +1718,12 @@ static void rt9455_remove(struct i2c_client *client) } static const struct i2c_device_id rt9455_i2c_id_table[] = { - { RT9455_DRIVER_NAME, 0 }, - { }, + { RT9455_DRIVER_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, rt9455_i2c_id_table); -static const struct of_device_id rt9455_of_match[] = { +static const struct of_device_id rt9455_of_match[] __maybe_unused = { { .compatible = "richtek,rt9455", }, { }, }; @@ -1737,7 +1738,7 @@ MODULE_DEVICE_TABLE(acpi, rt9455_i2c_acpi_match); #endif static struct i2c_driver rt9455_driver = { - .probe_new = rt9455_probe, + .probe = rt9455_probe, .remove = rt9455_remove, .id_table = rt9455_i2c_id_table, .driver = { diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c new file mode 100644 index 000000000000..44c26fb37a77 --- /dev/null +++ b/drivers/power/supply/rt9467-charger.c @@ -0,0 +1,1257 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 Richtek Technology Corp. + * + * Author: ChiYuan Huang <cy_huang@richtek.com> + * ChiaEn Wu <chiaen_wu@richtek.com> + */ + +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/kstrtox.h> +#include <linux/linear_range.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/units.h> +#include <linux/sysfs.h> + +#define RT9467_REG_CORE_CTRL0 0x00 +#define RT9467_REG_CHG_CTRL1 0x01 +#define RT9467_REG_CHG_CTRL2 0x02 +#define RT9467_REG_CHG_CTRL3 0x03 +#define RT9467_REG_CHG_CTRL4 0x04 +#define RT9467_REG_CHG_CTRL5 0x05 +#define RT9467_REG_CHG_CTRL6 0x06 +#define RT9467_REG_CHG_CTRL7 0x07 +#define RT9467_REG_CHG_CTRL8 0x08 +#define RT9467_REG_CHG_CTRL9 0x09 +#define RT9467_REG_CHG_CTRL10 0x0A +#define RT9467_REG_CHG_CTRL12 0x0C +#define RT9467_REG_CHG_CTRL13 0x0D +#define RT9467_REG_CHG_CTRL14 0x0E +#define RT9467_REG_CHG_ADC 0x11 +#define RT9467_REG_CHG_DPDM1 0x12 +#define RT9467_REG_CHG_DPDM2 0x13 +#define RT9467_REG_DEVICE_ID 0x40 +#define RT9467_REG_CHG_STAT 0x42 +#define RT9467_REG_ADC_DATA_H 0x44 +#define RT9467_REG_CHG_STATC 0x50 +#define RT9467_REG_CHG_IRQ1 0x53 +#define RT9467_REG_CHG_STATC_CTRL 0x60 +#define RT9467_REG_CHG_IRQ1_CTRL 0x63 + +#define RT9467_MASK_PWR_RDY BIT(7) +#define RT9467_MASK_MIVR_STAT BIT(6) +#define RT9467_MASK_OTG_CSEL GENMASK(2, 0) +#define RT9467_MASK_OTG_VSEL GENMASK(7, 2) +#define RT9467_MASK_OTG_EN BIT(0) +#define RT9467_MASK_ADC_IN_SEL GENMASK(7, 4) +#define RT9467_MASK_ADC_START BIT(0) + +#define RT9467_NUM_IRQ_REGS 4 +#define RT9467_ICHG_MIN_uA 100000 +#define RT9467_ICHG_MAX_uA 5000000 +#define RT9467_CV_MAX_uV 4710000 +#define RT9467_OTG_MIN_uV 4425000 +#define RT9467_OTG_MAX_uV 5825000 +#define RT9467_OTG_STEP_uV 25000 +#define RT9467_NUM_VOTG (RT9467_OTG_MAX_uV - RT9467_OTG_MIN_uV + 1) +#define RT9467_AICLVTH_GAP_uV 200000 +#define RT9467_ADCCONV_TIME_MS 35 + +#define RT9466_VID 0x8 +#define RT9467_VID 0x9 + +/* IRQ number */ +#define RT9467_IRQ_TS_STATC 0 +#define RT9467_IRQ_CHG_FAULT 1 +#define RT9467_IRQ_CHG_STATC 2 +#define RT9467_IRQ_CHG_TMR 3 +#define RT9467_IRQ_CHG_BATABS 4 +#define RT9467_IRQ_CHG_ADPBAD 5 +#define RT9467_IRQ_CHG_RVP 6 +#define RT9467_IRQ_OTP 7 + +#define RT9467_IRQ_CHG_AICLM 8 +#define RT9467_IRQ_CHG_ICHGM 9 +#define RT9467_IRQ_WDTMR 11 +#define RT9467_IRQ_SSFINISH 12 +#define RT9467_IRQ_CHG_RECHG 13 +#define RT9467_IRQ_CHG_TERM 14 +#define RT9467_IRQ_CHG_IEOC 15 + +#define RT9467_IRQ_ADC_DONE 16 +#define RT9467_IRQ_PUMPX_DONE 17 +#define RT9467_IRQ_BST_BATUV 21 +#define RT9467_IRQ_BST_MIDOV 22 +#define RT9467_IRQ_BST_OLP 23 + +#define RT9467_IRQ_ATTACH 24 +#define RT9467_IRQ_DETACH 25 +#define RT9467_IRQ_HVDCP_DET 29 +#define RT9467_IRQ_CHGDET 30 +#define RT9467_IRQ_DCDT 31 + +enum rt9467_fields { + /* RT9467_REG_CORE_CTRL0 */ + F_RST = 0, + /* RT9467_REG_CHG_CTRL1 */ + F_HZ, F_OTG_PIN_EN, F_OPA_MODE, + /* RT9467_REG_CHG_CTRL2 */ + F_SHIP_MODE, F_TE, F_IINLMTSEL, F_CFO_EN, F_CHG_EN, + /* RT9467_REG_CHG_CTRL3 */ + F_IAICR, F_ILIM_EN, + /* RT9467_REG_CHG_CTRL4 */ + F_VOREG, + /* RT9467_REG_CHG_CTRL6 */ + F_VMIVR, + /* RT9467_REG_CHG_CTRL7 */ + F_ICHG, + /* RT9467_REG_CHG_CTRL8 */ + F_IPREC, + /* RT9467_REG_CHG_CTRL9 */ + F_IEOC, + /* RT9467_REG_CHG_CTRL12 */ + F_WT_FC, + /* RT9467_REG_CHG_CTRL13 */ + F_OCP, + /* RT9467_REG_CHG_CTRL14 */ + F_AICL_MEAS, F_AICL_VTH, + /* RT9467_REG_CHG_DPDM1 */ + F_USBCHGEN, + /* RT9467_REG_CHG_DPDM2 */ + F_USB_STATUS, + /* RT9467_REG_DEVICE_ID */ + F_VENDOR, + /* RT9467_REG_CHG_STAT */ + F_CHG_STAT, + /* RT9467_REG_CHG_STATC */ + F_PWR_RDY, F_CHG_MIVR, + F_MAX_FIELDS +}; + +static const struct regmap_irq rt9467_irqs[] = { + REGMAP_IRQ_REG_LINE(RT9467_IRQ_TS_STATC, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHG_FAULT, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHG_STATC, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHG_TMR, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHG_BATABS, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHG_ADPBAD, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHG_RVP, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_OTP, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHG_AICLM, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHG_ICHGM, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_WDTMR, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_SSFINISH, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHG_RECHG, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHG_TERM, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHG_IEOC, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_ADC_DONE, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_PUMPX_DONE, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_BST_BATUV, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_BST_MIDOV, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_BST_OLP, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_ATTACH, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_DETACH, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_HVDCP_DET, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_CHGDET, 8), + REGMAP_IRQ_REG_LINE(RT9467_IRQ_DCDT, 8) +}; + +static const struct regmap_irq_chip rt9467_irq_chip = { + .name = "rt9467-irqs", + .status_base = RT9467_REG_CHG_IRQ1, + .mask_base = RT9467_REG_CHG_IRQ1_CTRL, + .num_regs = RT9467_NUM_IRQ_REGS, + .irqs = rt9467_irqs, + .num_irqs = ARRAY_SIZE(rt9467_irqs), +}; + +enum rt9467_ranges { + RT9467_RANGE_IAICR = 0, + RT9467_RANGE_VOREG, + RT9467_RANGE_VMIVR, + RT9467_RANGE_ICHG, + RT9467_RANGE_IPREC, + RT9467_RANGE_IEOC, + RT9467_RANGE_AICL_VTH, + RT9467_RANGES_MAX +}; + +static const struct linear_range rt9467_ranges[RT9467_RANGES_MAX] = { + LINEAR_RANGE_IDX(RT9467_RANGE_IAICR, 100000, 0x0, 0x3F, 50000), + LINEAR_RANGE_IDX(RT9467_RANGE_VOREG, 3900000, 0x0, 0x51, 10000), + LINEAR_RANGE_IDX(RT9467_RANGE_VMIVR, 3900000, 0x0, 0x5F, 100000), + LINEAR_RANGE_IDX(RT9467_RANGE_ICHG, 900000, 0x08, 0x31, 100000), + LINEAR_RANGE_IDX(RT9467_RANGE_IPREC, 100000, 0x0, 0x0F, 50000), + LINEAR_RANGE_IDX(RT9467_RANGE_IEOC, 100000, 0x0, 0x0F, 50000), + LINEAR_RANGE_IDX(RT9467_RANGE_AICL_VTH, 4100000, 0x0, 0x7, 100000), +}; + +static const struct reg_field rt9467_chg_fields[] = { + [F_RST] = REG_FIELD(RT9467_REG_CORE_CTRL0, 7, 7), + [F_HZ] = REG_FIELD(RT9467_REG_CHG_CTRL1, 2, 2), + [F_OTG_PIN_EN] = REG_FIELD(RT9467_REG_CHG_CTRL1, 1, 1), + [F_OPA_MODE] = REG_FIELD(RT9467_REG_CHG_CTRL1, 0, 0), + [F_SHIP_MODE] = REG_FIELD(RT9467_REG_CHG_CTRL2, 7, 7), + [F_TE] = REG_FIELD(RT9467_REG_CHG_CTRL2, 4, 4), + [F_IINLMTSEL] = REG_FIELD(RT9467_REG_CHG_CTRL2, 2, 3), + [F_CFO_EN] = REG_FIELD(RT9467_REG_CHG_CTRL2, 1, 1), + [F_CHG_EN] = REG_FIELD(RT9467_REG_CHG_CTRL2, 0, 0), + [F_IAICR] = REG_FIELD(RT9467_REG_CHG_CTRL3, 2, 7), + [F_ILIM_EN] = REG_FIELD(RT9467_REG_CHG_CTRL3, 0, 0), + [F_VOREG] = REG_FIELD(RT9467_REG_CHG_CTRL4, 1, 7), + [F_VMIVR] = REG_FIELD(RT9467_REG_CHG_CTRL6, 1, 7), + [F_ICHG] = REG_FIELD(RT9467_REG_CHG_CTRL7, 2, 7), + [F_IPREC] = REG_FIELD(RT9467_REG_CHG_CTRL8, 0, 3), + [F_IEOC] = REG_FIELD(RT9467_REG_CHG_CTRL9, 4, 7), + [F_WT_FC] = REG_FIELD(RT9467_REG_CHG_CTRL12, 5, 7), + [F_OCP] = REG_FIELD(RT9467_REG_CHG_CTRL13, 2, 2), + [F_AICL_MEAS] = REG_FIELD(RT9467_REG_CHG_CTRL14, 7, 7), + [F_AICL_VTH] = REG_FIELD(RT9467_REG_CHG_CTRL14, 0, 2), + [F_USBCHGEN] = REG_FIELD(RT9467_REG_CHG_DPDM1, 7, 7), + [F_USB_STATUS] = REG_FIELD(RT9467_REG_CHG_DPDM2, 0, 2), + [F_VENDOR] = REG_FIELD(RT9467_REG_DEVICE_ID, 4, 7), + [F_CHG_STAT] = REG_FIELD(RT9467_REG_CHG_STAT, 6, 7), + [F_PWR_RDY] = REG_FIELD(RT9467_REG_CHG_STATC, 7, 7), + [F_CHG_MIVR] = REG_FIELD(RT9467_REG_CHG_STATC, 6, 6), +}; + +enum { + RT9467_STAT_READY = 0, + RT9467_STAT_PROGRESS, + RT9467_STAT_CHARGE_DONE, + RT9467_STAT_FAULT +}; + +enum rt9467_adc_chan { + RT9467_ADC_VBUS_DIV5 = 0, + RT9467_ADC_VBUS_DIV2, + RT9467_ADC_VSYS, + RT9467_ADC_VBAT, + RT9467_ADC_TS_BAT, + RT9467_ADC_IBUS, + RT9467_ADC_IBAT, + RT9467_ADC_REGN, + RT9467_ADC_TEMP_JC +}; + +enum rt9467_chg_type { + RT9467_CHG_TYPE_NOVBUS = 0, + RT9467_CHG_TYPE_UNDER_GOING, + RT9467_CHG_TYPE_SDP, + RT9467_CHG_TYPE_SDPNSTD, + RT9467_CHG_TYPE_DCP, + RT9467_CHG_TYPE_CDP, + RT9467_CHG_TYPE_MAX +}; + +enum rt9467_iin_limit_sel { + RT9467_IINLMTSEL_3_2A = 0, + RT9467_IINLMTSEL_CHG_TYP, + RT9467_IINLMTSEL_AICR, + RT9467_IINLMTSEL_LOWER_LEVEL, /* lower of above three */ +}; + +struct rt9467_chg_data { + struct device *dev; + struct regmap *regmap; + struct regmap_field *rm_field[F_MAX_FIELDS]; + struct regmap_irq_chip_data *irq_chip_data; + struct power_supply *psy; + struct mutex adc_lock; + struct mutex attach_lock; + struct mutex ichg_ieoc_lock; + struct regulator_dev *rdev; + struct completion aicl_done; + enum power_supply_usb_type psy_usb_type; + unsigned int old_stat; + unsigned int vid; + int ichg_ua; + int ieoc_ua; +}; + +static int rt9467_otg_of_parse_cb(struct device_node *of, + const struct regulator_desc *desc, + struct regulator_config *cfg) +{ + struct rt9467_chg_data *data = cfg->driver_data; + + cfg->ena_gpiod = fwnode_gpiod_get_index(of_fwnode_handle(of), + "enable", 0, GPIOD_OUT_LOW | + GPIOD_FLAGS_BIT_NONEXCLUSIVE, + desc->name); + if (IS_ERR(cfg->ena_gpiod)) { + cfg->ena_gpiod = NULL; + return 0; + } + + return regmap_field_write(data->rm_field[F_OTG_PIN_EN], 1); +} + +static const struct regulator_ops rt9467_otg_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_current_limit = regulator_set_current_limit_regmap, + .get_current_limit = regulator_get_current_limit_regmap, +}; + +static const u32 rt9467_otg_microamp[] = { + 500000, 700000, 1100000, 1300000, 1800000, 2100000, 2400000, 3000000 +}; + +static const struct regulator_desc rt9467_otg_desc = { + .name = "rt9476-usb-otg-vbus", + .of_match = "usb-otg-vbus-regulator", + .of_parse_cb = rt9467_otg_of_parse_cb, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .min_uV = RT9467_OTG_MIN_uV, + .uV_step = RT9467_OTG_STEP_uV, + .n_voltages = RT9467_NUM_VOTG, + .curr_table = rt9467_otg_microamp, + .n_current_limits = ARRAY_SIZE(rt9467_otg_microamp), + .csel_reg = RT9467_REG_CHG_CTRL10, + .csel_mask = RT9467_MASK_OTG_CSEL, + .vsel_reg = RT9467_REG_CHG_CTRL5, + .vsel_mask = RT9467_MASK_OTG_VSEL, + .enable_reg = RT9467_REG_CHG_CTRL1, + .enable_mask = RT9467_MASK_OTG_EN, + .ops = &rt9467_otg_regulator_ops, +}; + +static int rt9467_register_otg_regulator(struct rt9467_chg_data *data) +{ + struct regulator_config cfg = { + .dev = data->dev, + .regmap = data->regmap, + .driver_data = data, + }; + + data->rdev = devm_regulator_register(data->dev, &rt9467_otg_desc, &cfg); + return PTR_ERR_OR_ZERO(data->rdev); +} + +static int rt9467_get_value_from_ranges(struct rt9467_chg_data *data, + enum rt9467_fields field, + enum rt9467_ranges rsel, + int *value) +{ + const struct linear_range *range = rt9467_ranges + rsel; + unsigned int sel; + int ret; + + ret = regmap_field_read(data->rm_field[field], &sel); + if (ret) + return ret; + + return linear_range_get_value(range, sel, value); +} + +static int rt9467_set_value_from_ranges(struct rt9467_chg_data *data, + enum rt9467_fields field, + enum rt9467_ranges rsel, + int value) +{ + const struct linear_range *range = rt9467_ranges + rsel; + unsigned int sel; + bool found; + int ret; + + if (rsel == RT9467_RANGE_VMIVR) { + ret = linear_range_get_selector_high(range, value, &sel, &found); + if (ret) + sel = range->max_sel; + } else { + linear_range_get_selector_within(range, value, &sel); + } + + return regmap_field_write(data->rm_field[field], sel); +} + +static int rt9467_get_adc_sel(enum rt9467_adc_chan chan, int *sel) +{ + switch (chan) { + case RT9467_ADC_VBUS_DIV5: + case RT9467_ADC_VBUS_DIV2: + case RT9467_ADC_VSYS: + case RT9467_ADC_VBAT: + *sel = chan + 1; + return 0; + case RT9467_ADC_TS_BAT: + *sel = chan + 2; + return 0; + case RT9467_ADC_IBUS: + case RT9467_ADC_IBAT: + *sel = chan + 3; + return 0; + case RT9467_ADC_REGN: + case RT9467_ADC_TEMP_JC: + *sel = chan + 4; + return 0; + default: + return -EINVAL; + } +} + +static int rt9467_get_adc_raw_data(struct rt9467_chg_data *data, + enum rt9467_adc_chan chan, int *val) +{ + unsigned int adc_stat, reg_val, adc_sel; + __be16 chan_raw_data; + int ret; + + mutex_lock(&data->adc_lock); + + ret = rt9467_get_adc_sel(chan, &adc_sel); + if (ret) + goto adc_unlock; + + ret = regmap_write(data->regmap, RT9467_REG_CHG_ADC, 0); + if (ret) { + dev_err(data->dev, "Failed to clear ADC enable\n"); + goto adc_unlock; + } + + reg_val = RT9467_MASK_ADC_START | FIELD_PREP(RT9467_MASK_ADC_IN_SEL, adc_sel); + ret = regmap_write(data->regmap, RT9467_REG_CHG_ADC, reg_val); + if (ret) + goto adc_unlock; + + /* Minimum wait time for one channel processing */ + msleep(RT9467_ADCCONV_TIME_MS); + + ret = regmap_read_poll_timeout(data->regmap, RT9467_REG_CHG_ADC, + adc_stat, + !(adc_stat & RT9467_MASK_ADC_START), + MILLI, RT9467_ADCCONV_TIME_MS * MILLI); + if (ret) { + dev_err(data->dev, "Failed to wait ADC conversion, chan = %d\n", chan); + goto adc_unlock; + } + + ret = regmap_raw_read(data->regmap, RT9467_REG_ADC_DATA_H, + &chan_raw_data, sizeof(chan_raw_data)); + if (ret) + goto adc_unlock; + + *val = be16_to_cpu(chan_raw_data); + +adc_unlock: + mutex_unlock(&data->adc_lock); + return ret; +} + +static int rt9467_get_adc(struct rt9467_chg_data *data, + enum rt9467_adc_chan chan, int *val) +{ + unsigned int aicr_ua, ichg_ua; + int ret; + + ret = rt9467_get_adc_raw_data(data, chan, val); + if (ret) + return ret; + + switch (chan) { + case RT9467_ADC_VBUS_DIV5: + *val *= 25000; + return 0; + case RT9467_ADC_VBUS_DIV2: + *val *= 10000; + return 0; + case RT9467_ADC_VBAT: + case RT9467_ADC_VSYS: + case RT9467_ADC_REGN: + *val *= 5000; + return 0; + case RT9467_ADC_TS_BAT: + *val /= 400; + return 0; + case RT9467_ADC_IBUS: + /* UUG MOS turn-on ratio will affect the IBUS adc scale */ + ret = rt9467_get_value_from_ranges(data, F_IAICR, + RT9467_RANGE_IAICR, &aicr_ua); + if (ret) + return ret; + + *val *= aicr_ua < 400000 ? 29480 : 50000; + return 0; + case RT9467_ADC_IBAT: + /* PP MOS turn-on ratio will affect the ICHG adc scale */ + ret = rt9467_get_value_from_ranges(data, F_ICHG, + RT9467_RANGE_ICHG, &ichg_ua); + if (ret) + return ret; + + *val *= ichg_ua <= 400000 ? 28500 : + ichg_ua <= 800000 ? 31500 : 500000; + return 0; + case RT9467_ADC_TEMP_JC: + *val = ((*val * 2) - 40) * 10; + return 0; + default: + return -EINVAL; + } +} + +static int rt9467_psy_get_status(struct rt9467_chg_data *data, int *state) +{ + unsigned int status; + int ret; + + ret = regmap_field_read(data->rm_field[F_CHG_STAT], &status); + if (ret) + return ret; + + switch (status) { + case RT9467_STAT_READY: + *state = POWER_SUPPLY_STATUS_NOT_CHARGING; + return 0; + case RT9467_STAT_PROGRESS: + *state = POWER_SUPPLY_STATUS_CHARGING; + return 0; + case RT9467_STAT_CHARGE_DONE: + *state = POWER_SUPPLY_STATUS_FULL; + return 0; + default: + *state = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + } +} + +static int rt9467_psy_set_ichg(struct rt9467_chg_data *data, int microamp) +{ + int ret; + + mutex_lock(&data->ichg_ieoc_lock); + + if (microamp < 500000) { + dev_err(data->dev, "Minimum value must be 500mA\n"); + microamp = 500000; + } + + ret = rt9467_set_value_from_ranges(data, F_ICHG, RT9467_RANGE_ICHG, microamp); + if (ret) + goto out; + + ret = rt9467_get_value_from_ranges(data, F_ICHG, RT9467_RANGE_ICHG, + &data->ichg_ua); + if (ret) + goto out; + +out: + mutex_unlock(&data->ichg_ieoc_lock); + return ret; +} + +static int rt9467_run_aicl(struct rt9467_chg_data *data) +{ + unsigned int statc, aicl_vth; + int mivr_vth, aicr_get; + int ret = 0; + + + ret = regmap_read(data->regmap, RT9467_REG_CHG_STATC, &statc); + if (ret) { + dev_err(data->dev, "Failed to read status\n"); + return ret; + } + + if (!(statc & RT9467_MASK_PWR_RDY) || !(statc & RT9467_MASK_MIVR_STAT)) { + dev_info(data->dev, "Condition not matched %d\n", statc); + return 0; + } + + ret = rt9467_get_value_from_ranges(data, F_VMIVR, RT9467_RANGE_VMIVR, + &mivr_vth); + if (ret) { + dev_err(data->dev, "Failed to get mivr\n"); + return ret; + } + + /* AICL_VTH = MIVR_VTH + 200mV */ + 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); + if (ret) { + dev_err(data->dev, "Failed to set aicl measurement\n"); + return ret; + } + + reinit_completion(&data->aicl_done); + ret = wait_for_completion_timeout(&data->aicl_done, msecs_to_jiffies(3500)); + if (ret == 0) + return -ETIMEDOUT; + + ret = rt9467_get_value_from_ranges(data, F_IAICR, RT9467_RANGE_IAICR, &aicr_get); + if (ret) { + dev_err(data->dev, "Failed to get aicr\n"); + return ret; + } + + dev_info(data->dev, "aicr get = %d uA\n", aicr_get); + return 0; +} + +static int rt9467_psy_set_ieoc(struct rt9467_chg_data *data, int microamp) +{ + int ret; + + mutex_lock(&data->ichg_ieoc_lock); + + ret = rt9467_set_value_from_ranges(data, F_IEOC, RT9467_RANGE_IEOC, microamp); + if (ret) + goto out; + + ret = rt9467_get_value_from_ranges(data, F_IEOC, RT9467_RANGE_IEOC, &data->ieoc_ua); + if (ret) + goto out; + +out: + mutex_unlock(&data->ichg_ieoc_lock); + return ret; +} + +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, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_PRECHARGE_CURRENT, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, +}; + +static int rt9467_psy_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt9467_chg_data *data = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + 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 || + data->psy_usb_type == POWER_SUPPLY_USB_TYPE_SDP) + val->intval = 500000; + else + 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; + mutex_unlock(&data->ichg_ieoc_lock); + return 0; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = RT9467_ICHG_MAX_uA; + return 0; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + return rt9467_get_value_from_ranges(data, F_VOREG, + RT9467_RANGE_VOREG, + &val->intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = RT9467_CV_MAX_uV; + return 0; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return rt9467_get_value_from_ranges(data, F_IAICR, + RT9467_RANGE_IAICR, + &val->intval); + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + return rt9467_get_value_from_ranges(data, F_VMIVR, + RT9467_RANGE_VMIVR, + &val->intval); + case POWER_SUPPLY_PROP_USB_TYPE: + mutex_lock(&data->attach_lock); + val->intval = data->psy_usb_type; + mutex_unlock(&data->attach_lock); + return 0; + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + return rt9467_get_value_from_ranges(data, F_IPREC, + RT9467_RANGE_IPREC, + &val->intval); + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + mutex_lock(&data->ichg_ieoc_lock); + val->intval = data->ieoc_ua; + mutex_unlock(&data->ichg_ieoc_lock); + return 0; + default: + return -ENODATA; + } +} + +static int rt9467_psy_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct rt9467_chg_data *data = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return regmap_field_write(data->rm_field[F_CHG_EN], val->intval); + case POWER_SUPPLY_PROP_ONLINE: + return regmap_field_write(data->rm_field[F_HZ], val->intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return rt9467_psy_set_ichg(data, val->intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + return rt9467_set_value_from_ranges(data, F_VOREG, + RT9467_RANGE_VOREG, val->intval); + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + if (val->intval == -1) + return rt9467_run_aicl(data); + else + return rt9467_set_value_from_ranges(data, F_IAICR, + RT9467_RANGE_IAICR, + val->intval); + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + return rt9467_set_value_from_ranges(data, F_VMIVR, + RT9467_RANGE_VMIVR, val->intval); + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + return rt9467_set_value_from_ranges(data, F_IPREC, + RT9467_RANGE_IPREC, val->intval); + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return rt9467_psy_set_ieoc(data, val->intval); + default: + return -EINVAL; + } +} + +static int rt9467_chg_prop_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_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + return 1; + default: + return 0; + } +} + +static const struct power_supply_desc rt9467_chg_psy_desc = { + .name = "rt9467-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 = rt9467_chg_properties, + .num_properties = ARRAY_SIZE(rt9467_chg_properties), + .property_is_writeable = rt9467_chg_prop_is_writeable, + .get_property = rt9467_psy_get_property, + .set_property = rt9467_psy_set_property, +}; + +static inline struct rt9467_chg_data *psy_device_to_chip(struct device *dev) +{ + return power_supply_get_drvdata(to_power_supply(dev)); +} + +static ssize_t sysoff_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rt9467_chg_data *data = psy_device_to_chip(dev); + unsigned int sysoff_enable; + int ret; + + ret = regmap_field_read(data->rm_field[F_SHIP_MODE], &sysoff_enable); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", sysoff_enable); +} + +static ssize_t sysoff_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rt9467_chg_data *data = psy_device_to_chip(dev); + unsigned int tmp; + int ret; + + ret = kstrtouint(buf, 10, &tmp); + if (ret) + return ret; + + ret = regmap_field_write(data->rm_field[F_SHIP_MODE], !!tmp); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(sysoff_enable); + +static struct attribute *rt9467_sysfs_attrs[] = { + &dev_attr_sysoff_enable.attr, + NULL +}; + +ATTRIBUTE_GROUPS(rt9467_sysfs); + +static int rt9467_register_psy(struct rt9467_chg_data *data) +{ + struct power_supply_config cfg = { + .drv_data = data, + .fwnode = dev_fwnode(data->dev), + .attr_grp = rt9467_sysfs_groups, + }; + + data->psy = devm_power_supply_register(data->dev, &rt9467_chg_psy_desc, + &cfg); + return PTR_ERR_OR_ZERO(data->psy); +} + +static int rt9467_mivr_handler(struct rt9467_chg_data *data) +{ + unsigned int mivr_act; + int ret, ibus_ma; + + /* + * back-boost workaround + * If (mivr_active & ibus < 100mA), toggle cfo bit + */ + ret = regmap_field_read(data->rm_field[F_CHG_MIVR], &mivr_act); + if (ret) { + dev_err(data->dev, "Failed to read MIVR stat\n"); + return ret; + } + + if (!mivr_act) + return 0; + + ret = rt9467_get_adc(data, RT9467_ADC_IBUS, &ibus_ma); + if (ret) { + dev_err(data->dev, "Failed to get IBUS\n"); + return ret; + } + + if (ibus_ma < 100000) { + ret = regmap_field_write(data->rm_field[F_CFO_EN], 0); + ret |= regmap_field_write(data->rm_field[F_CFO_EN], 1); + if (ret) + dev_err(data->dev, "Failed to toggle cfo\n"); + } + + return ret; +} + +static irqreturn_t rt9467_statc_handler(int irq, void *priv) +{ + struct rt9467_chg_data *data = priv; + unsigned int new_stat, evts = 0; + int ret; + + ret = regmap_read(data->regmap, RT9467_REG_CHG_STATC, &new_stat); + if (ret) { + dev_err(data->dev, "Failed to read chg_statc\n"); + return IRQ_NONE; + } + + evts = data->old_stat ^ new_stat; + data->old_stat = new_stat; + + if ((evts & new_stat) & RT9467_MASK_MIVR_STAT) { + ret = rt9467_mivr_handler(data); + if (ret) + dev_err(data->dev, "Failed to handle mivr stat\n"); + } + + return IRQ_HANDLED; +} + +static irqreturn_t rt9467_wdt_handler(int irq, void *priv) +{ + struct rt9467_chg_data *data = priv; + unsigned int dev_id; + int ret; + + /* Any i2c communication can kick watchdog timer */ + ret = regmap_read(data->regmap, RT9467_REG_DEVICE_ID, &dev_id); + if (ret) { + dev_err(data->dev, "Failed to kick wdt (%d)\n", ret); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static int rt9467_report_usb_state(struct rt9467_chg_data *data) +{ + unsigned int usb_stat, power_ready; + bool psy_changed = true; + int ret; + + ret = regmap_field_read(data->rm_field[F_USB_STATUS], &usb_stat); + ret |= regmap_field_read(data->rm_field[F_PWR_RDY], &power_ready); + if (ret) + return ret; + + if (!power_ready) + usb_stat = RT9467_CHG_TYPE_NOVBUS; + + mutex_lock(&data->attach_lock); + + switch (usb_stat) { + case RT9467_CHG_TYPE_NOVBUS: + data->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + break; + case RT9467_CHG_TYPE_SDP: + data->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP; + break; + case RT9467_CHG_TYPE_SDPNSTD: + data->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP; + break; + case RT9467_CHG_TYPE_DCP: + data->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP; + break; + case RT9467_CHG_TYPE_CDP: + data->psy_usb_type = POWER_SUPPLY_USB_TYPE_CDP; + break; + case RT9467_CHG_TYPE_UNDER_GOING: + default: + psy_changed = false; + break; + } + + mutex_unlock(&data->attach_lock); + + if (psy_changed) + power_supply_changed(data->psy); + + return 0; +} + +static irqreturn_t rt9467_usb_state_handler(int irq, void *priv) +{ + struct rt9467_chg_data *data = priv; + int ret; + + ret = rt9467_report_usb_state(data); + if (ret) { + dev_err(data->dev, "Failed to report attach type (%d)\n", ret); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static irqreturn_t rt9467_aiclmeas_handler(int irq, void *priv) +{ + struct rt9467_chg_data *data = priv; + + complete(&data->aicl_done); + return IRQ_HANDLED; +} + +#define RT9467_IRQ_DESC(_name, _handler_func, _hwirq) \ +{ \ + .name = #_name, \ + .handler = rt9467_##_handler_func##_handler, \ + .hwirq = _hwirq, \ +} + +static int rt9467_request_interrupt(struct rt9467_chg_data *data) +{ + struct device *dev = data->dev; + static const struct { + const char *name; + int hwirq; + irq_handler_t handler; + } rt9467_exclusive_irqs[] = { + RT9467_IRQ_DESC(statc, statc, RT9467_IRQ_TS_STATC), + RT9467_IRQ_DESC(wdt, wdt, RT9467_IRQ_WDTMR), + RT9467_IRQ_DESC(attach, usb_state, RT9467_IRQ_ATTACH), + RT9467_IRQ_DESC(detach, usb_state, RT9467_IRQ_DETACH), + RT9467_IRQ_DESC(aiclmeas, aiclmeas, RT9467_IRQ_CHG_AICLM), + }, rt9466_exclusive_irqs[] = { + RT9467_IRQ_DESC(statc, statc, RT9467_IRQ_TS_STATC), + RT9467_IRQ_DESC(wdt, wdt, RT9467_IRQ_WDTMR), + RT9467_IRQ_DESC(aiclmeas, aiclmeas, RT9467_IRQ_CHG_AICLM), + }, *chg_irqs; + int num_chg_irqs, i, virq, ret; + + if (data->vid == RT9466_VID) { + chg_irqs = rt9466_exclusive_irqs; + num_chg_irqs = ARRAY_SIZE(rt9466_exclusive_irqs); + } else { + chg_irqs = rt9467_exclusive_irqs; + num_chg_irqs = ARRAY_SIZE(rt9467_exclusive_irqs); + } + + for (i = 0; i < num_chg_irqs; i++) { + virq = regmap_irq_get_virq(data->irq_chip_data, chg_irqs[i].hwirq); + if (virq <= 0) + return dev_err_probe(dev, -EINVAL, "Failed to get (%s) irq\n", + chg_irqs[i].name); + + ret = devm_request_threaded_irq(dev, virq, NULL, chg_irqs[i].handler, + IRQF_ONESHOT, chg_irqs[i].name, data); + if (ret) + return dev_err_probe(dev, ret, "Failed to request (%s) irq\n", + chg_irqs[i].name); + } + + return 0; +} + +static int rt9467_do_charger_init(struct rt9467_chg_data *data) +{ + struct device *dev = data->dev; + int ret; + + ret = regmap_write(data->regmap, RT9467_REG_CHG_ADC, 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to reset ADC\n"); + + ret = rt9467_get_value_from_ranges(data, F_ICHG, RT9467_RANGE_ICHG, + &data->ichg_ua); + ret |= rt9467_get_value_from_ranges(data, F_IEOC, RT9467_RANGE_IEOC, + &data->ieoc_ua); + if (ret) + return dev_err_probe(dev, ret, "Failed to init ichg/ieoc value\n"); + + ret = regmap_update_bits(data->regmap, RT9467_REG_CHG_STATC_CTRL, + RT9467_MASK_PWR_RDY | RT9467_MASK_MIVR_STAT, 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to make statc unmask\n"); + + /* Select IINLMTSEL to use AICR */ + ret = regmap_field_write(data->rm_field[F_IINLMTSEL], + RT9467_IINLMTSEL_AICR); + if (ret) + return dev_err_probe(dev, ret, "Failed to set iinlmtsel to AICR\n"); + + /* Wait for AICR Rampping */ + msleep(150); + + /* Disable hardware ILIM */ + ret = regmap_field_write(data->rm_field[F_ILIM_EN], 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to disable hardware ILIM\n"); + + /* Set inductor OCP to high level */ + ret = regmap_field_write(data->rm_field[F_OCP], 1); + if (ret) + return dev_err_probe(dev, ret, "Failed to set higher inductor OCP level\n"); + + /* Set charge termination default enable */ + ret = regmap_field_write(data->rm_field[F_TE], 1); + if (ret) + return dev_err_probe(dev, ret, "Failed to set TE=1\n"); + + /* Set 12hrs fast charger timer */ + ret = regmap_field_write(data->rm_field[F_WT_FC], 4); + if (ret) + return dev_err_probe(dev, ret, "Failed to set WT_FC\n"); + + /* Toggle BC12 function */ + ret = regmap_field_write(data->rm_field[F_USBCHGEN], 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to disable BC12\n"); + + return regmap_field_write(data->rm_field[F_USBCHGEN], 1); +} + +static bool rt9467_is_accessible_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x00 ... 0x1A: + case 0x20 ... 0x38: + case 0x40 ... 0x49: + case 0x50 ... 0x57: + case 0x60 ... 0x67: + case 0x70 ... 0x79: + case 0x82 ... 0x85: + return true; + default: + return false; + } +} + +static const struct regmap_config rt9467_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x85, + .writeable_reg = rt9467_is_accessible_reg, + .readable_reg = rt9467_is_accessible_reg, +}; + +static int rt9467_check_vendor_info(struct rt9467_chg_data *data) +{ + unsigned int vid; + int ret; + + ret = regmap_field_read(data->rm_field[F_VENDOR], &vid); + if (ret) { + dev_err(data->dev, "Failed to get vid\n"); + return ret; + } + + if ((vid != RT9466_VID) && (vid != RT9467_VID)) + return dev_err_probe(data->dev, -ENODEV, + "VID not correct [0x%02X]\n", vid); + + data->vid = vid; + return 0; +} + +static int rt9467_reset_chip(struct rt9467_chg_data *data) +{ + int ret; + + /* Disable HZ before reset chip */ + ret = regmap_field_write(data->rm_field[F_HZ], 0); + if (ret) + return ret; + + return regmap_field_write(data->rm_field[F_RST], 1); +} + +static void rt9467_chg_complete_aicl_done(void *data) +{ + struct completion *aicl_done = data; + + complete(aicl_done); +} + +static int rt9467_charger_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct rt9467_chg_data *data; + struct gpio_desc *ceb_gpio; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &i2c->dev; + i2c_set_clientdata(i2c, data); + + /* Default pull charge enable gpio to make 'CHG_EN' by SW control only */ + ceb_gpio = devm_gpiod_get_optional(dev, "charge-enable", GPIOD_OUT_HIGH); + if (IS_ERR(ceb_gpio)) + return dev_err_probe(dev, PTR_ERR(ceb_gpio), + "Failed to config charge enable gpio\n"); + + data->regmap = devm_regmap_init_i2c(i2c, &rt9467_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "Failed to init regmap\n"); + + ret = devm_regmap_field_bulk_alloc(dev, data->regmap, + data->rm_field, rt9467_chg_fields, + ARRAY_SIZE(rt9467_chg_fields)); + if (ret) + return dev_err_probe(dev, ret, "Failed to alloc regmap fields\n"); + + ret = rt9467_check_vendor_info(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to check vendor info"); + + ret = rt9467_reset_chip(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to reset chip\n"); + + ret = devm_regmap_add_irq_chip(dev, data->regmap, i2c->irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, 0, + &rt9467_irq_chip, &data->irq_chip_data); + if (ret) + return dev_err_probe(dev, ret, "Failed to add irq chip\n"); + + ret = devm_mutex_init(dev, &data->adc_lock); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &data->attach_lock); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &data->ichg_ieoc_lock); + if (ret) + 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 ret; + + ret = rt9467_do_charger_init(data); + if (ret) + return ret; + + ret = rt9467_register_otg_regulator(data); + if (ret) + return ret; + + ret = rt9467_register_psy(data); + if (ret) + return ret; + + return rt9467_request_interrupt(data); +} + +static const struct of_device_id rt9467_charger_of_match_table[] = { + { .compatible = "richtek,rt9467", }, + {} +}; +MODULE_DEVICE_TABLE(of, rt9467_charger_of_match_table); + +static struct i2c_driver rt9467_charger_driver = { + .driver = { + .name = "rt9467-charger", + .of_match_table = rt9467_charger_of_match_table, + }, + .probe = rt9467_charger_probe, +}; +module_i2c_driver(rt9467_charger_driver); + +MODULE_DESCRIPTION("Richtek RT9467 Charger Driver"); +MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); +MODULE_AUTHOR("ChiaEn Wu <chiaen_wu@richtek.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/rt9471.c b/drivers/power/supply/rt9471.c new file mode 100644 index 000000000000..e7f843f12c98 --- /dev/null +++ b/drivers/power/supply/rt9471.c @@ -0,0 +1,939 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 Richtek Technology Corp. + * + * Authors: Alina Yu <alina_yu@richtek.com> + * ChiYuan Huang <cy_huang@richtek.com> + */ + +#include <linux/bits.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kstrtox.h> +#include <linux/linear_range.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/sysfs.h> + +#define RT9471_REG_OTGCFG 0x00 +#define RT9471_REG_TOP 0x01 +#define RT9471_REG_FUNC 0x02 +#define RT9471_REG_IBUS 0x03 +#define RT9471_REG_VBUS 0x04 +#define RT9471_REG_PRECHG 0x05 +#define RT9471_REG_VCHG 0x07 +#define RT9471_REG_ICHG 0x08 +#define RT9471_REG_CHGTMR 0x09 +#define RT9471_REG_EOC 0x0A +#define RT9471_REG_INFO 0x0B +#define RT9471_REG_JEITA 0x0C +#define RT9471_REG_PUMP_EXP 0x0D +#define RT9471_REG_DPDMDET 0x0E +#define RT9471_REG_ICSTAT 0x0F +#define RT9471_REG_STAT0 0x10 +#define RT9471_REG_STAT1 0x11 +#define RT9471_REG_STAT2 0x12 +#define RT9471_REG_IRQ0 0x20 +#define RT9471_REG_MASK0 0x30 + +#define RT9471_OTGCV_MASK GENMASK(7, 6) +#define RT9471_OTGCC_MASK BIT(0) +#define RT9471_OTGEN_MASK BIT(1) +#define RT9471_CHGFAULT_MASK GENMASK(4, 1) + +#define RT9471_NUM_IRQ_REGS 4 +#define RT9471_OTGCV_MINUV 4850000 +#define RT9471_OTGCV_STEPUV 150000 +#define RT9471_NUM_VOTG 4 +#define RT9471_VCHG_MAXUV 4700000 +#define RT9471_ICHG_MAXUA 3150000 + +/* Device ID */ +#define RT9470_DEVID 0x09 +#define RT9470D_DEVID 0x0A +#define RT9471_DEVID 0x0D +#define RT9471D_DEVID 0x0E + +/* IRQ number */ +#define RT9471_IRQ_BC12_DONE 0 +#define RT9471_IRQ_DETACH 1 +#define RT9471_IRQ_RECHG 2 +#define RT9471_IRQ_CHG_DONE 3 +#define RT9471_IRQ_BG_CHG 4 +#define RT9471_IRQ_IE0C 5 +#define RT9471_IRQ_CHG_RDY 6 +#define RT9471_IRQ_VBUS_GD 7 +#define RT9471_IRQ_CHG_BATOV 9 +#define RT9471_IRQ_CHG_SYSOV 10 +#define RT9471_IRQ_CHG_TOUT 11 +#define RT9471_IRQ_CHG_BUSUV 12 +#define RT9471_IRQ_CHG_THREG 13 +#define RT9471_IRQ_CHG_AICR 14 +#define RT9471_IRQ_CHG_MIVR 15 +#define RT9471_IRQ_SYS_SHORT 16 +#define RT9471_IRQ_SYS_MIN 17 +#define RT9471_IRQ_AICC_DONE 18 +#define RT9471_IRQ_PE_DONE 19 +#define RT9471_IRQ_JEITA_COLD 20 +#define RT9471_IRQ_JEITA_COOL 21 +#define RT9471_IRQ_JEITA_WARM 22 +#define RT9471_IRQ_JEITA_HOT 23 +#define RT9471_IRQ_OTG_FAULT 24 +#define RT9471_IRQ_OTG_LBP 25 +#define RT9471_IRQ_OTG_CC 26 +#define RT9471_IRQ_WDT 29 +#define RT9471_IRQ_VAC_OV 30 +#define RT9471_IRQ_OTP 31 + +enum rt9471_fields { + F_WDT = 0, + F_WDT_RST, + F_CHG_EN, + F_HZ, + F_BATFET_DIS, + F_AICR, + F_AICC_EN, + F_MIVR, + F_IPRE_CHG, + F_VPRE_CHG, + F_VBAT_REG, + F_ICHG_REG, + F_EOC_RST, + F_TE, + F_IEOC_CHG, + F_DEVICE_ID, + F_REG_RST, + F_BC12_EN, + F_IC_STAT, + F_PORT_STAT, + F_ST_CHG_DONE, + F_ST_CHG_RDY, + F_ST_VBUS_GD, + F_MAX_FIELDS +}; + +enum rt9471_ranges { + RT9471_RANGE_AICR = 0, + RT9471_RANGE_MIVR, + RT9471_RANGE_IPRE, + RT9471_RANGE_VCHG, + RT9471_RANGE_ICHG, + RT9471_RANGE_IEOC, + RT9471_MAX_RANGES +}; + +enum { + RT9471_PORTSTAT_APPLE_10W = 8, + RT9471_PORTSTAT_SAMSUNG_10W, + RT9471_PORTSTAT_APPLE_5W, + RT9471_PORTSTAT_APPLE_12W, + RT9471_PORTSTAT_NSTD, + RT9471_PORTSTAT_SDP, + RT9471_PORTSTAT_CDP, + RT9471_PORTSTAT_DCP, +}; + +enum { + RT9471_ICSTAT_SLEEP = 0, + RT9471_ICSTAT_VBUSRDY, + RT9471_ICSTAT_TRICKLECHG, + RT9471_ICSTAT_PRECHG, + RT9471_ICSTAT_FASTCHG, + RT9471_ICSTAT_IEOC, + RT9471_ICSTAT_BGCHG, + RT9471_ICSTAT_CHGDONE, + RT9471_ICSTAT_CHGFAULT, + RT9471_ICSTAT_OTG = 15, +}; + +struct rt9471_chip { + struct device *dev; + struct regmap *regmap; + struct regmap_field *rm_fields[F_MAX_FIELDS]; + struct regmap_irq_chip_data *irq_chip_data; + struct regulator_dev *otg_rdev; + struct power_supply *psy; + struct power_supply_desc psy_desc; + struct mutex var_lock; + enum power_supply_usb_type psy_usb_type; + int psy_usb_curr; +}; + +static const struct reg_field rt9471_reg_fields[F_MAX_FIELDS] = { + [F_WDT] = REG_FIELD(RT9471_REG_TOP, 0, 1), + [F_WDT_RST] = REG_FIELD(RT9471_REG_TOP, 2, 2), + [F_CHG_EN] = REG_FIELD(RT9471_REG_FUNC, 0, 0), + [F_HZ] = REG_FIELD(RT9471_REG_FUNC, 5, 5), + [F_BATFET_DIS] = REG_FIELD(RT9471_REG_FUNC, 7, 7), + [F_AICR] = REG_FIELD(RT9471_REG_IBUS, 0, 5), + [F_AICC_EN] = REG_FIELD(RT9471_REG_IBUS, 7, 7), + [F_MIVR] = REG_FIELD(RT9471_REG_VBUS, 0, 3), + [F_IPRE_CHG] = REG_FIELD(RT9471_REG_PRECHG, 0, 3), + [F_VPRE_CHG] = REG_FIELD(RT9471_REG_PRECHG, 4, 6), + [F_VBAT_REG] = REG_FIELD(RT9471_REG_VCHG, 0, 6), + [F_ICHG_REG] = REG_FIELD(RT9471_REG_ICHG, 0, 5), + [F_EOC_RST] = REG_FIELD(RT9471_REG_EOC, 0, 0), + [F_TE] = REG_FIELD(RT9471_REG_EOC, 1, 1), + [F_IEOC_CHG] = REG_FIELD(RT9471_REG_EOC, 4, 7), + [F_DEVICE_ID] = REG_FIELD(RT9471_REG_INFO, 3, 6), + [F_REG_RST] = REG_FIELD(RT9471_REG_INFO, 7, 7), + [F_BC12_EN] = REG_FIELD(RT9471_REG_DPDMDET, 7, 7), + [F_IC_STAT] = REG_FIELD(RT9471_REG_ICSTAT, 0, 3), + [F_PORT_STAT] = REG_FIELD(RT9471_REG_ICSTAT, 4, 7), + [F_ST_CHG_DONE] = REG_FIELD(RT9471_REG_STAT0, 3, 3), + [F_ST_CHG_RDY] = REG_FIELD(RT9471_REG_STAT0, 6, 6), + [F_ST_VBUS_GD] = REG_FIELD(RT9471_REG_STAT0, 7, 7), +}; + +static const struct linear_range rt9471_chg_ranges[RT9471_MAX_RANGES] = { + [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, + enum rt9471_fields field, + enum rt9471_ranges range, int val) +{ + unsigned int sel; + + if (val < 0) + return -EINVAL; + + linear_range_get_selector_within(rt9471_chg_ranges + range, val, &sel); + + return regmap_field_write(chip->rm_fields[field], sel); +} + + +static int rt9471_get_value_by_field_range(struct rt9471_chip *chip, + enum rt9471_fields field, + enum rt9471_ranges range, int *val) +{ + unsigned int sel, rvalue; + int ret; + + ret = regmap_field_read(chip->rm_fields[field], &sel); + if (ret) + return ret; + + ret = linear_range_get_value(rt9471_chg_ranges + range, sel, &rvalue); + if (ret) + return ret; + + *val = rvalue; + return 0; +} + +static int rt9471_set_ieoc(struct rt9471_chip *chip, int microamp) +{ + int ret; + + if (microamp == 0) + return regmap_field_write(chip->rm_fields[F_TE], 0); + + ret = rt9471_set_value_by_field_range(chip, F_IEOC_CHG, RT9471_RANGE_IEOC, microamp); + if (ret) + return ret; + + /* After applying the new IEOC value, enable charge termination */ + return regmap_field_write(chip->rm_fields[F_TE], 1); +} + +static int rt9471_get_ieoc(struct rt9471_chip *chip, int *microamp) +{ + unsigned int chg_term_enable; + int ret; + + ret = regmap_field_read(chip->rm_fields[F_TE], &chg_term_enable); + if (ret) + return ret; + + if (!chg_term_enable) { + *microamp = 0; + return 0; + } + + return rt9471_get_value_by_field_range(chip, F_IEOC_CHG, RT9471_RANGE_IEOC, microamp); +} + +static int rt9471_get_status(struct rt9471_chip *chip, int *status) +{ + unsigned int ic_stat; + int ret; + + ret = regmap_field_read(chip->rm_fields[F_IC_STAT], &ic_stat); + if (ret) + return ret; + + switch (ic_stat) { + case RT9471_ICSTAT_VBUSRDY: + case RT9471_ICSTAT_CHGFAULT: + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case RT9471_ICSTAT_TRICKLECHG ... RT9471_ICSTAT_BGCHG: + *status = POWER_SUPPLY_STATUS_CHARGING; + break; + case RT9471_ICSTAT_CHGDONE: + *status = POWER_SUPPLY_STATUS_FULL; + break; + case RT9471_ICSTAT_SLEEP: + case RT9471_ICSTAT_OTG: + *status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + *status = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + return 0; +} + +static int rt9471_get_vbus_good(struct rt9471_chip *chip, int *stat) +{ + unsigned int vbus_gd; + int ret; + + ret = regmap_field_read(chip->rm_fields[F_ST_VBUS_GD], &vbus_gd); + if (ret) + return ret; + + *stat = vbus_gd; + return 0; +} + +static int rt9471_get_usb_type(struct rt9471_chip *chip, int *usb_type) +{ + mutex_lock(&chip->var_lock); + *usb_type = chip->psy_usb_type; + mutex_unlock(&chip->var_lock); + + return 0; +} + +static int rt9471_get_usb_type_current(struct rt9471_chip *chip, + int *microamp) +{ + mutex_lock(&chip->var_lock); + *microamp = chip->psy_usb_curr; + mutex_unlock(&chip->var_lock); + + return 0; +} + +static enum power_supply_property rt9471_charger_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_PRECHARGE_CURRENT, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static int rt9471_charger_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_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return 1; + default: + return 0; + } +} + +static int rt9471_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct rt9471_chip *chip = power_supply_get_drvdata(psy); + int value = val->intval; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return regmap_field_write(chip->rm_fields[F_CHG_EN], !!value); + case POWER_SUPPLY_PROP_ONLINE: + return regmap_field_write(chip->rm_fields[F_HZ], !value); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return rt9471_set_value_by_field_range(chip, F_ICHG_REG, RT9471_RANGE_ICHG, value); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + return rt9471_set_value_by_field_range(chip, F_VBAT_REG, RT9471_RANGE_VCHG, value); + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return rt9471_set_value_by_field_range(chip, F_AICR, RT9471_RANGE_AICR, value); + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + return rt9471_set_value_by_field_range(chip, F_MIVR, RT9471_RANGE_MIVR, value); + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + return rt9471_set_value_by_field_range(chip, F_IPRE_CHG, RT9471_RANGE_IPRE, value); + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return rt9471_set_ieoc(chip, val->intval); + default: + return -EINVAL; + } +} + +static const char * const rt9471_manufacturer = "Richtek Technology Corp."; +static const char * const rt9471_model = "RT9471"; + +static int rt9471_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt9471_chip *chip = power_supply_get_drvdata(psy); + int *pvalue = &val->intval; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return rt9471_get_status(chip, pvalue); + case POWER_SUPPLY_PROP_ONLINE: + return rt9471_get_vbus_good(chip, pvalue); + case POWER_SUPPLY_PROP_CURRENT_MAX: + return rt9471_get_usb_type_current(chip, pvalue); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return rt9471_get_value_by_field_range(chip, F_ICHG_REG, RT9471_RANGE_ICHG, pvalue); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + *pvalue = RT9471_ICHG_MAXUA; + return 0; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + return rt9471_get_value_by_field_range(chip, F_VBAT_REG, RT9471_RANGE_VCHG, pvalue); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = RT9471_VCHG_MAXUV; + return 0; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return rt9471_get_value_by_field_range(chip, F_AICR, RT9471_RANGE_AICR, pvalue); + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + return rt9471_get_value_by_field_range(chip, F_MIVR, RT9471_RANGE_MIVR, pvalue); + case POWER_SUPPLY_PROP_USB_TYPE: + return rt9471_get_usb_type(chip, pvalue); + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + return rt9471_get_value_by_field_range(chip, F_IPRE_CHG, RT9471_RANGE_IPRE, pvalue); + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return rt9471_get_ieoc(chip, pvalue); + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = rt9471_model; + return 0; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = rt9471_manufacturer; + return 0; + default: + return -ENODATA; + } +} + +static irqreturn_t rt9471_vbus_gd_handler(int irqno, void *devid) +{ + struct rt9471_chip *chip = devid; + + power_supply_changed(chip->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t rt9471_detach_handler(int irqno, void *devid) +{ + struct rt9471_chip *chip = devid; + unsigned int vbus_gd; + int ret; + + ret = regmap_field_read(chip->rm_fields[F_ST_VBUS_GD], &vbus_gd); + if (ret) + return IRQ_NONE; + + /* Only focus on really detached */ + if (vbus_gd) + return IRQ_HANDLED; + + mutex_lock(&chip->var_lock); + chip->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + chip->psy_usb_curr = 0; + mutex_unlock(&chip->var_lock); + + power_supply_changed(chip->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t rt9471_bc12_done_handler(int irqno, void *devid) +{ + struct rt9471_chip *chip = devid; + enum power_supply_usb_type usb_type; + unsigned int port_stat; + int usb_curr, ret; + + ret = regmap_field_read(chip->rm_fields[F_PORT_STAT], &port_stat); + if (ret) + return IRQ_NONE; + + switch (port_stat) { + case RT9471_PORTSTAT_APPLE_10W: + usb_type = POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID; + usb_curr = 2000000; + break; + case RT9471_PORTSTAT_APPLE_5W: + usb_type = POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID; + usb_curr = 1000000; + break; + case RT9471_PORTSTAT_APPLE_12W: + usb_type = POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID; + usb_curr = 2400000; + break; + case RT9471_PORTSTAT_SAMSUNG_10W: + usb_type = POWER_SUPPLY_USB_TYPE_DCP; + usb_curr = 2000000; + break; + case RT9471_PORTSTAT_DCP: + usb_type = POWER_SUPPLY_USB_TYPE_DCP; + usb_curr = 1500000; + break; + case RT9471_PORTSTAT_NSTD: + case RT9471_PORTSTAT_SDP: + usb_type = POWER_SUPPLY_USB_TYPE_SDP; + usb_curr = 500000; + break; + case RT9471_PORTSTAT_CDP: + usb_type = POWER_SUPPLY_USB_TYPE_CDP; + usb_curr = 1500000; + break; + default: + usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + usb_curr = 0; + break; + } + + mutex_lock(&chip->var_lock); + chip->psy_usb_type = usb_type; + chip->psy_usb_curr = usb_curr; + mutex_unlock(&chip->var_lock); + + power_supply_changed(chip->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t rt9471_wdt_handler(int irqno, void *devid) +{ + struct rt9471_chip *chip = devid; + int ret; + + ret = regmap_field_write(chip->rm_fields[F_WDT_RST], 1); + + return ret ? IRQ_NONE : IRQ_HANDLED; +} + +static irqreturn_t rt9471_otg_fault_handler(int irqno, void *devid) +{ + struct rt9471_chip *chip = devid; + + regulator_notifier_call_chain(chip->otg_rdev, REGULATOR_EVENT_FAIL, NULL); + + return IRQ_HANDLED; +} + +#define RT9471_IRQ_DESC(_name, _hwirq) \ +{ \ + .name = #_name, \ + .hwirq = _hwirq, \ + .handler = rt9471_##_name##_handler, \ +} + +static int rt9471_register_interrupts(struct rt9471_chip *chip) +{ + struct device *dev = chip->dev; + static const struct { + char *name; + int hwirq; + irq_handler_t handler; + } chg_irqs[] = { + RT9471_IRQ_DESC(vbus_gd, RT9471_IRQ_VBUS_GD), + RT9471_IRQ_DESC(detach, RT9471_IRQ_DETACH), + RT9471_IRQ_DESC(bc12_done, RT9471_IRQ_BC12_DONE), + RT9471_IRQ_DESC(wdt, RT9471_IRQ_WDT), + RT9471_IRQ_DESC(otg_fault, RT9471_IRQ_OTG_FAULT), + }, *curr; + int i, virq, ret; + + for (i = 0; i < ARRAY_SIZE(chg_irqs); i++) { + curr = chg_irqs + i; + + virq = regmap_irq_get_virq(chip->irq_chip_data, curr->hwirq); + if (virq <= 0) + return virq; + + ret = devm_request_threaded_irq(dev, virq, NULL, curr->handler, + IRQF_ONESHOT, curr->name, chip); + if (ret) + return dev_err_probe(dev, ret, "Failed to register IRQ (%s)\n", + curr->name); + } + + return 0; +} + +static const struct regulator_ops rt9471_otg_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_current_limit = regulator_set_current_limit_regmap, + .get_current_limit = regulator_get_current_limit_regmap, +}; + +static const unsigned int rt9471_otg_microamp[] = { 500000, 1200000, }; + +static const struct regulator_desc rt9471_otg_rdesc = { + .of_match = of_match_ptr("usb-otg-vbus-regulator"), + .name = "rt9471-otg-vbus", + .owner = THIS_MODULE, + .type = REGULATOR_VOLTAGE, + .ops = &rt9471_otg_ops, + .min_uV = RT9471_OTGCV_MINUV, + .uV_step = RT9471_OTGCV_STEPUV, + .n_voltages = RT9471_NUM_VOTG, + .curr_table = rt9471_otg_microamp, + .n_current_limits = ARRAY_SIZE(rt9471_otg_microamp), + .enable_mask = RT9471_OTGEN_MASK, + .enable_reg = RT9471_REG_FUNC, + .vsel_reg = RT9471_REG_OTGCFG, + .vsel_mask = RT9471_OTGCV_MASK, + .csel_reg = RT9471_REG_OTGCFG, + .csel_mask = RT9471_OTGCC_MASK, +}; + +static int rt9471_register_otg_regulator(struct rt9471_chip *chip) +{ + struct device *dev = chip->dev; + struct regulator_config cfg = { .dev = dev, .driver_data = chip }; + + chip->otg_rdev = devm_regulator_register(dev, &rt9471_otg_rdesc, &cfg); + + return PTR_ERR_OR_ZERO(chip->otg_rdev); +} + +static inline struct rt9471_chip *psy_device_to_chip(struct device *dev) +{ + return power_supply_get_drvdata(to_power_supply(dev)); +} + +static ssize_t sysoff_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rt9471_chip *chip = psy_device_to_chip(dev); + unsigned int sysoff_enable; + int ret; + + ret = regmap_field_read(chip->rm_fields[F_BATFET_DIS], &sysoff_enable); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", sysoff_enable); +} + +static ssize_t sysoff_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rt9471_chip *chip = psy_device_to_chip(dev); + unsigned int tmp; + int ret; + + ret = kstrtouint(buf, 10, &tmp); + if (ret) + return ret; + + ret = regmap_field_write(chip->rm_fields[F_BATFET_DIS], !!tmp); + if (ret) + return ret; + + return count; +} + +static ssize_t port_detect_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rt9471_chip *chip = psy_device_to_chip(dev); + unsigned int bc12_enable; + int ret; + + ret = regmap_field_read(chip->rm_fields[F_BC12_EN], &bc12_enable); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", bc12_enable); +} + +static ssize_t port_detect_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rt9471_chip *chip = psy_device_to_chip(dev); + unsigned int tmp; + int ret; + + ret = kstrtouint(buf, 10, &tmp); + if (ret) + return ret; + + ret = regmap_field_write(chip->rm_fields[F_BC12_EN], !!tmp); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(sysoff_enable); +static DEVICE_ATTR_RW(port_detect_enable); + +static struct attribute *rt9471_sysfs_attrs[] = { + &dev_attr_sysoff_enable.attr, + &dev_attr_port_detect_enable.attr, + NULL +}; + +ATTRIBUTE_GROUPS(rt9471_sysfs); + +static int rt9471_register_psy(struct rt9471_chip *chip) +{ + struct device *dev = chip->dev; + struct power_supply_desc *desc = &chip->psy_desc; + struct power_supply_config cfg = {}; + char *psy_name; + + cfg.drv_data = chip; + cfg.fwnode = dev_fwnode(dev); + cfg.attr_grp = rt9471_sysfs_groups; + + psy_name = devm_kasprintf(dev, GFP_KERNEL, "rt9471-%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_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN); + desc->properties = rt9471_charger_properties; + desc->num_properties = ARRAY_SIZE(rt9471_charger_properties); + desc->get_property = rt9471_charger_get_property; + desc->set_property = rt9471_charger_set_property; + desc->property_is_writeable = rt9471_charger_property_is_writeable; + + chip->psy = devm_power_supply_register(dev, desc, &cfg); + + return PTR_ERR_OR_ZERO(chip->psy); +} + +static const struct regmap_irq rt9471_regmap_irqs[] = { + REGMAP_IRQ_REG_LINE(RT9471_IRQ_BC12_DONE, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_DETACH, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_RECHG, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_CHG_DONE, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_BG_CHG, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_IE0C, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_CHG_RDY, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_VBUS_GD, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_CHG_BATOV, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_CHG_SYSOV, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_CHG_TOUT, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_CHG_BUSUV, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_CHG_THREG, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_CHG_AICR, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_CHG_MIVR, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_SYS_SHORT, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_SYS_MIN, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_AICC_DONE, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_PE_DONE, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_JEITA_COLD, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_JEITA_COOL, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_JEITA_WARM, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_JEITA_HOT, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_OTG_FAULT, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_OTG_LBP, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_OTG_CC, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_WDT, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_VAC_OV, 8), + REGMAP_IRQ_REG_LINE(RT9471_IRQ_OTP, 8), +}; + +static const struct regmap_irq_chip rt9471_irq_chip = { + .name = "rt9471-irqs", + .status_base = RT9471_REG_IRQ0, + .mask_base = RT9471_REG_MASK0, + .num_regs = RT9471_NUM_IRQ_REGS, + .irqs = rt9471_regmap_irqs, + .num_irqs = ARRAY_SIZE(rt9471_regmap_irqs), +}; + +static const struct reg_sequence rt9471_init_regs[] = { + REG_SEQ0(RT9471_REG_INFO, 0x80), /* REG_RST */ + REG_SEQ0(RT9471_REG_TOP, 0xC0), /* WDT = 0 */ + REG_SEQ0(RT9471_REG_FUNC, 0x01), /* BATFET_DIS_DLY = 0 */ + REG_SEQ0(RT9471_REG_IBUS, 0x0A), /* AUTO_AICR = 0 */ + REG_SEQ0(RT9471_REG_VBUS, 0xC6), /* VAC_OVP = 14V */ + REG_SEQ0(RT9471_REG_JEITA, 0x38), /* JEITA = 0 */ + REG_SEQ0(RT9471_REG_DPDMDET, 0x31), /* BC12_EN = 0, DCP_DP_OPT = 1 */ +}; + +static int rt9471_check_devinfo(struct rt9471_chip *chip) +{ + struct device *dev = chip->dev; + unsigned int dev_id; + int ret; + + ret = regmap_field_read(chip->rm_fields[F_DEVICE_ID], &dev_id); + if (ret) + return dev_err_probe(dev, ret, "Failed to read device_id\n"); + + switch (dev_id) { + case RT9470_DEVID: + case RT9470D_DEVID: + case RT9471_DEVID: + case RT9471D_DEVID: + return 0; + default: + return dev_err_probe(dev, -ENODEV, "Incorrect device id\n"); + } +} + +static bool rt9471_accessible_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x00 ... 0x0F: + case 0x10 ... 0x13: + case 0x20 ... 0x33: + case 0x40 ... 0xA1: + return true; + default: + return false; + } +} + +static const struct regmap_config rt9471_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xA1, + .writeable_reg = rt9471_accessible_reg, + .readable_reg = rt9471_accessible_reg, +}; + +static int rt9471_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct rt9471_chip *chip; + struct gpio_desc *ce_gpio; + struct regmap *regmap; + int ret; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = dev; + mutex_init(&chip->var_lock); + i2c_set_clientdata(i2c, chip); + + /* Default pull charge enable gpio to make 'CHG_EN' by SW control only */ + ce_gpio = devm_gpiod_get_optional(dev, "charge-enable", GPIOD_OUT_HIGH); + if (IS_ERR(ce_gpio)) + return dev_err_probe(dev, PTR_ERR(ce_gpio), + "Failed to config charge enable gpio\n"); + + regmap = devm_regmap_init_i2c(i2c, &rt9471_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to init regmap\n"); + + chip->regmap = regmap; + + ret = devm_regmap_field_bulk_alloc(dev, regmap, chip->rm_fields, + rt9471_reg_fields, + ARRAY_SIZE(rt9471_reg_fields)); + if (ret) + return dev_err_probe(dev, ret, "Failed to alloc regmap field\n"); + + ret = rt9471_check_devinfo(chip); + if (ret) + return ret; + + ret = regmap_register_patch(regmap, rt9471_init_regs, + ARRAY_SIZE(rt9471_init_regs)); + if (ret) + return dev_err_probe(dev, ret, "Failed to init registers\n"); + + ret = devm_regmap_add_irq_chip(dev, regmap, i2c->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, + &rt9471_irq_chip, &chip->irq_chip_data); + if (ret) + return dev_err_probe(dev, ret, "Failed to add IRQ chip\n"); + + ret = rt9471_register_psy(chip); + if (ret) + return dev_err_probe(dev, ret, "Failed to register psy\n"); + + ret = rt9471_register_otg_regulator(chip); + if (ret) + return dev_err_probe(dev, ret, "Failed to register otg\n"); + + ret = rt9471_register_interrupts(chip); + if (ret) + return ret; + + /* After IRQs are all initialized, enable port detection by default */ + return regmap_field_write(chip->rm_fields[F_BC12_EN], 1); +} + +static void rt9471_shutdown(struct i2c_client *i2c) +{ + struct rt9471_chip *chip = i2c_get_clientdata(i2c); + + /* + * There's no external reset pin. Do register reset to guarantee charger + * function is normal after shutdown + */ + regmap_field_write(chip->rm_fields[F_REG_RST], 1); +} + +static const struct of_device_id rt9471_of_device_id[] = { + { .compatible = "richtek,rt9471" }, + {} +}; +MODULE_DEVICE_TABLE(of, rt9471_of_device_id); + +static struct i2c_driver rt9471_driver = { + .driver = { + .name = "rt9471", + .of_match_table = rt9471_of_device_id, + }, + .probe = rt9471_probe, + .shutdown = rt9471_shutdown, +}; +module_i2c_driver(rt9471_driver); + +MODULE_DESCRIPTION("Richtek RT9471 charger driver"); +MODULE_AUTHOR("Alina Yu <alina_yu@richtek.com>"); +MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); +MODULE_LICENSE("GPL"); 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 6e488ecf4dcb..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; @@ -192,14 +192,11 @@ static int rx51_battery_probe(struct platform_device *pdev) { struct power_supply_config psy_cfg = {}; struct rx51_device_info *di; - int ret; di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); if (!di) return -ENOMEM; - platform_set_drvdata(pdev, di); - di->dev = &pdev->dev; di->bat_desc.name = "rx51-battery"; di->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; @@ -209,52 +206,21 @@ static int rx51_battery_probe(struct platform_device *pdev) psy_cfg.drv_data = di; - di->channel_temp = iio_channel_get(di->dev, "temp"); - if (IS_ERR(di->channel_temp)) { - ret = PTR_ERR(di->channel_temp); - goto error; - } - - di->channel_bsi = iio_channel_get(di->dev, "bsi"); - if (IS_ERR(di->channel_bsi)) { - ret = PTR_ERR(di->channel_bsi); - goto error_channel_temp; - } - - di->channel_vbat = iio_channel_get(di->dev, "vbat"); - if (IS_ERR(di->channel_vbat)) { - ret = PTR_ERR(di->channel_vbat); - goto error_channel_bsi; - } - - di->bat = power_supply_register(di->dev, &di->bat_desc, &psy_cfg); - if (IS_ERR(di->bat)) { - ret = PTR_ERR(di->bat); - goto error_channel_vbat; - } - - return 0; - -error_channel_vbat: - iio_channel_release(di->channel_vbat); -error_channel_bsi: - iio_channel_release(di->channel_bsi); -error_channel_temp: - iio_channel_release(di->channel_temp); -error: + di->channel_temp = devm_iio_channel_get(di->dev, "temp"); + if (IS_ERR(di->channel_temp)) + return PTR_ERR(di->channel_temp); - return ret; -} - -static int rx51_battery_remove(struct platform_device *pdev) -{ - struct rx51_device_info *di = platform_get_drvdata(pdev); + di->channel_bsi = devm_iio_channel_get(di->dev, "bsi"); + if (IS_ERR(di->channel_bsi)) + return PTR_ERR(di->channel_bsi); - power_supply_unregister(di->bat); + di->channel_vbat = devm_iio_channel_get(di->dev, "vbat"); + if (IS_ERR(di->channel_vbat)) + return PTR_ERR(di->channel_vbat); - iio_channel_release(di->channel_vbat); - iio_channel_release(di->channel_bsi); - iio_channel_release(di->channel_temp); + di->bat = devm_power_supply_register(di->dev, &di->bat_desc, &psy_cfg); + if (IS_ERR(di->bat)) + return PTR_ERR(di->bat); return 0; } @@ -269,7 +235,6 @@ MODULE_DEVICE_TABLE(of, n900_battery_of_match); static struct platform_driver rx51_battery_driver = { .probe = rx51_battery_probe, - .remove = rx51_battery_remove, .driver = { .name = "rx51-battery", .of_match_table = of_match_ptr(n900_battery_of_match), diff --git a/drivers/power/supply/s3c_adc_battery.c b/drivers/power/supply/s3c_adc_battery.c deleted file mode 100644 index 68d31a3bee48..000000000000 --- a/drivers/power/supply/s3c_adc_battery.c +++ /dev/null @@ -1,453 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// -// iPAQ h1930/h1940/rx1950 battery controller driver -// Copyright (c) Vasily Khoruzhick -// Based on h1940_battery.c by Arnaud Patard - -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/power_supply.h> -#include <linux/leds.h> -#include <linux/gpio/consumer.h> -#include <linux/err.h> -#include <linux/timer.h> -#include <linux/jiffies.h> -#include <linux/s3c_adc_battery.h> -#include <linux/errno.h> -#include <linux/init.h> -#include <linux/module.h> - -#include <linux/soc/samsung/s3c-adc.h> - -#define BAT_POLL_INTERVAL 10000 /* ms */ -#define JITTER_DELAY 500 /* ms */ - -struct s3c_adc_bat { - struct power_supply *psy; - struct s3c_adc_client *client; - struct s3c_adc_bat_pdata *pdata; - struct gpio_desc *charge_finished; - int volt_value; - int cur_value; - unsigned int timestamp; - int level; - int status; - int cable_plugged:1; -}; - -static struct delayed_work bat_work; - -static void s3c_adc_bat_ext_power_changed(struct power_supply *psy) -{ - schedule_delayed_work(&bat_work, - msecs_to_jiffies(JITTER_DELAY)); -} - -static int gather_samples(struct s3c_adc_client *client, int num, int channel) -{ - int value, i; - - /* default to 1 if nothing is set */ - if (num < 1) - num = 1; - - value = 0; - for (i = 0; i < num; i++) - value += s3c_adc_read(client, channel); - value /= num; - - return value; -} - -static enum power_supply_property s3c_adc_backup_bat_props[] = { - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_MIN, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, -}; - -static int s3c_adc_backup_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct s3c_adc_bat *bat = power_supply_get_drvdata(psy); - - if (!bat) { - dev_err(&psy->dev, "%s: no battery infos ?!\n", __func__); - return -EINVAL; - } - - if (bat->volt_value < 0 || - jiffies_to_msecs(jiffies - bat->timestamp) > - BAT_POLL_INTERVAL) { - bat->volt_value = gather_samples(bat->client, - bat->pdata->backup_volt_samples, - bat->pdata->backup_volt_channel); - bat->volt_value *= bat->pdata->backup_volt_mult; - bat->timestamp = jiffies; - } - - switch (psp) { - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = bat->volt_value; - return 0; - case POWER_SUPPLY_PROP_VOLTAGE_MIN: - val->intval = bat->pdata->backup_volt_min; - return 0; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = bat->pdata->backup_volt_max; - return 0; - default: - return -EINVAL; - } -} - -static const struct power_supply_desc backup_bat_desc = { - .name = "backup-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = s3c_adc_backup_bat_props, - .num_properties = ARRAY_SIZE(s3c_adc_backup_bat_props), - .get_property = s3c_adc_backup_bat_get_property, - .use_for_apm = 1, -}; - -static struct s3c_adc_bat backup_bat; - -static enum power_supply_property s3c_adc_main_bat_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, -}; - -static int calc_full_volt(int volt_val, int cur_val, int impedance) -{ - return volt_val + cur_val * impedance / 1000; -} - -static int charge_finished(struct s3c_adc_bat *bat) -{ - return gpiod_get_value(bat->charge_finished); -} - -static int s3c_adc_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct s3c_adc_bat *bat = power_supply_get_drvdata(psy); - - int new_level; - int full_volt; - const struct s3c_adc_bat_thresh *lut; - unsigned int lut_size; - - if (!bat) { - dev_err(&psy->dev, "no battery infos ?!\n"); - return -EINVAL; - } - - lut = bat->pdata->lut_noac; - lut_size = bat->pdata->lut_noac_cnt; - - if (bat->volt_value < 0 || bat->cur_value < 0 || - jiffies_to_msecs(jiffies - bat->timestamp) > - BAT_POLL_INTERVAL) { - bat->volt_value = gather_samples(bat->client, - bat->pdata->volt_samples, - bat->pdata->volt_channel) * bat->pdata->volt_mult; - bat->cur_value = gather_samples(bat->client, - bat->pdata->current_samples, - bat->pdata->current_channel) * bat->pdata->current_mult; - bat->timestamp = jiffies; - } - - if (bat->cable_plugged && - (!bat->charge_finished || - !charge_finished(bat))) { - lut = bat->pdata->lut_acin; - lut_size = bat->pdata->lut_acin_cnt; - } - - new_level = 100000; - full_volt = calc_full_volt((bat->volt_value / 1000), - (bat->cur_value / 1000), bat->pdata->internal_impedance); - - if (full_volt < calc_full_volt(lut->volt, lut->cur, - bat->pdata->internal_impedance)) { - lut_size--; - while (lut_size--) { - int lut_volt1; - int lut_volt2; - - lut_volt1 = calc_full_volt(lut[0].volt, lut[0].cur, - bat->pdata->internal_impedance); - lut_volt2 = calc_full_volt(lut[1].volt, lut[1].cur, - bat->pdata->internal_impedance); - if (full_volt < lut_volt1 && full_volt >= lut_volt2) { - new_level = (lut[1].level + - (lut[0].level - lut[1].level) * - (full_volt - lut_volt2) / - (lut_volt1 - lut_volt2)) * 1000; - break; - } - new_level = lut[1].level * 1000; - lut++; - } - } - - bat->level = new_level; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - if (!bat->charge_finished) - val->intval = bat->level == 100000 ? - POWER_SUPPLY_STATUS_FULL : bat->status; - else - val->intval = bat->status; - return 0; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval = 100000; - return 0; - case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN: - val->intval = 0; - return 0; - case POWER_SUPPLY_PROP_CHARGE_NOW: - val->intval = bat->level; - return 0; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = bat->volt_value; - return 0; - case POWER_SUPPLY_PROP_CURRENT_NOW: - val->intval = bat->cur_value; - return 0; - default: - return -EINVAL; - } -} - -static const struct power_supply_desc main_bat_desc = { - .name = "main-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = s3c_adc_main_bat_props, - .num_properties = ARRAY_SIZE(s3c_adc_main_bat_props), - .get_property = s3c_adc_bat_get_property, - .external_power_changed = s3c_adc_bat_ext_power_changed, - .use_for_apm = 1, -}; - -static struct s3c_adc_bat main_bat; - -static void s3c_adc_bat_work(struct work_struct *work) -{ - struct s3c_adc_bat *bat = &main_bat; - int is_charged; - int is_plugged; - static int was_plugged; - - is_plugged = power_supply_am_i_supplied(bat->psy); - bat->cable_plugged = is_plugged; - if (is_plugged != was_plugged) { - was_plugged = is_plugged; - if (is_plugged) { - if (bat->pdata->enable_charger) - bat->pdata->enable_charger(); - bat->status = POWER_SUPPLY_STATUS_CHARGING; - } else { - if (bat->pdata->disable_charger) - bat->pdata->disable_charger(); - bat->status = POWER_SUPPLY_STATUS_DISCHARGING; - } - } else { - if (bat->charge_finished && is_plugged) { - is_charged = charge_finished(&main_bat); - if (is_charged) { - if (bat->pdata->disable_charger) - bat->pdata->disable_charger(); - bat->status = POWER_SUPPLY_STATUS_FULL; - } else { - if (bat->pdata->enable_charger) - bat->pdata->enable_charger(); - bat->status = POWER_SUPPLY_STATUS_CHARGING; - } - } - } - - power_supply_changed(bat->psy); -} - -static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id) -{ - schedule_delayed_work(&bat_work, - msecs_to_jiffies(JITTER_DELAY)); - return IRQ_HANDLED; -} - -static int s3c_adc_bat_probe(struct platform_device *pdev) -{ - struct s3c_adc_client *client; - struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; - struct power_supply_config psy_cfg = {}; - struct gpio_desc *gpiod; - int ret; - - client = s3c_adc_register(pdev, NULL, NULL, 0); - if (IS_ERR(client)) { - dev_err(&pdev->dev, "cannot register adc\n"); - return PTR_ERR(client); - } - - platform_set_drvdata(pdev, client); - - gpiod = devm_gpiod_get_optional(&pdev->dev, "charge-status", GPIOD_IN); - if (IS_ERR(gpiod)) { - /* Could be probe deferral etc */ - ret = PTR_ERR(gpiod); - dev_err(&pdev->dev, "no GPIO %d\n", ret); - return ret; - } - - main_bat.client = client; - main_bat.pdata = pdata; - main_bat.charge_finished = gpiod; - main_bat.volt_value = -1; - main_bat.cur_value = -1; - main_bat.cable_plugged = 0; - main_bat.status = POWER_SUPPLY_STATUS_DISCHARGING; - psy_cfg.drv_data = &main_bat; - - main_bat.psy = power_supply_register(&pdev->dev, &main_bat_desc, &psy_cfg); - if (IS_ERR(main_bat.psy)) { - ret = PTR_ERR(main_bat.psy); - goto err_reg_main; - } - if (pdata->backup_volt_mult) { - const struct power_supply_config backup_psy_cfg - = { .drv_data = &backup_bat, }; - - backup_bat.client = client; - backup_bat.pdata = pdev->dev.platform_data; - backup_bat.charge_finished = gpiod; - backup_bat.volt_value = -1; - backup_bat.psy = power_supply_register(&pdev->dev, - &backup_bat_desc, - &backup_psy_cfg); - if (IS_ERR(backup_bat.psy)) { - ret = PTR_ERR(backup_bat.psy); - goto err_reg_backup; - } - } - - INIT_DELAYED_WORK(&bat_work, s3c_adc_bat_work); - - if (gpiod) { - ret = request_irq(gpiod_to_irq(gpiod), - s3c_adc_bat_charged, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "battery charged", NULL); - if (ret) - goto err_irq; - } - - if (pdata->init) { - ret = pdata->init(); - if (ret) - goto err_platform; - } - - dev_info(&pdev->dev, "successfully loaded\n"); - device_init_wakeup(&pdev->dev, 1); - - /* Schedule timer to check current status */ - schedule_delayed_work(&bat_work, - msecs_to_jiffies(JITTER_DELAY)); - - return 0; - -err_platform: - if (gpiod) - free_irq(gpiod_to_irq(gpiod), NULL); -err_irq: - if (pdata->backup_volt_mult) - power_supply_unregister(backup_bat.psy); -err_reg_backup: - power_supply_unregister(main_bat.psy); -err_reg_main: - return ret; -} - -static int s3c_adc_bat_remove(struct platform_device *pdev) -{ - struct s3c_adc_client *client = platform_get_drvdata(pdev); - struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data; - - power_supply_unregister(main_bat.psy); - if (pdata->backup_volt_mult) - power_supply_unregister(backup_bat.psy); - - s3c_adc_release(client); - - if (main_bat.charge_finished) - free_irq(gpiod_to_irq(main_bat.charge_finished), NULL); - - cancel_delayed_work_sync(&bat_work); - - if (pdata->exit) - pdata->exit(); - - return 0; -} - -#ifdef CONFIG_PM -static int s3c_adc_bat_suspend(struct platform_device *pdev, - pm_message_t state) -{ - if (main_bat.charge_finished) { - if (device_may_wakeup(&pdev->dev)) - enable_irq_wake( - gpiod_to_irq(main_bat.charge_finished)); - else { - disable_irq(gpiod_to_irq(main_bat.charge_finished)); - main_bat.pdata->disable_charger(); - } - } - - return 0; -} - -static int s3c_adc_bat_resume(struct platform_device *pdev) -{ - if (main_bat.charge_finished) { - if (device_may_wakeup(&pdev->dev)) - disable_irq_wake( - gpiod_to_irq(main_bat.charge_finished)); - else - enable_irq(gpiod_to_irq(main_bat.charge_finished)); - } - - /* Schedule timer to check current status */ - schedule_delayed_work(&bat_work, - msecs_to_jiffies(JITTER_DELAY)); - - return 0; -} -#else -#define s3c_adc_bat_suspend NULL -#define s3c_adc_bat_resume NULL -#endif - -static struct platform_driver s3c_adc_bat_driver = { - .driver = { - .name = "s3c-adc-battery", - }, - .probe = s3c_adc_bat_probe, - .remove = s3c_adc_bat_remove, - .suspend = s3c_adc_bat_suspend, - .resume = s3c_adc_bat_resume, -}; - -module_platform_driver(s3c_adc_bat_driver); - -MODULE_AUTHOR("Vasily Khoruzhick <anarsoul@gmail.com>"); -MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controller driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/samsung-sdi-battery.c b/drivers/power/supply/samsung-sdi-battery.c index b33daab798b9..33565002ee27 100644 --- a/drivers/power/supply/samsung-sdi-battery.c +++ b/drivers/power/supply/samsung-sdi-battery.c @@ -25,7 +25,7 @@ struct samsung_sdi_battery { * tables apply depending on whether we are charging or not. */ -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb_l1m7flu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb_l1m7flu[] = { { .vbat_uv = 4240000, .ri_uohm = 160000 }, { .vbat_uv = 4210000, .ri_uohm = 179000 }, { .vbat_uv = 4180000, .ri_uohm = 183000 }, @@ -53,7 +53,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb_l1m7flu { .vbat_uv = 3300000, .ri_uohm = 339000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb_l1m7flu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb_l1m7flu[] = { { .vbat_uv = 4302000, .ri_uohm = 230000 }, { .vbat_uv = 4276000, .ri_uohm = 345000 }, { .vbat_uv = 4227000, .ri_uohm = 345000 }, @@ -73,7 +73,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb_l1m7flu[] { .vbat_uv = 3590000, .ri_uohm = 164000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161la[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161la[] = { { .vbat_uv = 4240000, .ri_uohm = 160000 }, { .vbat_uv = 4210000, .ri_uohm = 179000 }, { .vbat_uv = 4180000, .ri_uohm = 183000 }, @@ -105,7 +105,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161la { .vbat_uv = 3300000, .ri_uohm = 339000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161la[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161la[] = { { .vbat_uv = 4345000, .ri_uohm = 230000 }, { .vbat_uv = 4329000, .ri_uohm = 238000 }, { .vbat_uv = 4314000, .ri_uohm = 225000 }, @@ -182,7 +182,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161la[] { .vbat_uv = 3590000, .ri_uohm = 164000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161lu[] = { { .vbat_uv = 4240000, .ri_uohm = 160000 }, { .vbat_uv = 4210000, .ri_uohm = 179000 }, { .vbat_uv = 4180000, .ri_uohm = 183000 }, @@ -214,7 +214,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161lu { .vbat_uv = 3300000, .ri_uohm = 339000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161lu[] = { { .vbat_uv = 4346000, .ri_uohm = 293000 }, { .vbat_uv = 4336000, .ri_uohm = 290000 }, { .vbat_uv = 4315000, .ri_uohm = 274000 }, @@ -244,7 +244,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161lu[] { .vbat_uv = 3590000, .ri_uohm = 164000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb485159lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb485159lu[] = { { .vbat_uv = 4240000, .ri_uohm = 160000 }, { .vbat_uv = 4210000, .ri_uohm = 179000 }, { .vbat_uv = 4180000, .ri_uohm = 183000 }, @@ -271,7 +271,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb485159lu { .vbat_uv = 3300000, .ri_uohm = 339000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb485159lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb485159lu[] = { { .vbat_uv = 4302000, .ri_uohm = 200000 }, { .vbat_uv = 4258000, .ri_uohm = 206000 }, { .vbat_uv = 4200000, .ri_uohm = 231000 }, @@ -291,7 +291,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb485159lu[] { .vbat_uv = 3590000, .ri_uohm = 164000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb535151vu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb535151vu[] = { { .vbat_uv = 4071000, .ri_uohm = 158000 }, { .vbat_uv = 4019000, .ri_uohm = 187000 }, { .vbat_uv = 3951000, .ri_uohm = 191000 }, @@ -311,7 +311,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb535151vu { .vbat_uv = 3280000, .ri_uohm = 250000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb535151vu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb535151vu[] = { { .vbat_uv = 4190000, .ri_uohm = 214000 }, { .vbat_uv = 4159000, .ri_uohm = 252000 }, { .vbat_uv = 4121000, .ri_uohm = 245000 }, @@ -331,7 +331,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb535151vu[] { .vbat_uv = 3510000, .ri_uohm = 228000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb585157lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb585157lu[] = { { .vbat_uv = 4194000, .ri_uohm = 121000 }, { .vbat_uv = 4169000, .ri_uohm = 188000 }, { .vbat_uv = 4136000, .ri_uohm = 173000 }, @@ -401,7 +401,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb585157lu { .vbat_uv = 3161000, .ri_uohm = 452000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb585157lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb585157lu[] = { { .vbat_uv = 4360000, .ri_uohm = 128000 }, { .vbat_uv = 4325000, .ri_uohm = 130000 }, { .vbat_uv = 4316000, .ri_uohm = 148000 }, @@ -431,7 +431,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb585157lu[] * temperature compensation tables so we just state 100% for every temperature. * If you have the datasheets, please provide these tables. */ -static struct power_supply_resistance_temp_table samsung_temp2res[] = { +static const struct power_supply_resistance_temp_table samsung_temp2res[] = { { .temp = 50, .resistance = 100 }, { .temp = 40, .resistance = 100 }, { .temp = 30, .resistance = 100 }, @@ -447,7 +447,7 @@ static struct power_supply_resistance_temp_table samsung_temp2res[] = { * These must be sorted by falling OCV value. */ -static struct power_supply_battery_ocv_table samsung_ocv_cap_eb485159lu[] = { +static const struct power_supply_battery_ocv_table samsung_ocv_cap_eb485159lu[] = { { .ocv = 4330000, .capacity = 100}, { .ocv = 4320000, .capacity = 99}, { .ocv = 4283000, .capacity = 95}, @@ -499,7 +499,7 @@ static struct power_supply_battery_ocv_table samsung_ocv_cap_eb485159lu[] = { }; /* Same capacity table is used by eb-l1m7flu, eb425161la, eb425161lu */ -static struct power_supply_battery_ocv_table samsung_ocv_cap_1500mah[] = { +static const struct power_supply_battery_ocv_table samsung_ocv_cap_1500mah[] = { { .ocv = 4328000, .capacity = 100}, { .ocv = 4299000, .capacity = 99}, { .ocv = 4281000, .capacity = 98}, @@ -540,7 +540,7 @@ static struct power_supply_battery_ocv_table samsung_ocv_cap_1500mah[] = { { .ocv = 3300000, .capacity = 0}, }; -static struct power_supply_battery_ocv_table samsung_ocv_cap_eb535151vu[] = { +static const struct power_supply_battery_ocv_table samsung_ocv_cap_eb535151vu[] = { { .ocv = 4178000, .capacity = 100}, { .ocv = 4148000, .capacity = 99}, { .ocv = 4105000, .capacity = 95}, @@ -572,7 +572,7 @@ static struct power_supply_battery_ocv_table samsung_ocv_cap_eb535151vu[] = { { .ocv = 3300000, .capacity = 0}, }; -static struct power_supply_battery_ocv_table samsung_ocv_cap_eb585157lu[] = { +static const struct power_supply_battery_ocv_table samsung_ocv_cap_eb585157lu[] = { { .ocv = 4320000, .capacity = 100}, { .ocv = 4296000, .capacity = 99}, { .ocv = 4283000, .capacity = 98}, @@ -613,7 +613,7 @@ static struct power_supply_battery_ocv_table samsung_ocv_cap_eb585157lu[] = { { .ocv = 3300000, .capacity = 0}, }; -static struct power_supply_maintenance_charge_table samsung_maint_charge_table[] = { +static const struct power_supply_maintenance_charge_table samsung_maint_charge_table[] = { { /* Maintenance charging phase A, 60 hours */ .charge_current_max_ua = 600000, diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index c4a95b01463a..943c82ee978f 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -16,11 +16,12 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/property.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/power/sbs-battery.h> #include <linux/power_supply.h> #include <linux/slab.h> #include <linux/stat.h> +#include <linux/string_choices.h> enum { REG_MANUFACTURER_DATA, @@ -320,8 +321,8 @@ static int sbs_update_presence(struct sbs_info *chip, bool is_present) client->flags &= ~I2C_CLIENT_PEC; } - dev_dbg(&client->dev, "PEC: %s\n", (client->flags & I2C_CLIENT_PEC) ? - "enabled" : "disabled"); + dev_dbg(&client->dev, "PEC: %s\n", + str_enabled_disabled(client->flags & I2C_CLIENT_PEC)); if (!chip->is_present && is_present && !chip->charger_broadcasts) sbs_disable_charger_broadcasts(chip); @@ -1135,9 +1136,9 @@ static int sbs_probe(struct i2c_client *client) if (!chip) return -ENOMEM; - chip->flags = (u32)(uintptr_t)device_get_match_data(&client->dev); + 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); @@ -1253,9 +1254,9 @@ static SIMPLE_DEV_PM_OPS(sbs_pm_ops, sbs_suspend, NULL); #endif static const struct i2c_device_id sbs_id[] = { - { "bq20z65", 0 }, - { "bq20z75", 0 }, - { "sbs-battery", 1 }, + { "bq20z65", SBS_FLAGS_TI_BQ20ZX5 }, + { "bq20z75", SBS_FLAGS_TI_BQ20ZX5 }, + { "sbs-battery", 0 }, {} }; MODULE_DEVICE_TABLE(i2c, sbs_id); @@ -1275,7 +1276,7 @@ static const struct of_device_id sbs_dt_ids[] = { MODULE_DEVICE_TABLE(of, sbs_dt_ids); static struct i2c_driver sbs_battery_driver = { - .probe_new = sbs_probe, + .probe = sbs_probe, .alert = sbs_alert, .id_table = sbs_id, .driver = { diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c index 75ebcbf0a788..7d5e67620580 100644 --- a/drivers/power/supply/sbs-charger.c +++ b/drivers/power/supply/sbs-charger.c @@ -24,7 +24,7 @@ #define SBS_CHARGER_REG_STATUS 0x13 #define SBS_CHARGER_REG_ALARM_WARNING 0x16 -#define SBS_CHARGER_STATUS_CHARGE_INHIBITED BIT(1) +#define SBS_CHARGER_STATUS_CHARGE_INHIBITED BIT(0) #define SBS_CHARGER_STATUS_RES_COLD BIT(9) #define SBS_CHARGER_STATUS_RES_HOT BIT(10) #define SBS_CHARGER_STATUS_BATTERY_PRESENT BIT(14) @@ -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"); @@ -234,13 +244,13 @@ MODULE_DEVICE_TABLE(of, sbs_dt_ids); #endif static const struct i2c_device_id sbs_id[] = { - { "sbs-charger", 0 }, + { "sbs-charger" }, { } }; MODULE_DEVICE_TABLE(i2c, sbs_id); static struct i2c_driver sbs_driver = { - .probe_new = sbs_probe, + .probe = sbs_probe, .id_table = sbs_id, .driver = { .name = "sbs-charger", diff --git a/drivers/power/supply/sbs-manager.c b/drivers/power/supply/sbs-manager.c index bde977391fd4..6fe526222f7f 100644 --- a/drivers/power/supply/sbs-manager.c +++ b/drivers/power/supply/sbs-manager.c @@ -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); @@ -358,7 +358,7 @@ static int sbsm_probe(struct i2c_client *client) /* register muxed i2c channels. One for each supported battery */ for (i = 0; i < SBSM_MAX_BATS; ++i) { if (data->supported_bats & BIT(i)) { - ret = i2c_mux_add_adapter(data->muxc, 0, i + 1, 0); + ret = i2c_mux_add_adapter(data->muxc, 0, i + 1); if (ret) break; } @@ -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), @@ -389,8 +389,8 @@ static int sbsm_probe(struct i2c_client *client) } static const struct i2c_device_id sbsm_ids[] = { - { "sbs-manager", 0 }, - { "ltc1760", 0 }, + { "sbs-manager" }, + { "ltc1760" }, { } }; MODULE_DEVICE_TABLE(i2c, sbsm_ids); @@ -409,7 +409,7 @@ static struct i2c_driver sbsm_driver = { .name = "sbsm", .of_match_table = of_match_ptr(sbsm_dt_ids), }, - .probe_new = sbsm_probe, + .probe = sbsm_probe, .alert = sbsm_alert, .id_table = sbsm_ids }; diff --git a/drivers/power/supply/sc2731_charger.c b/drivers/power/supply/sc2731_charger.c index 9ac17cf7a126..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); @@ -511,13 +511,11 @@ static int sc2731_charger_probe(struct platform_device *pdev) return 0; } -static int sc2731_charger_remove(struct platform_device *pdev) +static void sc2731_charger_remove(struct platform_device *pdev) { struct sc2731_charger_info *info = platform_get_drvdata(pdev); usb_unregister_notifier(info->usb_phy, &info->usb_notify); - - return 0; } static const struct of_device_id sc2731_charger_of_match[] = { diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c index 632977f84b95..a7ed9de8a289 100644 --- a/drivers/power/supply/sc27xx_fuel_gauge.c +++ b/drivers/power/supply/sc27xx_fuel_gauge.c @@ -733,13 +733,6 @@ static int sc27xx_fgu_set_property(struct power_supply *psy, return ret; } -static void sc27xx_fgu_external_power_changed(struct power_supply *psy) -{ - struct sc27xx_fgu_data *data = power_supply_get_drvdata(psy); - - power_supply_changed(data->battery); -} - static int sc27xx_fgu_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { @@ -774,7 +767,7 @@ static const struct power_supply_desc sc27xx_fgu_desc = { .num_properties = ARRAY_SIZE(sc27xx_fgu_props), .get_property = sc27xx_fgu_get_property, .set_property = sc27xx_fgu_set_property, - .external_power_changed = sc27xx_fgu_external_power_changed, + .external_power_changed = power_supply_changed, .property_is_writeable = sc27xx_fgu_property_is_writeable, .no_thermal = true, }; @@ -999,7 +992,7 @@ static int sc27xx_fgu_calibration(struct sc27xx_fgu_data *data) static int sc27xx_fgu_hw_init(struct sc27xx_fgu_data *data) { struct power_supply_battery_info *info; - struct power_supply_battery_ocv_table *table; + const struct power_supply_battery_ocv_table *table; int ret, delta_clbcnt, alarm_adc; ret = power_supply_get_battery_info(data->battery, &info); @@ -1021,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; @@ -1148,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; @@ -1190,10 +1181,14 @@ static int sc27xx_fgu_probe(struct platform_device *pdev) return PTR_ERR(data->charge_chan); } - data->gpiod = devm_gpiod_get(dev, "bat-detect", GPIOD_IN); + data->gpiod = devm_gpiod_get(dev, "battery-detect", GPIOD_IN); if (IS_ERR(data->gpiod)) { - dev_err(dev, "failed to get battery detection GPIO\n"); - return PTR_ERR(data->gpiod); + data->gpiod = devm_gpiod_get(dev, "bat-detect", GPIOD_IN); + if (IS_ERR(data->gpiod)) { + dev_err(dev, "failed to get battery detection GPIO\n"); + return PTR_ERR(data->gpiod); + } + dev_warn(dev, "bat-detect is deprecated, please use battery-detect\n"); } ret = gpiod_get_value_cansleep(data->gpiod); @@ -1208,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 b5f038310282..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); @@ -1629,7 +1629,7 @@ static struct i2c_driver smb347_driver = { .name = "smb347", .of_match_table = smb3xx_of_match, }, - .probe_new = smb347_probe, + .probe = smb347_probe, .remove = smb347_remove, .shutdown = smb347_shutdown, .id_table = smb347_id, diff --git a/drivers/power/supply/stc3117_fuel_gauge.c b/drivers/power/supply/stc3117_fuel_gauge.c new file mode 100644 index 000000000000..a1bc5970370a --- /dev/null +++ b/drivers/power/supply/stc3117_fuel_gauge.c @@ -0,0 +1,612 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * stc3117_fuel_gauge.c - STMicroelectronics STC3117 Fuel Gauge Driver + * + * Copyright (c) 2024 Silicon Signals Pvt Ltd. + * Author: Hardevsinh Palaniya <hardevsinh.palaniya@siliconsignals.io> + * Bhavin Sharma <bhavin.sharma@siliconsignals.io> + */ + +#include <linux/crc8.h> +#include <linux/devm-helpers.h> +#include <linux/i2c.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/workqueue.h> + +#define STC3117_ADDR_MODE 0x00 +#define STC3117_ADDR_CTRL 0x01 +#define STC3117_ADDR_SOC_L 0x02 +#define STC3117_ADDR_SOC_H 0x03 +#define STC3117_ADDR_COUNTER_L 0x04 +#define STC3117_ADDR_COUNTER_H 0x05 +#define STC3117_ADDR_CURRENT_L 0x06 +#define STC3117_ADDR_CURRENT_H 0x07 +#define STC3117_ADDR_VOLTAGE_L 0x08 +#define STC3117_ADDR_VOLTAGE_H 0x09 +#define STC3117_ADDR_TEMPERATURE 0x0A +#define STC3117_ADDR_AVG_CURRENT_L 0x0B +#define STC3117_ADDR_AVG_CURRENT_H 0x0C +#define STC3117_ADDR_OCV_L 0x0D +#define STC3117_ADDR_OCV_H 0x0E +#define STC3117_ADDR_CC_CNF_L 0x0F +#define STC3117_ADDR_CC_CNF_H 0x10 +#define STC3117_ADDR_VM_CNF_L 0x11 +#define STC3117_ADDR_VM_CNF_H 0x12 +#define STC3117_ADDR_ALARM_soc 0x13 +#define STC3117_ADDR_ALARM_VOLTAGE 0x14 +#define STC3117_ADDR_ID 0x18 +#define STC3117_ADDR_CC_ADJ_L 0x1B +#define STC3117_ADDR_CC_ADJ_H 0x1C +#define STC3117_ADDR_VM_ADJ_L 0x1D +#define STC3117_ADDR_VM_ADJ_H 0x1E +#define STC3117_ADDR_RAM 0x20 +#define STC3117_ADDR_OCV_TABLE 0x30 +#define STC3117_ADDR_SOC_TABLE 0x30 + +/* Bit mask definition */ +#define STC3117_ID 0x16 +#define STC3117_MIXED_MODE 0x00 +#define STC3117_VMODE BIT(0) +#define STC3117_GG_RUN BIT(4) +#define STC3117_CC_MODE BIT(5) +#define STC3117_BATFAIL BIT(3) +#define STC3117_PORDET BIT(4) +#define STC3117_RAM_SIZE 16 +#define STC3117_OCV_TABLE_SIZE 16 +#define STC3117_RAM_TESTWORD 0x53A9 +#define STC3117_SOFT_RESET 0x11 +#define STC3117_NOMINAL_CAPACITY 2600 + +#define VOLTAGE_LSB_VALUE 9011 +#define CURRENT_LSB_VALUE 24084 +#define APP_CUTOFF_VOLTAGE 2500 +#define MAX_HRSOC 51200 +#define MAX_SOC 1000 +#define CHG_MIN_CURRENT 200 +#define CHG_END_CURRENT 20 +#define APP_MIN_CURRENT (-5) +#define BATTERY_FULL 95 +#define CRC8_POLYNOMIAL 0x07 +#define CRC8_INIT 0x00 + +DECLARE_CRC8_TABLE(stc3117_crc_table); + +enum stc3117_state { + STC3117_INIT, + STC3117_RUNNING, + STC3117_POWERDN, +}; + +/* Default ocv curve Li-ion battery */ +static const int ocv_value[16] = { + 3400, 3582, 3669, 3676, 3699, 3737, 3757, 3774, + 3804, 3844, 3936, 3984, 4028, 4131, 4246, 4320 +}; + +union stc3117_internal_ram { + u8 ram_bytes[STC3117_RAM_SIZE]; + struct { + u16 testword; /* 0-1 Bytes */ + u16 hrsoc; /* 2-3 Bytes */ + u16 cc_cnf; /* 4-5 Bytes */ + u16 vm_cnf; /* 6-7 Bytes */ + u8 soc; /* 8 Byte */ + u8 state; /* 9 Byte */ + u8 unused[5]; /* 10-14 Bytes */ + u8 crc; /* 15 Byte */ + } reg; +}; + +struct stc3117_battery_info { + int voltage_min_mv; + int voltage_max_mv; + int battery_capacity_mah; + int sense_resistor; +}; + +struct stc3117_data { + struct i2c_client *client; + struct regmap *regmap; + struct delayed_work update_work; + struct power_supply *battery; + union stc3117_internal_ram ram_data; + struct stc3117_battery_info battery_info; + + u8 soc_tab[16]; + int cc_cnf; + int vm_cnf; + int cc_adj; + int vm_adj; + int avg_current; + int avg_voltage; + int batt_current; + int voltage; + int temp; + int soc; + int ocv; + int hrsoc; + int presence; +}; + +static int stc3117_convert(int value, int factor) +{ + value = (value * factor) / 4096; + return value * 1000; +} + +static int stc3117_get_battery_data(struct stc3117_data *data) +{ + u8 reg_list[16]; + u8 data_adjust[4]; + int value, mode; + + regmap_bulk_read(data->regmap, STC3117_ADDR_MODE, + reg_list, sizeof(reg_list)); + + /* soc */ + value = (reg_list[3] << 8) + reg_list[2]; + data->hrsoc = value; + data->soc = (value * 10 + 256) / 512; + + /* current in uA*/ + value = (reg_list[7] << 8) + reg_list[6]; + data->batt_current = stc3117_convert(value, + CURRENT_LSB_VALUE / data->battery_info.sense_resistor); + + /* voltage in uV */ + value = (reg_list[9] << 8) + reg_list[8]; + data->voltage = stc3117_convert(value, VOLTAGE_LSB_VALUE); + + /* temp in 1/10 °C */ + data->temp = reg_list[10] * 10; + + /* Avg current in uA */ + value = (reg_list[12] << 8) + reg_list[11]; + regmap_read(data->regmap, STC3117_ADDR_MODE, &mode); + if (!(mode & STC3117_VMODE)) { + value = stc3117_convert(value, + CURRENT_LSB_VALUE / data->battery_info.sense_resistor); + value = value / 4; + } else { + value = stc3117_convert(value, 36 * STC3117_NOMINAL_CAPACITY); + } + data->avg_current = value; + + /* ocv in uV */ + value = (reg_list[14] << 8) + reg_list[13]; + value = stc3117_convert(value, VOLTAGE_LSB_VALUE); + value = (value + 2) / 4; + data->ocv = value; + + /* CC & VM adjustment counters */ + regmap_bulk_read(data->regmap, STC3117_ADDR_CC_ADJ_L, + data_adjust, sizeof(data_adjust)); + value = (data_adjust[1] << 8) + data_adjust[0]; + data->cc_adj = value; + + value = (data_adjust[3] << 8) + data_adjust[2]; + data->vm_adj = value; + + return 0; +} + +static int ram_write(struct stc3117_data *data) +{ + int ret; + + ret = regmap_bulk_write(data->regmap, STC3117_ADDR_RAM, + data->ram_data.ram_bytes, STC3117_RAM_SIZE); + if (ret) + return ret; + + return 0; +}; + +static int ram_read(struct stc3117_data *data) +{ + int ret; + + ret = regmap_bulk_read(data->regmap, STC3117_ADDR_RAM, + data->ram_data.ram_bytes, STC3117_RAM_SIZE); + if (ret) + return ret; + + return 0; +}; + +static int stc3117_set_para(struct stc3117_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, STC3117_ADDR_MODE, STC3117_VMODE); + + for (int i = 0; i < STC3117_OCV_TABLE_SIZE; i++) + ret |= regmap_write(data->regmap, STC3117_ADDR_OCV_TABLE + i, + ocv_value[i] * 100 / 55); + if (data->soc_tab[1] != 0) + ret |= regmap_bulk_write(data->regmap, STC3117_ADDR_SOC_TABLE, + data->soc_tab, STC3117_OCV_TABLE_SIZE); + + ret |= regmap_write(data->regmap, STC3117_ADDR_CC_CNF_H, + (data->ram_data.reg.cc_cnf >> 8) & 0xFF); + + ret |= regmap_write(data->regmap, STC3117_ADDR_CC_CNF_L, + data->ram_data.reg.cc_cnf & 0xFF); + + ret |= regmap_write(data->regmap, STC3117_ADDR_VM_CNF_H, + (data->ram_data.reg.vm_cnf >> 8) & 0xFF); + + ret |= regmap_write(data->regmap, STC3117_ADDR_VM_CNF_L, + data->ram_data.reg.vm_cnf & 0xFF); + + ret |= regmap_write(data->regmap, STC3117_ADDR_CTRL, 0x03); + + ret |= regmap_write(data->regmap, STC3117_ADDR_MODE, + STC3117_MIXED_MODE | STC3117_GG_RUN); + + return ret; +}; + +static int stc3117_init(struct stc3117_data *data) +{ + int id, ret; + int ctrl; + int ocv_m, ocv_l; + + regmap_read(data->regmap, STC3117_ADDR_ID, &id); + if (id != STC3117_ID) + return -EINVAL; + + data->cc_cnf = (data->battery_info.battery_capacity_mah * + data->battery_info.sense_resistor * 250 + 6194) / 12389; + data->vm_cnf = (data->battery_info.battery_capacity_mah + * 200 * 50 + 24444) / 48889; + + /* Battery has not been removed */ + data->presence = 1; + + /* Read RAM data */ + ret = ram_read(data); + if (ret) + return ret; + + if (data->ram_data.reg.testword != STC3117_RAM_TESTWORD || + (crc8(stc3117_crc_table, data->ram_data.ram_bytes, + STC3117_RAM_SIZE, CRC8_INIT)) != 0) { + data->ram_data.reg.testword = STC3117_RAM_TESTWORD; + data->ram_data.reg.cc_cnf = data->cc_cnf; + data->ram_data.reg.vm_cnf = data->vm_cnf; + data->ram_data.reg.crc = crc8(stc3117_crc_table, + data->ram_data.ram_bytes, + STC3117_RAM_SIZE - 1, CRC8_INIT); + + ret = regmap_read(data->regmap, STC3117_ADDR_OCV_H, &ocv_m); + + ret |= regmap_read(data->regmap, STC3117_ADDR_OCV_L, &ocv_l); + + ret |= stc3117_set_para(data); + + ret |= regmap_write(data->regmap, STC3117_ADDR_OCV_H, ocv_m); + + ret |= regmap_write(data->regmap, STC3117_ADDR_OCV_L, ocv_l); + if (ret) + return ret; + } else { + ret = regmap_read(data->regmap, STC3117_ADDR_CTRL, &ctrl); + if (ret) + return ret; + + if ((ctrl & STC3117_BATFAIL) != 0 || + (ctrl & STC3117_PORDET) != 0) { + ret = regmap_read(data->regmap, + STC3117_ADDR_OCV_H, &ocv_m); + + ret |= regmap_read(data->regmap, + STC3117_ADDR_OCV_L, &ocv_l); + + ret |= stc3117_set_para(data); + + ret |= regmap_write(data->regmap, + STC3117_ADDR_OCV_H, ocv_m); + + ret |= regmap_write(data->regmap, + STC3117_ADDR_OCV_L, ocv_l); + if (ret) + return ret; + } else { + ret = stc3117_set_para(data); + ret |= regmap_write(data->regmap, STC3117_ADDR_SOC_H, + (data->ram_data.reg.hrsoc >> 8 & 0xFF)); + ret |= regmap_write(data->regmap, STC3117_ADDR_SOC_L, + (data->ram_data.reg.hrsoc & 0xFF)); + if (ret) + return ret; + } + } + + data->ram_data.reg.state = STC3117_INIT; + data->ram_data.reg.crc = crc8(stc3117_crc_table, + data->ram_data.ram_bytes, + STC3117_RAM_SIZE - 1, CRC8_INIT); + ret = ram_write(data); + if (ret) + return ret; + + return 0; +}; + +static int stc3117_task(struct stc3117_data *data) +{ + int id, mode, ret; + int count_l, count_m; + int ocv_l, ocv_m; + + regmap_read(data->regmap, STC3117_ADDR_ID, &id); + if (id != STC3117_ID) { + data->presence = 0; + return -EINVAL; + } + + stc3117_get_battery_data(data); + + /* Read RAM data */ + ret = ram_read(data); + if (ret) + return ret; + + if (data->ram_data.reg.testword != STC3117_RAM_TESTWORD || + (crc8(stc3117_crc_table, data->ram_data.ram_bytes, + STC3117_RAM_SIZE, CRC8_INIT) != 0)) { + data->ram_data.reg.testword = STC3117_RAM_TESTWORD; + data->ram_data.reg.cc_cnf = data->cc_cnf; + data->ram_data.reg.vm_cnf = data->vm_cnf; + data->ram_data.reg.crc = crc8(stc3117_crc_table, + data->ram_data.ram_bytes, + STC3117_RAM_SIZE - 1, CRC8_INIT); + data->ram_data.reg.state = STC3117_INIT; + } + + /* check battery presence status */ + ret = regmap_read(data->regmap, STC3117_ADDR_CTRL, &mode); + if ((mode & STC3117_BATFAIL) != 0) { + data->presence = 0; + data->ram_data.reg.testword = 0; + data->ram_data.reg.state = STC3117_INIT; + ret = ram_write(data); + ret |= regmap_write(data->regmap, STC3117_ADDR_CTRL, STC3117_PORDET); + if (ret) + return ret; + } + + data->presence = 1; + + ret = regmap_read(data->regmap, STC3117_ADDR_MODE, &mode); + if (ret) + return ret; + if ((mode & STC3117_GG_RUN) == 0) { + if (data->ram_data.reg.state > STC3117_INIT) { + ret = stc3117_set_para(data); + + ret |= regmap_write(data->regmap, STC3117_ADDR_SOC_H, + (data->ram_data.reg.hrsoc >> 8 & 0xFF)); + ret |= regmap_write(data->regmap, STC3117_ADDR_SOC_L, + (data->ram_data.reg.hrsoc & 0xFF)); + if (ret) + return ret; + } else { + ret = regmap_read(data->regmap, STC3117_ADDR_OCV_H, &ocv_m); + + ret |= regmap_read(data->regmap, STC3117_ADDR_OCV_L, &ocv_l); + + ret |= stc3117_set_para(data); + + ret |= regmap_write(data->regmap, STC3117_ADDR_OCV_H, ocv_m); + + ret |= regmap_write(data->regmap, STC3117_ADDR_OCV_L, ocv_l); + if (ret) + return ret; + } + data->ram_data.reg.state = STC3117_INIT; + } + + regmap_read(data->regmap, STC3117_ADDR_COUNTER_L, &count_l); + regmap_read(data->regmap, STC3117_ADDR_COUNTER_H, &count_m); + + count_m = (count_m << 8) + count_l; + + /* INIT state, wait for batt_current & temperature value available: */ + if (data->ram_data.reg.state == STC3117_INIT && count_m > 4) { + data->avg_voltage = data->voltage; + data->avg_current = data->batt_current; + data->ram_data.reg.state = STC3117_RUNNING; + } + + if (data->ram_data.reg.state != STC3117_RUNNING) { + data->batt_current = -ENODATA; + data->temp = -ENODATA; + } else { + if (data->voltage < APP_CUTOFF_VOLTAGE) + data->soc = -ENODATA; + + if (mode & STC3117_VMODE) { + data->avg_current = -ENODATA; + data->batt_current = -ENODATA; + } + } + + data->ram_data.reg.hrsoc = data->hrsoc; + data->ram_data.reg.soc = (data->soc + 5) / 10; + data->ram_data.reg.crc = crc8(stc3117_crc_table, + data->ram_data.ram_bytes, + STC3117_RAM_SIZE - 1, CRC8_INIT); + + ret = ram_write(data); + if (ret) + return ret; + return 0; +}; + +static void fuel_gauge_update_work(struct work_struct *work) +{ + struct stc3117_data *data = + container_of(work, struct stc3117_data, update_work.work); + + stc3117_task(data); + + /* Schedule the work to run again in 2 seconds */ + schedule_delayed_work(&data->update_work, msecs_to_jiffies(2000)); +} + +static int stc3117_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct stc3117_data *data = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (data->soc > BATTERY_FULL) + val->intval = POWER_SUPPLY_STATUS_FULL; + else if (data->batt_current < 0) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (data->batt_current > 0) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = data->voltage; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = data->batt_current; + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = data->ocv; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = data->avg_current; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = data->soc; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = data->temp; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = data->presence; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property stc3117_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_PRESENT, +}; + +static const struct power_supply_desc stc3117_battery_desc = { + .name = "stc3117-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = stc3117_get_property, + .properties = stc3117_battery_props, + .num_properties = ARRAY_SIZE(stc3117_battery_props), +}; + +static const struct regmap_config stc3117_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int stc3117_probe(struct i2c_client *client) +{ + struct stc3117_data *data; + struct power_supply_config psy_cfg = {}; + struct power_supply_battery_info *info; + int ret; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + data->regmap = devm_regmap_init_i2c(client, &stc3117_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + psy_cfg.drv_data = data; + psy_cfg.fwnode = dev_fwnode(&client->dev); + + crc8_populate_msb(stc3117_crc_table, CRC8_POLYNOMIAL); + + data->battery = devm_power_supply_register(&client->dev, + &stc3117_battery_desc, &psy_cfg); + if (IS_ERR(data->battery)) + return dev_err_probe(&client->dev, PTR_ERR(data->battery), + "failed to register battery\n"); + + ret = device_property_read_u32(&client->dev, "shunt-resistor-micro-ohms", + &data->battery_info.sense_resistor); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to get shunt-resistor-micro-ohms\n"); + data->battery_info.sense_resistor = data->battery_info.sense_resistor / 1000; + + ret = power_supply_get_battery_info(data->battery, &info); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to get battery information\n"); + + data->battery_info.battery_capacity_mah = info->charge_full_design_uah / 1000; + data->battery_info.voltage_min_mv = info->voltage_min_design_uv / 1000; + data->battery_info.voltage_max_mv = info->voltage_max_design_uv / 1000; + + ret = stc3117_init(data); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to initialize of stc3117\n"); + + ret = devm_delayed_work_autocancel(&client->dev, &data->update_work, + fuel_gauge_update_work); + if (ret) + return ret; + + schedule_delayed_work(&data->update_work, 0); + + return 0; +} + +static const struct i2c_device_id stc3117_id[] = { + { "stc3117", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, stc3117_id); + +static const struct of_device_id stc3117_of_match[] = { + { .compatible = "st,stc3117" }, + { } +}; +MODULE_DEVICE_TABLE(of, stc3117_of_match); + +static struct i2c_driver stc3117_i2c_driver = { + .driver = { + .name = "stc3117_i2c_driver", + .of_match_table = stc3117_of_match, + }, + .probe = stc3117_probe, + .id_table = stc3117_id, +}; + +module_i2c_driver(stc3117_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Hardevsinh Palaniya <hardevsinh.palaniya@siliconsignals.io>"); +MODULE_AUTHOR("Bhavin Sharma <bhavin.sharma@siliconsignals.io>"); +MODULE_DESCRIPTION("STC3117 Fuel Gauge Driver"); diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c index 540707882bb0..c759add4df49 100644 --- a/drivers/power/supply/surface_battery.c +++ b/drivers/power/supply/surface_battery.c @@ -6,7 +6,7 @@ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> */ -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/jiffies.h> #include <linux/kernel.h> #include <linux/module.h> @@ -667,7 +667,7 @@ out: static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct spwr_battery_device *bat = power_supply_get_drvdata(psy); int status; @@ -681,7 +681,7 @@ static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, cha static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct power_supply *psy = dev_get_drvdata(dev); + struct power_supply *psy = dev_to_psy(dev); struct spwr_battery_device *bat = power_supply_get_drvdata(psy); unsigned long value; int status; @@ -722,7 +722,7 @@ static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_devic struct ssam_event_registry registry, const char *name) { mutex_init(&bat->lock); - strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1); + strscpy(bat->name, name, sizeof(bat->name)); bat->sdev = sdev; @@ -852,8 +852,8 @@ static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { }; static const struct ssam_device_id surface_battery_match[] = { - { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, - { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, + { SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, + { SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, { }, }; MODULE_DEVICE_TABLE(ssam, surface_battery_match); diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c index 59182d55742d..90b823848c99 100644 --- a/drivers/power/supply/surface_charger.c +++ b/drivers/power/supply/surface_charger.c @@ -6,7 +6,7 @@ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> */ -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> @@ -175,7 +175,7 @@ static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev, struct ssam_event_registry registry, const char *name) { mutex_init(&ac->lock); - strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1); + strscpy(ac->name, name, sizeof(ac->name)); ac->sdev = sdev; @@ -260,7 +260,7 @@ static const struct spwr_psy_properties spwr_psy_props_adp1 = { }; static const struct ssam_device_id surface_ac_match[] = { - { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, + { SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, { }, }; MODULE_DEVICE_TABLE(ssam, surface_ac_match); diff --git a/drivers/power/supply/test_power.c b/drivers/power/supply/test_power.c index 5f510ddc946d..2c0e9ad820c0 100644 --- a/drivers/power/supply/test_power.c +++ b/drivers/power/supply/test_power.c @@ -35,6 +35,11 @@ static int battery_capacity = 50; static int battery_voltage = 3300; 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; @@ -84,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; @@ -123,6 +128,12 @@ static int test_power_get_battery_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = battery_current; break; + 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__); @@ -131,6 +142,39 @@ static int test_power_get_battery_property(struct power_supply *psy, return 0; } +static int test_power_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR || psp == POWER_SUPPLY_PROP_CHARGE_TYPES; +} + +static int test_power_set_battery_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + if (val->intval < 0 || + val->intval >= BITS_PER_TYPE(typeof(psy->desc->charge_behaviours)) || + !(BIT(val->intval) & psy->desc->charge_behaviours)) { + return -EINVAL; + } + 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; + } + return 0; +} + static enum power_supply_property test_power_ac_props[] = { POWER_SUPPLY_PROP_ONLINE, }; @@ -156,6 +200,8 @@ static enum power_supply_property test_power_battery_props[] = { POWER_SUPPLY_PROP_VOLTAGE_NOW, 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[] = { @@ -178,6 +224,14 @@ static const struct power_supply_desc test_power_desc[] = { .properties = test_power_battery_props, .num_properties = ARRAY_SIZE(test_power_battery_props), .get_property = test_power_get_battery_property, + .set_property = test_power_set_battery_property, + .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", @@ -202,6 +256,91 @@ 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, +}; + +static int test_power_battery_extget_property(struct power_supply *psy, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property psp, + 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; + case POWER_SUPPLY_PROP_TEMP_MAX: + val->intval = test_power_battery_exttemp_max; + break; + default: + return -EINVAL; + } + return 0; +} + +static int test_power_battery_extset_property(struct power_supply *psy, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: + test_power_battery_extmanufacture_year = val->intval; + break; + case POWER_SUPPLY_PROP_TEMP_MAX: + test_power_battery_exttemp_max = val->intval; + break; + default: + return -EINVAL; + } + return 0; +} + +static int test_power_battery_extproperty_is_writeable(struct power_supply *psy, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property psp) +{ + return true; +} + +static const struct power_supply_ext test_power_battery_ext = { + .name = "test_power", + .properties = test_power_battery_extprops, + .num_properties = ARRAY_SIZE(test_power_battery_extprops), + .get_property = test_power_battery_extget_property, + .set_property = test_power_battery_extset_property, + .property_is_writeable = test_power_battery_extproperty_is_writeable, +}; + +static void test_power_configure_battery_extension(bool enable) +{ + struct power_supply *psy; + + psy = test_power_supplies[TEST_BATTERY]; + + if (enable) { + if (power_supply_register_extension(psy, &test_power_battery_ext, &psy->dev, + NULL)) { + pr_err("registering battery extension failed\n"); + return; + } + } else { + power_supply_unregister_extension(psy, &test_power_battery_ext); + } + + battery_extension = enable; +} + static int __init test_power_init(void) { int i; @@ -222,6 +361,8 @@ static int __init test_power_init(void) } } + test_power_configure_battery_extension(true); + module_initialized = true; return 0; failed: @@ -306,8 +447,7 @@ static int map_get_value(struct battery_property_map *map, const char *key, char buf[MAX_KEYLENGTH]; int cr; - strncpy(buf, key, MAX_KEYLENGTH); - buf[MAX_KEYLENGTH-1] = '\0'; + strscpy(buf, key, MAX_KEYLENGTH); cr = strnlen(buf, MAX_KEYLENGTH) - 1; if (cr < 0) @@ -489,6 +629,26 @@ static int param_set_battery_current(const char *key, #define param_get_battery_current param_get_int +static int param_set_battery_extension(const char *key, + const struct kernel_param *kp) +{ + bool prev_battery_extension; + int ret; + + prev_battery_extension = battery_extension; + + ret = param_set_bool(key, kp); + if (ret) + return ret; + + if (prev_battery_extension != battery_extension) + test_power_configure_battery_extension(battery_extension); + + return 0; +} + +#define param_get_battery_extension param_get_bool + static const struct kernel_param_ops param_ops_ac_online = { .set = param_set_ac_online, .get = param_get_ac_online, @@ -539,6 +699,11 @@ static const struct kernel_param_ops param_ops_battery_current = { .get = param_get_battery_current, }; +static const struct kernel_param_ops param_ops_battery_extension = { + .set = param_set_battery_extension, + .get = param_get_battery_extension, +}; + #define param_check_ac_online(name, p) __param_check(name, p, void); #define param_check_usb_online(name, p) __param_check(name, p, void); #define param_check_battery_status(name, p) __param_check(name, p, void); @@ -549,6 +714,7 @@ static const struct kernel_param_ops param_ops_battery_current = { #define param_check_battery_voltage(name, p) __param_check(name, p, void); #define param_check_battery_charge_counter(name, p) __param_check(name, p, void); #define param_check_battery_current(name, p) __param_check(name, p, void); +#define param_check_battery_extension(name, p) __param_check(name, p, void); module_param(ac_online, ac_online, 0644); @@ -586,6 +752,9 @@ MODULE_PARM_DESC(battery_charge_counter, module_param(battery_current, battery_current, 0644); MODULE_PARM_DESC(battery_current, "battery current (milliampere)"); +module_param(battery_extension, battery_extension, 0644); +MODULE_PARM_DESC(battery_extension, "battery extension"); + MODULE_DESCRIPTION("Power supply driver for testing"); MODULE_AUTHOR("Anton Vorontsov <cbouatmailru@gmail.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/tosa_battery.c b/drivers/power/supply/tosa_battery.c deleted file mode 100644 index 73d4aca4c386..000000000000 --- a/drivers/power/supply/tosa_battery.c +++ /dev/null @@ -1,512 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Battery and Power Management code for the Sharp SL-6000x - * - * Copyright (c) 2005 Dirk Opfer - * Copyright (c) 2008 Dmitry Baryshkov - */ -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/power_supply.h> -#include <linux/wm97xx.h> -#include <linux/delay.h> -#include <linux/spinlock.h> -#include <linux/interrupt.h> -#include <linux/gpio/consumer.h> - -#include <asm/mach-types.h> - -static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ -static struct work_struct bat_work; - -struct tosa_bat { - int status; - struct power_supply *psy; - int full_chrg; - - struct mutex work_lock; /* protects data */ - - bool (*is_present)(struct tosa_bat *bat); - struct gpio_desc *gpiod_full; - struct gpio_desc *gpiod_charge_off; - - int technology; - - struct gpio_desc *gpiod_bat; - int adc_bat; - int adc_bat_divider; - int bat_max; - int bat_min; - - struct gpio_desc *gpiod_temp; - int adc_temp; - int adc_temp_divider; -}; - -static struct gpio_desc *jacket_detect; -static struct tosa_bat tosa_bat_main; -static struct tosa_bat tosa_bat_jacket; - -static unsigned long tosa_read_bat(struct tosa_bat *bat) -{ - unsigned long value = 0; - - if (!bat->gpiod_bat || bat->adc_bat < 0) - return 0; - - mutex_lock(&bat_lock); - gpiod_set_value(bat->gpiod_bat, 1); - msleep(5); - value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent), - bat->adc_bat); - gpiod_set_value(bat->gpiod_bat, 0); - mutex_unlock(&bat_lock); - - value = value * 1000000 / bat->adc_bat_divider; - - return value; -} - -static unsigned long tosa_read_temp(struct tosa_bat *bat) -{ - unsigned long value = 0; - - if (!bat->gpiod_temp || bat->adc_temp < 0) - return 0; - - mutex_lock(&bat_lock); - gpiod_set_value(bat->gpiod_temp, 1); - msleep(5); - value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent), - bat->adc_temp); - gpiod_set_value(bat->gpiod_temp, 0); - mutex_unlock(&bat_lock); - - value = value * 10000 / bat->adc_temp_divider; - - return value; -} - -static int tosa_bat_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret = 0; - struct tosa_bat *bat = power_supply_get_drvdata(psy); - - if (bat->is_present && !bat->is_present(bat) - && psp != POWER_SUPPLY_PROP_PRESENT) { - return -ENODEV; - } - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = bat->status; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = bat->technology; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = tosa_read_bat(bat); - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX: - if (bat->full_chrg == -1) - val->intval = bat->bat_max; - else - val->intval = bat->full_chrg; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = bat->bat_max; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = bat->bat_min; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = tosa_read_temp(bat); - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = bat->is_present ? bat->is_present(bat) : 1; - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static bool tosa_jacket_bat_is_present(struct tosa_bat *bat) -{ - return gpiod_get_value(jacket_detect) == 0; -} - -static void tosa_bat_external_power_changed(struct power_supply *psy) -{ - schedule_work(&bat_work); -} - -static irqreturn_t tosa_bat_gpio_isr(int irq, void *data) -{ - pr_info("tosa_bat_gpio irq\n"); - schedule_work(&bat_work); - return IRQ_HANDLED; -} - -static void tosa_bat_update(struct tosa_bat *bat) -{ - int old; - struct power_supply *psy = bat->psy; - - mutex_lock(&bat->work_lock); - - old = bat->status; - - if (bat->is_present && !bat->is_present(bat)) { - printk(KERN_NOTICE "%s not present\n", psy->desc->name); - bat->status = POWER_SUPPLY_STATUS_UNKNOWN; - bat->full_chrg = -1; - } else if (power_supply_am_i_supplied(psy)) { - if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) { - gpiod_set_value(bat->gpiod_charge_off, 0); - mdelay(15); - } - - if (gpiod_get_value(bat->gpiod_full)) { - if (old == POWER_SUPPLY_STATUS_CHARGING || - bat->full_chrg == -1) - bat->full_chrg = tosa_read_bat(bat); - - gpiod_set_value(bat->gpiod_charge_off, 1); - bat->status = POWER_SUPPLY_STATUS_FULL; - } else { - gpiod_set_value(bat->gpiod_charge_off, 0); - bat->status = POWER_SUPPLY_STATUS_CHARGING; - } - } else { - gpiod_set_value(bat->gpiod_charge_off, 1); - bat->status = POWER_SUPPLY_STATUS_DISCHARGING; - } - - if (old != bat->status) - power_supply_changed(psy); - - mutex_unlock(&bat->work_lock); -} - -static void tosa_bat_work(struct work_struct *work) -{ - tosa_bat_update(&tosa_bat_main); - tosa_bat_update(&tosa_bat_jacket); -} - - -static enum power_supply_property tosa_bat_main_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_MAX, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_PRESENT, -}; - -static enum power_supply_property tosa_bat_bu_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_PRESENT, -}; - -static const struct power_supply_desc tosa_bat_main_desc = { - .name = "main-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = tosa_bat_main_props, - .num_properties = ARRAY_SIZE(tosa_bat_main_props), - .get_property = tosa_bat_get_property, - .external_power_changed = tosa_bat_external_power_changed, - .use_for_apm = 1, -}; - -static const struct power_supply_desc tosa_bat_jacket_desc = { - .name = "jacket-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = tosa_bat_main_props, - .num_properties = ARRAY_SIZE(tosa_bat_main_props), - .get_property = tosa_bat_get_property, - .external_power_changed = tosa_bat_external_power_changed, -}; - -static const struct power_supply_desc tosa_bat_bu_desc = { - .name = "backup-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = tosa_bat_bu_props, - .num_properties = ARRAY_SIZE(tosa_bat_bu_props), - .get_property = tosa_bat_get_property, - .external_power_changed = tosa_bat_external_power_changed, -}; - -static struct tosa_bat tosa_bat_main = { - .status = POWER_SUPPLY_STATUS_DISCHARGING, - .full_chrg = -1, - .psy = NULL, - - .gpiod_full = NULL, - .gpiod_charge_off = NULL, - - .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, - - .gpiod_bat = NULL, - .adc_bat = WM97XX_AUX_ID3, - .adc_bat_divider = 414, - .bat_max = 4310000, - .bat_min = 1551 * 1000000 / 414, - - .gpiod_temp = NULL, - .adc_temp = WM97XX_AUX_ID2, - .adc_temp_divider = 10000, -}; - -static struct tosa_bat tosa_bat_jacket = { - .status = POWER_SUPPLY_STATUS_DISCHARGING, - .full_chrg = -1, - .psy = NULL, - - .is_present = tosa_jacket_bat_is_present, - .gpiod_full = NULL, - .gpiod_charge_off = NULL, - - .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, - - .gpiod_bat = NULL, - .adc_bat = WM97XX_AUX_ID3, - .adc_bat_divider = 414, - .bat_max = 4310000, - .bat_min = 1551 * 1000000 / 414, - - .gpiod_temp = NULL, - .adc_temp = WM97XX_AUX_ID2, - .adc_temp_divider = 10000, -}; - -static struct tosa_bat tosa_bat_bu = { - .status = POWER_SUPPLY_STATUS_UNKNOWN, - .full_chrg = -1, - .psy = NULL, - - .gpiod_full = NULL, - .gpiod_charge_off = NULL, - - .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, - - .gpiod_bat = NULL, - .adc_bat = WM97XX_AUX_ID4, - .adc_bat_divider = 1266, - - .gpiod_temp = NULL, - .adc_temp = -1, - .adc_temp_divider = -1, -}; - -#ifdef CONFIG_PM -static int tosa_bat_suspend(struct platform_device *dev, pm_message_t state) -{ - /* flush all pending status updates */ - flush_work(&bat_work); - return 0; -} - -static int tosa_bat_resume(struct platform_device *dev) -{ - /* things may have changed while we were away */ - schedule_work(&bat_work); - return 0; -} -#else -#define tosa_bat_suspend NULL -#define tosa_bat_resume NULL -#endif - -static int tosa_bat_probe(struct platform_device *pdev) -{ - int ret; - struct power_supply_config main_psy_cfg = {}, - jacket_psy_cfg = {}, - bu_psy_cfg = {}; - struct device *dev = &pdev->dev; - struct gpio_desc *dummy; - - if (!machine_is_tosa()) - return -ENODEV; - - /* Main charging control GPIOs */ - tosa_bat_main.gpiod_charge_off = devm_gpiod_get(dev, "main charge off", GPIOD_OUT_HIGH); - if (IS_ERR(tosa_bat_main.gpiod_charge_off)) - return dev_err_probe(dev, PTR_ERR(tosa_bat_main.gpiod_charge_off), - "no main charger GPIO\n"); - tosa_bat_jacket.gpiod_charge_off = devm_gpiod_get(dev, "jacket charge off", GPIOD_OUT_HIGH); - if (IS_ERR(tosa_bat_jacket.gpiod_charge_off)) - return dev_err_probe(dev, PTR_ERR(tosa_bat_jacket.gpiod_charge_off), - "no jacket charger GPIO\n"); - - /* Per-battery output check (routes battery voltage to ADC) */ - tosa_bat_main.gpiod_bat = devm_gpiod_get(dev, "main battery", GPIOD_OUT_LOW); - if (IS_ERR(tosa_bat_main.gpiod_bat)) - return dev_err_probe(dev, PTR_ERR(tosa_bat_main.gpiod_bat), - "no main battery GPIO\n"); - tosa_bat_jacket.gpiod_bat = devm_gpiod_get(dev, "jacket battery", GPIOD_OUT_LOW); - if (IS_ERR(tosa_bat_jacket.gpiod_bat)) - return dev_err_probe(dev, PTR_ERR(tosa_bat_jacket.gpiod_bat), - "no jacket battery GPIO\n"); - tosa_bat_bu.gpiod_bat = devm_gpiod_get(dev, "backup battery", GPIOD_OUT_LOW); - if (IS_ERR(tosa_bat_bu.gpiod_bat)) - return dev_err_probe(dev, PTR_ERR(tosa_bat_bu.gpiod_bat), - "no backup battery GPIO\n"); - - /* Battery full detect GPIOs (using PXA SoC GPIOs) */ - tosa_bat_main.gpiod_full = devm_gpiod_get(dev, "main battery full", GPIOD_IN); - if (IS_ERR(tosa_bat_main.gpiod_full)) - return dev_err_probe(dev, PTR_ERR(tosa_bat_main.gpiod_full), - "no main battery full GPIO\n"); - tosa_bat_jacket.gpiod_full = devm_gpiod_get(dev, "jacket battery full", GPIOD_IN); - if (IS_ERR(tosa_bat_jacket.gpiod_full)) - return dev_err_probe(dev, PTR_ERR(tosa_bat_jacket.gpiod_full), - "no jacket battery full GPIO\n"); - - /* Battery temperature GPIOs (routes thermistor voltage to ADC) */ - tosa_bat_main.gpiod_temp = devm_gpiod_get(dev, "main battery temp", GPIOD_OUT_LOW); - if (IS_ERR(tosa_bat_main.gpiod_temp)) - return dev_err_probe(dev, PTR_ERR(tosa_bat_main.gpiod_temp), - "no main battery temp GPIO\n"); - tosa_bat_jacket.gpiod_temp = devm_gpiod_get(dev, "jacket battery temp", GPIOD_OUT_LOW); - if (IS_ERR(tosa_bat_jacket.gpiod_temp)) - return dev_err_probe(dev, PTR_ERR(tosa_bat_jacket.gpiod_temp), - "no jacket battery temp GPIO\n"); - - /* Jacket detect GPIO */ - jacket_detect = devm_gpiod_get(dev, "jacket detect", GPIOD_IN); - if (IS_ERR(jacket_detect)) - return dev_err_probe(dev, PTR_ERR(jacket_detect), - "no jacket detect GPIO\n"); - - /* Battery low indication GPIOs (not used, we just request them) */ - dummy = devm_gpiod_get(dev, "main battery low", GPIOD_IN); - if (IS_ERR(dummy)) - return dev_err_probe(dev, PTR_ERR(dummy), - "no main battery low GPIO\n"); - dummy = devm_gpiod_get(dev, "jacket battery low", GPIOD_IN); - if (IS_ERR(dummy)) - return dev_err_probe(dev, PTR_ERR(dummy), - "no jacket battery low GPIO\n"); - - /* Battery switch GPIO (not used just requested) */ - dummy = devm_gpiod_get(dev, "battery switch", GPIOD_OUT_LOW); - if (IS_ERR(dummy)) - return dev_err_probe(dev, PTR_ERR(dummy), - "no battery switch GPIO\n"); - - mutex_init(&tosa_bat_main.work_lock); - mutex_init(&tosa_bat_jacket.work_lock); - - INIT_WORK(&bat_work, tosa_bat_work); - - main_psy_cfg.drv_data = &tosa_bat_main; - tosa_bat_main.psy = power_supply_register(dev, - &tosa_bat_main_desc, - &main_psy_cfg); - if (IS_ERR(tosa_bat_main.psy)) { - ret = PTR_ERR(tosa_bat_main.psy); - goto err_psy_reg_main; - } - - jacket_psy_cfg.drv_data = &tosa_bat_jacket; - tosa_bat_jacket.psy = power_supply_register(dev, - &tosa_bat_jacket_desc, - &jacket_psy_cfg); - if (IS_ERR(tosa_bat_jacket.psy)) { - ret = PTR_ERR(tosa_bat_jacket.psy); - goto err_psy_reg_jacket; - } - - bu_psy_cfg.drv_data = &tosa_bat_bu; - tosa_bat_bu.psy = power_supply_register(dev, &tosa_bat_bu_desc, - &bu_psy_cfg); - if (IS_ERR(tosa_bat_bu.psy)) { - ret = PTR_ERR(tosa_bat_bu.psy); - goto err_psy_reg_bu; - } - - ret = request_irq(gpiod_to_irq(tosa_bat_main.gpiod_full), - tosa_bat_gpio_isr, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "main full", &tosa_bat_main); - if (ret) - goto err_req_main; - - ret = request_irq(gpiod_to_irq(tosa_bat_jacket.gpiod_full), - tosa_bat_gpio_isr, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "jacket full", &tosa_bat_jacket); - if (ret) - goto err_req_jacket; - - ret = request_irq(gpiod_to_irq(jacket_detect), - tosa_bat_gpio_isr, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "jacket detect", &tosa_bat_jacket); - if (!ret) { - schedule_work(&bat_work); - return 0; - } - - free_irq(gpiod_to_irq(tosa_bat_jacket.gpiod_full), &tosa_bat_jacket); -err_req_jacket: - free_irq(gpiod_to_irq(tosa_bat_main.gpiod_full), &tosa_bat_main); -err_req_main: - power_supply_unregister(tosa_bat_bu.psy); -err_psy_reg_bu: - power_supply_unregister(tosa_bat_jacket.psy); -err_psy_reg_jacket: - power_supply_unregister(tosa_bat_main.psy); -err_psy_reg_main: - - /* see comment in tosa_bat_remove */ - cancel_work_sync(&bat_work); - - return ret; -} - -static int tosa_bat_remove(struct platform_device *dev) -{ - free_irq(gpiod_to_irq(jacket_detect), &tosa_bat_jacket); - free_irq(gpiod_to_irq(tosa_bat_jacket.gpiod_full), &tosa_bat_jacket); - free_irq(gpiod_to_irq(tosa_bat_main.gpiod_full), &tosa_bat_main); - - power_supply_unregister(tosa_bat_bu.psy); - power_supply_unregister(tosa_bat_jacket.psy); - power_supply_unregister(tosa_bat_main.psy); - - /* - * Now cancel the bat_work. We won't get any more schedules, - * since all sources (isr and external_power_changed) are - * unregistered now. - */ - cancel_work_sync(&bat_work); - return 0; -} - -static struct platform_driver tosa_bat_driver = { - .driver.name = "wm97xx-battery", - .driver.owner = THIS_MODULE, - .probe = tosa_bat_probe, - .remove = tosa_bat_remove, - .suspend = tosa_bat_suspend, - .resume = tosa_bat_resume, -}; - -module_platform_driver(tosa_bat_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Dmitry Baryshkov"); -MODULE_DESCRIPTION("Tosa battery driver"); -MODULE_ALIAS("platform:wm97xx-battery"); diff --git a/drivers/power/supply/tps65090-charger.c b/drivers/power/supply/tps65090-charger.c index 0990b2fa6cd8..d010f013af8c 100644 --- a/drivers/power/supply/tps65090-charger.c +++ b/drivers/power/supply/tps65090-charger.c @@ -13,7 +13,7 @@ #include <linux/kernel.h> #include <linux/kthread.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/slab.h> @@ -259,10 +259,10 @@ 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 = power_supply_register(&pdev->dev, &tps65090_charger_desc, + cdata->ac = devm_power_supply_register(&pdev->dev, &tps65090_charger_desc, &psy_cfg); if (IS_ERR(cdata->ac)) { dev_err(&pdev->dev, "failed: power supply register\n"); @@ -277,7 +277,7 @@ static int tps65090_charger_probe(struct platform_device *pdev) ret = tps65090_config_charger(cdata); if (ret < 0) { dev_err(&pdev->dev, "charger config failed, err %d\n", ret); - goto fail_unregister_supply; + return ret; } /* Check for charger presence */ @@ -286,14 +286,14 @@ static int tps65090_charger_probe(struct platform_device *pdev) if (ret < 0) { dev_err(cdata->dev, "%s(): Error in reading reg 0x%x", __func__, TPS65090_REG_CG_STATUS1); - goto fail_unregister_supply; + return ret; } if (status1 != 0) { ret = tps65090_enable_charging(cdata); if (ret < 0) { dev_err(cdata->dev, "error enabling charger\n"); - goto fail_unregister_supply; + return ret; } cdata->ac_online = 1; power_supply_changed(cdata->ac); @@ -306,7 +306,7 @@ static int tps65090_charger_probe(struct platform_device *pdev) dev_err(cdata->dev, "Unable to register irq %d err %d\n", irq, ret); - goto fail_unregister_supply; + return ret; } } else { cdata->poll_task = kthread_run(tps65090_charger_poll_task, @@ -316,27 +316,19 @@ static int tps65090_charger_probe(struct platform_device *pdev) ret = PTR_ERR(cdata->poll_task); dev_err(cdata->dev, "Unable to run kthread err %d\n", ret); - goto fail_unregister_supply; + return ret; } } return 0; - -fail_unregister_supply: - power_supply_unregister(cdata->ac); - - return ret; } -static int tps65090_charger_remove(struct platform_device *pdev) +static void tps65090_charger_remove(struct platform_device *pdev) { struct tps65090_charger *cdata = platform_get_drvdata(pdev); if (cdata->irq == -ENXIO) kthread_stop(cdata->poll_task); - power_supply_unregister(cdata->ac); - - return 0; } static const struct of_device_id of_tps65090_charger_match[] = { diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c index a4bc9f2a10bc..6af17ce0b204 100644 --- a/drivers/power/supply/tps65217_charger.c +++ b/drivers/power/supply/tps65217_charger.c @@ -17,7 +17,6 @@ #include <linux/slab.h> #include <linux/err.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/power_supply.h> #include <linux/mfd/core.h> @@ -199,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, @@ -238,7 +237,7 @@ static int tps65217_charger_probe(struct platform_device *pdev) for (i = 0; i < NUM_CHARGER_IRQS; i++) { ret = devm_request_threaded_irq(&pdev->dev, irq[i], NULL, tps65217_charger_irq, - IRQF_ONESHOT, "tps65217-charger", + IRQF_SHARED, "tps65217-charger", charger); if (ret) { dev_err(charger->dev, @@ -254,14 +253,12 @@ static int tps65217_charger_probe(struct platform_device *pdev) return 0; } -static int tps65217_charger_remove(struct platform_device *pdev) +static void tps65217_charger_remove(struct platform_device *pdev) { struct tps65217_charger *charger = platform_get_drvdata(pdev); if (charger->poll_task) kthread_stop(charger->poll_task); - - return 0; } static const struct of_device_id tps65217_charger_match_table[] = { @@ -272,7 +269,7 @@ MODULE_DEVICE_TABLE(of, tps65217_charger_match_table); static struct platform_driver tps65217_charger_driver = { .probe = tps65217_charger_probe, - .remove = tps65217_charger_remove, + .remove = tps65217_charger_remove, .driver = { .name = "tps65217-charger", .of_match_table = of_match_ptr(tps65217_charger_match_table), diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index 1bc49b2e12e8..04216b2bfb6c 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -12,6 +12,7 @@ #include <linux/module.h> #include <linux/slab.h> #include <linux/err.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/mfd/twl.h> @@ -362,7 +363,7 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci) if (status < 0) return status; cur_reg |= oldreg << 8; - if (reg != oldreg) { + if (reg != cur_reg) { /* disable write protection for one write access for * BCIIREF */ status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7, @@ -511,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; } @@ -726,11 +726,9 @@ twl4030_bci_mode_show(struct device *dev, for (i = 0; i < ARRAY_SIZE(modes); i++) if (mode == i) - len += scnprintf(buf+len, PAGE_SIZE-len, - "[%s] ", modes[i]); + len += sysfs_emit_at(buf, len, "[%s] ", modes[i]); else - len += scnprintf(buf+len, PAGE_SIZE-len, - "%s ", modes[i]); + len += sysfs_emit_at(buf, len, "%s ", modes[i]); buf[len-1] = '\n'; return len; } @@ -1109,7 +1107,7 @@ static int twl4030_bci_probe(struct platform_device *pdev) return 0; } -static int twl4030_bci_remove(struct platform_device *pdev) +static void twl4030_bci_remove(struct platform_device *pdev) { struct twl4030_bci *bci = platform_get_drvdata(pdev); @@ -1124,11 +1122,9 @@ static int twl4030_bci_remove(struct platform_device *pdev) TWL4030_INTERRUPTS_BCIIMR1A); twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, TWL4030_INTERRUPTS_BCIIMR2A); - - return 0; } -static const struct of_device_id twl_bci_of_match[] = { +static const struct of_device_id twl_bci_of_match[] __maybe_unused = { {.compatible = "ti,twl4030-bci", }, { } }; @@ -1136,7 +1132,7 @@ MODULE_DEVICE_TABLE(of, twl_bci_of_match); static struct platform_driver twl4030_bci_driver = { .probe = twl4030_bci_probe, - .remove = twl4030_bci_remove, + .remove = twl4030_bci_remove, .driver = { .name = "twl4030_bci", .of_match_table = of_match_ptr(twl_bci_of_match), diff --git a/drivers/power/supply/twl4030_madc_battery.c b/drivers/power/supply/twl4030_madc_battery.c index 48649dcfe3a9..3935162e350b 100644 --- a/drivers/power/supply/twl4030_madc_battery.c +++ b/drivers/power/supply/twl4030_madc_battery.c @@ -168,19 +168,13 @@ static int twl4030_madc_bat_get_property(struct power_supply *psy, return 0; } -static void twl4030_madc_bat_ext_changed(struct power_supply *psy) -{ - power_supply_changed(psy); -} - static const struct power_supply_desc twl4030_madc_bat_desc = { .name = "twl4030_battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = twl4030_madc_bat_props, .num_properties = ARRAY_SIZE(twl4030_madc_bat_props), .get_property = twl4030_madc_bat_get_property, - .external_power_changed = twl4030_madc_bat_ext_changed, - + .external_power_changed = power_supply_changed, }; static int twl4030_cmp(const void *a, const void *b) @@ -194,30 +188,23 @@ static int twl4030_madc_battery_probe(struct platform_device *pdev) struct twl4030_madc_battery *twl4030_madc_bat; struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data; struct power_supply_config psy_cfg = {}; - int ret = 0; twl4030_madc_bat = devm_kzalloc(&pdev->dev, sizeof(*twl4030_madc_bat), GFP_KERNEL); if (!twl4030_madc_bat) return -ENOMEM; - twl4030_madc_bat->channel_temp = iio_channel_get(&pdev->dev, "temp"); - if (IS_ERR(twl4030_madc_bat->channel_temp)) { - ret = PTR_ERR(twl4030_madc_bat->channel_temp); - goto err; - } + twl4030_madc_bat->channel_temp = devm_iio_channel_get(&pdev->dev, "temp"); + if (IS_ERR(twl4030_madc_bat->channel_temp)) + return PTR_ERR(twl4030_madc_bat->channel_temp); - twl4030_madc_bat->channel_ichg = iio_channel_get(&pdev->dev, "ichg"); - if (IS_ERR(twl4030_madc_bat->channel_ichg)) { - ret = PTR_ERR(twl4030_madc_bat->channel_ichg); - goto err_temp; - } + twl4030_madc_bat->channel_ichg = devm_iio_channel_get(&pdev->dev, "ichg"); + if (IS_ERR(twl4030_madc_bat->channel_ichg)) + return PTR_ERR(twl4030_madc_bat->channel_ichg); - twl4030_madc_bat->channel_vbat = iio_channel_get(&pdev->dev, "vbat"); - if (IS_ERR(twl4030_madc_bat->channel_vbat)) { - ret = PTR_ERR(twl4030_madc_bat->channel_vbat); - goto err_ichg; - } + twl4030_madc_bat->channel_vbat = devm_iio_channel_get(&pdev->dev, "vbat"); + if (IS_ERR(twl4030_madc_bat->channel_vbat)) + return PTR_ERR(twl4030_madc_bat->channel_vbat); /* sort charging and discharging calibration data */ sort(pdata->charging, pdata->charging_size, @@ -228,37 +215,12 @@ static int twl4030_madc_battery_probe(struct platform_device *pdev) twl4030_cmp, NULL); twl4030_madc_bat->pdata = pdata; - platform_set_drvdata(pdev, twl4030_madc_bat); psy_cfg.drv_data = twl4030_madc_bat; - twl4030_madc_bat->psy = power_supply_register(&pdev->dev, - &twl4030_madc_bat_desc, - &psy_cfg); - if (IS_ERR(twl4030_madc_bat->psy)) { - ret = PTR_ERR(twl4030_madc_bat->psy); - goto err_vbat; - } - - return 0; - -err_vbat: - iio_channel_release(twl4030_madc_bat->channel_vbat); -err_ichg: - iio_channel_release(twl4030_madc_bat->channel_ichg); -err_temp: - iio_channel_release(twl4030_madc_bat->channel_temp); -err: - return ret; -} - -static int twl4030_madc_battery_remove(struct platform_device *pdev) -{ - struct twl4030_madc_battery *bat = platform_get_drvdata(pdev); - - power_supply_unregister(bat->psy); - - iio_channel_release(bat->channel_vbat); - iio_channel_release(bat->channel_ichg); - iio_channel_release(bat->channel_temp); + twl4030_madc_bat->psy = devm_power_supply_register(&pdev->dev, + &twl4030_madc_bat_desc, + &psy_cfg); + if (IS_ERR(twl4030_madc_bat->psy)) + return PTR_ERR(twl4030_madc_bat->psy); return 0; } @@ -268,7 +230,6 @@ static struct platform_driver twl4030_madc_battery_driver = { .name = "twl4030_madc_battery", }, .probe = twl4030_madc_battery_probe, - .remove = twl4030_madc_battery_remove, }; module_platform_driver(twl4030_madc_battery_driver); diff --git a/drivers/power/supply/twl6030_charger.c b/drivers/power/supply/twl6030_charger.c new file mode 100644 index 000000000000..b4ec26ff257c --- /dev/null +++ b/drivers/power/supply/twl6030_charger.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TWL6030 charger + * + * Copyright (C) 2024 Andreas Kemnade <andreas@kemnade.info> + * + * based on older 6030 driver found in a v3.0 vendor kernel + * + * based on twl4030_bci_battery.c by TI + * Copyright (C) 2008 Texas Instruments, Inc. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/bits.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/mfd/twl.h> +#include <linux/power_supply.h> +#include <linux/notifier.h> +#include <linux/usb/otg.h> +#include <linux/iio/consumer.h> +#include <linux/devm-helpers.h> + +#define CONTROLLER_INT_MASK 0x00 +#define CONTROLLER_CTRL1 0x01 +#define CONTROLLER_WDG 0x02 +#define CONTROLLER_STAT1 0x03 +#define CHARGERUSB_INT_STATUS 0x04 +#define CHARGERUSB_INT_MASK 0x05 +#define CHARGERUSB_STATUS_INT1 0x06 +#define CHARGERUSB_STATUS_INT2 0x07 +#define CHARGERUSB_CTRL1 0x08 +#define CHARGERUSB_CTRL2 0x09 +#define CHARGERUSB_CTRL3 0x0A +#define CHARGERUSB_STAT1 0x0B +#define CHARGERUSB_VOREG 0x0C +#define CHARGERUSB_VICHRG 0x0D +#define CHARGERUSB_CINLIMIT 0x0E +#define CHARGERUSB_CTRLLIMIT1 0x0F +#define CHARGERUSB_CTRLLIMIT2 0x10 +#define ANTICOLLAPSE_CTRL1 0x11 +#define ANTICOLLAPSE_CTRL2 0x12 + +/* TWL6032 registers 0xDA to 0xDE - TWL6032_MODULE_CHARGER */ +#define CONTROLLER_CTRL2 0x00 +#define CONTROLLER_VSEL_COMP 0x01 +#define CHARGERUSB_VSYSREG 0x02 +#define CHARGERUSB_VICHRG_PC 0x03 +#define LINEAR_CHRG_STS 0x04 + +#define LINEAR_CHRG_STS_CRYSTL_OSC_OK 0x40 +#define LINEAR_CHRG_STS_END_OF_CHARGE 0x20 +#define LINEAR_CHRG_STS_VBATOV 0x10 +#define LINEAR_CHRG_STS_VSYSOV 0x08 +#define LINEAR_CHRG_STS_DPPM_STS 0x04 +#define LINEAR_CHRG_STS_CV_STS 0x02 +#define LINEAR_CHRG_STS_CC_STS 0x01 + +#define FG_REG_00 0x00 +#define FG_REG_01 0x01 +#define FG_REG_02 0x02 +#define FG_REG_03 0x03 +#define FG_REG_04 0x04 +#define FG_REG_05 0x05 +#define FG_REG_06 0x06 +#define FG_REG_07 0x07 +#define FG_REG_08 0x08 +#define FG_REG_09 0x09 +#define FG_REG_10 0x0A +#define FG_REG_11 0x0B + +/* CONTROLLER_INT_MASK */ +#define MVAC_FAULT BIT(7) +#define MAC_EOC BIT(6) +#define LINCH_GATED BIT(5) +#define MBAT_REMOVED BIT(4) +#define MFAULT_WDG BIT(3) +#define MBAT_TEMP BIT(2) +#define MVBUS_DET BIT(1) +#define MVAC_DET BIT(0) + +/* CONTROLLER_CTRL1 */ +#define CONTROLLER_CTRL1_EN_LINCH BIT(5) +#define CONTROLLER_CTRL1_EN_CHARGER BIT(4) +#define CONTROLLER_CTRL1_SEL_CHARGER BIT(3) + +/* CONTROLLER_STAT1 */ +#define CONTROLLER_STAT1_EXTCHRG_STATZ BIT(7) +#define CONTROLLER_STAT1_LINCH_GATED BIT(6) +#define CONTROLLER_STAT1_CHRG_DET_N BIT(5) +#define CONTROLLER_STAT1_FAULT_WDG BIT(4) +#define CONTROLLER_STAT1_VAC_DET BIT(3) +#define VAC_DET BIT(3) +#define CONTROLLER_STAT1_VBUS_DET BIT(2) +#define VBUS_DET BIT(2) +#define CONTROLLER_STAT1_BAT_REMOVED BIT(1) +#define CONTROLLER_STAT1_BAT_TEMP_OVRANGE BIT(0) + +/* CHARGERUSB_INT_STATUS */ +#define EN_LINCH BIT(4) +#define CURRENT_TERM_INT BIT(3) +#define CHARGERUSB_STAT BIT(2) +#define CHARGERUSB_THMREG BIT(1) +#define CHARGERUSB_FAULT BIT(0) + +/* CHARGERUSB_INT_MASK */ +#define MASK_MCURRENT_TERM BIT(3) +#define MASK_MCHARGERUSB_STAT BIT(2) +#define MASK_MCHARGERUSB_THMREG BIT(1) +#define MASK_MCHARGERUSB_FAULT BIT(0) + +/* CHARGERUSB_STATUS_INT1 */ +#define CHARGERUSB_STATUS_INT1_TMREG BIT(7) +#define CHARGERUSB_STATUS_INT1_NO_BAT BIT(6) +#define CHARGERUSB_STATUS_INT1_BST_OCP BIT(5) +#define CHARGERUSB_STATUS_INT1_TH_SHUTD BIT(4) +#define CHARGERUSB_STATUS_INT1_BAT_OVP BIT(3) +#define CHARGERUSB_STATUS_INT1_POOR_SRC BIT(2) +#define CHARGERUSB_STATUS_INT1_SLP_MODE BIT(1) +#define CHARGERUSB_STATUS_INT1_VBUS_OVP BIT(0) + +/* CHARGERUSB_STATUS_INT2 */ +#define ICCLOOP BIT(3) +#define CURRENT_TERM BIT(2) +#define CHARGE_DONE BIT(1) +#define ANTICOLLAPSE BIT(0) + +/* CHARGERUSB_CTRL1 */ +#define SUSPEND_BOOT BIT(7) +#define OPA_MODE BIT(6) +#define HZ_MODE BIT(5) +#define TERM BIT(4) + +/* CHARGERUSB_CTRL2 */ +#define UA_TO_VITERM(x) (((x) / 50000 - 1) << 5) + +/* CHARGERUSB_CTRL3 */ +#define VBUSCHRG_LDO_OVRD BIT(7) +#define CHARGE_ONCE BIT(6) +#define BST_HW_PR_DIS BIT(5) +#define AUTOSUPPLY BIT(3) +#define BUCK_HSILIM BIT(0) + +/* CHARGERUSB_VOREG */ +#define UV_TO_VOREG(x) (((x) - 3500000) / 20000) +#define VOREG_TO_UV(x) (((x) & 0x3F) * 20000 + 3500000) +#define CHARGERUSB_VOREG_3P52 0x01 +#define CHARGERUSB_VOREG_4P0 0x19 +#define CHARGERUSB_VOREG_4P2 0x23 +#define CHARGERUSB_VOREG_4P76 0x3F + +/* CHARGERUSB_VICHRG */ +/* + * might be inaccurate for < 500 mA, diffent scale might apply, + * either starting from 100 mA or 300 mA + */ +#define UA_TO_VICHRG(x) (((x) / 100000) - 1) +#define VICHRG_TO_UA(x) (((x) & 0xf) * 100000 + 100000) + +/* CHARGERUSB_CINLIMIT */ +#define CHARGERUSB_CIN_LIMIT_100 0x1 +#define CHARGERUSB_CIN_LIMIT_300 0x5 +#define CHARGERUSB_CIN_LIMIT_500 0x9 +#define CHARGERUSB_CIN_LIMIT_NONE 0xF + +/* CHARGERUSB_CTRLLIMIT2 */ +#define CHARGERUSB_CTRLLIMIT2_1500 0x0E +#define LOCK_LIMIT BIT(4) + +/* ANTICOLLAPSE_CTRL2 */ +#define BUCK_VTH_SHIFT 5 + +/* FG_REG_00 */ +#define CC_ACTIVE_MODE_SHIFT 6 +#define CC_AUTOCLEAR BIT(2) +#define CC_CAL_EN BIT(1) +#define CC_PAUSE BIT(0) + +#define REG_TOGGLE1 0x90 +#define REG_PWDNSTATUS1 0x93 +#define FGDITHS BIT(7) +#define FGDITHR BIT(6) +#define FGS BIT(5) +#define FGR BIT(4) +#define BBSPOR_CFG 0xE6 +#define BB_CHG_EN BIT(3) + +struct twl6030_charger_info { + struct device *dev; + struct power_supply *usb; + struct power_supply_battery_info *binfo; + struct work_struct work; + int irq_chg; + int input_current_limit; + struct iio_channel *channel_vusb; + struct delayed_work charger_monitor; + bool extended_current_range; +}; + +struct twl6030_charger_chip_data { + bool extended_current_range; +}; + +static int twl6030_charger_read(u8 reg, u8 *val) +{ + return twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, val, reg); +} + +static int twl6030_charger_write(u8 reg, u8 val) +{ + return twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, val, reg); +} + +static int twl6030_config_cinlimit_reg(struct twl6030_charger_info *charger, + unsigned int ua) +{ + if (ua >= 50000 && ua <= 750000) { + ua = (ua - 50000) / 50000; + } else if ((ua > 750000) && (ua <= 1500000) && charger->extended_current_range) { + ua = ((ua % 100000) ? 0x30 : 0x20) + ((ua - 100000) / 100000); + } else { + if (ua < 50000) { + dev_err(charger->dev, "invalid input current limit\n"); + return -EINVAL; + } + /* This is no current limit */ + ua = 0x0F; + } + + return twl6030_charger_write(CHARGERUSB_CINLIMIT, ua); +} + +/* + * rewriting all stuff here, resets to extremely conservative defaults were + * seen under some circumstances, like charge voltage to 3.5V + */ +static int twl6030_enable_usb(struct twl6030_charger_info *charger) +{ + int ret; + + ret = twl6030_charger_write(CHARGERUSB_VICHRG, + UA_TO_VICHRG(charger->binfo->constant_charge_current_max_ua)); + if (ret < 0) + return ret; + + ret = twl6030_charger_write(CONTROLLER_WDG, 0xff); + if (ret < 0) + return ret; + + charger->input_current_limit = 500000; + ret = twl6030_config_cinlimit_reg(charger, charger->input_current_limit); + if (ret < 0) + return ret; + + ret = twl6030_charger_write(CHARGERUSB_CINLIMIT, CHARGERUSB_CIN_LIMIT_500); + if (ret < 0) + return ret; + + ret = twl6030_charger_write(CHARGERUSB_VOREG, + UV_TO_VOREG(charger->binfo->constant_charge_voltage_max_uv)); + if (ret < 0) + return ret; + + ret = twl6030_charger_write(CHARGERUSB_CTRL1, TERM); + if (ret < 0) + return ret; + + if (charger->binfo->charge_term_current_ua != -EINVAL) { + ret = twl6030_charger_write(CHARGERUSB_CTRL2, + UA_TO_VITERM(charger->binfo->charge_term_current_ua)); + if (ret < 0) + return ret; + } + + return twl6030_charger_write(CONTROLLER_CTRL1, CONTROLLER_CTRL1_EN_CHARGER); +} + +static void twl6030_charger_wdg(struct work_struct *data) +{ + struct twl6030_charger_info *charger = + container_of(data, struct twl6030_charger_info, + charger_monitor.work); + + u8 val; + u8 int_stat; + u8 stat_int1; + u8 stat_int2; + + twl6030_charger_read(CONTROLLER_STAT1, &val); + twl6030_charger_read(CHARGERUSB_INT_STATUS, &int_stat); + twl6030_charger_read(CHARGERUSB_STATUS_INT1, &stat_int1); + twl6030_charger_read(CHARGERUSB_STATUS_INT2, &stat_int2); + dev_dbg(charger->dev, + "wdg: stat1: %02x %s INT_STATUS %02x STATUS_INT1 %02x STATUS_INT2 %02x\n", + val, (val & VBUS_DET) ? "usb online" : "usb offline", + int_stat, stat_int1, stat_int2); + + twl6030_charger_write(CONTROLLER_WDG, 0xff); + schedule_delayed_work(&charger->charger_monitor, + msecs_to_jiffies(10000)); +} + +static irqreturn_t twl6030_charger_interrupt(int irq, void *arg) +{ + struct twl6030_charger_info *charger = arg; + u8 val; + u8 int_stat; + u8 stat_int1; + u8 stat_int2; + + if (twl6030_charger_read(CONTROLLER_STAT1, &val) < 0) + return IRQ_HANDLED; + + if (twl6030_charger_read(CHARGERUSB_INT_STATUS, &int_stat) < 0) + return IRQ_HANDLED; + + if (twl6030_charger_read(CHARGERUSB_STATUS_INT1, &stat_int1) < 0) + return IRQ_HANDLED; + + if (twl6030_charger_read(CHARGERUSB_STATUS_INT2, &stat_int2) < 0) + return IRQ_HANDLED; + + dev_dbg(charger->dev, + "charger irq: stat1: %02x %s INT_STATUS %02x STATUS_INT1 %02x STATUS_INT2 %02x\n", + val, (val & VBUS_DET) ? "usb online" : "usb offline", + int_stat, stat_int1, stat_int2); + power_supply_changed(charger->usb); + + if (val & VBUS_DET) { + if (twl6030_charger_read(CONTROLLER_CTRL1, &val) < 0) + return IRQ_HANDLED; + + if (!(val & CONTROLLER_CTRL1_EN_CHARGER)) { + if (twl6030_enable_usb(charger) < 0) + return IRQ_HANDLED; + + schedule_delayed_work(&charger->charger_monitor, + msecs_to_jiffies(10000)); + } + } else { + cancel_delayed_work(&charger->charger_monitor); + } + return IRQ_HANDLED; +} + +static int twl6030_charger_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl6030_charger_info *charger = power_supply_get_drvdata(psy); + int ret; + u8 stat1; + u8 intstat; + + ret = twl6030_charger_read(CONTROLLER_STAT1, &stat1); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!(stat1 & VBUS_DET)) { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + ret = twl6030_charger_read(CHARGERUSB_STATUS_INT2, &intstat); + if (ret) + return ret; + + if (intstat & CHARGE_DONE) + val->intval = POWER_SUPPLY_STATUS_FULL; + else if (intstat & CURRENT_TERM) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (!charger->channel_vusb) + return -ENODATA; + + ret = iio_read_channel_processed_scale(charger->channel_vusb, &val->intval, 1000); + if (ret < 0) + return ret; + + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(stat1 & VBUS_DET); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + val->intval = charger->input_current_limit; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int twl6030_charger_usb_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct twl6030_charger_info *charger = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + charger->input_current_limit = val->intval; + return twl6030_config_cinlimit_reg(charger, charger->input_current_limit); + default: + return -EINVAL; + } + + return 0; +} + +static int twl6030_charger_usb_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + dev_info(&psy->dev, "is %d writeable?\n", (int)psp); + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return true; + default: + return false; + } +} + +static enum power_supply_property twl6030_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, +}; + +static const struct power_supply_desc twl6030_charger_usb_desc = { + .name = "twl6030_usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = twl6030_charger_props, + .num_properties = ARRAY_SIZE(twl6030_charger_props), + .get_property = twl6030_charger_usb_get_property, + .set_property = twl6030_charger_usb_set_property, + .property_is_writeable = twl6030_charger_usb_property_is_writeable, +}; + +static int twl6030_charger_probe(struct platform_device *pdev) +{ + struct twl6030_charger_info *charger; + const struct twl6030_charger_chip_data *chip_data; + struct power_supply_config psy_cfg = {}; + int ret; + u8 val; + + charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->dev = &pdev->dev; + charger->irq_chg = platform_get_irq(pdev, 0); + + chip_data = device_get_match_data(&pdev->dev); + if (!chip_data) + return dev_err_probe(&pdev->dev, -EINVAL, "missing chip data\n"); + + charger->extended_current_range = chip_data->extended_current_range; + platform_set_drvdata(pdev, charger); + psy_cfg.drv_data = charger; + psy_cfg.fwnode = dev_fwnode(&pdev->dev); + + charger->channel_vusb = devm_iio_channel_get(&pdev->dev, "vusb"); + if (IS_ERR(charger->channel_vusb)) { + ret = PTR_ERR(charger->channel_vusb); + if (ret == -EPROBE_DEFER) + return ret; /* iio not ready */ + dev_warn(&pdev->dev, "could not request vusb iio channel (%d)", + ret); + charger->channel_vusb = NULL; + } + + charger->usb = devm_power_supply_register(&pdev->dev, + &twl6030_charger_usb_desc, + &psy_cfg); + if (IS_ERR(charger->usb)) + return dev_err_probe(&pdev->dev, PTR_ERR(charger->usb), + "Failed to register usb\n"); + + ret = power_supply_get_battery_info(charger->usb, &charger->binfo); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "Failed to get battery info\n"); + + dev_info(&pdev->dev, "battery with vmax %d imax: %d\n", + charger->binfo->constant_charge_voltage_max_uv, + charger->binfo->constant_charge_current_max_ua); + + if (charger->binfo->constant_charge_voltage_max_uv == -EINVAL) { + ret = twl6030_charger_read(CHARGERUSB_CTRLLIMIT1, &val); + if (ret < 0) + return ret; + + charger->binfo->constant_charge_voltage_max_uv = + VOREG_TO_UV(val); + } + + if (charger->binfo->constant_charge_voltage_max_uv > 4760000 || + charger->binfo->constant_charge_voltage_max_uv < 350000) + return dev_err_probe(&pdev->dev, -EINVAL, + "Invalid charge voltage\n"); + + if (charger->binfo->constant_charge_current_max_ua == -EINVAL) { + ret = twl6030_charger_read(CHARGERUSB_CTRLLIMIT2, &val); + if (ret < 0) + return ret; + + charger->binfo->constant_charge_current_max_ua = VICHRG_TO_UA(val); + } + + if (charger->binfo->constant_charge_current_max_ua < 100000 || + charger->binfo->constant_charge_current_max_ua > 1500000) { + return dev_err_probe(&pdev->dev, -EINVAL, + "Invalid charge current\n"); + } + + if ((charger->binfo->charge_term_current_ua != -EINVAL) && + (charger->binfo->charge_term_current_ua > 400000 || + charger->binfo->charge_term_current_ua < 50000)) { + return dev_err_probe(&pdev->dev, -EINVAL, + "Invalid charge termination current\n"); + } + + ret = devm_delayed_work_autocancel(&pdev->dev, + &charger->charger_monitor, + twl6030_charger_wdg); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "Failed to register delayed work\n"); + + ret = devm_request_threaded_irq(&pdev->dev, charger->irq_chg, NULL, + twl6030_charger_interrupt, + IRQF_ONESHOT, pdev->name, + charger); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "could not request irq %d\n", + charger->irq_chg); + + /* turing to charging to configure things */ + twl6030_charger_write(CONTROLLER_CTRL1, 0); + twl6030_charger_interrupt(0, charger); + + return 0; +} + +static const struct twl6030_charger_chip_data twl6030_data = { + .extended_current_range = false, +}; + +static const struct twl6030_charger_chip_data twl6032_data = { + .extended_current_range = true, +}; + +static const struct of_device_id twl_charger_of_match[] = { + {.compatible = "ti,twl6030-charger", .data = &twl6030_data}, + {.compatible = "ti,twl6032-charger", .data = &twl6032_data}, + { } +}; +MODULE_DEVICE_TABLE(of, twl_charger_of_match); + +static struct platform_driver twl6030_charger_driver = { + .probe = twl6030_charger_probe, + .driver = { + .name = "twl6030_charger", + .of_match_table = twl_charger_of_match, + }, +}; +module_platform_driver(twl6030_charger_driver); + +MODULE_DESCRIPTION("TWL6030 Battery Charger Interface driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c index 836d44c9fb74..fe94435340de 100644 --- a/drivers/power/supply/ucs1002_power.c +++ b/drivers/power/supply/ucs1002_power.c @@ -296,22 +296,17 @@ static int ucs1002_set_max_current(struct ucs1002_info *info, u32 val) return 0; } -static enum power_supply_usb_type ucs1002_usb_types[] = { - POWER_SUPPLY_USB_TYPE_PD, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_UNKNOWN, -}; - static int ucs1002_set_usb_type(struct ucs1002_info *info, int val) { unsigned int mode; - if (val < 0 || val >= ARRAY_SIZE(ucs1002_usb_types)) - return -EINVAL; - - switch (ucs1002_usb_types[val]) { + switch (val) { + /* + * POWER_SUPPLY_USB_TYPE_UNKNOWN == 0, map this to dedicated for + * userspace API compatibility with older versions of this driver + * which mapped 0 to dedicated. + */ + case POWER_SUPPLY_USB_TYPE_UNKNOWN: case POWER_SUPPLY_USB_TYPE_PD: mode = V_SET_ACTIVE_MODE_DEDICATED; break; @@ -384,7 +379,8 @@ static int ucs1002_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_USB_TYPE: return ucs1002_get_usb_type(info, val); case POWER_SUPPLY_PROP_HEALTH: - return val->intval = info->health; + val->intval = info->health; + return 0; case POWER_SUPPLY_PROP_PRESENT: val->intval = info->present; return 0; @@ -427,8 +423,11 @@ static int ucs1002_property_is_writeable(struct power_supply *psy, static const struct power_supply_desc ucs1002_charger_desc = { .name = "ucs1002", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = ucs1002_usb_types, - .num_usb_types = ARRAY_SIZE(ucs1002_usb_types), + .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_PD) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .get_property = ucs1002_get_property, .set_property = ucs1002_set_property, .property_is_writeable = ucs1002_property_is_writeable, @@ -494,7 +493,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; } @@ -561,7 +560,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); @@ -680,7 +679,7 @@ static struct i2c_driver ucs1002_driver = { .name = "ucs1002", .of_match_table = ucs1002_of_match, }, - .probe_new = ucs1002_probe, + .probe = ucs1002_probe, }; module_i2c_driver(ucs1002_driver); diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c index fbc966842509..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,284 +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_TECHNOLOGY, - 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_TECHNOLOGY: - val->intval = chip->info->technology; - 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) @@ -386,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; @@ -395,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); @@ -421,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; } @@ -457,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; } @@ -476,11 +205,13 @@ static struct i2c_driver ug3105_i2c_driver = { .name = "ug3105", .pm = &ug3105_pm_ops, }, - .probe_new = ug3105_probe, + .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_backup.c b/drivers/power/supply/wm831x_backup.c index ffb265b8526d..9673fcf7f3af 100644 --- a/drivers/power/supply/wm831x_backup.c +++ b/drivers/power/supply/wm831x_backup.c @@ -171,7 +171,6 @@ static int wm831x_backup_probe(struct platform_device *pdev) return -ENOMEM; devdata->wm831x = wm831x; - platform_set_drvdata(pdev, devdata); /* We ignore configuration failures since we can still read * back the status without enabling the charger (which may @@ -191,24 +190,14 @@ static int wm831x_backup_probe(struct platform_device *pdev) devdata->backup_desc.properties = wm831x_backup_props; devdata->backup_desc.num_properties = ARRAY_SIZE(wm831x_backup_props); devdata->backup_desc.get_property = wm831x_backup_get_prop; - devdata->backup = power_supply_register(&pdev->dev, - &devdata->backup_desc, NULL); + devdata->backup = devm_power_supply_register(&pdev->dev, + &devdata->backup_desc, NULL); return PTR_ERR_OR_ZERO(devdata->backup); } -static int wm831x_backup_remove(struct platform_device *pdev) -{ - struct wm831x_backup *devdata = platform_get_drvdata(pdev); - - power_supply_unregister(devdata->backup); - - return 0; -} - static struct platform_driver wm831x_backup_driver = { .probe = wm831x_backup_probe, - .remove = wm831x_backup_remove, .driver = { .name = "wm831x-backup", }, diff --git a/drivers/power/supply/wm831x_power.c b/drivers/power/supply/wm831x_power.c index 82e31066c746..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", @@ -570,8 +576,9 @@ static int wm831x_power_probe(struct platform_device *pdev) power->wall_desc.properties = wm831x_wall_props; power->wall_desc.num_properties = ARRAY_SIZE(wm831x_wall_props); power->wall_desc.get_property = wm831x_wall_get_prop; - power->wall = power_supply_register(&pdev->dev, &power->wall_desc, - NULL); + power->wall = devm_power_supply_register(&pdev->dev, + &power->wall_desc, + NULL); if (IS_ERR(power->wall)) { ret = PTR_ERR(power->wall); goto err; @@ -582,7 +589,9 @@ static int wm831x_power_probe(struct platform_device *pdev) power->usb_desc.properties = wm831x_usb_props; power->usb_desc.num_properties = ARRAY_SIZE(wm831x_usb_props); power->usb_desc.get_property = wm831x_usb_get_prop; - power->usb = power_supply_register(&pdev->dev, &power->usb_desc, NULL); + power->usb = devm_power_supply_register(&pdev->dev, + &power->usb_desc, + NULL); if (IS_ERR(power->usb)) { ret = PTR_ERR(power->usb); goto err_wall; @@ -599,9 +608,9 @@ static int wm831x_power_probe(struct platform_device *pdev) power->battery_desc.num_properties = ARRAY_SIZE(wm831x_bat_props); power->battery_desc.get_property = wm831x_bat_get_prop; power->battery_desc.use_for_apm = 1; - power->battery = power_supply_register(&pdev->dev, - &power->battery_desc, - NULL); + power->battery = devm_power_supply_register(&pdev->dev, + &power->battery_desc, + NULL); if (IS_ERR(power->battery)) { ret = PTR_ERR(power->battery); goto err_usb; @@ -684,17 +693,13 @@ err_syslo: irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); free_irq(irq, power); err_battery: - if (power->have_battery) - power_supply_unregister(power->battery); err_usb: - power_supply_unregister(power->usb); err_wall: - power_supply_unregister(power->wall); err: return ret; } -static int wm831x_power_remove(struct platform_device *pdev) +static void wm831x_power_remove(struct platform_device *pdev) { struct wm831x_power *wm831x_power = platform_get_drvdata(pdev); struct wm831x *wm831x = wm831x_power->wm831x; @@ -717,12 +722,6 @@ static int wm831x_power_remove(struct platform_device *pdev) irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); free_irq(irq, wm831x_power); - - if (wm831x_power->have_battery) - power_supply_unregister(wm831x_power->battery); - power_supply_unregister(wm831x_power->wall); - power_supply_unregister(wm831x_power->usb); - return 0; } static struct platform_driver wm831x_power_driver = { diff --git a/drivers/power/supply/wm8350_power.c b/drivers/power/supply/wm8350_power.c index 908cfd45d262..b0eb6e0ce8bc 100644 --- a/drivers/power/supply/wm8350_power.c +++ b/drivers/power/supply/wm8350_power.c @@ -176,7 +176,7 @@ static ssize_t charger_state_show(struct device *dev, return 0; } - return sprintf(buf, "%s\n", charge); + return sysfs_emit(buf, "%s\n", charge); } static DEVICE_ATTR_RO(charger_state); @@ -540,22 +540,17 @@ static int wm8350_power_probe(struct platform_device *pdev) struct wm8350_charger_policy *policy = power->policy; int ret; - power->ac = power_supply_register(&pdev->dev, &wm8350_ac_desc, NULL); + power->ac = devm_power_supply_register(&pdev->dev, &wm8350_ac_desc, NULL); if (IS_ERR(power->ac)) return PTR_ERR(power->ac); - power->battery = power_supply_register(&pdev->dev, &wm8350_battery_desc, - NULL); - if (IS_ERR(power->battery)) { - ret = PTR_ERR(power->battery); - goto battery_failed; - } + power->battery = devm_power_supply_register(&pdev->dev, &wm8350_battery_desc, NULL); + if (IS_ERR(power->battery)) + return PTR_ERR(power->battery); - power->usb = power_supply_register(&pdev->dev, &wm8350_usb_desc, NULL); - if (IS_ERR(power->usb)) { - ret = PTR_ERR(power->usb); - goto usb_failed; - } + power->usb = devm_power_supply_register(&pdev->dev, &wm8350_usb_desc, NULL); + if (IS_ERR(power->usb)) + return PTR_ERR(power->usb); ret = device_create_file(&pdev->dev, &dev_attr_charger_state); if (ret < 0) @@ -570,26 +565,14 @@ static int wm8350_power_probe(struct platform_device *pdev) } return ret; - -usb_failed: - power_supply_unregister(power->battery); -battery_failed: - power_supply_unregister(power->ac); - - return ret; } -static int wm8350_power_remove(struct platform_device *pdev) +static void wm8350_power_remove(struct platform_device *pdev) { struct wm8350 *wm8350 = platform_get_drvdata(pdev); - struct wm8350_power *power = &wm8350->power; free_charger_irq(wm8350); device_remove_file(&pdev->dev, &dev_attr_charger_state); - power_supply_unregister(power->battery); - power_supply_unregister(power->ac); - power_supply_unregister(power->usb); - return 0; } static struct platform_driver wm8350_power_driver = { diff --git a/drivers/power/supply/wm97xx_battery.c b/drivers/power/supply/wm97xx_battery.c index a0e1eaa25d93..b3b0c37a9dd2 100644 --- a/drivers/power/supply/wm97xx_battery.c +++ b/drivers/power/supply/wm97xx_battery.c @@ -248,14 +248,13 @@ err3: return ret; } -static int wm97xx_bat_remove(struct platform_device *dev) +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); - return 0; } static struct platform_driver wm97xx_bat_driver = { @@ -271,6 +270,5 @@ static struct platform_driver wm97xx_bat_driver = { module_platform_driver(wm97xx_bat_driver); -MODULE_LICENSE("GPL"); MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); MODULE_DESCRIPTION("WM97xx battery driver"); diff --git a/drivers/power/supply/z2_battery.c b/drivers/power/supply/z2_battery.c deleted file mode 100644 index 0ba4a590a0a5..000000000000 --- a/drivers/power/supply/z2_battery.c +++ /dev/null @@ -1,318 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Battery measurement code for Zipit Z2 - * - * Copyright (C) 2009 Peter Edwards <sweetlilmre@gmail.com> - */ - -#include <linux/module.h> -#include <linux/gpio/consumer.h> -#include <linux/i2c.h> -#include <linux/interrupt.h> -#include <linux/irq.h> -#include <linux/power_supply.h> -#include <linux/slab.h> -#include <linux/z2_battery.h> - -#define Z2_DEFAULT_NAME "Z2" - -struct z2_charger { - struct z2_battery_info *info; - struct gpio_desc *charge_gpiod; - int bat_status; - struct i2c_client *client; - struct power_supply *batt_ps; - struct power_supply_desc batt_ps_desc; - struct mutex work_lock; - struct work_struct bat_work; -}; - -static unsigned long z2_read_bat(struct z2_charger *charger) -{ - int data; - data = i2c_smbus_read_byte_data(charger->client, - charger->info->batt_I2C_reg); - if (data < 0) - return 0; - - return data * charger->info->batt_mult / charger->info->batt_div; -} - -static int z2_batt_get_property(struct power_supply *batt_ps, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct z2_charger *charger = power_supply_get_drvdata(batt_ps); - struct z2_battery_info *info = charger->info; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = charger->bat_status; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = info->batt_tech; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - if (info->batt_I2C_reg >= 0) - val->intval = z2_read_bat(charger); - else - return -EINVAL; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX: - if (info->max_voltage >= 0) - val->intval = info->max_voltage; - else - return -EINVAL; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN: - if (info->min_voltage >= 0) - val->intval = info->min_voltage; - else - return -EINVAL; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = 1; - break; - default: - return -EINVAL; - } - - return 0; -} - -static void z2_batt_ext_power_changed(struct power_supply *batt_ps) -{ - struct z2_charger *charger = power_supply_get_drvdata(batt_ps); - - schedule_work(&charger->bat_work); -} - -static void z2_batt_update(struct z2_charger *charger) -{ - int old_status = charger->bat_status; - - mutex_lock(&charger->work_lock); - - charger->bat_status = charger->charge_gpiod ? - (gpiod_get_value(charger->charge_gpiod) ? - POWER_SUPPLY_STATUS_CHARGING : - POWER_SUPPLY_STATUS_DISCHARGING) : - POWER_SUPPLY_STATUS_UNKNOWN; - - if (old_status != charger->bat_status) { - pr_debug("%s: %i -> %i\n", charger->batt_ps->desc->name, - old_status, - charger->bat_status); - power_supply_changed(charger->batt_ps); - } - - mutex_unlock(&charger->work_lock); -} - -static void z2_batt_work(struct work_struct *work) -{ - struct z2_charger *charger; - charger = container_of(work, struct z2_charger, bat_work); - z2_batt_update(charger); -} - -static irqreturn_t z2_charge_switch_irq(int irq, void *devid) -{ - struct z2_charger *charger = devid; - schedule_work(&charger->bat_work); - return IRQ_HANDLED; -} - -static int z2_batt_ps_init(struct z2_charger *charger, int props) -{ - int i = 0; - enum power_supply_property *prop; - struct z2_battery_info *info = charger->info; - - if (charger->charge_gpiod) - props++; /* POWER_SUPPLY_PROP_STATUS */ - if (info->batt_tech >= 0) - props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ - if (info->batt_I2C_reg >= 0) - props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ - if (info->max_voltage >= 0) - props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ - if (info->min_voltage >= 0) - props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ - - prop = kcalloc(props, sizeof(*prop), GFP_KERNEL); - if (!prop) - return -ENOMEM; - - prop[i++] = POWER_SUPPLY_PROP_PRESENT; - if (charger->charge_gpiod) - prop[i++] = POWER_SUPPLY_PROP_STATUS; - if (info->batt_tech >= 0) - prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; - if (info->batt_I2C_reg >= 0) - prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; - if (info->max_voltage >= 0) - prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; - if (info->min_voltage >= 0) - prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; - - if (!info->batt_name) { - dev_info(&charger->client->dev, - "Please consider setting proper battery " - "name in platform definition file, falling " - "back to name \" Z2_DEFAULT_NAME \"\n"); - charger->batt_ps_desc.name = Z2_DEFAULT_NAME; - } else - charger->batt_ps_desc.name = info->batt_name; - - charger->batt_ps_desc.properties = prop; - charger->batt_ps_desc.num_properties = props; - charger->batt_ps_desc.type = POWER_SUPPLY_TYPE_BATTERY; - charger->batt_ps_desc.get_property = z2_batt_get_property; - charger->batt_ps_desc.external_power_changed = - z2_batt_ext_power_changed; - charger->batt_ps_desc.use_for_apm = 1; - - return 0; -} - -static int z2_batt_probe(struct i2c_client *client) -{ - int ret = 0; - int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ - struct z2_charger *charger; - struct z2_battery_info *info = client->dev.platform_data; - struct power_supply_config psy_cfg = {}; - - if (info == NULL) { - dev_err(&client->dev, - "Please set platform device platform_data" - " to a valid z2_battery_info pointer!\n"); - return -EINVAL; - } - - charger = kzalloc(sizeof(*charger), GFP_KERNEL); - if (charger == NULL) - return -ENOMEM; - - charger->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; - charger->info = info; - charger->client = client; - i2c_set_clientdata(client, charger); - psy_cfg.drv_data = charger; - - mutex_init(&charger->work_lock); - - charger->charge_gpiod = devm_gpiod_get_optional(&client->dev, - NULL, GPIOD_IN); - if (IS_ERR(charger->charge_gpiod)) { - ret = dev_err_probe(&client->dev, - PTR_ERR(charger->charge_gpiod), - "failed to get charge GPIO\n"); - goto err; - } - - if (charger->charge_gpiod) { - gpiod_set_consumer_name(charger->charge_gpiod, "BATT CHRG"); - - irq_set_irq_type(gpiod_to_irq(charger->charge_gpiod), - IRQ_TYPE_EDGE_BOTH); - ret = request_irq(gpiod_to_irq(charger->charge_gpiod), - z2_charge_switch_irq, 0, - "AC Detect", charger); - if (ret) - goto err; - } - - ret = z2_batt_ps_init(charger, props); - if (ret) - goto err3; - - INIT_WORK(&charger->bat_work, z2_batt_work); - - charger->batt_ps = power_supply_register(&client->dev, - &charger->batt_ps_desc, - &psy_cfg); - if (IS_ERR(charger->batt_ps)) { - ret = PTR_ERR(charger->batt_ps); - goto err4; - } - - schedule_work(&charger->bat_work); - - return 0; - -err4: - kfree(charger->batt_ps_desc.properties); -err3: - if (charger->charge_gpiod) - free_irq(gpiod_to_irq(charger->charge_gpiod), charger); -err: - kfree(charger); - return ret; -} - -static void z2_batt_remove(struct i2c_client *client) -{ - struct z2_charger *charger = i2c_get_clientdata(client); - - cancel_work_sync(&charger->bat_work); - power_supply_unregister(charger->batt_ps); - - kfree(charger->batt_ps_desc.properties); - if (charger->charge_gpiod) - free_irq(gpiod_to_irq(charger->charge_gpiod), charger); - - kfree(charger); -} - -#ifdef CONFIG_PM -static int z2_batt_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct z2_charger *charger = i2c_get_clientdata(client); - - flush_work(&charger->bat_work); - return 0; -} - -static int z2_batt_resume(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct z2_charger *charger = i2c_get_clientdata(client); - - schedule_work(&charger->bat_work); - return 0; -} - -static const struct dev_pm_ops z2_battery_pm_ops = { - .suspend = z2_batt_suspend, - .resume = z2_batt_resume, -}; - -#define Z2_BATTERY_PM_OPS (&z2_battery_pm_ops) - -#else -#define Z2_BATTERY_PM_OPS (NULL) -#endif - -static const struct i2c_device_id z2_batt_id[] = { - { "aer915", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, z2_batt_id); - -static struct i2c_driver z2_batt_driver = { - .driver = { - .name = "z2-battery", - .pm = Z2_BATTERY_PM_OPS - }, - .probe_new = z2_batt_probe, - .remove = z2_batt_remove, - .id_table = z2_batt_id, -}; -module_i2c_driver(z2_batt_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Peter Edwards <sweetlilmre@gmail.com>"); -MODULE_DESCRIPTION("Zipit Z2 battery driver"); |
