summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/reset/Kconfig57
-rw-r--r--drivers/power/reset/Makefile5
-rw-r--r--drivers/power/reset/at91-reset.c5
-rw-r--r--drivers/power/reset/at91-sama5d2_shdwc.c3
-rw-r--r--drivers/power/reset/keystone-reset.c11
-rw-r--r--drivers/power/reset/macsmc-reboot.c290
-rw-r--r--drivers/power/reset/nvmem-reboot-mode.c15
-rw-r--r--drivers/power/reset/qcom-pon.c30
-rw-r--r--drivers/power/reset/qemu-virt-ctrl.c122
-rw-r--r--drivers/power/reset/reboot-mode.c176
-rw-r--r--drivers/power/reset/sc27xx-poweroff.c10
-rw-r--r--drivers/power/reset/spacemit-p1-reboot.c88
-rw-r--r--drivers/power/reset/syscon-reboot.c98
-rw-r--r--drivers/power/reset/tdx-ec-poweroff.c169
-rw-r--r--drivers/power/reset/th1520-aon-reboot.c98
-rw-r--r--drivers/power/sequencing/Kconfig21
-rw-r--r--drivers/power/sequencing/Makefile2
-rw-r--r--drivers/power/sequencing/core.c21
-rw-r--r--drivers/power/sequencing/pwrseq-pcie-m2.c486
-rw-r--r--drivers/power/sequencing/pwrseq-qcom-wcn.c143
-rw-r--r--drivers/power/sequencing/pwrseq-thead-gpu.c249
-rw-r--r--drivers/power/supply/88pm860x_charger.c8
-rw-r--r--drivers/power/supply/Kconfig136
-rw-r--r--drivers/power/supply/Makefile16
-rw-r--r--drivers/power/supply/ab8500_btemp.c3
-rw-r--r--drivers/power/supply/ab8500_chargalg.c15
-rw-r--r--drivers/power/supply/ab8500_charger.c44
-rw-r--r--drivers/power/supply/acer_a500_battery.c3
-rw-r--r--drivers/power/supply/act8945a_charger.c18
-rw-r--r--drivers/power/supply/adc-battery-helper.c327
-rw-r--r--drivers/power/supply/adc-battery-helper.h62
-rw-r--r--drivers/power/supply/apm_power.c3
-rw-r--r--drivers/power/supply/axp20x_ac_power.c2
-rw-r--r--drivers/power/supply/axp20x_battery.c23
-rw-r--r--drivers/power/supply/axp20x_usb_power.c4
-rw-r--r--drivers/power/supply/axp288_charger.c71
-rw-r--r--drivers/power/supply/bd71828-power.c1206
-rw-r--r--drivers/power/supply/bd99954-charger.c5
-rw-r--r--drivers/power/supply/bq2415x_charger.c8
-rw-r--r--drivers/power/supply/bq24190_charger.c43
-rw-r--r--drivers/power/supply/bq24257_charger.c2
-rw-r--r--drivers/power/supply/bq24735-charger.c2
-rw-r--r--drivers/power/supply/bq2515x_charger.c6
-rw-r--r--drivers/power/supply/bq256xx_charger.c21
-rw-r--r--drivers/power/supply/bq257xx_charger.c755
-rw-r--r--drivers/power/supply/bq25890_charger.c2
-rw-r--r--drivers/power/supply/bq25980_charger.c27
-rw-r--r--drivers/power/supply/bq27xxx_battery.c69
-rw-r--r--drivers/power/supply/bq27xxx_battery_i2c.c13
-rw-r--r--drivers/power/supply/chagall-battery.c291
-rw-r--r--drivers/power/supply/collie_battery.c1
-rw-r--r--drivers/power/supply/cpcap-battery.c41
-rw-r--r--drivers/power/supply/cpcap-charger.c7
-rw-r--r--drivers/power/supply/cros_charge-control.c23
-rw-r--r--drivers/power/supply/cros_peripheral_charger.c2
-rw-r--r--drivers/power/supply/cros_usbpd-charger.c2
-rw-r--r--drivers/power/supply/cw2015_battery.c18
-rw-r--r--drivers/power/supply/da9030_battery.c3
-rw-r--r--drivers/power/supply/ds2760_battery.c54
-rw-r--r--drivers/power/supply/ds2780_battery.c10
-rw-r--r--drivers/power/supply/ds2781_battery.c10
-rw-r--r--drivers/power/supply/generic-adc-battery.c2
-rw-r--r--drivers/power/supply/goldfish_battery.c12
-rw-r--r--drivers/power/supply/gpio-charger.c13
-rw-r--r--drivers/power/supply/huawei-gaokun-battery.c645
-rw-r--r--drivers/power/supply/ingenic-battery.c2
-rw-r--r--drivers/power/supply/intel_dc_ti_battery.c391
-rw-r--r--drivers/power/supply/ip5xxx_power.c9
-rw-r--r--drivers/power/supply/ipaq_micro_battery.c49
-rw-r--r--drivers/power/supply/lego_ev3_battery.c3
-rw-r--r--drivers/power/supply/lt3651-charger.c2
-rw-r--r--drivers/power/supply/ltc4162-l-charger.c4
-rw-r--r--drivers/power/supply/macsmc-power.c855
-rw-r--r--drivers/power/supply/max14577_charger.c4
-rw-r--r--drivers/power/supply/max17040_battery.c9
-rw-r--r--drivers/power/supply/max17042_battery.c132
-rw-r--r--drivers/power/supply/max1720x_battery.c64
-rw-r--r--drivers/power/supply/max77650-charger.c2
-rw-r--r--drivers/power/supply/max77693_charger.c2
-rw-r--r--drivers/power/supply/max77705_charger.c699
-rw-r--r--drivers/power/supply/max77759_charger.c774
-rw-r--r--drivers/power/supply/max77976_charger.c12
-rw-r--r--drivers/power/supply/max8903_charger.c2
-rw-r--r--drivers/power/supply/max8971_charger.c752
-rw-r--r--drivers/power/supply/mm8013.c2
-rw-r--r--drivers/power/supply/mt6360_charger.c2
-rw-r--r--drivers/power/supply/mt6370-charger.c32
-rw-r--r--drivers/power/supply/olpc_battery.c8
-rw-r--r--drivers/power/supply/pcf50633-charger.c466
-rw-r--r--drivers/power/supply/pf1550-charger.c641
-rw-r--r--drivers/power/supply/pm8916_bms_vm.c20
-rw-r--r--drivers/power/supply/pm8916_lbc.c20
-rw-r--r--drivers/power/supply/pmu_battery.c3
-rw-r--r--drivers/power/supply/power_supply_core.c283
-rw-r--r--drivers/power/supply/power_supply_leds.c2
-rw-r--r--drivers/power/supply/power_supply_sysfs.c44
-rw-r--r--drivers/power/supply/qcom_battmgr.c347
-rw-r--r--drivers/power/supply/qcom_smbb.c2
-rw-r--r--drivers/power/supply/qcom_smbx.c (renamed from drivers/power/supply/qcom_pmi8998_charger.c)165
-rw-r--r--drivers/power/supply/rk817_charger.c8
-rw-r--r--drivers/power/supply/rt5033_battery.c2
-rw-r--r--drivers/power/supply/rt5033_charger.c5
-rw-r--r--drivers/power/supply/rt9455_charger.c21
-rw-r--r--drivers/power/supply/rt9467-charger.c55
-rw-r--r--drivers/power/supply/rt9471.c14
-rw-r--r--drivers/power/supply/rt9756.c955
-rw-r--r--drivers/power/supply/rx51_battery.c2
-rw-r--r--drivers/power/supply/s2mu005-battery.c307
-rw-r--r--drivers/power/supply/sbs-battery.c38
-rw-r--r--drivers/power/supply/sbs-charger.c18
-rw-r--r--drivers/power/supply/sbs-manager.c6
-rw-r--r--drivers/power/supply/sc2731_charger.c2
-rw-r--r--drivers/power/supply/sc27xx_fuel_gauge.c8
-rw-r--r--drivers/power/supply/smb347-charger.c4
-rw-r--r--drivers/power/supply/test_power.c25
-rw-r--r--drivers/power/supply/tps65090-charger.c2
-rw-r--r--drivers/power/supply/tps65217_charger.c2
-rw-r--r--drivers/power/supply/twl4030_charger.c1
-rw-r--r--drivers/power/supply/twl4030_madc_battery.c2
-rw-r--r--drivers/power/supply/ucs1002_power.c5
-rw-r--r--drivers/power/supply/ug3105_battery.c397
-rw-r--r--drivers/power/supply/wm831x_power.c30
-rw-r--r--drivers/power/supply/wm97xx_battery.c40
123 files changed, 12208 insertions, 1672 deletions
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 60bf0ca64cf3..124afb99febe 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -97,7 +97,7 @@ config POWER_RESET_GEMINI_POWEROFF
config POWER_RESET_GPIO
bool "GPIO power-off driver"
- depends on OF_GPIO
+ depends on OF
help
This driver supports turning off your board via a GPIO line.
If your board needs a GPIO high/low to power down, say Y and
@@ -105,7 +105,7 @@ config POWER_RESET_GPIO
config POWER_RESET_GPIO_RESTART
bool "GPIO restart driver"
- depends on OF_GPIO
+ depends on OF
help
This driver supports restarting your board via a GPIO line.
If your board needs a GPIO high/low to restart, say Y and
@@ -128,6 +128,15 @@ config POWER_RESET_LINKSTATION
Say Y here if you have a Buffalo LinkStation LS421D/E.
+config POWER_RESET_MACSMC
+ tristate "Apple SMC reset/power-off driver"
+ depends on MFD_MACSMC
+ help
+ This driver supports reset and power-off on Apple Mac machines
+ that implement this functionality via the SMC.
+
+ Say Y here if you have an Apple Silicon Mac.
+
config POWER_RESET_MSM
bool "Qualcomm MSM power-off driver"
depends on ARCH_QCOM
@@ -172,7 +181,7 @@ config POWER_RESET_PIIX4_POWEROFF
config POWER_RESET_LTC2952
bool "LTC2952 PowerPath power-off driver"
- depends on OF_GPIO
+ depends on OF
help
This driver supports an external powerdown trigger and board power
down via the LTC2952. Bindings are made in the device tree.
@@ -189,7 +198,7 @@ config POWER_RESET_MT6323
config POWER_RESET_QNAP
bool "QNAP power-off driver"
- depends on OF_GPIO && PLAT_ORION
+ depends on PLAT_ORION
help
This driver supports turning off QNAP NAS devices by sending
commands to the microcontroller which controls the main power.
@@ -216,6 +225,27 @@ config POWER_RESET_ST
help
Reset support for STMicroelectronics boards.
+config POWER_RESET_TH1520_AON
+ tristate "T-Head TH1520 AON firmware poweroff and reset driver"
+ depends on TH1520_PM_DOMAINS
+ help
+ This driver supports power-off and reset operations for T-Head
+ TH1520 SoCs running the AON firmware.
+
+config POWER_RESET_TORADEX_EC
+ tristate "Toradex Embedded Controller power-off and reset driver"
+ depends on ARCH_MXC || COMPILE_TEST
+ depends on I2C
+ select REGMAP_I2C
+ help
+ This driver supports power-off and reset for SMARC Toradex SoMs,
+ for example the SMARC iMX8MP and SMARC iMX95, using Toradex
+ Embedded Controller (EC).
+
+ Say Y here if you have a Toradex SMARC SoM.
+
+ If unsure, say N.
+
config POWER_RESET_TPS65086
bool "TPS65086 restart driver"
depends on MFD_TPS65086
@@ -253,6 +283,15 @@ config POWER_RESET_KEYSTONE
help
Reboot support for the KEYSTONE SoCs.
+config POWER_RESET_SPACEMIT_P1
+ tristate "SpacemiT P1 poweroff and reset driver"
+ depends on ARCH_SPACEMIT || COMPILE_TEST
+ depends on MFD_SPACEMIT_P1
+ default MFD_SPACEMIT_P1
+ help
+ This driver supports power-off and reset operations for the SpacemiT
+ P1 PMIC.
+
config POWER_RESET_SYSCON
bool "Generic SYSCON regmap reset driver"
depends on OF
@@ -315,4 +354,14 @@ config POWER_MLXBF
help
This driver supports reset or low power mode handling for Mellanox BlueField.
+config POWER_RESET_QEMU_VIRT_CTRL
+ tristate "QEMU Virt Machine System Controller"
+ depends on HAS_IOMEM
+ help
+ This driver supports the system reset and power off functionality
+ provided by the QEMU 'virt-ctrl' device.
+
+ Say Y here if you are running Linux on a QEMU virtual machine that
+ provides this controller, such as the m68k virt machine.
+
endif
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 10782d32e1da..d7ae97241a83 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o
+obj-$(CONFIG_POWER_RESET_MACSMC) += macsmc-reboot.o
obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o
obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
@@ -23,7 +24,10 @@ obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o
obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
obj-$(CONFIG_POWER_RESET_REGULATOR) += regulator-poweroff.o
obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
+obj-$(CONFIG_POWER_RESET_SPACEMIT_P1) += spacemit-p1-reboot.o
obj-$(CONFIG_POWER_RESET_ST) += st-poweroff.o
+obj-$(CONFIG_POWER_RESET_TH1520_AON) += th1520-aon-reboot.o
+obj-$(CONFIG_POWER_RESET_TORADEX_EC) += tdx-ec-poweroff.o
obj-$(CONFIG_POWER_RESET_TPS65086) += tps65086-restart.o
obj-$(CONFIG_POWER_RESET_VERSATILE) += arm-versatile-reboot.o
obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o
@@ -37,3 +41,4 @@ obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
obj-$(CONFIG_POWER_RESET_SC27XX) += sc27xx-poweroff.o
obj-$(CONFIG_NVMEM_REBOOT_MODE) += nvmem-reboot-mode.o
obj-$(CONFIG_POWER_MLXBF) += pwr-mlxbf.o
+obj-$(CONFIG_POWER_RESET_QEMU_VIRT_CTRL) += qemu-virt-ctrl.o
diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c
index 036b18a1f90f..511f5a8f8961 100644
--- a/drivers/power/reset/at91-reset.c
+++ b/drivers/power/reset/at91-reset.c
@@ -129,12 +129,11 @@ static int at91_reset(struct notifier_block *this, unsigned long mode,
" str %4, [%0, %6]\n\t"
/* Disable SDRAM1 accesses */
"1: tst %1, #0\n\t"
- " beq 2f\n\t"
" strne %3, [%1, #" __stringify(AT91_DDRSDRC_RTR) "]\n\t"
/* Power down SDRAM1 */
" strne %4, [%1, %6]\n\t"
/* Reset CPU */
- "2: str %5, [%2, #" __stringify(AT91_RSTC_CR) "]\n\t"
+ " str %5, [%2, #" __stringify(AT91_RSTC_CR) "]\n\t"
" b .\n\t"
:
@@ -145,7 +144,7 @@ static int at91_reset(struct notifier_block *this, unsigned long mode,
"r" cpu_to_le32(AT91_DDRSDRC_LPCB_POWER_DOWN),
"r" (reset->data->reset_args),
"r" (reset->ramc_lpr)
- : "r4");
+ );
return NOTIFY_DONE;
}
diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c
index c2801bd6384d..ecf15694f925 100644
--- a/drivers/power/reset/at91-sama5d2_shdwc.c
+++ b/drivers/power/reset/at91-sama5d2_shdwc.c
@@ -129,7 +129,7 @@ static void at91_wakeup_status(struct platform_device *pdev)
else if (SHDW_RTTWK(reg, &rcfg->shdwc))
reason = "RTT";
- pr_info("AT91: Wake-Up source: %s\n", reason);
+ dev_info(&pdev->dev, "Wake-Up source: %s\n", reason);
}
static void at91_poweroff(void)
@@ -327,6 +327,7 @@ static const struct of_device_id at91_pmc_ids[] = {
{ .compatible = "microchip,sam9x60-pmc" },
{ .compatible = "microchip,sama7g5-pmc" },
{ .compatible = "microchip,sam9x7-pmc" },
+ { .compatible = "microchip,sama7d65-pmc" },
{ /* Sentinel. */ }
};
diff --git a/drivers/power/reset/keystone-reset.c b/drivers/power/reset/keystone-reset.c
index d9268d150e1f..3c44cd6cee0a 100644
--- a/drivers/power/reset/keystone-reset.c
+++ b/drivers/power/reset/keystone-reset.c
@@ -48,8 +48,7 @@ static inline int rsctrl_enable_rspll_write(void)
RSCTRL_KEY_MASK, RSCTRL_KEY);
}
-static int rsctrl_restart_handler(struct notifier_block *this,
- unsigned long mode, void *cmd)
+static int rsctrl_restart_handler(struct sys_off_data *data)
{
/* enable write access to RSTCTRL */
rsctrl_enable_rspll_write();
@@ -61,11 +60,6 @@ static int rsctrl_restart_handler(struct notifier_block *this,
return NOTIFY_DONE;
}
-static struct notifier_block rsctrl_restart_nb = {
- .notifier_call = rsctrl_restart_handler,
- .priority = 128,
-};
-
static const struct of_device_id rsctrl_of_match[] = {
{.compatible = "ti,keystone-reset", },
{},
@@ -140,7 +134,8 @@ static int rsctrl_probe(struct platform_device *pdev)
return ret;
}
- ret = register_restart_handler(&rsctrl_restart_nb);
+ ret = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART, 128,
+ rsctrl_restart_handler, NULL);
if (ret)
dev_err(dev, "cannot register restart handler (err=%d)\n", ret);
diff --git a/drivers/power/reset/macsmc-reboot.c b/drivers/power/reset/macsmc-reboot.c
new file mode 100644
index 000000000000..e9702acdd366
--- /dev/null
+++ b/drivers/power/reset/macsmc-reboot.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC Reboot/Poweroff Handler
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/delay.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+
+struct macsmc_reboot_nvmem {
+ struct nvmem_cell *shutdown_flag;
+ struct nvmem_cell *boot_stage;
+ struct nvmem_cell *boot_error_count;
+ struct nvmem_cell *panic_count;
+};
+
+static const char * const nvmem_names[] = {
+ "shutdown_flag",
+ "boot_stage",
+ "boot_error_count",
+ "panic_count",
+};
+
+enum boot_stage {
+ BOOT_STAGE_SHUTDOWN = 0x00, /* Clean shutdown */
+ BOOT_STAGE_IBOOT_DONE = 0x2f, /* Last stage of bootloader */
+ BOOT_STAGE_KERNEL_STARTED = 0x30, /* Normal OS booting */
+};
+
+struct macsmc_reboot {
+ struct device *dev;
+ struct apple_smc *smc;
+ struct notifier_block reboot_notify;
+
+ union {
+ struct macsmc_reboot_nvmem nvm;
+ struct nvmem_cell *nvm_cells[ARRAY_SIZE(nvmem_names)];
+ };
+};
+
+/* Helpers to read/write a u8 given a struct nvmem_cell */
+static int nvmem_cell_get_u8(struct nvmem_cell *cell)
+{
+ size_t len;
+ void *bfr;
+ u8 val;
+
+ bfr = nvmem_cell_read(cell, &len);
+ if (IS_ERR(bfr))
+ return PTR_ERR(bfr);
+
+ if (len < 1) {
+ kfree(bfr);
+ return -EINVAL;
+ }
+
+ val = *(u8 *)bfr;
+ kfree(bfr);
+ return val;
+}
+
+static int nvmem_cell_set_u8(struct nvmem_cell *cell, u8 val)
+{
+ return nvmem_cell_write(cell, &val, sizeof(val));
+}
+
+/*
+ * SMC 'MBSE' key actions:
+ *
+ * 'offw' - shutdown warning
+ * 'slpw' - sleep warning
+ * 'rest' - restart warning
+ * 'off1' - shutdown (needs PMU bit set to stay on)
+ * 'susp' - suspend
+ * 'phra' - restart ("PE Halt Restart Action"?)
+ * 'panb' - panic beginning
+ * 'pane' - panic end
+ */
+
+static int macsmc_prepare_atomic(struct sys_off_data *data)
+{
+ struct macsmc_reboot *reboot = data->cb_data;
+
+ dev_info(reboot->dev, "Preparing SMC for atomic mode\n");
+
+ apple_smc_enter_atomic(reboot->smc);
+ return NOTIFY_OK;
+}
+
+static int macsmc_power_off(struct sys_off_data *data)
+{
+ struct macsmc_reboot *reboot = data->cb_data;
+
+ dev_info(reboot->dev, "Issuing power off (off1)\n");
+
+ if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(off1)) < 0) {
+ dev_err(reboot->dev, "Failed to issue MBSE = off1 (power_off)\n");
+ } else {
+ mdelay(100);
+ WARN_ONCE(1, "Unable to power off system\n");
+ }
+
+ return NOTIFY_OK;
+}
+
+static int macsmc_restart(struct sys_off_data *data)
+{
+ struct macsmc_reboot *reboot = data->cb_data;
+
+ dev_info(reboot->dev, "Issuing restart (phra)\n");
+
+ if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(phra)) < 0) {
+ dev_err(reboot->dev, "Failed to issue MBSE = phra (restart)\n");
+ } else {
+ mdelay(100);
+ WARN_ONCE(1, "Unable to restart system\n");
+ }
+
+ return NOTIFY_OK;
+}
+
+static int macsmc_reboot_notify(struct notifier_block *this, unsigned long action, void *data)
+{
+ struct macsmc_reboot *reboot = container_of(this, struct macsmc_reboot, reboot_notify);
+ u8 shutdown_flag;
+ u32 val;
+
+ switch (action) {
+ case SYS_RESTART:
+ val = SMC_KEY(rest);
+ shutdown_flag = 0;
+ break;
+ case SYS_POWER_OFF:
+ val = SMC_KEY(offw);
+ shutdown_flag = 1;
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ dev_info(reboot->dev, "Preparing for reboot (%p4ch)\n", &val);
+
+ /* On the Mac Mini, this will turn off the LED for power off */
+ if (apple_smc_write_u32(reboot->smc, SMC_KEY(MBSE), val) < 0)
+ dev_err(reboot->dev, "Failed to issue MBSE = %p4ch (reboot_prepare)\n", &val);
+
+ /* Set the boot_stage to 0, which means we're doing a clean shutdown/reboot. */
+ if (reboot->nvm.boot_stage &&
+ nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_SHUTDOWN) < 0)
+ dev_err(reboot->dev, "Failed to write boot_stage\n");
+
+ /*
+ * Set the PMU flag to actually reboot into the off state.
+ * Without this, the device will just reboot. We make it optional in case it is no longer
+ * necessary on newer hardware.
+ */
+ if (reboot->nvm.shutdown_flag &&
+ nvmem_cell_set_u8(reboot->nvm.shutdown_flag, shutdown_flag) < 0)
+ dev_err(reboot->dev, "Failed to write shutdown_flag\n");
+
+ return NOTIFY_OK;
+}
+
+static void macsmc_power_init_error_counts(struct macsmc_reboot *reboot)
+{
+ int boot_error_count, panic_count;
+
+ if (!reboot->nvm.boot_error_count || !reboot->nvm.panic_count)
+ return;
+
+ boot_error_count = nvmem_cell_get_u8(reboot->nvm.boot_error_count);
+ if (boot_error_count < 0) {
+ dev_err(reboot->dev, "Failed to read boot_error_count (%d)\n", boot_error_count);
+ return;
+ }
+
+ panic_count = nvmem_cell_get_u8(reboot->nvm.panic_count);
+ if (panic_count < 0) {
+ dev_err(reboot->dev, "Failed to read panic_count (%d)\n", panic_count);
+ return;
+ }
+
+ if (!boot_error_count && !panic_count)
+ return;
+
+ dev_warn(reboot->dev, "PMU logged %d boot error(s) and %d panic(s)\n",
+ boot_error_count, panic_count);
+
+ if (nvmem_cell_set_u8(reboot->nvm.panic_count, 0) < 0)
+ dev_err(reboot->dev, "Failed to reset panic_count\n");
+ if (nvmem_cell_set_u8(reboot->nvm.boot_error_count, 0) < 0)
+ dev_err(reboot->dev, "Failed to reset boot_error_count\n");
+}
+
+static int macsmc_reboot_probe(struct platform_device *pdev)
+{
+ struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+ struct macsmc_reboot *reboot;
+ int ret, i;
+
+ reboot = devm_kzalloc(&pdev->dev, sizeof(*reboot), GFP_KERNEL);
+ if (!reboot)
+ return -ENOMEM;
+
+ reboot->dev = &pdev->dev;
+ reboot->smc = smc;
+
+ platform_set_drvdata(pdev, reboot);
+
+ for (i = 0; i < ARRAY_SIZE(nvmem_names); i++) {
+ struct nvmem_cell *cell;
+
+ cell = devm_nvmem_cell_get(&pdev->dev,
+ nvmem_names[i]);
+ if (IS_ERR(cell)) {
+ if (PTR_ERR(cell) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n",
+ nvmem_names[i], PTR_ERR(cell));
+ /* Non fatal, we'll deal with it */
+ cell = NULL;
+ }
+ reboot->nvm_cells[i] = cell;
+ }
+
+ /* Set the boot_stage to indicate we're running the OS kernel */
+ if (reboot->nvm.boot_stage &&
+ nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_KERNEL_STARTED) < 0)
+ dev_err(reboot->dev, "Failed to write boot_stage\n");
+
+ /* Display and clear the error counts */
+ macsmc_power_init_error_counts(reboot);
+
+ reboot->reboot_notify.notifier_call = macsmc_reboot_notify;
+
+ ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF_PREPARE,
+ SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to register power-off prepare handler\n");
+ ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH,
+ macsmc_power_off, reboot);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to register power-off handler\n");
+
+ ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART_PREPARE,
+ SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to register restart prepare handler\n");
+ ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_HIGH,
+ macsmc_restart, reboot);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to register restart handler\n");
+
+ ret = devm_register_reboot_notifier(&pdev->dev, &reboot->reboot_notify);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to register reboot notifier\n");
+
+ dev_info(&pdev->dev, "Handling reboot and poweroff requests via SMC\n");
+
+ return 0;
+}
+
+static const struct of_device_id macsmc_reboot_of_table[] = {
+ { .compatible = "apple,smc-reboot", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, macsmc_reboot_of_table);
+
+static struct platform_driver macsmc_reboot_driver = {
+ .driver = {
+ .name = "macsmc-reboot",
+ .of_match_table = macsmc_reboot_of_table,
+ },
+ .probe = macsmc_reboot_probe,
+};
+module_platform_driver(macsmc_reboot_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC reboot/poweroff driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
diff --git a/drivers/power/reset/nvmem-reboot-mode.c b/drivers/power/reset/nvmem-reboot-mode.c
index 41530b70cfc4..d260715fccf6 100644
--- a/drivers/power/reset/nvmem-reboot-mode.c
+++ b/drivers/power/reset/nvmem-reboot-mode.c
@@ -10,6 +10,7 @@
#include <linux/nvmem-consumer.h>
#include <linux/platform_device.h>
#include <linux/reboot-mode.h>
+#include <linux/slab.h>
struct nvmem_reboot_mode {
struct reboot_mode_driver reboot;
@@ -19,12 +20,22 @@ struct nvmem_reboot_mode {
static int nvmem_reboot_mode_write(struct reboot_mode_driver *reboot,
unsigned int magic)
{
- int ret;
struct nvmem_reboot_mode *nvmem_rbm;
+ size_t buf_len;
+ void *buf;
+ int ret;
nvmem_rbm = container_of(reboot, struct nvmem_reboot_mode, reboot);
- ret = nvmem_cell_write(nvmem_rbm->cell, &magic, sizeof(magic));
+ buf = nvmem_cell_read(nvmem_rbm->cell, &buf_len);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+ kfree(buf);
+
+ if (buf_len > sizeof(magic))
+ return -EINVAL;
+
+ ret = nvmem_cell_write(nvmem_rbm->cell, &magic, buf_len);
if (ret < 0)
dev_err(reboot->dev, "update reboot mode bits failed\n");
diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c
index 1344b361a475..7e108982a582 100644
--- a/drivers/power/reset/qcom-pon.c
+++ b/drivers/power/reset/qcom-pon.c
@@ -19,7 +19,7 @@
#define NO_REASON_SHIFT 0
-struct pm8916_pon {
+struct qcom_pon {
struct device *dev;
struct regmap *regmap;
u32 baseaddr;
@@ -27,11 +27,11 @@ struct pm8916_pon {
long reason_shift;
};
-static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot,
+static int qcom_pon_reboot_mode_write(struct reboot_mode_driver *reboot,
unsigned int magic)
{
- struct pm8916_pon *pon = container_of
- (reboot, struct pm8916_pon, reboot_mode);
+ struct qcom_pon *pon = container_of
+ (reboot, struct qcom_pon, reboot_mode);
int ret;
ret = regmap_update_bits(pon->regmap,
@@ -44,9 +44,9 @@ static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot,
return ret;
}
-static int pm8916_pon_probe(struct platform_device *pdev)
+static int qcom_pon_probe(struct platform_device *pdev)
{
- struct pm8916_pon *pon;
+ struct qcom_pon *pon;
long reason_shift;
int error;
@@ -72,7 +72,7 @@ static int pm8916_pon_probe(struct platform_device *pdev)
if (reason_shift != NO_REASON_SHIFT) {
pon->reboot_mode.dev = &pdev->dev;
pon->reason_shift = reason_shift;
- pon->reboot_mode.write = pm8916_reboot_mode_write;
+ pon->reboot_mode.write = qcom_pon_reboot_mode_write;
error = devm_reboot_mode_register(&pdev->dev, &pon->reboot_mode);
if (error) {
dev_err(&pdev->dev, "can't register reboot mode\n");
@@ -85,7 +85,7 @@ static int pm8916_pon_probe(struct platform_device *pdev)
return devm_of_platform_populate(&pdev->dev);
}
-static const struct of_device_id pm8916_pon_id_table[] = {
+static const struct of_device_id qcom_pon_id_table[] = {
{ .compatible = "qcom,pm8916-pon", .data = (void *)GEN1_REASON_SHIFT },
{ .compatible = "qcom,pm8941-pon", .data = (void *)NO_REASON_SHIFT },
{ .compatible = "qcom,pms405-pon", .data = (void *)GEN1_REASON_SHIFT },
@@ -93,16 +93,16 @@ static const struct of_device_id pm8916_pon_id_table[] = {
{ .compatible = "qcom,pmk8350-pon", .data = (void *)GEN2_REASON_SHIFT },
{ }
};
-MODULE_DEVICE_TABLE(of, pm8916_pon_id_table);
+MODULE_DEVICE_TABLE(of, qcom_pon_id_table);
-static struct platform_driver pm8916_pon_driver = {
- .probe = pm8916_pon_probe,
+static struct platform_driver qcom_pon_driver = {
+ .probe = qcom_pon_probe,
.driver = {
- .name = "pm8916-pon",
- .of_match_table = pm8916_pon_id_table,
+ .name = "qcom-pon",
+ .of_match_table = qcom_pon_id_table,
},
};
-module_platform_driver(pm8916_pon_driver);
+module_platform_driver(qcom_pon_driver);
-MODULE_DESCRIPTION("pm8916 Power On driver");
+MODULE_DESCRIPTION("Qualcomm Power On driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/qemu-virt-ctrl.c b/drivers/power/reset/qemu-virt-ctrl.c
new file mode 100644
index 000000000000..01409dfe2265
--- /dev/null
+++ b/drivers/power/reset/qemu-virt-ctrl.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * QEMU Virt Machine System Controller Driver
+ *
+ * Copyright (C) 2026 Kuan-Wei Chiu <visitorckw@gmail.com>
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+
+/* Registers */
+#define VIRT_CTRL_REG_FEATURES 0x00
+#define VIRT_CTRL_REG_CMD 0x04
+
+/* Commands */
+#define CMD_NOOP 0
+#define CMD_RESET 1
+#define CMD_HALT 2
+#define CMD_PANIC 3
+
+struct qemu_virt_ctrl {
+ void __iomem *base;
+ struct notifier_block reboot_nb;
+};
+
+static inline void virt_ctrl_write32(u32 val, void __iomem *addr)
+{
+ if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
+ iowrite32be(val, addr);
+ else
+ iowrite32(val, addr);
+}
+
+static int qemu_virt_ctrl_power_off(struct sys_off_data *data)
+{
+ struct qemu_virt_ctrl *ctrl = data->cb_data;
+
+ virt_ctrl_write32(CMD_HALT, ctrl->base + VIRT_CTRL_REG_CMD);
+
+ return NOTIFY_DONE;
+}
+
+static int qemu_virt_ctrl_restart(struct sys_off_data *data)
+{
+ struct qemu_virt_ctrl *ctrl = data->cb_data;
+
+ virt_ctrl_write32(CMD_RESET, ctrl->base + VIRT_CTRL_REG_CMD);
+
+ return NOTIFY_DONE;
+}
+
+static int qemu_virt_ctrl_reboot_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct qemu_virt_ctrl *ctrl = container_of(nb, struct qemu_virt_ctrl, reboot_nb);
+
+ if (action == SYS_HALT)
+ virt_ctrl_write32(CMD_HALT, ctrl->base + VIRT_CTRL_REG_CMD);
+
+ return NOTIFY_DONE;
+}
+
+static int qemu_virt_ctrl_probe(struct platform_device *pdev)
+{
+ struct qemu_virt_ctrl *ctrl;
+ int ret;
+
+ ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ ctrl->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctrl->base))
+ return PTR_ERR(ctrl->base);
+
+ ret = devm_register_sys_off_handler(&pdev->dev,
+ SYS_OFF_MODE_RESTART,
+ SYS_OFF_PRIO_DEFAULT,
+ qemu_virt_ctrl_restart,
+ ctrl);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "cannot register restart handler\n");
+
+ ret = devm_register_sys_off_handler(&pdev->dev,
+ SYS_OFF_MODE_POWER_OFF,
+ SYS_OFF_PRIO_DEFAULT,
+ qemu_virt_ctrl_power_off,
+ ctrl);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "cannot register power-off handler\n");
+
+ ctrl->reboot_nb.notifier_call = qemu_virt_ctrl_reboot_notify;
+ ret = devm_register_reboot_notifier(&pdev->dev, &ctrl->reboot_nb);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "cannot register reboot notifier\n");
+
+ return 0;
+}
+
+static const struct platform_device_id qemu_virt_ctrl_id[] = {
+ { "qemu-virt-ctrl", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, qemu_virt_ctrl_id);
+
+static struct platform_driver qemu_virt_ctrl_driver = {
+ .probe = qemu_virt_ctrl_probe,
+ .driver = {
+ .name = "qemu-virt-ctrl",
+ },
+ .id_table = qemu_virt_ctrl_id,
+};
+module_platform_driver(qemu_virt_ctrl_driver);
+
+MODULE_AUTHOR("Kuan-Wei Chiu <visitorckw@gmail.com>");
+MODULE_DESCRIPTION("QEMU Virt Machine System Controller Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c
index b4076b10b893..d20e44db0532 100644
--- a/drivers/power/reset/reboot-mode.c
+++ b/drivers/power/reset/reboot-mode.c
@@ -4,12 +4,16 @@
*/
#include <linux/device.h>
+#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/reboot.h>
#include <linux/reboot-mode.h>
+#include <linux/slab.h>
+#include <linux/string.h>
#define PREFIX "mode-"
@@ -19,24 +23,81 @@ struct mode_info {
struct list_head list;
};
+struct reboot_mode_sysfs_data {
+ struct device *reboot_mode_device;
+ struct list_head head;
+};
+
+static inline void reboot_mode_release_list(struct reboot_mode_sysfs_data *priv)
+{
+ struct mode_info *info;
+ struct mode_info *next;
+
+ list_for_each_entry_safe(info, next, &priv->head, list) {
+ list_del(&info->list);
+ kfree_const(info->mode);
+ kfree(info);
+ }
+}
+
+static ssize_t reboot_modes_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct reboot_mode_sysfs_data *priv;
+ struct mode_info *sysfs_info;
+ ssize_t size = 0;
+
+ priv = dev_get_drvdata(dev);
+ if (!priv)
+ return -ENODATA;
+
+ list_for_each_entry(sysfs_info, &priv->head, list)
+ size += sysfs_emit_at(buf, size, "%s ", sysfs_info->mode);
+
+ if (!size)
+ return -ENODATA;
+
+ return size + sysfs_emit_at(buf, size - 1, "\n");
+}
+static DEVICE_ATTR_RO(reboot_modes);
+
+static struct attribute *reboot_mode_attrs[] = {
+ &dev_attr_reboot_modes.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(reboot_mode);
+
+static const struct class reboot_mode_class = {
+ .name = "reboot-mode",
+ .dev_groups = reboot_mode_groups,
+};
+
static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot,
const char *cmd)
{
const char *normal = "normal";
- int magic = 0;
struct mode_info *info;
+ char cmd_[110];
if (!cmd)
cmd = normal;
- list_for_each_entry(info, &reboot->head, list) {
- if (!strcmp(info->mode, cmd)) {
- magic = info->magic;
- break;
- }
- }
+ list_for_each_entry(info, &reboot->head, list)
+ if (!strcmp(info->mode, cmd))
+ return info->magic;
+
+ /* try to match again, replacing characters impossible in DT */
+ if (strscpy(cmd_, cmd, sizeof(cmd_)) == -E2BIG)
+ return 0;
- return magic;
+ strreplace(cmd_, ' ', '-');
+ strreplace(cmd_, ',', '-');
+ strreplace(cmd_, '/', '-');
+
+ list_for_each_entry(info, &reboot->head, list)
+ if (!strcmp(info->mode, cmd_))
+ return info->magic;
+
+ return 0;
}
static int reboot_mode_notify(struct notifier_block *this,
@@ -53,6 +114,52 @@ static int reboot_mode_notify(struct notifier_block *this,
return NOTIFY_DONE;
}
+static int reboot_mode_create_device(struct reboot_mode_driver *reboot)
+{
+ struct reboot_mode_sysfs_data *priv;
+ struct mode_info *sysfs_info;
+ struct mode_info *info;
+ int ret;
+
+ priv = kzalloc_obj(*priv, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&priv->head);
+
+ list_for_each_entry(info, &reboot->head, list) {
+ sysfs_info = kzalloc_obj(*sysfs_info, GFP_KERNEL);
+ if (!sysfs_info) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ sysfs_info->mode = kstrdup_const(info->mode, GFP_KERNEL);
+ if (!sysfs_info->mode) {
+ kfree(sysfs_info);
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ list_add_tail(&sysfs_info->list, &priv->head);
+ }
+
+ priv->reboot_mode_device = device_create(&reboot_mode_class, NULL, 0,
+ (void *)priv, "%s",
+ reboot->dev->driver->name);
+ if (IS_ERR(priv->reboot_mode_device)) {
+ ret = PTR_ERR(priv->reboot_mode_device);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ reboot_mode_release_list(priv);
+ kfree(priv);
+ return ret;
+}
+
/**
* reboot_mode_register - register a reboot mode driver
* @reboot: reboot mode driver
@@ -104,16 +211,49 @@ int reboot_mode_register(struct reboot_mode_driver *reboot)
reboot->reboot_notifier.notifier_call = reboot_mode_notify;
register_reboot_notifier(&reboot->reboot_notifier);
+ ret = reboot_mode_create_device(reboot);
+ if (ret)
+ goto error;
+
return 0;
error:
- list_for_each_entry(info, &reboot->head, list)
- kfree_const(info->mode);
-
+ reboot_mode_unregister(reboot);
return ret;
}
EXPORT_SYMBOL_GPL(reboot_mode_register);
+static int reboot_mode_match_by_name(struct device *dev, const void *data)
+{
+ const char *name = data;
+
+ if (!dev || !data)
+ return 0;
+
+ return dev_name(dev) && strcmp(dev_name(dev), name) == 0;
+}
+
+static inline void reboot_mode_unregister_device(struct reboot_mode_driver *reboot)
+{
+ struct reboot_mode_sysfs_data *priv;
+ struct device *reboot_mode_device;
+
+ reboot_mode_device = class_find_device(&reboot_mode_class, NULL, reboot->dev->driver->name,
+ reboot_mode_match_by_name);
+
+ if (!reboot_mode_device)
+ return;
+
+ priv = dev_get_drvdata(reboot_mode_device);
+ device_unregister(reboot_mode_device);
+
+ if (!priv)
+ return;
+
+ reboot_mode_release_list(priv);
+ kfree(priv);
+}
+
/**
* reboot_mode_unregister - unregister a reboot mode driver
* @reboot: reboot mode driver
@@ -123,6 +263,7 @@ int reboot_mode_unregister(struct reboot_mode_driver *reboot)
struct mode_info *info;
unregister_reboot_notifier(&reboot->reboot_notifier);
+ reboot_mode_unregister_device(reboot);
list_for_each_entry(info, &reboot->head, list)
kfree_const(info->mode);
@@ -190,6 +331,19 @@ void devm_reboot_mode_unregister(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_reboot_mode_unregister);
+static int __init reboot_mode_init(void)
+{
+ return class_register(&reboot_mode_class);
+}
+
+static void __exit reboot_mode_exit(void)
+{
+ class_unregister(&reboot_mode_class);
+}
+
+subsys_initcall(reboot_mode_init);
+module_exit(reboot_mode_exit);
+
MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
MODULE_DESCRIPTION("System reboot mode core library");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/sc27xx-poweroff.c b/drivers/power/reset/sc27xx-poweroff.c
index 90287c31992c..393bd1c33b73 100644
--- a/drivers/power/reset/sc27xx-poweroff.c
+++ b/drivers/power/reset/sc27xx-poweroff.c
@@ -28,7 +28,7 @@ static struct regmap *regmap;
* taking cpus down to avoid racing regmap or spi mutex lock when poweroff
* system through PMIC.
*/
-static void sc27xx_poweroff_shutdown(void)
+static void sc27xx_poweroff_shutdown(void *data)
{
#ifdef CONFIG_HOTPLUG_CPU
int cpu;
@@ -40,10 +40,14 @@ static void sc27xx_poweroff_shutdown(void)
#endif
}
-static struct syscore_ops poweroff_syscore_ops = {
+static const struct syscore_ops poweroff_syscore_ops = {
.shutdown = sc27xx_poweroff_shutdown,
};
+static struct syscore poweroff_syscore = {
+ .ops = &poweroff_syscore_ops,
+};
+
static void sc27xx_poweroff_do_poweroff(void)
{
/* Disable the external subsys connection's power firstly */
@@ -62,7 +66,7 @@ static int sc27xx_poweroff_probe(struct platform_device *pdev)
return -ENODEV;
pm_power_off = sc27xx_poweroff_do_poweroff;
- register_syscore_ops(&poweroff_syscore_ops);
+ register_syscore(&poweroff_syscore);
return 0;
}
diff --git a/drivers/power/reset/spacemit-p1-reboot.c b/drivers/power/reset/spacemit-p1-reboot.c
new file mode 100644
index 000000000000..9ec3d1fff8f3
--- /dev/null
+++ b/drivers/power/reset/spacemit-p1-reboot.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 by Aurelien Jarno
+ */
+
+#include <linux/bits.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reboot.h>
+
+/* Power Control Register 2 */
+#define PWR_CTRL2 0x7e
+#define PWR_CTRL2_SHUTDOWN BIT(2) /* Shutdown request */
+#define PWR_CTRL2_RST BIT(1) /* Reset request */
+
+static int spacemit_p1_pwroff_handler(struct sys_off_data *data)
+{
+ struct regmap *regmap = data->cb_data;
+ int ret;
+
+ /* Put the PMIC into shutdown state */
+ ret = regmap_set_bits(regmap, PWR_CTRL2, PWR_CTRL2_SHUTDOWN);
+ if (ret) {
+ dev_err(data->dev, "shutdown failed: %d\n", ret);
+ return notifier_from_errno(ret);
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int spacemit_p1_restart_handler(struct sys_off_data *data)
+{
+ struct regmap *regmap = data->cb_data;
+ int ret;
+
+ /* Put the PMIC into reset state */
+ ret = regmap_set_bits(regmap, PWR_CTRL2, PWR_CTRL2_RST);
+ if (ret) {
+ dev_err(data->dev, "restart failed: %d\n", ret);
+ return notifier_from_errno(ret);
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int spacemit_p1_reboot_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct regmap *regmap;
+ int ret;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return -ENODEV;
+
+ ret = devm_register_power_off_handler(dev, &spacemit_p1_pwroff_handler,
+ regmap);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register power off handler\n");
+
+ ret = devm_register_restart_handler(dev, spacemit_p1_restart_handler,
+ regmap);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register restart handler\n");
+
+ return 0;
+}
+
+static const struct platform_device_id spacemit_p1_reboot_id_table[] = {
+ { "spacemit-p1-reboot", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, spacemit_p1_reboot_id_table);
+
+static struct platform_driver spacemit_p1_reboot_driver = {
+ .driver = {
+ .name = "spacemit-p1-reboot",
+ },
+ .probe = spacemit_p1_reboot_probe,
+ .id_table = spacemit_p1_reboot_id_table,
+};
+module_platform_driver(spacemit_p1_reboot_driver);
+
+MODULE_DESCRIPTION("SpacemiT P1 reboot/poweroff driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/reset/syscon-reboot.c b/drivers/power/reset/syscon-reboot.c
index d623d77e657e..2e2cf5f62d73 100644
--- a/drivers/power/reset/syscon-reboot.c
+++ b/drivers/power/reset/syscon-reboot.c
@@ -14,11 +14,24 @@
#include <linux/reboot.h>
#include <linux/regmap.h>
-struct syscon_reboot_context {
- struct regmap *map;
+struct reboot_mode_bits {
u32 offset;
- u32 value;
u32 mask;
+ u32 value;
+ bool valid;
+};
+
+struct reboot_data {
+ struct reboot_mode_bits mode_bits[REBOOT_SOFT + 1];
+ struct reboot_mode_bits catchall;
+};
+
+struct syscon_reboot_context {
+ struct regmap *map;
+
+ const struct reboot_data *rd; /* from of match data, if any */
+ struct reboot_mode_bits catchall; /* from DT */
+
struct notifier_block restart_handler;
};
@@ -28,9 +41,21 @@ static int syscon_restart_handle(struct notifier_block *this,
struct syscon_reboot_context *ctx =
container_of(this, struct syscon_reboot_context,
restart_handler);
+ const struct reboot_mode_bits *mode_bits;
+
+ if (ctx->rd) {
+ if (mode < ARRAY_SIZE(ctx->rd->mode_bits) &&
+ ctx->rd->mode_bits[mode].valid)
+ mode_bits = &ctx->rd->mode_bits[mode];
+ else
+ mode_bits = &ctx->rd->catchall;
+ } else {
+ mode_bits = &ctx->catchall;
+ }
/* Issue the reboot */
- regmap_update_bits(ctx->map, ctx->offset, ctx->mask, ctx->value);
+ regmap_update_bits(ctx->map, mode_bits->offset, mode_bits->mask,
+ mode_bits->value);
mdelay(1000);
@@ -42,7 +67,6 @@ static int syscon_reboot_probe(struct platform_device *pdev)
{
struct syscon_reboot_context *ctx;
struct device *dev = &pdev->dev;
- int mask_err, value_err;
int priority;
int err;
@@ -60,24 +84,33 @@ static int syscon_reboot_probe(struct platform_device *pdev)
if (of_property_read_s32(pdev->dev.of_node, "priority", &priority))
priority = 192;
- if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset))
- if (of_property_read_u32(pdev->dev.of_node, "reg", &ctx->offset))
- return -EINVAL;
+ ctx->rd = of_device_get_match_data(dev);
+ if (!ctx->rd) {
+ int mask_err, value_err;
- value_err = of_property_read_u32(pdev->dev.of_node, "value", &ctx->value);
- mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask);
- if (value_err && mask_err) {
- dev_err(dev, "unable to read 'value' and 'mask'");
- return -EINVAL;
- }
+ if (of_property_read_u32(pdev->dev.of_node, "offset",
+ &ctx->catchall.offset) &&
+ of_property_read_u32(pdev->dev.of_node, "reg",
+ &ctx->catchall.offset))
+ return -EINVAL;
- if (value_err) {
- /* support old binding */
- ctx->value = ctx->mask;
- ctx->mask = 0xFFFFFFFF;
- } else if (mask_err) {
- /* support value without mask*/
- ctx->mask = 0xFFFFFFFF;
+ value_err = of_property_read_u32(pdev->dev.of_node, "value",
+ &ctx->catchall.value);
+ mask_err = of_property_read_u32(pdev->dev.of_node, "mask",
+ &ctx->catchall.mask);
+ if (value_err && mask_err) {
+ dev_err(dev, "unable to read 'value' and 'mask'");
+ return -EINVAL;
+ }
+
+ if (value_err) {
+ /* support old binding */
+ ctx->catchall.value = ctx->catchall.mask;
+ ctx->catchall.mask = 0xFFFFFFFF;
+ } else if (mask_err) {
+ /* support value without mask */
+ ctx->catchall.mask = 0xFFFFFFFF;
+ }
}
ctx->restart_handler.notifier_call = syscon_restart_handle;
@@ -89,7 +122,30 @@ static int syscon_reboot_probe(struct platform_device *pdev)
return err;
}
+static const struct reboot_data gs101_reboot_data = {
+ .mode_bits = {
+ [REBOOT_WARM] = {
+ .offset = 0x3a00, /* SYSTEM_CONFIGURATION */
+ .mask = 0x00000002, /* SWRESET_SYSTEM */
+ .value = 0x00000002,
+ .valid = true,
+ },
+ [REBOOT_SOFT] = {
+ .offset = 0x3a00, /* SYSTEM_CONFIGURATION */
+ .mask = 0x00000002, /* SWRESET_SYSTEM */
+ .value = 0x00000002,
+ .valid = true,
+ },
+ },
+ .catchall = {
+ .offset = 0x3e9c, /* PAD_CTRL_PWR_HOLD */
+ .mask = 0x00000100,
+ .value = 0x00000000,
+ },
+};
+
static const struct of_device_id syscon_reboot_of_match[] = {
+ { .compatible = "google,gs101-reboot", .data = &gs101_reboot_data },
{ .compatible = "syscon-reboot" },
{}
};
diff --git a/drivers/power/reset/tdx-ec-poweroff.c b/drivers/power/reset/tdx-ec-poweroff.c
new file mode 100644
index 000000000000..8040aa03d74d
--- /dev/null
+++ b/drivers/power/reset/tdx-ec-poweroff.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Toradex Embedded Controller driver
+ *
+ * Copyright (C) 2025 Toradex
+ *
+ * Author: Emanuele Ghidoli <emanuele.ghidoli@toradex.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define EC_CHIP_ID_REG 0x00
+#define EC_CHIP_ID_SMARC_IMX95 0x11
+#define EC_CHIP_ID_SMARC_IMX8MP 0x12
+
+#define EC_VERSION_REG_MAJOR 0x01
+#define EC_VERSION_REG_MINOR 0x02
+#define EC_ID_VERSION_LEN 3
+
+#define EC_CMD_REG 0xD0
+#define EC_CMD_POWEROFF 0x01
+#define EC_CMD_RESET 0x02
+
+#define EC_REG_MAX 0xD0
+
+#define EC_CMD_TIMEOUT_MS 1000
+
+static const struct regmap_range volatile_ranges[] = {
+ regmap_reg_range(EC_CMD_REG, EC_CMD_REG),
+};
+
+static const struct regmap_access_table volatile_table = {
+ .yes_ranges = volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(volatile_ranges),
+};
+
+static const struct regmap_range read_ranges[] = {
+ regmap_reg_range(EC_CHIP_ID_REG, EC_VERSION_REG_MINOR),
+};
+
+static const struct regmap_access_table read_table = {
+ .yes_ranges = read_ranges,
+ .n_yes_ranges = ARRAY_SIZE(read_ranges),
+};
+
+static const struct regmap_config regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = EC_REG_MAX,
+ .cache_type = REGCACHE_RBTREE,
+ .rd_table = &read_table,
+ .volatile_table = &volatile_table,
+};
+
+static int tdx_ec_cmd(struct regmap *regmap, u8 cmd)
+{
+ int err = regmap_write(regmap, EC_CMD_REG, cmd);
+
+ if (err)
+ dev_err(regmap_get_device(regmap), "Failed to send command 0x%02X: %d\n", cmd, err);
+
+ return err;
+}
+
+static int tdx_ec_power_off(struct sys_off_data *data)
+{
+ struct regmap *regmap = data->cb_data;
+ int err;
+
+ err = tdx_ec_cmd(regmap, EC_CMD_POWEROFF);
+
+ if (err) {
+ dev_err(data->dev, "Failed to send power off command\n");
+ } else {
+ mdelay(EC_CMD_TIMEOUT_MS);
+ WARN_ONCE(1, "Unable to power off system\n");
+ }
+
+ return err ? NOTIFY_BAD : NOTIFY_DONE;
+}
+
+static int tdx_ec_restart(struct sys_off_data *data)
+{
+ struct regmap *regmap = data->cb_data;
+ int err;
+
+ err = tdx_ec_cmd(regmap, EC_CMD_RESET);
+
+ if (err) {
+ dev_err(data->dev, "Failed to send restart command\n");
+ } else {
+ mdelay(EC_CMD_TIMEOUT_MS);
+ WARN_ONCE(1, "Unable to restart system\n");
+ }
+
+ return err ? NOTIFY_BAD : NOTIFY_DONE;
+}
+
+static int tdx_ec_register_power_off_restart(struct device *dev, struct regmap *regmap)
+{
+ int err;
+
+ err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART,
+ SYS_OFF_PRIO_FIRMWARE,
+ tdx_ec_restart, regmap);
+ if (err)
+ return err;
+
+ return devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF,
+ SYS_OFF_PRIO_FIRMWARE,
+ tdx_ec_power_off, regmap);
+}
+
+static int tdx_ec_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ u8 reg_val[EC_ID_VERSION_LEN];
+ struct regmap *regmap;
+ int err;
+
+ regmap = devm_regmap_init_i2c(client, &regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ err = regmap_bulk_read(regmap, EC_CHIP_ID_REG, &reg_val, EC_ID_VERSION_LEN);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Cannot read id and version registers\n");
+
+ dev_info(dev, "Toradex Embedded Controller id %x - Firmware %u.%u\n",
+ reg_val[0], reg_val[1], reg_val[2]);
+
+ err = tdx_ec_register_power_off_restart(dev, regmap);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Cannot register system restart handler\n");
+
+ return 0;
+}
+
+static const struct of_device_id __maybe_unused of_tdx_ec_match[] = {
+ { .compatible = "toradex,smarc-ec" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_tdx_ec_match);
+
+static struct i2c_driver tdx_ec_driver = {
+ .probe = tdx_ec_probe,
+ .driver = {
+ .name = "toradex-smarc-ec",
+ .of_match_table = of_tdx_ec_match,
+ },
+};
+module_i2c_driver(tdx_ec_driver);
+
+MODULE_AUTHOR("Emanuele Ghidoli <emanuele.ghidoli@toradex.com>");
+MODULE_DESCRIPTION("Toradex SMARC Embedded Controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/reset/th1520-aon-reboot.c b/drivers/power/reset/th1520-aon-reboot.c
new file mode 100644
index 000000000000..ec249667a0ff
--- /dev/null
+++ b/drivers/power/reset/th1520-aon-reboot.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * T-HEAD TH1520 AON Firmware Reboot Driver
+ *
+ * Copyright (c) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/firmware/thead/thead,th1520-aon.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+
+#define TH1520_AON_REBOOT_PRIORITY 200
+
+struct th1520_aon_msg_empty_body {
+ struct th1520_aon_rpc_msg_hdr hdr;
+ u16 reserved[12];
+} __packed __aligned(1);
+
+static int th1520_aon_pwroff_handler(struct sys_off_data *data)
+{
+ struct th1520_aon_chan *aon_chan = data->cb_data;
+ struct th1520_aon_msg_empty_body msg = {};
+
+ msg.hdr.svc = TH1520_AON_RPC_SVC_WDG;
+ msg.hdr.func = TH1520_AON_WDG_FUNC_POWER_OFF;
+ msg.hdr.size = TH1520_AON_RPC_MSG_NUM;
+
+ th1520_aon_call_rpc(aon_chan, &msg);
+
+ return NOTIFY_DONE;
+}
+
+static int th1520_aon_restart_handler(struct sys_off_data *data)
+{
+ struct th1520_aon_chan *aon_chan = data->cb_data;
+ struct th1520_aon_msg_empty_body msg = {};
+
+ msg.hdr.svc = TH1520_AON_RPC_SVC_WDG;
+ msg.hdr.func = TH1520_AON_WDG_FUNC_RESTART;
+ msg.hdr.size = TH1520_AON_RPC_MSG_NUM;
+
+ th1520_aon_call_rpc(aon_chan, &msg);
+
+ return NOTIFY_DONE;
+}
+
+static int th1520_aon_reboot_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ int ret;
+
+ /* Expect struct th1520_aon_chan to be passed via platform_data */
+ ret = devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF,
+ TH1520_AON_REBOOT_PRIORITY,
+ th1520_aon_pwroff_handler,
+ adev->dev.platform_data);
+
+ if (ret) {
+ dev_err(dev, "Failed to register power off handler\n");
+ return ret;
+ }
+
+ ret = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART,
+ TH1520_AON_REBOOT_PRIORITY,
+ th1520_aon_restart_handler,
+ adev->dev.platform_data);
+
+ if (ret) {
+ dev_err(dev, "Failed to register restart handler\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct auxiliary_device_id th1520_aon_reboot_id_table[] = {
+ { .name = "th1520_pm_domains.reboot" },
+ {},
+};
+MODULE_DEVICE_TABLE(auxiliary, th1520_aon_reboot_id_table);
+
+static struct auxiliary_driver th1520_aon_reboot_driver = {
+ .driver = {
+ .name = "th1520-aon-reboot",
+ },
+ .probe = th1520_aon_reboot_probe,
+ .id_table = th1520_aon_reboot_id_table,
+};
+module_auxiliary_driver(th1520_aon_reboot_driver);
+
+MODULE_AUTHOR("Icenowy Zheng <uwu@icenowy.me>");
+MODULE_DESCRIPTION("T-HEAD TH1520 AON-firmware-based reboot driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig
index ddcc42a98492..1c5f5820f5b7 100644
--- a/drivers/power/sequencing/Kconfig
+++ b/drivers/power/sequencing/Kconfig
@@ -16,7 +16,7 @@ if POWER_SEQUENCING
config POWER_SEQUENCING_QCOM_WCN
tristate "Qualcomm WCN family PMU driver"
default m if ARCH_QCOM
- depends on OF
+ depends on OF || COMPILE_TEST
help
Say Y here to enable the power sequencing driver for Qualcomm
WCN Bluetooth/WLAN chipsets.
@@ -27,4 +27,23 @@ config POWER_SEQUENCING_QCOM_WCN
this driver is needed for correct power control or else we'd risk not
respecting the required delays between enabling Bluetooth and WLAN.
+config POWER_SEQUENCING_TH1520_GPU
+ tristate "T-HEAD TH1520 GPU power sequencing driver"
+ depends on (ARCH_THEAD && AUXILIARY_BUS) || COMPILE_TEST
+ help
+ Say Y here to enable the power sequencing driver for the TH1520 SoC
+ GPU. This driver handles the complex clock and reset sequence
+ required to power on the Imagination BXM GPU on this platform.
+
+config POWER_SEQUENCING_PCIE_M2
+ tristate "PCIe M.2 connector power sequencing driver"
+ depends on OF
+ depends on PCI
+ depends on SERIAL_DEV_BUS
+ select OF_DYNAMIC
+ help
+ Say Y here to enable the power sequencing driver for PCIe M.2
+ connectors. This driver handles the power sequencing for the M.2
+ connectors exposing multiple interfaces like PCIe, SATA, UART, etc...
+
endif
diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/Makefile
index 2eec2df7912d..0911d4618298 100644
--- a/drivers/power/sequencing/Makefile
+++ b/drivers/power/sequencing/Makefile
@@ -4,3 +4,5 @@ obj-$(CONFIG_POWER_SEQUENCING) += pwrseq-core.o
pwrseq-core-y := core.o
obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) += pwrseq-qcom-wcn.o
+obj-$(CONFIG_POWER_SEQUENCING_TH1520_GPU) += pwrseq-thead-gpu.o
+obj-$(CONFIG_POWER_SEQUENCING_PCIE_M2) += pwrseq-pcie-m2.o
diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c
index 0ffc259c6bb6..4dff71be11b6 100644
--- a/drivers/power/sequencing/core.c
+++ b/drivers/power/sequencing/core.c
@@ -90,7 +90,7 @@ static struct pwrseq_unit *pwrseq_unit_new(const struct pwrseq_unit_data *data)
{
struct pwrseq_unit *unit;
- unit = kzalloc(sizeof(*unit), GFP_KERNEL);
+ unit = kzalloc_obj(*unit);
if (!unit)
return NULL;
@@ -138,7 +138,7 @@ static struct pwrseq_unit_dep *pwrseq_unit_dep_new(struct pwrseq_unit *unit)
{
struct pwrseq_unit_dep *dep;
- dep = kzalloc(sizeof(*dep), GFP_KERNEL);
+ dep = kzalloc_obj(*dep);
if (!dep)
return NULL;
@@ -195,7 +195,7 @@ pwrseq_target_new(const struct pwrseq_target_data *data)
{
struct pwrseq_target *target;
- target = kzalloc(sizeof(*target), GFP_KERNEL);
+ target = kzalloc_obj(*target);
if (!target)
return NULL;
@@ -628,7 +628,7 @@ static int pwrseq_match_device(struct device *pwrseq_dev, void *data)
return 0;
ret = pwrseq->match(pwrseq, match_data->dev);
- if (ret <= 0)
+ if (ret == PWRSEQ_NO_MATCH || ret < 0)
return ret;
/* We got the matching device, let's find the right target. */
@@ -651,7 +651,7 @@ static int pwrseq_match_device(struct device *pwrseq_dev, void *data)
match_data->desc->pwrseq = pwrseq_device_get(pwrseq);
- return 1;
+ return PWRSEQ_MATCH_OK;
}
/**
@@ -669,8 +669,7 @@ struct pwrseq_desc *pwrseq_get(struct device *dev, const char *target)
struct pwrseq_match_data match_data;
int ret;
- struct pwrseq_desc *desc __free(kfree) = kzalloc(sizeof(*desc),
- GFP_KERNEL);
+ struct pwrseq_desc *desc __free(kfree) = kzalloc_obj(*desc);
if (!desc)
return ERR_PTR(-ENOMEM);
@@ -684,7 +683,7 @@ struct pwrseq_desc *pwrseq_get(struct device *dev, const char *target)
pwrseq_match_device);
if (ret < 0)
return ERR_PTR(ret);
- if (ret == 0)
+ if (ret == PWRSEQ_NO_MATCH)
/* No device matched. */
return ERR_PTR(-EPROBE_DEFER);
@@ -914,8 +913,10 @@ int pwrseq_power_on(struct pwrseq_desc *desc)
if (target->post_enable) {
ret = target->post_enable(pwrseq);
if (ret) {
- pwrseq_unit_disable(pwrseq, unit);
- desc->powered_on = false;
+ scoped_guard(mutex, &pwrseq->state_lock) {
+ pwrseq_unit_disable(pwrseq, unit);
+ desc->powered_on = false;
+ }
}
}
diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c
new file mode 100644
index 000000000000..ef69ae268059
--- /dev/null
+++ b/drivers/power/sequencing/pwrseq-pcie-m2.c
@@ -0,0 +1,486 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * Author: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/pwrseq/provider.h>
+#include <linux/regulator/consumer.h>
+#include <linux/serdev.h>
+#include <linux/slab.h>
+
+struct pwrseq_pcie_m2_pdata {
+ const struct pwrseq_target_data **targets;
+};
+
+struct pwrseq_pcie_m2_ctx {
+ struct pwrseq_device *pwrseq;
+ struct device_node *of_node;
+ const struct pwrseq_pcie_m2_pdata *pdata;
+ struct regulator_bulk_data *regs;
+ size_t num_vregs;
+ struct notifier_block nb;
+ struct gpio_desc *w_disable1_gpio;
+ struct gpio_desc *w_disable2_gpio;
+ struct serdev_device *serdev;
+ struct of_changeset *ocs;
+ struct device *dev;
+};
+
+static int pwrseq_pcie_m2_vregs_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_bulk_enable(ctx->num_vregs, ctx->regs);
+}
+
+static int pwrseq_pcie_m2_vregs_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_bulk_disable(ctx->num_vregs, ctx->regs);
+}
+
+static const struct pwrseq_unit_data pwrseq_pcie_m2_vregs_unit_data = {
+ .name = "regulators-enable",
+ .enable = pwrseq_pcie_m2_vregs_enable,
+ .disable = pwrseq_pcie_m2_vregs_disable,
+};
+
+static const struct pwrseq_unit_data *pwrseq_pcie_m2_unit_deps[] = {
+ &pwrseq_pcie_m2_vregs_unit_data,
+ NULL
+};
+
+static int pwrseq_pci_m2_e_uart_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 0);
+}
+
+static int pwrseq_pci_m2_e_uart_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 1);
+}
+
+static const struct pwrseq_unit_data pwrseq_pcie_m2_e_uart_unit_data = {
+ .name = "uart-enable",
+ .deps = pwrseq_pcie_m2_unit_deps,
+ .enable = pwrseq_pci_m2_e_uart_enable,
+ .disable = pwrseq_pci_m2_e_uart_disable,
+};
+
+static int pwrseq_pci_m2_e_pcie_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 0);
+}
+
+static int pwrseq_pci_m2_e_pcie_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 1);
+}
+
+static const struct pwrseq_unit_data pwrseq_pcie_m2_e_pcie_unit_data = {
+ .name = "pcie-enable",
+ .deps = pwrseq_pcie_m2_unit_deps,
+ .enable = pwrseq_pci_m2_e_pcie_enable,
+ .disable = pwrseq_pci_m2_e_pcie_disable,
+};
+
+static const struct pwrseq_unit_data pwrseq_pcie_m2_m_pcie_unit_data = {
+ .name = "pcie-enable",
+ .deps = pwrseq_pcie_m2_unit_deps,
+};
+
+static int pwrseq_pcie_m2_e_pwup_delay(struct pwrseq_device *pwrseq)
+{
+ /*
+ * FIXME: This delay is only required for some Qcom WLAN/BT cards like
+ * WCN7850 and not for all devices. But currently, there is no way to
+ * identify the device model before enumeration.
+ */
+ msleep(50);
+
+ return 0;
+}
+
+static const struct pwrseq_target_data pwrseq_pcie_m2_e_uart_target_data = {
+ .name = "uart",
+ .unit = &pwrseq_pcie_m2_e_uart_unit_data,
+ .post_enable = pwrseq_pcie_m2_e_pwup_delay,
+};
+
+static const struct pwrseq_target_data pwrseq_pcie_m2_e_pcie_target_data = {
+ .name = "pcie",
+ .unit = &pwrseq_pcie_m2_e_pcie_unit_data,
+ .post_enable = pwrseq_pcie_m2_e_pwup_delay,
+};
+
+static const struct pwrseq_target_data pwrseq_pcie_m2_m_pcie_target_data = {
+ .name = "pcie",
+ .unit = &pwrseq_pcie_m2_m_pcie_unit_data,
+};
+
+static const struct pwrseq_target_data *pwrseq_pcie_m2_e_targets[] = {
+ &pwrseq_pcie_m2_e_pcie_target_data,
+ &pwrseq_pcie_m2_e_uart_target_data,
+ NULL
+};
+
+static const struct pwrseq_target_data *pwrseq_pcie_m2_m_targets[] = {
+ &pwrseq_pcie_m2_m_pcie_target_data,
+ NULL
+};
+
+static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_e_of_data = {
+ .targets = pwrseq_pcie_m2_e_targets,
+};
+
+static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_m_of_data = {
+ .targets = pwrseq_pcie_m2_m_targets,
+};
+
+static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq,
+ struct device *dev)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+ struct device_node *endpoint __free(device_node) = NULL;
+
+ /*
+ * Traverse the 'remote-endpoint' nodes and check if the remote node's
+ * parent matches the OF node of 'dev'.
+ */
+ for_each_endpoint_of_node(ctx->of_node, endpoint) {
+ struct device_node *remote __free(device_node) =
+ of_graph_get_remote_port_parent(endpoint);
+ if (remote && (remote == dev_of_node(dev)))
+ return PWRSEQ_MATCH_OK;
+ }
+
+ return PWRSEQ_NO_MATCH;
+}
+
+static int pwrseq_m2_pcie_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx,
+ struct device_node *parent)
+{
+ struct device *dev = ctx->dev;
+ struct device_node *np;
+ int ret;
+
+ ctx->ocs = kzalloc_obj(*ctx->ocs);
+ if (!ctx->ocs)
+ return -ENOMEM;
+
+ of_changeset_init(ctx->ocs);
+
+ np = of_changeset_create_node(ctx->ocs, parent, "bluetooth");
+ if (!np) {
+ dev_err(dev, "Failed to create bluetooth node\n");
+ ret = -ENODEV;
+ goto err_destroy_changeset;
+ }
+
+ ret = of_changeset_add_prop_string(ctx->ocs, np, "compatible", "qcom,wcn7850-bt");
+ if (ret) {
+ dev_err(dev, "Failed to add bluetooth compatible: %d\n", ret);
+ goto err_destroy_changeset;
+ }
+
+ ret = of_changeset_apply(ctx->ocs);
+ if (ret) {
+ dev_err(dev, "Failed to apply changeset: %d\n", ret);
+ goto err_destroy_changeset;
+ }
+
+ ret = device_add_of_node(&ctx->serdev->dev, np);
+ if (ret) {
+ dev_err(dev, "Failed to add OF node: %d\n", ret);
+ goto err_revert_changeset;
+ }
+
+ return 0;
+
+err_revert_changeset:
+ of_changeset_revert(ctx->ocs);
+err_destroy_changeset:
+ of_changeset_destroy(ctx->ocs);
+ kfree(ctx->ocs);
+ ctx->ocs = NULL;
+
+ return ret;
+}
+
+static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx)
+{
+ struct serdev_controller *serdev_ctrl;
+ struct device *dev = ctx->dev;
+ int ret;
+
+ struct device_node *serdev_parent __free(device_node) =
+ of_graph_get_remote_node(dev_of_node(ctx->dev), 3, 0);
+ if (!serdev_parent)
+ return 0;
+
+ serdev_ctrl = of_find_serdev_controller_by_node(serdev_parent);
+ if (!serdev_ctrl)
+ return 0;
+
+ /* Bail out if the device was already attached to this controller */
+ if (serdev_ctrl->serdev) {
+ serdev_controller_put(serdev_ctrl);
+ return 0;
+ }
+
+ ctx->serdev = serdev_device_alloc(serdev_ctrl);
+ if (!ctx->serdev) {
+ ret = -ENOMEM;
+ goto err_put_ctrl;
+ }
+
+ ret = pwrseq_m2_pcie_create_bt_node(ctx, serdev_parent);
+ if (ret)
+ goto err_free_serdev;
+
+ ret = serdev_device_add(ctx->serdev);
+ if (ret) {
+ dev_err(dev, "Failed to add serdev for WCN7850: %d\n", ret);
+ goto err_free_dt_node;
+ }
+
+ serdev_controller_put(serdev_ctrl);
+
+ return 0;
+
+err_free_dt_node:
+ device_remove_of_node(&ctx->serdev->dev);
+ of_changeset_revert(ctx->ocs);
+ of_changeset_destroy(ctx->ocs);
+ kfree(ctx->ocs);
+ ctx->ocs = NULL;
+err_free_serdev:
+ serdev_device_put(ctx->serdev);
+ ctx->serdev = NULL;
+err_put_ctrl:
+ serdev_controller_put(serdev_ctrl);
+
+ return ret;
+}
+
+static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx)
+{
+ if (ctx->serdev) {
+ device_remove_of_node(&ctx->serdev->dev);
+ serdev_device_remove(ctx->serdev);
+ ctx->serdev = NULL;
+ }
+
+ if (ctx->ocs) {
+ of_changeset_revert(ctx->ocs);
+ of_changeset_destroy(ctx->ocs);
+ kfree(ctx->ocs);
+ ctx->ocs = NULL;
+ }
+}
+
+static int pwrseq_m2_pcie_notify(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = container_of(nb, struct pwrseq_pcie_m2_ctx, nb);
+ struct pci_dev *pdev = to_pci_dev(data);
+ int ret;
+
+ /*
+ * Check whether the PCI device is associated with this M.2 connector or
+ * not, by comparing the OF node of the PCI device parent and the Port 0
+ * (PCIe) remote node parent OF node.
+ */
+ struct device_node *pci_parent __free(device_node) =
+ of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0);
+ if (!pci_parent || (pci_parent != pdev->dev.parent->of_node))
+ return NOTIFY_DONE;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ /* Create serdev device for WCN7850 */
+ if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107) {
+ ret = pwrseq_pcie_m2_create_serdev(ctx);
+ if (ret)
+ return notifier_from_errno(ret);
+ }
+ break;
+ case BUS_NOTIFY_REMOVED_DEVICE:
+ /* Destroy serdev device for WCN7850 */
+ if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107)
+ pwrseq_pcie_m2_remove_serdev(ctx);
+
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static bool pwrseq_pcie_m2_check_remote_node(struct device *dev, u8 port, u8 endpoint,
+ const char *node)
+{
+ struct device_node *remote __free(device_node) =
+ of_graph_get_remote_node(dev_of_node(dev), port, endpoint);
+
+ if (remote && of_node_name_eq(remote, node))
+ return true;
+
+ return false;
+}
+
+/*
+ * If the connector exposes a non-discoverable bus like UART, the respective
+ * protocol device needs to be created manually with the help of the notifier
+ * of the discoverable bus like PCIe.
+ */
+static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx, struct device *dev)
+{
+ int ret;
+
+ /*
+ * Register a PCI notifier for Key E connector that has PCIe as Port
+ * 0/Endpoint 0 interface and Serial as Port 3/Endpoint 0 interface.
+ */
+ if (pwrseq_pcie_m2_check_remote_node(dev, 3, 0, "serial")) {
+ if (pwrseq_pcie_m2_check_remote_node(dev, 0, 0, "pcie")) {
+ ctx->dev = dev;
+ ctx->nb.notifier_call = pwrseq_m2_pcie_notify;
+ ret = bus_register_notifier(&pci_bus_type, &ctx->nb);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register notifier for serdev\n");
+ }
+ }
+
+ return 0;
+}
+
+static int pwrseq_pcie_m2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwrseq_pcie_m2_ctx *ctx;
+ struct pwrseq_config config = {};
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, ctx);
+ ctx->of_node = dev_of_node(dev);
+ ctx->pdata = device_get_match_data(dev);
+ if (!ctx->pdata)
+ return dev_err_probe(dev, -ENODEV,
+ "Failed to obtain platform data\n");
+
+ /*
+ * Currently, of_regulator_bulk_get_all() is the only regulator API that
+ * allows to get all supplies in the devicetree node without manually
+ * specifying them.
+ */
+ ret = of_regulator_bulk_get_all(dev, dev_of_node(dev), &ctx->regs);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to get all regulators\n");
+
+ ctx->num_vregs = ret;
+
+ ctx->w_disable1_gpio = devm_gpiod_get_optional(dev, "w-disable1", GPIOD_OUT_HIGH);
+ if (IS_ERR(ctx->w_disable1_gpio)) {
+ ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable1_gpio),
+ "Failed to get the W_DISABLE_1# GPIO\n");
+ goto err_free_regulators;
+ }
+
+ ctx->w_disable2_gpio = devm_gpiod_get_optional(dev, "w-disable2", GPIOD_OUT_HIGH);
+ if (IS_ERR(ctx->w_disable2_gpio)) {
+ ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable2_gpio),
+ "Failed to get the W_DISABLE_2# GPIO\n");
+ goto err_free_regulators;
+ }
+
+ config.parent = dev;
+ config.owner = THIS_MODULE;
+ config.drvdata = ctx;
+ config.match = pwrseq_pcie_m2_match;
+ config.targets = ctx->pdata->targets;
+
+ ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
+ if (IS_ERR(ctx->pwrseq)) {
+ ret = dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
+ "Failed to register the power sequencer\n");
+ goto err_free_regulators;
+ }
+
+ /*
+ * Register a notifier for creating protocol devices for
+ * non-discoverable busses like UART.
+ */
+ ret = pwrseq_pcie_m2_register_notifier(ctx, dev);
+ if (ret)
+ goto err_free_regulators;
+
+ return 0;
+
+err_free_regulators:
+ regulator_bulk_free(ctx->num_vregs, ctx->regs);
+
+ return ret;
+}
+
+static void pwrseq_pcie_m2_remove(struct platform_device *pdev)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = platform_get_drvdata(pdev);
+
+ bus_unregister_notifier(&pci_bus_type, &ctx->nb);
+ pwrseq_pcie_m2_remove_serdev(ctx);
+
+ regulator_bulk_free(ctx->num_vregs, ctx->regs);
+}
+
+static const struct of_device_id pwrseq_pcie_m2_of_match[] = {
+ {
+ .compatible = "pcie-m2-m-connector",
+ .data = &pwrseq_pcie_m2_m_of_data,
+ },
+ {
+ .compatible = "pcie-m2-e-connector",
+ .data = &pwrseq_pcie_m2_e_of_data,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pwrseq_pcie_m2_of_match);
+
+static struct platform_driver pwrseq_pcie_m2_driver = {
+ .driver = {
+ .name = "pwrseq-pcie-m2",
+ .of_match_table = pwrseq_pcie_m2_of_match,
+ },
+ .probe = pwrseq_pcie_m2_probe,
+ .remove = pwrseq_pcie_m2_remove,
+};
+module_platform_driver(pwrseq_pcie_m2_driver);
+
+MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>");
+MODULE_DESCRIPTION("Power Sequencing driver for PCIe M.2 connector");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c
index e8f5030f2639..b55b4317e21b 100644
--- a/drivers/power/sequencing/pwrseq-qcom-wcn.c
+++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/pwrseq/provider.h>
#include <linux/string.h>
@@ -23,6 +24,8 @@ struct pwrseq_qcom_wcn_pdata {
unsigned int pwup_delay_ms;
unsigned int gpio_enable_delay_ms;
const struct pwrseq_target_data **targets;
+ bool has_vddio; /* separate VDD IO regulator */
+ int (*match)(struct pwrseq_device *pwrseq, struct device *dev);
};
struct pwrseq_qcom_wcn_ctx {
@@ -30,6 +33,7 @@ struct pwrseq_qcom_wcn_ctx {
struct device_node *of_node;
const struct pwrseq_qcom_wcn_pdata *pdata;
struct regulator_bulk_data *regs;
+ struct regulator *vddio;
struct gpio_desc *bt_gpio;
struct gpio_desc *wlan_gpio;
struct gpio_desc *xo_clk_gpio;
@@ -52,6 +56,26 @@ static void pwrseq_qcom_wcn_ensure_gpio_delay(struct pwrseq_qcom_wcn_ctx *ctx)
msleep(ctx->pdata->gpio_enable_delay_ms - diff_msecs);
}
+static int pwrseq_qcom_wcn_vddio_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_enable(ctx->vddio);
+}
+
+static int pwrseq_qcom_wcn_vddio_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_disable(ctx->vddio);
+}
+
+static const struct pwrseq_unit_data pwrseq_qcom_wcn_vddio_unit_data = {
+ .name = "vddio-enable",
+ .enable = pwrseq_qcom_wcn_vddio_enable,
+ .disable = pwrseq_qcom_wcn_vddio_disable,
+};
+
static int pwrseq_qcom_wcn_vregs_enable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
@@ -94,6 +118,19 @@ static const struct pwrseq_unit_data pwrseq_qcom_wcn_clk_unit_data = {
.disable = pwrseq_qcom_wcn_clk_disable,
};
+static const struct pwrseq_unit_data *pwrseq_qcom_wcn3990_unit_deps[] = {
+ &pwrseq_qcom_wcn_vddio_unit_data,
+ &pwrseq_qcom_wcn_vregs_unit_data,
+ NULL,
+};
+
+static const struct pwrseq_unit_data pwrseq_qcom_wcn3990_unit_data = {
+ .name = "clock-enable",
+ .deps = pwrseq_qcom_wcn3990_unit_deps,
+ .enable = pwrseq_qcom_wcn_clk_enable,
+ .disable = pwrseq_qcom_wcn_clk_disable,
+};
+
static const struct pwrseq_unit_data *pwrseq_qcom_wcn_unit_deps[] = {
&pwrseq_qcom_wcn_vregs_unit_data,
&pwrseq_qcom_wcn_clk_unit_data,
@@ -155,7 +192,7 @@ static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = {
};
static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_bt_unit_data = {
- .name = "wlan-enable",
+ .name = "bluetooth-enable",
.deps = pwrseq_qcom_wcn6855_unit_deps,
.enable = pwrseq_qcom_wcn_bt_enable,
.disable = pwrseq_qcom_wcn_bt_disable,
@@ -229,6 +266,17 @@ static const struct pwrseq_target_data pwrseq_qcom_wcn_wlan_target_data = {
.post_enable = pwrseq_qcom_wcn_pwup_delay,
};
+/* There are no separate BT and WLAN enablement pins */
+static const struct pwrseq_target_data pwrseq_qcom_wcn3990_bt_target_data = {
+ .name = "bluetooth",
+ .unit = &pwrseq_qcom_wcn3990_unit_data,
+};
+
+static const struct pwrseq_target_data pwrseq_qcom_wcn3990_wlan_target_data = {
+ .name = "wlan",
+ .unit = &pwrseq_qcom_wcn3990_unit_data,
+};
+
static const struct pwrseq_target_data pwrseq_qcom_wcn6855_bt_target_data = {
.name = "bluetooth",
.unit = &pwrseq_qcom_wcn6855_bt_unit_data,
@@ -247,6 +295,12 @@ static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = {
NULL
};
+static const struct pwrseq_target_data *pwrseq_qcom_wcn3990_targets[] = {
+ &pwrseq_qcom_wcn3990_bt_target_data,
+ &pwrseq_qcom_wcn3990_wlan_target_data,
+ NULL
+};
+
static const struct pwrseq_target_data *pwrseq_qcom_wcn6855_targets[] = {
&pwrseq_qcom_wcn6855_bt_target_data,
&pwrseq_qcom_wcn6855_wlan_target_data,
@@ -272,6 +326,26 @@ static const struct pwrseq_qcom_wcn_pdata pwrseq_qca6390_of_data = {
.targets = pwrseq_qcom_wcn_targets,
};
+static const char *const pwrseq_wcn3990_vregs[] = {
+ /* vddio is handled separately */
+ "vddxo",
+ "vddrf",
+ "vddch0",
+ "vddch1",
+};
+
+static int pwrseq_qcom_wcn3990_match(struct pwrseq_device *pwrseq,
+ struct device *dev);
+
+static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn3990_of_data = {
+ .vregs = pwrseq_wcn3990_vregs,
+ .num_vregs = ARRAY_SIZE(pwrseq_wcn3990_vregs),
+ .pwup_delay_ms = 50,
+ .targets = pwrseq_qcom_wcn3990_targets,
+ .has_vddio = true,
+ .match = pwrseq_qcom_wcn3990_match,
+};
+
static const char *const pwrseq_wcn6750_vregs[] = {
"vddaon",
"vddasd",
@@ -328,8 +402,9 @@ static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn7850_of_data = {
.targets = pwrseq_qcom_wcn_targets,
};
-static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
- struct device *dev)
+static int pwrseq_qcom_wcn_match_regulator(struct pwrseq_device *pwrseq,
+ struct device *dev,
+ const char *name)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
struct device_node *dev_node = dev->of_node;
@@ -340,13 +415,13 @@ static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
* 'vddaon-supply' property and whether it leads us to the right
* device.
*/
- if (!of_property_present(dev_node, "vddaon-supply"))
- return 0;
+ if (!of_property_present(dev_node, name))
+ return PWRSEQ_NO_MATCH;
struct device_node *reg_node __free(device_node) =
- of_parse_phandle(dev_node, "vddaon-supply", 0);
+ of_parse_phandle(dev_node, name, 0);
if (!reg_node)
- return 0;
+ return PWRSEQ_NO_MATCH;
/*
* `reg_node` is the PMU AON regulator, its parent is the `regulators`
@@ -355,9 +430,29 @@ static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
*/
if (!reg_node->parent || !reg_node->parent->parent ||
reg_node->parent->parent != ctx->of_node)
- return 0;
+ return PWRSEQ_NO_MATCH;
+
+ return PWRSEQ_MATCH_OK;
+}
+
+static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
+ struct device *dev)
+{
+ return pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vddaon-supply");
+}
+
+static int pwrseq_qcom_wcn3990_match(struct pwrseq_device *pwrseq,
+ struct device *dev)
+{
+ int ret;
+
+ /* BT device */
+ ret = pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vddio-supply");
+ if (ret == PWRSEQ_MATCH_OK)
+ return ret;
- return 1;
+ /* WiFi device match */
+ return pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vdd-1.8-xo-supply");
}
static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
@@ -373,7 +468,7 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
ctx->of_node = dev->of_node;
- ctx->pdata = of_device_get_match_data(dev);
+ ctx->pdata = device_get_match_data(dev);
if (!ctx->pdata)
return dev_err_probe(dev, -ENODEV,
"Failed to obtain platform data\n");
@@ -391,6 +486,12 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
return dev_err_probe(dev, ret,
"Failed to get all regulators\n");
+ if (ctx->pdata->has_vddio) {
+ ctx->vddio = devm_regulator_get(dev, "vddio");
+ if (IS_ERR(ctx->vddio))
+ return dev_err_probe(dev, PTR_ERR(ctx->vddio), "Failed to get VDDIO\n");
+ }
+
ctx->bt_gpio = devm_gpiod_get_optional(dev, "bt-enable", GPIOD_OUT_LOW);
if (IS_ERR(ctx->bt_gpio))
return dev_err_probe(dev, PTR_ERR(ctx->bt_gpio),
@@ -432,7 +533,7 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
config.parent = dev;
config.owner = THIS_MODULE;
config.drvdata = ctx;
- config.match = pwrseq_qcom_wcn_match;
+ config.match = ctx->pdata->match ? : pwrseq_qcom_wcn_match;
config.targets = ctx->pdata->targets;
ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
@@ -445,6 +546,26 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
static const struct of_device_id pwrseq_qcom_wcn_of_match[] = {
{
+ .compatible = "qcom,wcn3950-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3988-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3990-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3991-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3998-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
.compatible = "qcom,qca6390-pmu",
.data = &pwrseq_qca6390_of_data,
},
diff --git a/drivers/power/sequencing/pwrseq-thead-gpu.c b/drivers/power/sequencing/pwrseq-thead-gpu.c
new file mode 100644
index 000000000000..a45318b4b2c1
--- /dev/null
+++ b/drivers/power/sequencing/pwrseq-thead-gpu.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * T-HEAD TH1520 GPU Power Sequencer Driver
+ *
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ * Author: Michal Wilczynski <m.wilczynski@samsung.com>
+ *
+ * This driver implements the power sequence for the Imagination BXM-4-64
+ * GPU on the T-HEAD TH1520 SoC. The sequence requires coordinating resources
+ * from both the sequencer's parent device node (clkgen_reset) and the GPU's
+ * device node (clocks and core reset).
+ *
+ * The `match` function is used to acquire the GPU's resources when the
+ * GPU driver requests the "gpu-power" sequence target.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pwrseq/provider.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/power/thead,th1520-power.h>
+
+struct pwrseq_thead_gpu_ctx {
+ struct pwrseq_device *pwrseq;
+ struct reset_control *clkgen_reset;
+ struct device_node *aon_node;
+
+ /* Consumer resources */
+ struct device_node *consumer_node;
+ struct clk_bulk_data *clks;
+ int num_clks;
+ struct reset_control *gpu_reset;
+};
+
+static int pwrseq_thead_gpu_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+ int ret;
+
+ if (!ctx->clks || !ctx->gpu_reset)
+ return -ENODEV;
+
+ ret = clk_bulk_prepare_enable(ctx->num_clks, ctx->clks);
+ if (ret)
+ return ret;
+
+ ret = reset_control_deassert(ctx->clkgen_reset);
+ if (ret)
+ goto err_disable_clks;
+
+ /*
+ * According to the hardware manual, a delay of at least 32 clock
+ * cycles is required between de-asserting the clkgen reset and
+ * de-asserting the GPU reset. Assuming a worst-case scenario with
+ * a very high GPU clock frequency, a delay of 1 microsecond is
+ * sufficient to ensure this requirement is met across all
+ * feasible GPU clock speeds.
+ */
+ udelay(1);
+
+ ret = reset_control_deassert(ctx->gpu_reset);
+ if (ret)
+ goto err_assert_clkgen;
+
+ return 0;
+
+err_assert_clkgen:
+ reset_control_assert(ctx->clkgen_reset);
+err_disable_clks:
+ clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks);
+ return ret;
+}
+
+static int pwrseq_thead_gpu_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+ int ret = 0, err;
+
+ if (!ctx->clks || !ctx->gpu_reset)
+ return -ENODEV;
+
+ err = reset_control_assert(ctx->gpu_reset);
+ if (err)
+ ret = err;
+
+ err = reset_control_assert(ctx->clkgen_reset);
+ if (err && !ret)
+ ret = err;
+
+ clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks);
+
+ /* ret stores values of the first error code */
+ return ret;
+}
+
+static const struct pwrseq_unit_data pwrseq_thead_gpu_unit = {
+ .name = "gpu-power-sequence",
+ .enable = pwrseq_thead_gpu_enable,
+ .disable = pwrseq_thead_gpu_disable,
+};
+
+static const struct pwrseq_target_data pwrseq_thead_gpu_target = {
+ .name = "gpu-power",
+ .unit = &pwrseq_thead_gpu_unit,
+};
+
+static const struct pwrseq_target_data *pwrseq_thead_gpu_targets[] = {
+ &pwrseq_thead_gpu_target,
+ NULL
+};
+
+static int pwrseq_thead_gpu_match(struct pwrseq_device *pwrseq,
+ struct device *dev)
+{
+ struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+ static const char *const clk_names[] = { "core", "sys" };
+ struct of_phandle_args pwr_spec;
+ int i, ret;
+
+ /* We only match the specific T-HEAD TH1520 GPU compatible */
+ if (!of_device_is_compatible(dev->of_node, "thead,th1520-gpu"))
+ return PWRSEQ_NO_MATCH;
+
+ ret = of_parse_phandle_with_args(dev->of_node, "power-domains",
+ "#power-domain-cells", 0, &pwr_spec);
+ if (ret)
+ return PWRSEQ_NO_MATCH;
+
+ /* Additionally verify consumer device has AON as power-domain */
+ if (pwr_spec.np != ctx->aon_node || pwr_spec.args[0] != TH1520_GPU_PD) {
+ of_node_put(pwr_spec.np);
+ return PWRSEQ_NO_MATCH;
+ }
+
+ of_node_put(pwr_spec.np);
+
+ /* If a consumer is already bound, only allow a re-match from it */
+ if (ctx->consumer_node)
+ return ctx->consumer_node == dev->of_node ?
+ PWRSEQ_MATCH_OK : PWRSEQ_NO_MATCH;
+
+ ctx->num_clks = ARRAY_SIZE(clk_names);
+ ctx->clks = kzalloc_objs(*ctx->clks, ctx->num_clks);
+ if (!ctx->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < ctx->num_clks; i++)
+ ctx->clks[i].id = clk_names[i];
+
+ ret = clk_bulk_get(dev, ctx->num_clks, ctx->clks);
+ if (ret)
+ goto err_free_clks;
+
+ ctx->gpu_reset = reset_control_get_shared(dev, NULL);
+ if (IS_ERR(ctx->gpu_reset)) {
+ ret = PTR_ERR(ctx->gpu_reset);
+ goto err_put_clks;
+ }
+
+ ctx->consumer_node = of_node_get(dev->of_node);
+
+ return PWRSEQ_MATCH_OK;
+
+err_put_clks:
+ clk_bulk_put(ctx->num_clks, ctx->clks);
+err_free_clks:
+ kfree(ctx->clks);
+ ctx->clks = NULL;
+
+ return ret;
+}
+
+static int pwrseq_thead_gpu_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct device *parent_dev = dev->parent;
+ struct pwrseq_thead_gpu_ctx *ctx;
+ struct pwrseq_config config = {};
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->aon_node = parent_dev->of_node;
+
+ ctx->clkgen_reset =
+ devm_reset_control_get_exclusive(parent_dev, "gpu-clkgen");
+ if (IS_ERR(ctx->clkgen_reset))
+ return dev_err_probe(
+ dev, PTR_ERR(ctx->clkgen_reset),
+ "Failed to get GPU clkgen reset from parent\n");
+
+ config.parent = dev;
+ config.owner = THIS_MODULE;
+ config.drvdata = ctx;
+ config.match = pwrseq_thead_gpu_match;
+ config.targets = pwrseq_thead_gpu_targets;
+
+ ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
+ if (IS_ERR(ctx->pwrseq))
+ return dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
+ "Failed to register power sequencer\n");
+
+ auxiliary_set_drvdata(adev, ctx);
+
+ return 0;
+}
+
+static void pwrseq_thead_gpu_remove(struct auxiliary_device *adev)
+{
+ struct pwrseq_thead_gpu_ctx *ctx = auxiliary_get_drvdata(adev);
+
+ if (ctx->gpu_reset)
+ reset_control_put(ctx->gpu_reset);
+
+ if (ctx->clks) {
+ clk_bulk_put(ctx->num_clks, ctx->clks);
+ kfree(ctx->clks);
+ }
+
+ if (ctx->consumer_node)
+ of_node_put(ctx->consumer_node);
+}
+
+static const struct auxiliary_device_id pwrseq_thead_gpu_id_table[] = {
+ { .name = "th1520_pm_domains.pwrseq-gpu" },
+ {},
+};
+MODULE_DEVICE_TABLE(auxiliary, pwrseq_thead_gpu_id_table);
+
+static struct auxiliary_driver pwrseq_thead_gpu_driver = {
+ .driver = {
+ .name = "pwrseq-thead-gpu",
+ },
+ .probe = pwrseq_thead_gpu_probe,
+ .remove = pwrseq_thead_gpu_remove,
+ .id_table = pwrseq_thead_gpu_id_table,
+};
+module_auxiliary_driver(pwrseq_thead_gpu_driver);
+
+MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>");
+MODULE_DESCRIPTION("T-HEAD TH1520 GPU power sequencer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/88pm860x_charger.c b/drivers/power/supply/88pm860x_charger.c
index 2b9fcb7e71d7..8d99c6ff72ed 100644
--- a/drivers/power/supply/88pm860x_charger.c
+++ b/drivers/power/supply/88pm860x_charger.c
@@ -284,8 +284,8 @@ static int set_charging_fsm(struct pm860x_charger_info *info)
{
struct power_supply *psy;
union power_supply_propval data;
- unsigned char fsm_state[][16] = { "init", "discharge", "precharge",
- "fastcharge",
+ static const unsigned char fsm_state[][16] = {
+ "init", "discharge", "precharge", "fastcharge",
};
int ret;
int vbatt;
@@ -313,7 +313,7 @@ static int set_charging_fsm(struct pm860x_charger_info *info)
dev_dbg(info->dev, "Entering FSM:%s, Charger:%s, Battery:%s, "
"Allowed:%d\n",
- &fsm_state[info->state][0],
+ fsm_state[info->state],
(info->online) ? "online" : "N/A",
(info->present) ? "present" : "N/A", info->allowed);
dev_dbg(info->dev, "set_charging_fsm:vbatt:%d(mV)\n", vbatt);
@@ -385,7 +385,7 @@ static int set_charging_fsm(struct pm860x_charger_info *info)
}
dev_dbg(info->dev,
"Out FSM:%s, Charger:%s, Battery:%s, Allowed:%d\n",
- &fsm_state[info->state][0],
+ fsm_state[info->state],
(info->online) ? "online" : "N/A",
(info->present) ? "present" : "N/A", info->allowed);
mutex_unlock(&info->lock);
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 7b18358f194a..83392ed6a8da 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -35,6 +35,9 @@ config APM_POWER
Say Y here to enable support APM status emulation using
battery class devices.
+config ADC_BATTERY_HELPER
+ tristate
+
config GENERIC_ADC_BATTERY
tristate "Generic battery support using IIO"
depends on IIO
@@ -107,6 +110,18 @@ config BATTERY_ACT8945A
Say Y here to enable support for power supply provided by
Active-semi ActivePath ACT8945A charger.
+config BATTERY_CHAGALL
+ tristate "Pegatron Chagall battery driver"
+ depends on I2C
+ depends on LEDS_CLASS
+ help
+ Say Y to include support for Cypress CG7153AM IC based battery
+ fuel gauge with custom firmware found in Pegatron Chagall based
+ tablet line.
+
+ This driver can also be built as a module. If so, the module will be
+ called chagall-battery.
+
config BATTERY_CPCAP
tristate "Motorola CPCAP PMIC battery driver"
depends on MFD_CPCAP && IIO
@@ -161,6 +176,16 @@ config BATTERY_DS2782
Say Y here to enable support for the DS2782/DS2786 standalone battery
gas-gauge.
+config BATTERY_HUAWEI_GAOKUN
+ tristate "Huawei Matebook E Go power supply"
+ depends on EC_HUAWEI_GAOKUN
+ help
+ This driver enables battery and adapter support on the Huawei Matebook
+ E Go, which is a sc8280xp-based 2-in-1 tablet.
+
+ To compile the driver as a module, choose M here: the module will be
+ called huawei-gaokun-battery.
+
config BATTERY_LEGO_EV3
tristate "LEGO MINDSTORMS EV3 battery"
depends on OF && IIO && GPIOLIB && (ARCH_DAVINCI_DA850 || COMPILE_TEST)
@@ -204,6 +229,17 @@ config BATTERY_SAMSUNG_SDI
Say Y to enable support for Samsung SDI battery data.
These batteries are used in Samsung mobile phones.
+config BATTERY_S2MU005
+ tristate "Samsung S2MU005 PMIC fuel gauge driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the Samsung S2MU005 PMIC integrated
+ fuel gauge, which works indepenently of the PMIC battery charger
+ counterpart, and reports battery metrics.
+
+ This driver, if built as a module, will be called s2mu005-fuel-gauge.
+
config BATTERY_COLLIE
tristate "Sharp SL-5500 (collie) battery"
depends on SA1100_COLLIE && MCP_UCB1200
@@ -222,6 +258,18 @@ config BATTERY_INGENIC
This driver can also be built as a module. If so, the module will be
called ingenic-battery.
+config BATTERY_INTEL_DC_TI
+ tristate "Intel Bay / Cherry Trail Dollar Cove TI battery driver"
+ depends on INTEL_SOC_PMIC_CHTDC_TI && INTEL_DC_TI_ADC && IIO && ACPI
+ select ADC_BATTERY_HELPER
+ help
+ Choose this option if you want to monitor battery status on Intel
+ Bay Trail / Cherry Trail tablets using the Dollar Cove TI PMIC's
+ coulomb-counter as fuel-gauge.
+
+ To compile this driver as a module, choose M here: the module will be
+ called intel_dc_ti_battery.
+
config BATTERY_IPAQ_MICRO
tristate "iPAQ Atmel Micro ASIC battery driver"
depends on MFD_IPAQ_MICRO
@@ -449,11 +497,16 @@ config CHARGER_88PM860X
help
Say Y here to enable charger for Marvell 88PM860x chip.
-config CHARGER_PCF50633
- tristate "NXP PCF50633 MBC"
- depends on MFD_PCF50633
+config CHARGER_PF1550
+ tristate "NXP PF1550 battery charger driver"
+ depends on MFD_PF1550
help
- Say Y to include support for NXP PCF50633 Main Battery Charger.
+ Say Y to enable support for the NXP PF1550 battery charger.
+ The device is a single cell Li-Ion/Li-Polymer battery charger for
+ portable application.
+
+ This driver can also be built as a module. If so, the module will be
+ called pf1550-charger.
config BATTERY_RX51
tristate "Nokia RX-51 (N900) battery driver"
@@ -583,6 +636,23 @@ config CHARGER_MAX77693
help
Say Y to enable support for the Maxim MAX77693 battery charger.
+config CHARGER_MAX77705
+ tristate "Maxim MAX77705 battery charger driver"
+ depends on MFD_MAX77705
+ help
+ Say Y to enable support for the Maxim MAX77705 battery charger.
+
+config CHARGER_MAX77759
+ tristate "Maxim MAX77759 battery charger driver"
+ depends on MFD_MAX77759 && REGULATOR
+ default MFD_MAX77759
+ help
+ Say M or Y here to enable the MAX77759 battery charger. MAX77759
+ charger is a function of the MAX77759 PMIC. This is a dual input
+ switch-mode charger. This driver supports buck and OTG boost modes.
+
+ If built as a module, it will be called max77759_charger.
+
config CHARGER_MAX77976
tristate "Maxim MAX77976 battery charger driver"
depends on I2C
@@ -595,6 +665,21 @@ config CHARGER_MAX77976
This driver can also be built as a module. If so, the module will be
called max77976_charger.
+config CHARGER_MAX8971
+ tristate "Maxim MAX8971 battery charger driver"
+ depends on I2C
+ depends on EXTCON || !EXTCON
+ select REGMAP_I2C
+ help
+ The MAX8971 is a compact, high-frequency, high-efficiency switch-mode
+ charger for a one-cell lithium-ion (Li+) battery. It delivers up to
+ 1.55A of current to the battery from inputs up to 7.5V and withstands
+ transient inputs up to 22V.
+
+ Say Y to enable support for the Maxim MAX8971 battery charger.
+ This driver can also be built as a module. If so, the module will be
+ called max8971_charger.
+
config CHARGER_MAX8997
tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
depends on MFD_MAX8997 && REGULATOR_MAX8997
@@ -730,6 +815,13 @@ config CHARGER_BQ2515X
rail, ADC for battery and system monitoring, and push-button
controller.
+config CHARGER_BQ257XX
+ tristate "TI BQ257XX battery charger family"
+ depends on MFD_BQ257XX
+ help
+ Say Y to enable support for the TI BQ257XX family of battery
+ charging integrated circuits.
+
config CHARGER_BQ25890
tristate "TI BQ25890 battery charger driver"
depends on I2C
@@ -872,6 +964,21 @@ config CHARGER_RT9471
This driver can also be built as a module. If so, the module will be
called rt9471.
+config CHARGER_RT9756
+ tristate "Richtek RT9756 smart cap divider charger driver"
+ depends on I2C
+ select REGMAP_I2C
+ select LINEAR_RANGES
+ help
+ This adds support for Richtek RT9756 smart cap divider charger driver.
+ It's a high efficiency and high charge current charger. the device
+ integrates smart cap divider topology with 9-channel high speed
+ ADCs that can provide input and output voltage, current and
+ temperature monitoring.
+
+ This driver can also be built as a module. If so, the module will be
+ called rt9756.
+
config CHARGER_CROS_USBPD
tristate "ChromeOS EC based USBPD charger"
depends on CROS_USBPD_NOTIFY
@@ -937,6 +1044,15 @@ config CHARGER_UCS1002
Say Y to enable support for Microchip UCS1002 Programmable
USB Port Power Controller with Charger Emulation.
+config CHARGER_BD71828
+ tristate "Power-supply driver for ROHM BD71828 and BD71815 PMIC"
+ depends on MFD_ROHM_BD71828
+ help
+ Say Y here to enable support for charger and battery
+ in ROHM BD71815, BD71817, ROHM BD71828 power management
+ ICs. This driver gets various bits of information about battery
+ and charger states.
+
config CHARGER_BD99954
tristate "ROHM bd99954 charger driver"
depends on I2C
@@ -1006,6 +1122,7 @@ config CHARGER_SURFACE
config BATTERY_UG3105
tristate "uPI uG3105 battery monitor driver"
depends on I2C
+ select ADC_BATTERY_HELPER
help
Battery monitor driver for the uPI uG3105 battery monitor.
@@ -1037,4 +1154,15 @@ config FUEL_GAUGE_MM8013
the state of charge, temperature, cycle count, actual and design
capacity, etc.
+config MACSMC_POWER
+ tristate "Apple SMC Battery and Power Driver"
+ depends on MFD_MACSMC
+ help
+ This driver provides support for the battery and AC adapter on
+ Apple Silicon machines. It exposes battery telemetry (voltage,
+ current, health) and AC adapter status through the standard Linux
+ power supply framework.
+
+ Say Y or M here if you have an Apple Silicon based Mac.
+
endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index b55cc48a4c86..7ee839dca7f3 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -7,6 +7,7 @@ power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o
obj-$(CONFIG_POWER_SUPPLY) += power_supply.o
obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o
+obj-$(CONFIG_ADC_BATTERY_HELPER) += adc-battery-helper.o
obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o
obj-$(CONFIG_APM_POWER) += apm_power.o
@@ -23,6 +24,7 @@ obj-$(CONFIG_CHARGER_ADP5061) += adp5061.o
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
+obj-$(CONFIG_BATTERY_CHAGALL) += chagall-battery.o
obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o
obj-$(CONFIG_BATTERY_CW2015) += cw2015_battery.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
@@ -31,14 +33,17 @@ obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o
obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o
obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o
+obj-$(CONFIG_BATTERY_HUAWEI_GAOKUN) += huawei-gaokun-battery.o
obj-$(CONFIG_BATTERY_LEGO_EV3) += lego_ev3_battery.o
obj-$(CONFIG_BATTERY_LENOVO_YOGA_C630) += lenovo_yoga_c630_battery.o
obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
obj-$(CONFIG_BATTERY_QCOM_BATTMGR) += qcom_battmgr.o
obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
obj-$(CONFIG_BATTERY_SAMSUNG_SDI) += samsung-sdi-battery.o
+obj-$(CONFIG_BATTERY_S2MU005) += s2mu005-battery.o
obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
obj-$(CONFIG_BATTERY_INGENIC) += ingenic-battery.o
+obj-$(CONFIG_BATTERY_INTEL_DC_TI) += intel_dc_ti_battery.o
obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o
obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o
@@ -60,9 +65,10 @@ obj-$(CONFIG_CHARGER_RT5033) += rt5033_charger.o
obj-$(CONFIG_CHARGER_RT9455) += rt9455_charger.o
obj-$(CONFIG_CHARGER_RT9467) += rt9467-charger.o
obj-$(CONFIG_CHARGER_RT9471) += rt9471.o
+obj-$(CONFIG_CHARGER_RT9756) += rt9756.o
obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
-obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
+obj-$(CONFIG_CHARGER_PF1550) += pf1550-charger.o
obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o ab8500_chargalg.o
obj-$(CONFIG_CHARGER_CPCAP) += cpcap-charger.o
@@ -80,7 +86,9 @@ obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o
obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
+obj-$(CONFIG_CHARGER_MAX77705) += max77705_charger.o
obj-$(CONFIG_CHARGER_MAX77976) += max77976_charger.o
+obj-$(CONFIG_CHARGER_MAX8971) += max8971_charger.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
obj-$(CONFIG_CHARGER_MP2629) += mp2629_charger.o
@@ -94,6 +102,7 @@ obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o
obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o
obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o
obj-$(CONFIG_CHARGER_BQ2515X) += bq2515x_charger.o
+obj-$(CONFIG_CHARGER_BQ257XX) += bq257xx_charger.o
obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o
obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o
obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o
@@ -110,6 +119,7 @@ obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o
obj-$(CONFIG_FUEL_GAUGE_STC3117) += stc3117_fuel_gauge.o
obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o
+obj-$(CONFIG_CHARGER_BD71828) += bd71828-power.o
obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
@@ -117,5 +127,7 @@ obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
-obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_pmi8998_charger.o
+obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_smbx.o
obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o
+obj-$(CONFIG_MACSMC_POWER) += macsmc-power.o
+obj-$(CONFIG_CHARGER_MAX77759) += max77759_charger.o
diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c
index b00c84fbc33c..e5202a7b6209 100644
--- a/drivers/power/supply/ab8500_btemp.c
+++ b/drivers/power/supply/ab8500_btemp.c
@@ -667,7 +667,8 @@ static int ab8500_btemp_bind(struct device *dev, struct device *master,
/* Create a work queue for the btemp */
di->btemp_wq =
- alloc_workqueue("ab8500_btemp_wq", WQ_MEM_RECLAIM, 0);
+ alloc_workqueue("ab8500_btemp_wq", WQ_MEM_RECLAIM | WQ_PERCPU,
+ 0);
if (di->btemp_wq == NULL) {
dev_err(dev, "failed to create work queue\n");
return -ENOMEM;
diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c
index dc6c8b0dd1cf..d2b9880b0770 100644
--- a/drivers/power/supply/ab8500_chargalg.c
+++ b/drivers/power/supply/ab8500_chargalg.c
@@ -170,13 +170,13 @@ struct ab8500_chargalg_events {
* @original_iset_ua: the non optimized/maximised charger current
* @current_iset_ua: the charging current used at this moment
* @condition_cnt: number of iterations needed before a new charger current
- is set
+ * is set
* @max_current_ua: maximum charger current
* @wait_cnt: to avoid too fast current step down in case of charger
* voltage collapse, we insert this delay between step
* down
* @level: tells in how many steps the charging current has been
- increased
+ * increased
*/
struct ab8500_charge_curr_maximization {
int original_iset_ua;
@@ -199,18 +199,20 @@ enum maxim_ret {
* @charge_status: battery operating status
* @eoc_cnt: counter used to determine end-of_charge
* @maintenance_chg: indicate if maintenance charge is active
- * @t_hyst_norm temperature hysteresis when the temperature has been
+ * @t_hyst_norm: temperature hysteresis when the temperature has been
* over or under normal limits
- * @t_hyst_lowhigh temperature hysteresis when the temperature has been
+ * @t_hyst_lowhigh: temperature hysteresis when the temperature has been
* over or under the high or low limits
* @charge_state: current state of the charging algorithm
- * @ccm charging current maximization parameters
+ * @ccm: charging current maximization parameters
* @chg_info: information about connected charger types
* @batt_data: data of the battery
* @bm: Platform specific battery management information
* @parent: pointer to the struct ab8500
* @chargalg_psy: structure that holds the battery properties exposed by
* the charging algorithm
+ * @ac_chg: AC charger power supply
+ * @usb_chg: USB charger power supply
* @events: structure for information about events triggered
* @chargalg_wq: work queue for running the charging algorithm
* @chargalg_periodic_work: work to run the charging algorithm periodically
@@ -300,6 +302,7 @@ ab8500_chargalg_maintenance_timer_expired(struct hrtimer *timer)
/**
* ab8500_chargalg_state_to() - Change charge state
* @di: pointer to the ab8500_chargalg structure
+ * @state: new charge algorithm state
*
* This function gets called when a charge state change should occur
*/
@@ -763,7 +766,7 @@ static void init_maxim_chg_curr(struct ab8500_chargalg *di)
/**
* ab8500_chargalg_chg_curr_maxim - increases the charger current to
* compensate for the system load
- * @di pointer to the ab8500_chargalg structure
+ * @di: pointer to the ab8500_chargalg structure
*
* This maximization function is used to raise the charger current to get the
* battery current as close to the optimal value as possible. The battery
diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index 1042d37424f5..1813fbdfa1c1 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -3466,26 +3466,6 @@ static int ab8500_charger_probe(struct platform_device *pdev)
return ret;
}
- /* Request interrupts */
- for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
- irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
- if (irq < 0)
- return irq;
-
- ret = devm_request_threaded_irq(dev,
- irq, NULL, ab8500_charger_irq[i].isr,
- IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT,
- ab8500_charger_irq[i].name, di);
-
- if (ret != 0) {
- dev_err(dev, "failed to request %s IRQ %d: %d\n"
- , ab8500_charger_irq[i].name, irq, ret);
- return ret;
- }
- dev_dbg(dev, "Requested %s IRQ %d: %d\n",
- ab8500_charger_irq[i].name, irq, ret);
- }
-
/* initialize lock */
spin_lock_init(&di->usb_state.usb_lock);
mutex_init(&di->usb_ipt_crnt_lock);
@@ -3494,11 +3474,11 @@ static int ab8500_charger_probe(struct platform_device *pdev)
di->invalid_charger_detect_state = 0;
/* AC and USB supply config */
- ac_psy_cfg.of_node = np;
+ ac_psy_cfg.fwnode = dev_fwnode(dev);
ac_psy_cfg.supplied_to = supply_interface;
ac_psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface);
ac_psy_cfg.drv_data = &di->ac_chg;
- usb_psy_cfg.of_node = np;
+ usb_psy_cfg.fwnode = dev_fwnode(dev);
usb_psy_cfg.supplied_to = supply_interface;
usb_psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface);
usb_psy_cfg.drv_data = &di->usb_chg;
@@ -3614,6 +3594,26 @@ static int ab8500_charger_probe(struct platform_device *pdev)
return PTR_ERR(di->usb_chg.psy);
}
+ /* Request interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(dev,
+ irq, NULL, ab8500_charger_irq[i].isr,
+ IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ ab8500_charger_irq[i].name, di);
+
+ if (ret != 0) {
+ dev_err(dev, "failed to request %s IRQ %d: %d\n"
+ , ab8500_charger_irq[i].name, irq, ret);
+ return ret;
+ }
+ dev_dbg(dev, "Requested %s IRQ %d: %d\n",
+ ab8500_charger_irq[i].name, irq, ret);
+ }
+
/*
* Check what battery we have, since we always have the USB
* psy, use that as a handle.
diff --git a/drivers/power/supply/acer_a500_battery.c b/drivers/power/supply/acer_a500_battery.c
index 39d85b11a13c..daf01dc8025b 100644
--- a/drivers/power/supply/acer_a500_battery.c
+++ b/drivers/power/supply/acer_a500_battery.c
@@ -17,6 +17,7 @@
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
+#include <linux/property.h>
enum {
REG_CAPACITY,
@@ -231,7 +232,7 @@ static int a500_battery_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, bat);
- psy_cfg.of_node = pdev->dev.parent->of_node;
+ psy_cfg.fwnode = dev_fwnode(pdev->dev.parent);
psy_cfg.drv_data = bat;
psy_cfg.no_wakeup_source = true;
diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c
index b2b82f97a471..9dec4486b143 100644
--- a/drivers/power/supply/act8945a_charger.c
+++ b/drivers/power/supply/act8945a_charger.c
@@ -597,14 +597,6 @@ static int act8945a_charger_probe(struct platform_device *pdev)
return irq ?: -ENXIO;
}
- ret = devm_request_irq(&pdev->dev, irq, act8945a_status_changed,
- IRQF_TRIGGER_FALLING, "act8945a_interrupt",
- charger);
- if (ret) {
- dev_err(&pdev->dev, "failed to request nIRQ pin IRQ\n");
- return ret;
- }
-
charger->desc.name = "act8945a-charger";
charger->desc.get_property = act8945a_charger_get_property;
charger->desc.properties = act8945a_charger_props;
@@ -614,7 +606,7 @@ static int act8945a_charger_probe(struct platform_device *pdev)
if (ret)
return -EINVAL;
- psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&pdev->dev);
psy_cfg.drv_data = charger;
charger->psy = devm_power_supply_register(&pdev->dev,
@@ -625,6 +617,14 @@ static int act8945a_charger_probe(struct platform_device *pdev)
return PTR_ERR(charger->psy);
}
+ ret = devm_request_irq(&pdev->dev, irq, act8945a_status_changed,
+ IRQF_TRIGGER_FALLING, "act8945a_interrupt",
+ charger);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request nIRQ pin IRQ\n");
+ return ret;
+ }
+
platform_set_drvdata(pdev, charger);
INIT_WORK(&charger->work, act8945a_work);
diff --git a/drivers/power/supply/adc-battery-helper.c b/drivers/power/supply/adc-battery-helper.c
new file mode 100644
index 000000000000..6e0f5b6d73d7
--- /dev/null
+++ b/drivers/power/supply/adc-battery-helper.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Helper for batteries with accurate current and voltage measurement, but
+ * without temperature measurement or without a "resistance-temp-table".
+ *
+ * Some fuel-gauges are not full-featured autonomous fuel-gauges.
+ * These fuel-gauges offer accurate current and voltage measurements but
+ * their coulomb-counters are intended to work together with an always on
+ * micro-controller monitoring the fuel-gauge.
+ *
+ * This adc-battery-helper code offers open-circuit-voltage (ocv) and through
+ * that capacity estimation for devices where such limited functionality
+ * fuel-gauges are exposed directly to Linux.
+ *
+ * This helper requires the hw to provide accurate battery current_now and
+ * voltage_now measurement and this helper the provides the following properties
+ * based on top of those readings:
+ *
+ * POWER_SUPPLY_PROP_STATUS
+ * POWER_SUPPLY_PROP_VOLTAGE_OCV
+ * POWER_SUPPLY_PROP_VOLTAGE_NOW
+ * POWER_SUPPLY_PROP_CURRENT_NOW
+ * POWER_SUPPLY_PROP_CAPACITY
+ *
+ * As well as optional the following properties assuming an always present
+ * system-scope battery, allowing direct use of adc_battery_helper_get_prop()
+ * in this common case:
+ * POWER_SUPPLY_PROP_PRESENT
+ * POWER_SUPPLY_PROP_SCOPE
+ *
+ * Using this helper is as simple as:
+ *
+ * 1. Embed a struct adc_battery_helper this MUST be the first member of
+ * the battery driver's data struct.
+ * 2. Use adc_battery_helper_props[] or add the above properties to
+ * the list of properties in power_supply_desc
+ * 3. Call adc_battery_helper_init() after registering the power_supply and
+ * before returning from the probe() function
+ * 4. Use adc_battery_helper_get_prop() as the power-supply's get_property()
+ * method, or call it for the above properties.
+ * 5. Use adc_battery_helper_external_power_changed() as the power-supply's
+ * external_power_changed() method or call it from that method.
+ * 6. Use adc_battery_helper_[suspend|resume]() as suspend-resume methods or
+ * call them from the driver's suspend-resume methods.
+ *
+ * The provided get_voltage_and_current_now() method will be called by this
+ * helper at adc_battery_helper_init() time and later.
+ *
+ * Copyright (c) 2021-2025 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/devm-helpers.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/workqueue.h>
+
+#include "adc-battery-helper.h"
+
+#define MOV_AVG_WINDOW_SIZE ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE
+#define INIT_POLL_TIME (5 * HZ)
+#define POLL_TIME (30 * HZ)
+#define SETTLE_TIME (1 * HZ)
+
+#define INIT_POLL_COUNT 30
+
+#define CURR_HYST_UA 65000
+
+#define LOW_BAT_UV 3700000
+#define FULL_BAT_HYST_UV 38000
+
+#define AMBIENT_TEMP_CELSIUS 25
+
+static int adc_battery_helper_get_status(struct adc_battery_helper *help)
+{
+ int full_uv =
+ help->psy->battery_info->constant_charge_voltage_max_uv - FULL_BAT_HYST_UV;
+
+ if (help->curr_ua > CURR_HYST_UA)
+ return POWER_SUPPLY_STATUS_CHARGING;
+
+ if (help->curr_ua < -CURR_HYST_UA)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ if (help->supplied) {
+ bool full;
+
+ if (help->charge_finished)
+ full = gpiod_get_value_cansleep(help->charge_finished);
+ else
+ full = help->ocv_avg_uv > full_uv;
+
+ if (full)
+ return POWER_SUPPLY_STATUS_FULL;
+ }
+
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+}
+
+static void adc_battery_helper_work(struct work_struct *work)
+{
+ struct adc_battery_helper *help = container_of(work, struct adc_battery_helper,
+ work.work);
+ int i, curr_diff_ua, volt_diff_uv, res_mohm, ret, win_size;
+ struct device *dev = help->psy->dev.parent;
+ int volt_uv, prev_volt_uv = help->volt_uv;
+ int curr_ua, prev_curr_ua = help->curr_ua;
+ bool prev_supplied = help->supplied;
+ int prev_status = help->status;
+
+ guard(mutex)(&help->lock);
+
+ ret = help->get_voltage_and_current_now(help->psy, &volt_uv, &curr_ua);
+ if (ret)
+ goto out;
+
+ help->volt_uv = volt_uv;
+ help->curr_ua = curr_ua;
+
+ help->ocv_uv[help->ocv_avg_index] =
+ help->volt_uv - help->curr_ua * help->intern_res_avg_mohm / 1000;
+ dev_dbg(dev, "volt-now: %d, curr-now: %d, volt-ocv: %d\n",
+ help->volt_uv, help->curr_ua, help->ocv_uv[help->ocv_avg_index]);
+ help->ocv_avg_index = (help->ocv_avg_index + 1) % MOV_AVG_WINDOW_SIZE;
+ help->poll_count++;
+
+ help->ocv_avg_uv = 0;
+ win_size = min(help->poll_count, MOV_AVG_WINDOW_SIZE);
+ for (i = 0; i < win_size; i++)
+ help->ocv_avg_uv += help->ocv_uv[i];
+ help->ocv_avg_uv /= win_size;
+
+ help->supplied = power_supply_am_i_supplied(help->psy);
+ help->status = adc_battery_helper_get_status(help);
+ if (help->status == POWER_SUPPLY_STATUS_FULL)
+ help->capacity = 100;
+ else
+ help->capacity = power_supply_batinfo_ocv2cap(help->psy->battery_info,
+ help->ocv_avg_uv,
+ AMBIENT_TEMP_CELSIUS);
+
+ /*
+ * Skip internal resistance calc on charger [un]plug and
+ * when the battery is almost empty (voltage low).
+ */
+ if (help->supplied != prev_supplied ||
+ help->volt_uv < LOW_BAT_UV ||
+ help->poll_count < 2)
+ goto out;
+
+ /*
+ * Assuming that the OCV voltage does not change significantly
+ * between 2 polls, then we can calculate the internal resistance
+ * on a significant current change by attributing all voltage
+ * change between the 2 readings to the internal resistance.
+ */
+ curr_diff_ua = abs(help->curr_ua - prev_curr_ua);
+ if (curr_diff_ua < CURR_HYST_UA)
+ goto out;
+
+ volt_diff_uv = abs(help->volt_uv - prev_volt_uv);
+ res_mohm = volt_diff_uv * 1000 / curr_diff_ua;
+
+ if ((res_mohm < (help->intern_res_avg_mohm * 2 / 3)) ||
+ (res_mohm > (help->intern_res_avg_mohm * 4 / 3))) {
+ dev_dbg(dev, "Ignoring outlier internal resistance %d mOhm\n", res_mohm);
+ goto out;
+ }
+
+ dev_dbg(dev, "Internal resistance %d mOhm\n", res_mohm);
+
+ help->intern_res_mohm[help->intern_res_avg_index] = res_mohm;
+ help->intern_res_avg_index = (help->intern_res_avg_index + 1) % MOV_AVG_WINDOW_SIZE;
+ help->intern_res_poll_count++;
+
+ help->intern_res_avg_mohm = 0;
+ win_size = min(help->intern_res_poll_count, MOV_AVG_WINDOW_SIZE);
+ for (i = 0; i < win_size; i++)
+ help->intern_res_avg_mohm += help->intern_res_mohm[i];
+ help->intern_res_avg_mohm /= win_size;
+
+out:
+ queue_delayed_work(system_percpu_wq, &help->work,
+ (help->poll_count <= INIT_POLL_COUNT) ?
+ INIT_POLL_TIME : POLL_TIME);
+
+ if (help->status != prev_status)
+ power_supply_changed(help->psy);
+}
+
+const enum power_supply_property adc_battery_helper_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_SCOPE,
+};
+EXPORT_SYMBOL_GPL(adc_battery_helper_properties);
+
+static_assert(ARRAY_SIZE(adc_battery_helper_properties) ==
+ ADC_HELPER_NUM_PROPERTIES);
+
+int adc_battery_helper_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct adc_battery_helper *help = power_supply_get_drvdata(psy);
+ int dummy, ret = 0;
+
+ /*
+ * Avoid racing with adc_battery_helper_work() while it is updating
+ * variables and avoid calling get_voltage_and_current_now() reentrantly.
+ */
+ guard(mutex)(&help->lock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = help->status;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = help->get_voltage_and_current_now(psy, &val->intval, &dummy);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ val->intval = help->ocv_avg_uv;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = help->get_voltage_and_current_now(psy, &dummy, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = help->capacity;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_get_property);
+
+void adc_battery_helper_external_power_changed(struct power_supply *psy)
+{
+ struct adc_battery_helper *help = power_supply_get_drvdata(psy);
+
+ dev_dbg(help->psy->dev.parent, "external power changed\n");
+ mod_delayed_work(system_percpu_wq, &help->work, SETTLE_TIME);
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_external_power_changed);
+
+static void adc_battery_helper_start_work(struct adc_battery_helper *help)
+{
+ help->poll_count = 0;
+ help->ocv_avg_index = 0;
+
+ queue_delayed_work(system_percpu_wq, &help->work, 0);
+ flush_delayed_work(&help->work);
+}
+
+int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy,
+ adc_battery_helper_get_func get_voltage_and_current_now,
+ struct gpio_desc *charge_finished_gpio)
+{
+ struct device *dev = psy->dev.parent;
+ int ret;
+
+ help->psy = psy;
+ help->get_voltage_and_current_now = get_voltage_and_current_now;
+ help->charge_finished = charge_finished_gpio;
+
+ ret = devm_mutex_init(dev, &help->lock);
+ if (ret)
+ return ret;
+
+ ret = devm_delayed_work_autocancel(dev, &help->work, adc_battery_helper_work);
+ if (ret)
+ return ret;
+
+ if (!help->psy->battery_info ||
+ help->psy->battery_info->factory_internal_resistance_uohm == -EINVAL ||
+ help->psy->battery_info->constant_charge_voltage_max_uv == -EINVAL ||
+ !psy->battery_info->ocv_table[0]) {
+ dev_err(dev, "error required properties are missing\n");
+ return -ENODEV;
+ }
+
+ /* Use provided internal resistance as start point (in milli-ohm) */
+ help->intern_res_avg_mohm =
+ help->psy->battery_info->factory_internal_resistance_uohm / 1000;
+ /* Also add it to the internal resistance moving average window */
+ help->intern_res_mohm[0] = help->intern_res_avg_mohm;
+ help->intern_res_avg_index = 1;
+ help->intern_res_poll_count = 1;
+
+ adc_battery_helper_start_work(help);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_init);
+
+int adc_battery_helper_suspend(struct device *dev)
+{
+ struct adc_battery_helper *help = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&help->work);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_suspend);
+
+int adc_battery_helper_resume(struct device *dev)
+{
+ struct adc_battery_helper *help = dev_get_drvdata(dev);
+
+ adc_battery_helper_start_work(help);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_resume);
+
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_DESCRIPTION("ADC battery capacity estimation helper");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/adc-battery-helper.h b/drivers/power/supply/adc-battery-helper.h
new file mode 100644
index 000000000000..4e42181c8983
--- /dev/null
+++ b/drivers/power/supply/adc-battery-helper.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Helper for batteries with accurate current and voltage measurement, but
+ * without temperature measurement or without a "resistance-temp-table".
+ * Copyright (c) 2021-2025 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+#define ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE 8
+
+struct power_supply;
+struct gpio_desc;
+
+/*
+ * The adc battery helper code needs voltage- and current-now to be sampled as
+ * close to each other (in sample-time) as possible. A single getter function is
+ * used to allow the battery driver to handle this in the best way possible.
+ */
+typedef int (*adc_battery_helper_get_func)(struct power_supply *psy, int *volt, int *curr);
+
+struct adc_battery_helper {
+ struct power_supply *psy;
+ struct gpio_desc *charge_finished;
+ struct delayed_work work;
+ struct mutex lock;
+ adc_battery_helper_get_func get_voltage_and_current_now;
+ int ocv_uv[ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE]; /* micro-volt */
+ int intern_res_mohm[ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE]; /* milli-ohm */
+ int poll_count;
+ int ocv_avg_index;
+ int ocv_avg_uv; /* micro-volt */
+ int intern_res_poll_count;
+ int intern_res_avg_index;
+ int intern_res_avg_mohm; /* milli-ohm */
+ int volt_uv; /* micro-volt */
+ int curr_ua; /* micro-ampere */
+ int capacity; /* percent */
+ int status;
+ bool supplied;
+};
+
+extern const enum power_supply_property adc_battery_helper_properties[];
+/* Must be const cannot be an external. Asserted in adc-battery-helper.c */
+#define ADC_HELPER_NUM_PROPERTIES 7
+
+int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy,
+ adc_battery_helper_get_func get_voltage_and_current_now,
+ struct gpio_desc *charge_finished_gpio);
+/*
+ * The below functions can be directly used as power-supply / suspend-resume
+ * callbacks. They cast the power_supply_get_drvdata() / dev_get_drvdata() data
+ * directly to struct adc_battery_helper. Therefor struct adc_battery_helper
+ * MUST be the first member of the battery driver's data struct.
+ */
+int adc_battery_helper_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val);
+void adc_battery_helper_external_power_changed(struct power_supply *psy);
+int adc_battery_helper_suspend(struct device *dev);
+int adc_battery_helper_resume(struct device *dev);
diff --git a/drivers/power/supply/apm_power.c b/drivers/power/supply/apm_power.c
index 9236e0078578..9933cdc5c387 100644
--- a/drivers/power/supply/apm_power.c
+++ b/drivers/power/supply/apm_power.c
@@ -364,7 +364,8 @@ static int __init apm_battery_init(void)
static void __exit apm_battery_exit(void)
{
- apm_get_power_status = NULL;
+ if (apm_get_power_status == apm_battery_apm_get_power_status)
+ apm_get_power_status = NULL;
}
module_init(apm_battery_init);
diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c
index e5733cb9e19e..5f6ea416fa30 100644
--- a/drivers/power/supply/axp20x_ac_power.c
+++ b/drivers/power/supply/axp20x_ac_power.c
@@ -364,7 +364,7 @@ static int axp20x_ac_power_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, power);
- psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&pdev->dev);
psy_cfg.drv_data = power;
power->supply = devm_power_supply_register(&pdev->dev,
diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c
index 3c3158f31a48..50ca8e110085 100644
--- a/drivers/power/supply/axp20x_battery.c
+++ b/drivers/power/supply/axp20x_battery.c
@@ -89,6 +89,8 @@
#define AXP717_BAT_CC_MIN_UA 0
#define AXP717_BAT_CC_MAX_UA 3008000
+#define AXP717_TS_PIN_DISABLE BIT(4)
+
struct axp20x_batt_ps;
struct axp_data {
@@ -117,6 +119,7 @@ struct axp20x_batt_ps {
/* Maximum constant charge current */
unsigned int max_ccc;
const struct axp_data *data;
+ bool ts_disable;
};
static int axp20x_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt,
@@ -984,6 +987,24 @@ static void axp717_set_battery_info(struct platform_device *pdev,
int ccc = info->constant_charge_current_max_ua;
int val;
+ axp_batt->ts_disable = (device_property_read_bool(axp_batt->dev,
+ "x-powers,no-thermistor"));
+
+ /*
+ * Under rare conditions an incorrectly programmed efuse for
+ * the temp sensor on the PMIC may trigger a fault condition.
+ * Allow users to hard-code if the ts pin is not used to work
+ * around this problem. Note that this requires the battery
+ * be correctly defined in the device tree with a monitored
+ * battery node.
+ */
+ if (axp_batt->ts_disable) {
+ regmap_update_bits(axp_batt->regmap,
+ AXP717_TS_PIN_CFG,
+ AXP717_TS_PIN_DISABLE,
+ AXP717_TS_PIN_DISABLE);
+ }
+
if (vmin > 0 && axp717_set_voltage_min_design(axp_batt, vmin))
dev_err(&pdev->dev,
"couldn't set voltage_min_design\n");
@@ -1090,7 +1111,7 @@ static int axp20x_power_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, axp20x_batt);
psy_cfg.drv_data = axp20x_batt;
- psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&pdev->dev);
axp20x_batt->data = (struct axp_data *)of_device_get_match_data(dev);
diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c
index 9722912268fe..e75d1e377ac1 100644
--- a/drivers/power/supply/axp20x_usb_power.c
+++ b/drivers/power/supply/axp20x_usb_power.c
@@ -492,7 +492,7 @@ static int axp717_usb_power_set_input_current_limit(struct axp20x_usb_power *pow
if (power->max_input_cur && (intval > power->max_input_cur)) {
dev_warn(power->dev,
- "reqested current %d clamped to max current %d\n",
+ "requested current %d clamped to max current %d\n",
intval, power->max_input_cur);
intval = power->max_input_cur;
}
@@ -1011,7 +1011,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
return ret;
}
- psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&pdev->dev);
psy_cfg.drv_data = power;
power->supply = devm_power_supply_register(&pdev->dev,
diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c
index ac05942e4e6a..ea0f5caee8f0 100644
--- a/drivers/power/supply/axp288_charger.c
+++ b/drivers/power/supply/axp288_charger.c
@@ -10,6 +10,7 @@
#include <linux/acpi.h>
#include <linux/bitops.h>
#include <linux/module.h>
+#include <linux/devm-helpers.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include <linux/workqueue.h>
@@ -821,14 +822,6 @@ static int charger_init_hw_regs(struct axp288_chrg_info *info)
return 0;
}
-static void axp288_charger_cancel_work(void *data)
-{
- struct axp288_chrg_info *info = data;
-
- cancel_work_sync(&info->otg.work);
- cancel_work_sync(&info->cable.work);
-}
-
static int axp288_charger_probe(struct platform_device *pdev)
{
int ret, i, pirq;
@@ -866,12 +859,10 @@ static int axp288_charger_probe(struct platform_device *pdev)
info->regmap_irqc = axp20x->regmap_irqc;
info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME);
- if (IS_ERR(info->cable.edev)) {
- dev_err_probe(dev, PTR_ERR(info->cable.edev),
- "extcon_get_extcon_dev(%s) failed\n",
- AXP288_EXTCON_DEV_NAME);
- return PTR_ERR(info->cable.edev);
- }
+ if (IS_ERR(info->cable.edev))
+ return dev_err_probe(dev, PTR_ERR(info->cable.edev),
+ "extcon_get_extcon_dev(%s) failed\n",
+ AXP288_EXTCON_DEV_NAME);
/*
* On devices with broken ACPI GPIO event handlers there also is no ACPI
@@ -885,12 +876,11 @@ static int axp288_charger_probe(struct platform_device *pdev)
if (extcon_name) {
info->otg.cable = extcon_get_extcon_dev(extcon_name);
- if (IS_ERR(info->otg.cable)) {
- dev_err_probe(dev, PTR_ERR(info->otg.cable),
- "extcon_get_extcon_dev(%s) failed\n",
- USB_HOST_EXTCON_NAME);
- return PTR_ERR(info->otg.cable);
- }
+ if (IS_ERR(info->otg.cable))
+ return dev_err_probe(dev, PTR_ERR(info->otg.cable),
+ "extcon_get_extcon_dev(%s) failed\n",
+ USB_HOST_EXTCON_NAME);
+
dev_info(dev, "Using " USB_HOST_EXTCON_HID " extcon for usb-id\n");
}
@@ -904,38 +894,39 @@ static int axp288_charger_probe(struct platform_device *pdev)
charger_cfg.drv_data = info;
info->psy_usb = devm_power_supply_register(dev, &axp288_charger_desc,
&charger_cfg);
- if (IS_ERR(info->psy_usb)) {
- ret = PTR_ERR(info->psy_usb);
- dev_err(dev, "failed to register power supply: %d\n", ret);
- return ret;
- }
+ if (IS_ERR(info->psy_usb))
+ return dev_err_probe(dev, PTR_ERR(info->psy_usb),
+ "failed to register power supply: %d\n", ret);
/* Cancel our work on cleanup, register this before the notifiers */
- ret = devm_add_action(dev, axp288_charger_cancel_work, info);
+ ret = devm_work_autocancel(dev, &info->cable.work,
+ axp288_charger_extcon_evt_worker);
if (ret)
return ret;
/* Register for extcon notification */
- INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker);
info->cable.nb.notifier_call = axp288_charger_handle_cable_evt;
ret = devm_extcon_register_notifier_all(dev, info->cable.edev,
&info->cable.nb);
- if (ret) {
- dev_err(dev, "failed to register cable extcon notifier\n");
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register cable extcon notifier\n");
+
schedule_work(&info->cable.work);
+ ret = devm_work_autocancel(dev, &info->otg.work,
+ axp288_charger_otg_evt_worker);
+ if (ret)
+ return ret;
+
/* Register for OTG notification */
- INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker);
info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt;
if (info->otg.cable) {
ret = devm_extcon_register_notifier(dev, info->otg.cable,
EXTCON_USB_HOST, &info->otg.id_nb);
- if (ret) {
- dev_err(dev, "failed to register EXTCON_USB_HOST notifier\n");
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register EXTCON_USB_HOST notifier\n");
+
schedule_work(&info->otg.work);
}
@@ -954,11 +945,9 @@ static int axp288_charger_probe(struct platform_device *pdev)
ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i],
NULL, axp288_charger_irq_thread_handler,
IRQF_ONESHOT, info->pdev->name, info);
- if (ret) {
- dev_err(dev, "failed to request interrupt=%d\n",
- info->irq[i]);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to request interrupt=%d\n",
+ info->irq[i]);
}
return 0;
diff --git a/drivers/power/supply/bd71828-power.c b/drivers/power/supply/bd71828-power.c
new file mode 100644
index 000000000000..5e78faa0a4aa
--- /dev/null
+++ b/drivers/power/supply/bd71828-power.c
@@ -0,0 +1,1206 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* ROHM BD71815, BD71828 and BD71878 Charger driver */
+
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/rohm-bd71815.h>
+#include <linux/mfd/rohm-bd71828.h>
+#include <linux/mfd/rohm-bd72720.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+/* common defines */
+#define BD7182x_MASK_VBAT_U 0x1f
+#define BD7182x_MASK_VDCIN_U 0x0f
+#define BD7182x_MASK_IBAT_U 0x3f
+#define BD7182x_MASK_CURDIR_DISCHG 0x80
+#define BD7182x_MASK_CHG_STATE 0x7f
+#define BD7182x_MASK_BAT_TEMP 0x07
+#define BD7182x_MASK_DCIN_DET BIT(0)
+#define BD7182x_MASK_CONF_PON BIT(0)
+#define BD71815_MASK_CONF_XSTB BIT(1)
+#define BD7182x_MASK_BAT_STAT 0x3f
+#define BD7182x_MASK_ILIM 0x3f
+#define BD7182x_MASK_DCIN_STAT 0x07
+
+#define BD7182x_MASK_WDT_AUTO 0x40
+#define BD7182x_MASK_VBAT_ALM_LIMIT_U 0x01
+#define BD7182x_MASK_CHG_EN 0x01
+
+#define BD7182x_DCIN_COLLAPSE_DEFAULT 0x36
+
+#define MAX_CURRENT_DEFAULT 890000 /* uA */
+#define AC_NAME "bd71828_ac"
+#define BAT_NAME "bd71828_bat"
+
+#define BAT_OPEN 0x7
+
+/*
+ * VBAT Low voltage detection Threshold
+ * 0x00D4*16mV = 212*0.016 = 3.392v
+ */
+#define VBAT_LOW_TH 0x00D4
+
+struct pwr_regs {
+ unsigned int vbat_avg;
+ unsigned int ibat;
+ unsigned int ibat_avg;
+ unsigned int ilim_stat;
+ unsigned int btemp_vth;
+ unsigned int chg_state;
+ unsigned int bat_temp;
+ unsigned int dcin_set;
+ unsigned int dcin_stat;
+ unsigned int dcin_online_mask;
+ unsigned int dcin_collapse_limit;
+ unsigned int chg_set1;
+ unsigned int chg_en;
+ unsigned int vbat_alm_limit_u;
+ unsigned int conf;
+ unsigned int vdcin;
+ unsigned int vdcin_himask;
+};
+
+static const struct pwr_regs pwr_regs_bd71828 = {
+ .vbat_avg = BD71828_REG_VBAT_U,
+ .ibat = BD71828_REG_IBAT_U,
+ .ibat_avg = BD71828_REG_IBAT_AVG_U,
+ .ilim_stat = BD71828_REG_ILIM_STAT,
+ .btemp_vth = BD71828_REG_VM_BTMP_U,
+ .chg_state = BD71828_REG_CHG_STATE,
+ .bat_temp = BD71828_REG_BAT_TEMP,
+ .dcin_set = BD71828_REG_DCIN_SET,
+ .dcin_stat = BD71828_REG_DCIN_STAT,
+ .dcin_online_mask = BD7182x_MASK_DCIN_DET,
+ .dcin_collapse_limit = BD71828_REG_DCIN_CLPS,
+ .chg_set1 = BD71828_REG_CHG_SET1,
+ .chg_en = BD71828_REG_CHG_EN,
+ .vbat_alm_limit_u = BD71828_REG_ALM_VBAT_LIMIT_U,
+ .conf = BD71828_REG_CONF,
+ .vdcin = BD71828_REG_VDCIN_U,
+ .vdcin_himask = BD7182x_MASK_VDCIN_U,
+};
+
+static const struct pwr_regs pwr_regs_bd71815 = {
+ .vbat_avg = BD71815_REG_VM_SA_VBAT_U,
+ /* BD71815 does not have separate current and current avg */
+ .ibat = BD71815_REG_CC_CURCD_U,
+ .ibat_avg = BD71815_REG_CC_CURCD_U,
+
+ .btemp_vth = BD71815_REG_VM_BTMP,
+ .chg_state = BD71815_REG_CHG_STATE,
+ .bat_temp = BD71815_REG_BAT_TEMP,
+ .dcin_stat = BD71815_REG_DCIN_STAT,
+ .dcin_online_mask = BD7182x_MASK_DCIN_DET,
+ .dcin_collapse_limit = BD71815_REG_DCIN_CLPS,
+ .chg_set1 = BD71815_REG_CHG_SET1,
+ .chg_en = BD71815_REG_CHG_SET1,
+ .vbat_alm_limit_u = BD71815_REG_ALM_VBAT_TH_U,
+ .conf = BD71815_REG_CONF,
+
+ .vdcin = BD71815_REG_VM_DCIN_U,
+ .vdcin_himask = BD7182x_MASK_VDCIN_U,
+};
+
+static struct pwr_regs pwr_regs_bd72720 = {
+ .vbat_avg = BD72720_REG_VM_SA_VBAT_U,
+ .ibat = BD72720_REG_CC_CURCD_U,
+ .ibat_avg = BD72720_REG_CC_SA_CURCD_U,
+ .btemp_vth = BD72720_REG_VM_BTMP_U,
+ /*
+ * Note, state 0x40 IMP_CHK. not documented
+ * on other variants but was still handled in
+ * existing code. No memory traces as to why.
+ */
+ .chg_state = BD72720_REG_CHG_STATE,
+ .bat_temp = BD72720_REG_CHG_BAT_TEMP_STAT,
+ .dcin_stat = BD72720_REG_INT_VBUS_SRC,
+ .dcin_online_mask = BD72720_MASK_DCIN_DET,
+ .dcin_collapse_limit = -1, /* Automatic. Setting not supported */
+ .chg_set1 = BD72720_REG_CHG_SET_1,
+ .chg_en = BD72720_REG_CHG_EN,
+ /* 15mV note in data-sheet */
+ .vbat_alm_limit_u = BD72720_REG_ALM_VBAT_TH_U,
+ .conf = BD72720_REG_CONF, /* o XSTB, only PON. Seprate slave addr */
+ .vdcin = BD72720_REG_VM_VBUS_U, /* 10 bits not 11 as with other ICs */
+ .vdcin_himask = BD72720_MASK_VDCIN_U,
+};
+
+struct bd71828_power {
+ struct regmap *regmap;
+ enum rohm_chip_type chip_type;
+ struct device *dev;
+ struct power_supply *ac;
+ struct power_supply *bat;
+
+ const struct pwr_regs *regs;
+ /* Reg val to uA */
+ int curr_factor;
+ int rsens;
+ int (*get_temp)(struct bd71828_power *pwr, int *temp);
+ int (*bat_inserted)(struct bd71828_power *pwr);
+};
+
+static int bd7182x_write16(struct bd71828_power *pwr, int reg, u16 val)
+{
+ __be16 tmp;
+
+ tmp = cpu_to_be16(val);
+
+ return regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp));
+}
+
+static int bd7182x_read16_himask(struct bd71828_power *pwr, int reg, int himask,
+ u16 *val)
+{
+ struct regmap *regmap = pwr->regmap;
+ int ret;
+ __be16 rvals;
+ u8 *tmp = (u8 *)&rvals;
+
+ ret = regmap_bulk_read(regmap, reg, &rvals, sizeof(*val));
+ if (!ret) {
+ *tmp &= himask;
+ *val = be16_to_cpu(rvals);
+ }
+
+ return ret;
+}
+
+static int bd71828_get_vbat(struct bd71828_power *pwr, int *vcell)
+{
+ u16 tmp_vcell;
+ int ret;
+
+ ret = bd7182x_read16_himask(pwr, pwr->regs->vbat_avg,
+ BD7182x_MASK_VBAT_U, &tmp_vcell);
+ if (ret)
+ dev_err(pwr->dev, "Failed to read battery average voltage\n");
+ else
+ *vcell = ((int)tmp_vcell) * 1000;
+
+ return ret;
+}
+
+static int bd71828_get_current_ds_adc(struct bd71828_power *pwr, int *curr, int *curr_avg)
+{
+ __be16 tmp_curr;
+ char *tmp = (char *)&tmp_curr;
+ int dir = 1;
+ int regs[] = { pwr->regs->ibat, pwr->regs->ibat_avg };
+ int *vals[] = { curr, curr_avg };
+ int ret, i;
+
+ for (dir = 1, i = 0; i < ARRAY_SIZE(regs); i++) {
+ ret = regmap_bulk_read(pwr->regmap, regs[i], &tmp_curr,
+ sizeof(tmp_curr));
+ if (ret)
+ break;
+
+ if (*tmp & BD7182x_MASK_CURDIR_DISCHG)
+ dir = -1;
+
+ *tmp &= BD7182x_MASK_IBAT_U;
+
+ *vals[i] = dir * ((int)be16_to_cpu(tmp_curr)) * pwr->curr_factor;
+ }
+
+ return ret;
+}
+
+/* Unit is tenths of degree C */
+static int bd71815_get_temp(struct bd71828_power *pwr, int *temp)
+{
+ struct regmap *regmap = pwr->regmap;
+ int ret;
+ int t;
+
+ ret = regmap_read(regmap, pwr->regs->btemp_vth, &t);
+ if (ret)
+ return ret;
+
+ t = 200 - t;
+
+ if (t > 200) {
+ dev_err(pwr->dev, "Failed to read battery temperature\n");
+ return -ENODATA;
+ }
+
+ return 0;
+}
+
+/* Unit is tenths of degree C */
+static int bd71828_get_temp(struct bd71828_power *pwr, int *temp)
+{
+ u16 t;
+ int ret;
+ int tmp = 200 * 10000;
+
+ ret = bd7182x_read16_himask(pwr, pwr->regs->btemp_vth,
+ BD71828_MASK_VM_BTMP_U, &t);
+ if (ret)
+ return ret;
+
+ if (t > 3200) {
+ dev_err(pwr->dev,
+ "Failed to read battery temperature\n");
+ return -ENODATA;
+ }
+
+ tmp -= 625ULL * (unsigned int)t;
+ *temp = tmp / 1000;
+
+ return ret;
+}
+
+static int bd71828_charge_status(struct bd71828_power *pwr,
+ int *s, int *h)
+{
+ unsigned int state;
+ int status, health;
+ int ret = 1;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->chg_state, &state);
+ if (ret) {
+ dev_err(pwr->dev, "charger status reading failed (%d)\n", ret);
+ return ret;
+ }
+
+ state &= BD7182x_MASK_CHG_STATE;
+
+ dev_dbg(pwr->dev, "CHG_STATE %d\n", state);
+
+ switch (state) {
+ case 0x00:
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x0E:
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x0F:
+ status = POWER_SUPPLY_STATUS_FULL;
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ case 0x20:
+ case 0x21:
+ case 0x22:
+ case 0x23:
+ case 0x24:
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+ case 0x30:
+ case 0x31:
+ case 0x32:
+ case 0x40:
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x7f:
+ default:
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ health = POWER_SUPPLY_HEALTH_DEAD;
+ break;
+ }
+
+ if (s)
+ *s = status;
+ if (h)
+ *h = health;
+
+ return ret;
+}
+
+static int get_chg_online(struct bd71828_power *pwr, int *chg_online)
+{
+ int r, ret;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->dcin_stat, &r);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read DCIN status\n");
+ return ret;
+ }
+ *chg_online = ((r & pwr->regs->dcin_online_mask) != 0);
+
+ return 0;
+}
+
+static int get_bat_online(struct bd71828_power *pwr, int *bat_online)
+{
+ int r, ret;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->bat_temp, &r);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read battery temperature\n");
+ return ret;
+ }
+ *bat_online = ((r & BD7182x_MASK_BAT_TEMP) != BAT_OPEN);
+
+ return 0;
+}
+
+static int bd71828_bat_inserted(struct bd71828_power *pwr)
+{
+ int ret, val;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->conf, &val);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read CONF register\n");
+ return 0;
+ }
+ ret = val & BD7182x_MASK_CONF_PON;
+
+ if (ret)
+ if (regmap_update_bits(pwr->regmap, pwr->regs->conf, BD7182x_MASK_CONF_PON, 0))
+ dev_err(pwr->dev, "Failed to write CONF register\n");
+
+ return ret;
+}
+
+static int bd71815_bat_inserted(struct bd71828_power *pwr)
+{
+ int ret, val;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->conf, &val);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read CONF register\n");
+ return ret;
+ }
+
+ ret = !(val & BD71815_MASK_CONF_XSTB);
+ if (ret)
+ regmap_write(pwr->regmap, pwr->regs->conf, val |
+ BD71815_MASK_CONF_XSTB);
+
+ return ret;
+}
+
+static int bd71828_init_hardware(struct bd71828_power *pwr)
+{
+ int ret;
+
+ /* TODO: Collapse limit should come from device-tree ? */
+ if (pwr->regs->dcin_collapse_limit != (unsigned int)-1) {
+ ret = regmap_write(pwr->regmap, pwr->regs->dcin_collapse_limit,
+ BD7182x_DCIN_COLLAPSE_DEFAULT);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to write DCIN collapse limit\n");
+ return ret;
+ }
+ }
+
+ ret = pwr->bat_inserted(pwr);
+ if (ret < 0)
+ return ret;
+
+ if (ret) {
+ /* WDT_FST auto set */
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_set1,
+ BD7182x_MASK_WDT_AUTO,
+ BD7182x_MASK_WDT_AUTO);
+ if (ret)
+ return ret;
+
+ ret = bd7182x_write16(pwr, pwr->regs->vbat_alm_limit_u,
+ VBAT_LOW_TH);
+ if (ret)
+ return ret;
+
+ /*
+ * On BD71815 "we mask the power-state" from relax detection.
+ * I am unsure what the impact of the power-state would be if
+ * we didn't - but this is what the vendor driver did - and
+ * that driver has been used in few projects so I just assume
+ * this is needed.
+ */
+ if (pwr->chip_type == ROHM_CHIP_TYPE_BD71815) {
+ ret = regmap_set_bits(pwr->regmap,
+ BD71815_REG_REX_CTRL_1,
+ REX_PMU_STATE_MASK);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int bd71828_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent);
+ u32 vot;
+ u16 tmp;
+ int t;
+ int online;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = get_chg_online(pwr, &online);
+ if (!ret)
+ val->intval = online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = bd7182x_read16_himask(pwr, pwr->regs->vdcin,
+ pwr->regs->vdcin_himask, &tmp);
+ if (ret)
+ return ret;
+
+ vot = tmp;
+ /* 5 milli volt steps */
+ val->intval = 5000 * vot;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ if (!pwr->regs->ilim_stat)
+ return -ENODATA;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->ilim_stat, &t);
+ if (ret)
+ return ret;
+
+ t++;
+ val->intval = (t & BD7182x_MASK_ILIM) * 50000;
+ if (val->intval > 2000000)
+ val->intval = 2000000;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bd71828_charger_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ if (val->intval > 2000000)
+ return -EINVAL;
+
+ if (val->intval < 50000)
+ return -EINVAL;
+
+ if (!pwr->regs->dcin_set)
+ return -EINVAL;
+
+ return regmap_update_bits(pwr->regmap, pwr->regs->dcin_set,
+ BD7182x_MASK_ILIM,
+ val->intval / 50000 - 1);
+ break;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bd71828_charger_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return !!(pwr->regs->dcin_set);
+ default:
+ return false;
+ }
+}
+
+static int bd71828_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent);
+ int ret = 0;
+ int status, health, tmp, curr, curr_avg, chg_en;
+
+ if (psp == POWER_SUPPLY_PROP_STATUS ||
+ psp == POWER_SUPPLY_PROP_HEALTH ||
+ psp == POWER_SUPPLY_PROP_CHARGE_TYPE)
+ ret = bd71828_charge_status(pwr, &status, &health);
+ else if (psp == POWER_SUPPLY_PROP_CURRENT_AVG ||
+ psp == POWER_SUPPLY_PROP_CURRENT_NOW)
+ ret = bd71828_get_current_ds_adc(pwr, &curr, &curr_avg);
+ if (ret)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = get_bat_online(pwr, &tmp);
+ if (!ret)
+ val->intval = tmp;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = bd71828_get_vbat(pwr, &tmp);
+ val->intval = tmp;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = curr_avg;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = curr;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = MAX_CURRENT_DEFAULT;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = pwr->get_temp(pwr, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ ret = regmap_read(pwr->regmap, pwr->regs->chg_en, &chg_en);
+ if (ret)
+ return ret;
+
+ val->intval = (chg_en & BD7182x_MASK_CHG_EN) ?
+ POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO :
+ POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int bd71828_battery_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ if (val->intval == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO)
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en,
+ BD7182x_MASK_CHG_EN,
+ BD7182x_MASK_CHG_EN);
+ else
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en,
+ BD7182x_MASK_CHG_EN,
+ 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int bd71828_battery_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/** @brief ac properties */
+static const enum power_supply_property bd71828_charger_props[] = {
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static const enum power_supply_property bd71828_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+};
+
+/** @brief powers supplied by bd71828_ac */
+static char *bd71828_ac_supplied_to[] = {
+ BAT_NAME,
+};
+
+static const struct power_supply_desc bd71828_ac_desc = {
+ .name = AC_NAME,
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = bd71828_charger_props,
+ .num_properties = ARRAY_SIZE(bd71828_charger_props),
+ .get_property = bd71828_charger_get_property,
+ .set_property = bd71828_charger_set_property,
+ .property_is_writeable = bd71828_charger_property_is_writeable,
+};
+
+static const struct power_supply_desc bd71828_bat_desc = {
+ .name = BAT_NAME,
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) |
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE),
+ .properties = bd71828_battery_props,
+ .num_properties = ARRAY_SIZE(bd71828_battery_props),
+ .get_property = bd71828_battery_get_property,
+ .set_property = bd71828_battery_set_property,
+ .property_is_writeable = bd71828_battery_property_is_writeable,
+};
+
+#define RSENS_CURR 10000000LLU
+
+#define BD_ISR_NAME(name) \
+bd7181x_##name##_isr
+
+#define BD_ISR_BAT(name, print, run_gauge) \
+static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \
+{ \
+ struct bd71828_power *pwr = (struct bd71828_power *)data; \
+ \
+ dev_dbg(pwr->dev, "%s\n", print); \
+ power_supply_changed(pwr->bat); \
+ \
+ return IRQ_HANDLED; \
+}
+
+#define BD_ISR_AC(name, print, run_gauge) \
+static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \
+{ \
+ struct bd71828_power *pwr = (struct bd71828_power *)data; \
+ \
+ power_supply_changed(pwr->ac); \
+ dev_dbg(pwr->dev, "%s\n", print); \
+ power_supply_changed(pwr->bat); \
+ \
+ return IRQ_HANDLED; \
+}
+
+#define BD_ISR_DUMMY(name, print) \
+static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \
+{ \
+ struct bd71828_power *pwr = (struct bd71828_power *)data; \
+ \
+ dev_dbg(pwr->dev, "%s\n", print); \
+ \
+ return IRQ_HANDLED; \
+}
+
+BD_ISR_BAT(chg_state_changed, "CHG state changed", true)
+/* DCIN voltage changes */
+BD_ISR_AC(dcin_removed, "DCIN removed", true)
+BD_ISR_AC(clps_out, "DCIN voltage back to normal", true)
+BD_ISR_AC(clps_in, "DCIN voltage collapsed", false)
+BD_ISR_AC(dcin_ovp_res, "DCIN voltage normal", true)
+BD_ISR_AC(dcin_ovp_det, "DCIN OVER VOLTAGE", true)
+
+BD_ISR_DUMMY(dcin_mon_det, "DCIN voltage below threshold")
+BD_ISR_DUMMY(dcin_mon_res, "DCIN voltage above threshold")
+
+BD_ISR_DUMMY(vbus_curr_limit, "VBUS current limited")
+BD_ISR_DUMMY(vsys_ov_res, "VSYS over-voltage cleared")
+BD_ISR_DUMMY(vsys_ov_det, "VSYS over-voltage")
+BD_ISR_DUMMY(vsys_uv_res, "VSYS under-voltage cleared")
+BD_ISR_DUMMY(vsys_uv_det, "VSYS under-voltage")
+BD_ISR_DUMMY(vsys_low_res, "'VSYS low' cleared")
+BD_ISR_DUMMY(vsys_low_det, "VSYS low")
+BD_ISR_DUMMY(vsys_mon_res, "VSYS mon - resumed")
+BD_ISR_DUMMY(vsys_mon_det, "VSYS mon - detected")
+BD_ISR_BAT(chg_wdg_temp, "charger temperature watchdog triggered", true)
+BD_ISR_BAT(chg_wdg, "charging watchdog triggered", true)
+BD_ISR_BAT(bat_removed, "Battery removed", true)
+BD_ISR_BAT(bat_det, "Battery detected", true)
+/* TODO: Verify the meaning of these interrupts */
+BD_ISR_BAT(rechg_det, "Recharging", true)
+BD_ISR_BAT(rechg_res, "Recharge ending", true)
+BD_ISR_DUMMY(temp_transit, "Temperature transition")
+BD_ISR_BAT(therm_rmv, "bd71815-therm-rmv", false)
+BD_ISR_BAT(therm_det, "bd71815-therm-det", true)
+BD_ISR_BAT(bat_dead, "bd71815-bat-dead", false)
+BD_ISR_BAT(bat_short_res, "bd71815-bat-short-res", true)
+BD_ISR_BAT(bat_short, "bd71815-bat-short-det", false)
+BD_ISR_BAT(bat_low_res, "bd71815-bat-low-res", true)
+BD_ISR_BAT(bat_low, "bd71815-bat-low-det", true)
+BD_ISR_BAT(bat_ov_res, "bd71815-bat-over-res", true)
+/* What should we do here? */
+BD_ISR_BAT(bat_ov, "bd71815-bat-over-det", false)
+BD_ISR_BAT(bat_mon_res, "bd71815-bat-mon-res", true)
+BD_ISR_BAT(bat_mon, "bd71815-bat-mon-det", true)
+BD_ISR_BAT(bat_cc_mon, "bd71815-bat-cc-mon2", false)
+BD_ISR_BAT(bat_oc1_res, "bd71815-bat-oc1-res", true)
+BD_ISR_BAT(bat_oc1, "bd71815-bat-oc1-det", false)
+BD_ISR_BAT(bat_oc2_res, "bd71815-bat-oc2-res", true)
+BD_ISR_BAT(bat_oc2, "bd71815-bat-oc2-det", false)
+BD_ISR_BAT(bat_oc3_res, "bd71815-bat-oc3-res", true)
+BD_ISR_BAT(bat_oc3, "bd71815-bat-oc3-det", false)
+BD_ISR_BAT(temp_bat_low_res, "bd71815-temp-bat-low-res", true)
+BD_ISR_BAT(temp_bat_low, "bd71815-temp-bat-low-det", true)
+BD_ISR_BAT(temp_bat_hi_res, "bd71815-temp-bat-hi-res", true)
+BD_ISR_BAT(temp_bat_hi, "bd71815-temp-bat-hi-det", true)
+
+static irqreturn_t bd7182x_dcin_removed(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ power_supply_changed(pwr->ac);
+ dev_dbg(pwr->dev, "DCIN removed\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd718x7_chg_done(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ power_supply_changed(pwr->bat);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd7182x_dcin_detected(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ dev_dbg(pwr->dev, "DCIN inserted\n");
+ power_supply_changed(pwr->ac);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71828_vbat_low_res(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ dev_dbg(pwr->dev, "VBAT LOW Resumed\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71828_vbat_low_det(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ dev_dbg(pwr->dev, "VBAT LOW Detected\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71828_temp_bat_hi_det(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ dev_warn(pwr->dev, "Overtemp Detected\n");
+ power_supply_changed(pwr->bat);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71828_temp_bat_hi_res(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ dev_dbg(pwr->dev, "Overtemp Resumed\n");
+ power_supply_changed(pwr->bat);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71828_temp_bat_low_det(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ dev_dbg(pwr->dev, "Lowtemp Detected\n");
+ power_supply_changed(pwr->bat);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71828_temp_bat_low_res(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ dev_dbg(pwr->dev, "Lowtemp Resumed\n");
+ power_supply_changed(pwr->bat);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71828_temp_vf_det(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ dev_dbg(pwr->dev, "VF Detected\n");
+ power_supply_changed(pwr->bat);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71828_temp_vf_res(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ dev_dbg(pwr->dev, "VF Resumed\n");
+ power_supply_changed(pwr->bat);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71828_temp_vf125_det(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ dev_dbg(pwr->dev, "VF125 Detected\n");
+ power_supply_changed(pwr->bat);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71828_temp_vf125_res(int irq, void *data)
+{
+ struct bd71828_power *pwr = (struct bd71828_power *)data;
+
+ dev_dbg(pwr->dev, "VF125 Resumed\n");
+ power_supply_changed(pwr->bat);
+
+ return IRQ_HANDLED;
+}
+
+struct bd7182x_irq_res {
+ const char *name;
+ irq_handler_t handler;
+};
+
+#define BDIRQ(na, hn) { .name = (na), .handler = (hn) }
+
+static int bd7182x_get_irqs(struct platform_device *pdev,
+ struct bd71828_power *pwr)
+{
+ int i, irq, ret;
+ static const struct bd7182x_irq_res bd71815_irqs[] = {
+ BDIRQ("bd71815-dcin-rmv", BD_ISR_NAME(dcin_removed)),
+ BDIRQ("bd71815-dcin-clps-out", BD_ISR_NAME(clps_out)),
+ BDIRQ("bd71815-dcin-clps-in", BD_ISR_NAME(clps_in)),
+ BDIRQ("bd71815-dcin-ovp-res", BD_ISR_NAME(dcin_ovp_res)),
+ BDIRQ("bd71815-dcin-ovp-det", BD_ISR_NAME(dcin_ovp_det)),
+ BDIRQ("bd71815-dcin-mon-res", BD_ISR_NAME(dcin_mon_res)),
+ BDIRQ("bd71815-dcin-mon-det", BD_ISR_NAME(dcin_mon_det)),
+
+ BDIRQ("bd71815-vsys-uv-res", BD_ISR_NAME(vsys_uv_res)),
+ BDIRQ("bd71815-vsys-uv-det", BD_ISR_NAME(vsys_uv_det)),
+ BDIRQ("bd71815-vsys-low-res", BD_ISR_NAME(vsys_low_res)),
+ BDIRQ("bd71815-vsys-low-det", BD_ISR_NAME(vsys_low_det)),
+ BDIRQ("bd71815-vsys-mon-res", BD_ISR_NAME(vsys_mon_res)),
+ BDIRQ("bd71815-vsys-mon-det", BD_ISR_NAME(vsys_mon_det)),
+ BDIRQ("bd71815-chg-wdg-temp", BD_ISR_NAME(chg_wdg_temp)),
+ BDIRQ("bd71815-chg-wdg", BD_ISR_NAME(chg_wdg)),
+ BDIRQ("bd71815-rechg-det", BD_ISR_NAME(rechg_det)),
+ BDIRQ("bd71815-rechg-res", BD_ISR_NAME(rechg_res)),
+ BDIRQ("bd71815-ranged-temp-transit", BD_ISR_NAME(temp_transit)),
+ BDIRQ("bd71815-chg-state-change", BD_ISR_NAME(chg_state_changed)),
+ BDIRQ("bd71815-bat-temp-normal", bd71828_temp_bat_hi_res),
+ BDIRQ("bd71815-bat-temp-erange", bd71828_temp_bat_hi_det),
+ BDIRQ("bd71815-bat-rmv", BD_ISR_NAME(bat_removed)),
+ BDIRQ("bd71815-bat-det", BD_ISR_NAME(bat_det)),
+
+ /* Add ISRs for these */
+ BDIRQ("bd71815-therm-rmv", BD_ISR_NAME(therm_rmv)),
+ BDIRQ("bd71815-therm-det", BD_ISR_NAME(therm_det)),
+ BDIRQ("bd71815-bat-dead", BD_ISR_NAME(bat_dead)),
+ BDIRQ("bd71815-bat-short-res", BD_ISR_NAME(bat_short_res)),
+ BDIRQ("bd71815-bat-short-det", BD_ISR_NAME(bat_short)),
+ BDIRQ("bd71815-bat-low-res", BD_ISR_NAME(bat_low_res)),
+ BDIRQ("bd71815-bat-low-det", BD_ISR_NAME(bat_low)),
+ BDIRQ("bd71815-bat-over-res", BD_ISR_NAME(bat_ov_res)),
+ BDIRQ("bd71815-bat-over-det", BD_ISR_NAME(bat_ov)),
+ BDIRQ("bd71815-bat-mon-res", BD_ISR_NAME(bat_mon_res)),
+ BDIRQ("bd71815-bat-mon-det", BD_ISR_NAME(bat_mon)),
+ /* cc-mon 1 & 3 ? */
+ BDIRQ("bd71815-bat-cc-mon2", BD_ISR_NAME(bat_cc_mon)),
+ BDIRQ("bd71815-bat-oc1-res", BD_ISR_NAME(bat_oc1_res)),
+ BDIRQ("bd71815-bat-oc1-det", BD_ISR_NAME(bat_oc1)),
+ BDIRQ("bd71815-bat-oc2-res", BD_ISR_NAME(bat_oc2_res)),
+ BDIRQ("bd71815-bat-oc2-det", BD_ISR_NAME(bat_oc2)),
+ BDIRQ("bd71815-bat-oc3-res", BD_ISR_NAME(bat_oc3_res)),
+ BDIRQ("bd71815-bat-oc3-det", BD_ISR_NAME(bat_oc3)),
+ BDIRQ("bd71815-temp-bat-low-res", BD_ISR_NAME(temp_bat_low_res)),
+ BDIRQ("bd71815-temp-bat-low-det", BD_ISR_NAME(temp_bat_low)),
+ BDIRQ("bd71815-temp-bat-hi-res", BD_ISR_NAME(temp_bat_hi_res)),
+ BDIRQ("bd71815-temp-bat-hi-det", BD_ISR_NAME(temp_bat_hi)),
+ /*
+ * TODO: add rest of the IRQs and re-check the handling.
+ * Check the bd71815-bat-cc-mon1, bd71815-bat-cc-mon3,
+ * bd71815-bat-low-res, bd71815-bat-low-det,
+ * bd71815-bat-hi-res, bd71815-bat-hi-det.
+ */
+ };
+ static const struct bd7182x_irq_res bd71828_irqs[] = {
+ BDIRQ("bd71828-chg-done", bd718x7_chg_done),
+ BDIRQ("bd71828-pwr-dcin-in", bd7182x_dcin_detected),
+ BDIRQ("bd71828-pwr-dcin-out", bd7182x_dcin_removed),
+ BDIRQ("bd71828-vbat-normal", bd71828_vbat_low_res),
+ BDIRQ("bd71828-vbat-low", bd71828_vbat_low_det),
+ BDIRQ("bd71828-btemp-hi", bd71828_temp_bat_hi_det),
+ BDIRQ("bd71828-btemp-cool", bd71828_temp_bat_hi_res),
+ BDIRQ("bd71828-btemp-lo", bd71828_temp_bat_low_det),
+ BDIRQ("bd71828-btemp-warm", bd71828_temp_bat_low_res),
+ BDIRQ("bd71828-temp-hi", bd71828_temp_vf_det),
+ BDIRQ("bd71828-temp-norm", bd71828_temp_vf_res),
+ BDIRQ("bd71828-temp-125-over", bd71828_temp_vf125_det),
+ BDIRQ("bd71828-temp-125-under", bd71828_temp_vf125_res),
+ };
+ static const struct bd7182x_irq_res bd72720_irqs[] = {
+ BDIRQ("bd72720_int_vbus_rmv", BD_ISR_NAME(dcin_removed)),
+ BDIRQ("bd72720_int_vbus_det", bd7182x_dcin_detected),
+ BDIRQ("bd72720_int_vbus_mon_res", BD_ISR_NAME(dcin_mon_res)),
+ BDIRQ("bd72720_int_vbus_mon_det", BD_ISR_NAME(dcin_mon_det)),
+ BDIRQ("bd72720_int_vsys_mon_res", BD_ISR_NAME(vsys_mon_res)),
+ BDIRQ("bd72720_int_vsys_mon_det", BD_ISR_NAME(vsys_mon_det)),
+ BDIRQ("bd72720_int_vsys_uv_res", BD_ISR_NAME(vsys_uv_res)),
+ BDIRQ("bd72720_int_vsys_uv_det", BD_ISR_NAME(vsys_uv_det)),
+ BDIRQ("bd72720_int_vsys_lo_res", BD_ISR_NAME(vsys_low_res)),
+ BDIRQ("bd72720_int_vsys_lo_det", BD_ISR_NAME(vsys_low_det)),
+ BDIRQ("bd72720_int_vsys_ov_res", BD_ISR_NAME(vsys_ov_res)),
+ BDIRQ("bd72720_int_vsys_ov_det", BD_ISR_NAME(vsys_ov_det)),
+ BDIRQ("bd72720_int_bat_ilim", BD_ISR_NAME(vbus_curr_limit)),
+ BDIRQ("bd72720_int_chg_done", bd718x7_chg_done),
+ BDIRQ("bd72720_int_extemp_tout", BD_ISR_NAME(chg_wdg_temp)),
+ BDIRQ("bd72720_int_chg_wdt_exp", BD_ISR_NAME(chg_wdg)),
+ BDIRQ("bd72720_int_bat_mnt_out", BD_ISR_NAME(rechg_res)),
+ BDIRQ("bd72720_int_bat_mnt_in", BD_ISR_NAME(rechg_det)),
+ BDIRQ("bd72720_int_chg_trns", BD_ISR_NAME(chg_state_changed)),
+
+ BDIRQ("bd72720_int_vbat_mon_res", BD_ISR_NAME(bat_mon_res)),
+ BDIRQ("bd72720_int_vbat_mon_det", BD_ISR_NAME(bat_mon)),
+ BDIRQ("bd72720_int_vbat_sht_res", BD_ISR_NAME(bat_short_res)),
+ BDIRQ("bd72720_int_vbat_sht_det", BD_ISR_NAME(bat_short)),
+ BDIRQ("bd72720_int_vbat_lo_res", BD_ISR_NAME(bat_low_res)),
+ BDIRQ("bd72720_int_vbat_lo_det", BD_ISR_NAME(bat_low)),
+ BDIRQ("bd72720_int_vbat_ov_res", BD_ISR_NAME(bat_ov_res)),
+ BDIRQ("bd72720_int_vbat_ov_det", BD_ISR_NAME(bat_ov)),
+ BDIRQ("bd72720_int_bat_rmv", BD_ISR_NAME(bat_removed)),
+ BDIRQ("bd72720_int_bat_det", BD_ISR_NAME(bat_det)),
+ BDIRQ("bd72720_int_dbat_det", BD_ISR_NAME(bat_dead)),
+ BDIRQ("bd72720_int_bat_temp_trns", BD_ISR_NAME(temp_transit)),
+ BDIRQ("bd72720_int_lobtmp_res", BD_ISR_NAME(temp_bat_low_res)),
+ BDIRQ("bd72720_int_lobtmp_det", BD_ISR_NAME(temp_bat_low)),
+ BDIRQ("bd72720_int_ovbtmp_res", BD_ISR_NAME(temp_bat_hi_res)),
+ BDIRQ("bd72720_int_ovbtmp_det", BD_ISR_NAME(temp_bat_hi)),
+ BDIRQ("bd72720_int_ocur1_res", BD_ISR_NAME(bat_oc1_res)),
+ BDIRQ("bd72720_int_ocur1_det", BD_ISR_NAME(bat_oc1)),
+ BDIRQ("bd72720_int_ocur2_res", BD_ISR_NAME(bat_oc2_res)),
+ BDIRQ("bd72720_int_ocur2_det", BD_ISR_NAME(bat_oc2)),
+ BDIRQ("bd72720_int_ocur3_res", BD_ISR_NAME(bat_oc3_res)),
+ BDIRQ("bd72720_int_ocur3_det", BD_ISR_NAME(bat_oc3)),
+ BDIRQ("bd72720_int_cc_mon2_det", BD_ISR_NAME(bat_cc_mon)),
+ };
+ int num_irqs;
+ const struct bd7182x_irq_res *irqs;
+
+ switch (pwr->chip_type) {
+ case ROHM_CHIP_TYPE_BD71828:
+ irqs = &bd71828_irqs[0];
+ num_irqs = ARRAY_SIZE(bd71828_irqs);
+ break;
+ case ROHM_CHIP_TYPE_BD71815:
+ irqs = &bd71815_irqs[0];
+ num_irqs = ARRAY_SIZE(bd71815_irqs);
+ break;
+ case ROHM_CHIP_TYPE_BD72720:
+ irqs = &bd72720_irqs[0];
+ num_irqs = ARRAY_SIZE(bd72720_irqs);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ for (i = 0; i < num_irqs; i++) {
+ irq = platform_get_irq_byname(pdev, irqs[i].name);
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ irqs[i].handler, 0,
+ irqs[i].name, pwr);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+#define RSENS_DEFAULT_30MOHM 30000 /* 30 mOhm in uOhms*/
+
+static int bd7182x_get_rsens(struct bd71828_power *pwr)
+{
+ u64 tmp = RSENS_CURR;
+ int rsens_ohm = RSENS_DEFAULT_30MOHM;
+ struct fwnode_handle *node = NULL;
+
+ if (pwr->dev->parent)
+ node = dev_fwnode(pwr->dev->parent);
+
+ if (node) {
+ int ret;
+ u32 rs;
+
+ ret = fwnode_property_read_u32(node,
+ "rohm,charger-sense-resistor-micro-ohms",
+ &rs);
+ if (ret) {
+ if (ret == -EINVAL) {
+ rs = RSENS_DEFAULT_30MOHM;
+ } else {
+ dev_err(pwr->dev, "Bad RSENS dt property\n");
+ return ret;
+ }
+ }
+ if (!rs) {
+ dev_err(pwr->dev, "Bad RSENS value\n");
+ return -EINVAL;
+ }
+
+ rsens_ohm = (int)rs;
+ }
+
+ /* Reg val to uA */
+ do_div(tmp, rsens_ohm);
+
+ pwr->curr_factor = tmp;
+ pwr->rsens = rsens_ohm;
+ dev_dbg(pwr->dev, "Setting rsens to %u micro ohm\n", pwr->rsens);
+ dev_dbg(pwr->dev, "Setting curr-factor to %u\n", pwr->curr_factor);
+
+ return 0;
+}
+
+static int bd71828_power_probe(struct platform_device *pdev)
+{
+ struct bd71828_power *pwr;
+ struct power_supply_config ac_cfg = {};
+ struct power_supply_config bat_cfg = {};
+ int ret;
+
+ pwr = devm_kzalloc(&pdev->dev, sizeof(*pwr), GFP_KERNEL);
+ if (!pwr)
+ return -ENOMEM;
+
+ /*
+ * The BD72720 MFD device registers two regmaps. Power-supply driver
+ * uses the "wrap-map", which provides access to both of the I2C slave
+ * addresses used by the BD72720
+ */
+ pwr->chip_type = platform_get_device_id(pdev)->driver_data;
+ if (pwr->chip_type != ROHM_CHIP_TYPE_BD72720)
+ pwr->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ else
+ pwr->regmap = dev_get_regmap(pdev->dev.parent, "wrap-map");
+ if (!pwr->regmap)
+ return dev_err_probe(&pdev->dev, -EINVAL, "No parent regmap\n");
+
+ pwr->dev = &pdev->dev;
+
+ switch (pwr->chip_type) {
+ case ROHM_CHIP_TYPE_BD71828:
+ pwr->bat_inserted = bd71828_bat_inserted;
+ pwr->get_temp = bd71828_get_temp;
+ pwr->regs = &pwr_regs_bd71828;
+ break;
+ case ROHM_CHIP_TYPE_BD71815:
+ pwr->bat_inserted = bd71815_bat_inserted;
+ pwr->get_temp = bd71815_get_temp;
+ pwr->regs = &pwr_regs_bd71815;
+ break;
+ case ROHM_CHIP_TYPE_BD72720:
+ pwr->bat_inserted = bd71828_bat_inserted;
+ pwr->regs = &pwr_regs_bd72720;
+ pwr->get_temp = bd71828_get_temp;
+ dev_dbg(pwr->dev, "Found ROHM BD72720\n");
+ break;
+ default:
+ return dev_err_probe(pwr->dev, -EINVAL, "Unknown PMIC\n");
+ }
+
+ ret = bd7182x_get_rsens(pwr);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "sense resistor missing\n");
+
+ dev_set_drvdata(&pdev->dev, pwr);
+ bd71828_init_hardware(pwr);
+
+ bat_cfg.drv_data = pwr;
+ bat_cfg.fwnode = dev_fwnode(&pdev->dev);
+
+ ac_cfg.supplied_to = bd71828_ac_supplied_to;
+ ac_cfg.num_supplicants = ARRAY_SIZE(bd71828_ac_supplied_to);
+ ac_cfg.drv_data = pwr;
+
+ pwr->ac = devm_power_supply_register(&pdev->dev, &bd71828_ac_desc,
+ &ac_cfg);
+ if (IS_ERR(pwr->ac))
+ return dev_err_probe(&pdev->dev, PTR_ERR(pwr->ac),
+ "failed to register ac\n");
+
+ pwr->bat = devm_power_supply_register(&pdev->dev, &bd71828_bat_desc,
+ &bat_cfg);
+ if (IS_ERR(pwr->bat))
+ return dev_err_probe(&pdev->dev, PTR_ERR(pwr->bat),
+ "failed to register bat\n");
+
+ ret = bd7182x_get_irqs(pdev, pwr);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to request IRQs");
+
+ /* Configure wakeup capable */
+ device_set_wakeup_capable(pwr->dev, 1);
+ device_set_wakeup_enable(pwr->dev, 1);
+
+ return 0;
+}
+
+static const struct platform_device_id bd71828_charger_id[] = {
+ { "bd71815-power", ROHM_CHIP_TYPE_BD71815 },
+ { "bd71828-power", ROHM_CHIP_TYPE_BD71828 },
+ { "bd72720-power", ROHM_CHIP_TYPE_BD72720 },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, bd71828_charger_id);
+
+static struct platform_driver bd71828_power_driver = {
+ .driver = {
+ .name = "bd718xx-power",
+ },
+ .probe = bd71828_power_probe,
+ .id_table = bd71828_charger_id,
+};
+
+module_platform_driver(bd71828_power_driver);
+
+MODULE_AUTHOR("Cong Pham <cpham2403@gmail.com>");
+MODULE_DESCRIPTION("ROHM BD718(15/28/78) PMIC Battery Charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/bd99954-charger.c b/drivers/power/supply/bd99954-charger.c
index 54bf88262510..5c447b088223 100644
--- a/drivers/power/supply/bd99954-charger.c
+++ b/drivers/power/supply/bd99954-charger.c
@@ -56,7 +56,6 @@
*/
#include <linux/delay.h>
-#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
@@ -156,7 +155,7 @@ static const struct regmap_config bd9995x_regmap_config = {
.reg_stride = 1,
.max_register = 3 * 0x100,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.ranges = regmap_range_cfg,
.num_ranges = ARRAY_SIZE(regmap_range_cfg),
@@ -982,7 +981,7 @@ static int bd9995x_probe(struct i2c_client *client)
bd->client = client;
bd->dev = dev;
psy_cfg.drv_data = bd;
- psy_cfg.of_node = dev->of_node;
+ psy_cfg.fwnode = dev_fwnode(dev);
mutex_init(&bd->lock);
diff --git a/drivers/power/supply/bq2415x_charger.c b/drivers/power/supply/bq2415x_charger.c
index 22f6a3b71632..b50a28b9dd38 100644
--- a/drivers/power/supply/bq2415x_charger.c
+++ b/drivers/power/supply/bq2415x_charger.c
@@ -842,7 +842,7 @@ static int bq2415x_notifier_call(struct notifier_block *nb,
if (bq->automode < 1)
return NOTIFY_OK;
- mod_delayed_work(system_wq, &bq->work, 0);
+ mod_delayed_work(system_percpu_wq, &bq->work, 0);
return NOTIFY_OK;
}
@@ -1497,7 +1497,7 @@ static int bq2415x_power_supply_init(struct bq2415x_device *bq)
char revstr[8];
struct power_supply_config psy_cfg = {
.drv_data = bq,
- .of_node = bq->dev->of_node,
+ .fwnode = dev_fwnode(bq->dev),
.attr_grp = bq2415x_sysfs_groups,
};
@@ -1516,7 +1516,7 @@ static int bq2415x_power_supply_init(struct bq2415x_device *bq)
ret = bq2415x_detect_revision(bq);
if (ret < 0)
- strcpy(revstr, "unknown");
+ strscpy(revstr, "unknown", sizeof(revstr));
else
sprintf(revstr, "1.%d", ret);
@@ -1674,7 +1674,7 @@ static int bq2415x_probe(struct i2c_client *client)
/* Query for initial reported_mode and set it */
if (bq->nb.notifier_call) {
if (np) {
- notify_psy = power_supply_get_by_phandle(np,
+ notify_psy = power_supply_get_by_reference(of_fwnode_handle(np),
"ti,usb-charger-detection");
if (IS_ERR(notify_psy))
notify_psy = NULL;
diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c
index b4ba01744368..55da91bacc3e 100644
--- a/drivers/power/supply/bq24190_charger.c
+++ b/drivers/power/supply/bq24190_charger.c
@@ -9,6 +9,7 @@
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
+#include <linux/devm-helpers.h>
#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
#include <linux/power/bq24190_charger.h>
@@ -207,6 +208,7 @@ enum bq24190_chip {
BQ24190,
BQ24192,
BQ24192i,
+ BQ24193,
BQ24196,
BQ24296,
BQ24297,
@@ -503,7 +505,6 @@ static ssize_t bq24190_sysfs_show(struct device *dev,
else
count = sysfs_emit(buf, "%hhx\n", v);
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return count;
@@ -534,7 +535,6 @@ static ssize_t bq24190_sysfs_store(struct device *dev,
if (ret)
count = ret;
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return count;
@@ -561,7 +561,6 @@ static int bq24190_set_otg_vbus(struct bq24190_dev_info *bdi, bool enable)
else
ret = bq24190_charger_set_charge_type(bdi, &val);
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return ret;
@@ -604,7 +603,6 @@ static int bq24296_set_otg_vbus(struct bq24190_dev_info *bdi, bool enable)
}
out:
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return ret;
@@ -637,7 +635,6 @@ static int bq24190_vbus_is_enabled(struct regulator_dev *dev)
BQ24190_REG_POC_CHG_CONFIG_MASK,
BQ24190_REG_POC_CHG_CONFIG_SHIFT, &val);
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
if (ret)
@@ -674,7 +671,6 @@ static int bq24296_vbus_is_enabled(struct regulator_dev *dev)
BQ24296_REG_POC_OTG_CONFIG_MASK,
BQ24296_REG_POC_OTG_CONFIG_SHIFT, &val);
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
if (ret)
@@ -1375,7 +1371,6 @@ static int bq24190_charger_get_property(struct power_supply *psy,
ret = -ENODATA;
}
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return ret;
@@ -1418,7 +1413,6 @@ static int bq24190_charger_set_property(struct power_supply *psy,
ret = -EINVAL;
}
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return ret;
@@ -1474,7 +1468,7 @@ static void bq24190_charger_external_power_changed(struct power_supply *psy)
* too low default 500mA iinlim. Delay setting the input-current-limit
* for 300ms to avoid this.
*/
- queue_delayed_work(system_wq, &bdi->input_current_limit_work,
+ queue_delayed_work(system_percpu_wq, &bdi->input_current_limit_work,
msecs_to_jiffies(300));
}
@@ -1681,7 +1675,6 @@ static int bq24190_battery_get_property(struct power_supply *psy,
ret = -ENODATA;
}
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return ret;
@@ -1712,7 +1705,6 @@ static int bq24190_battery_set_property(struct power_supply *psy,
ret = -EINVAL;
}
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return ret;
@@ -1860,7 +1852,6 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
return IRQ_NONE;
}
bq24190_check_status(bdi);
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
bdi->irq_event = false;
@@ -1982,6 +1973,8 @@ static int bq24190_get_config(struct bq24190_dev_info *bdi)
v = info->constant_charge_voltage_max_uv;
if (v >= bq24190_cvc_vreg_values[0] && v <= bdi->vreg_max)
bdi->vreg = bdi->vreg_max = v;
+
+ power_supply_put_battery_info(bdi->charger, info);
}
return 0;
@@ -2021,6 +2014,17 @@ static const struct bq24190_chip_info bq24190_chip_info_tbl[] = {
.get_ntc_status = bq24190_charger_get_ntc_status,
.set_otg_vbus = bq24190_set_otg_vbus,
},
+ [BQ24193] = {
+ .ichg_array_size = ARRAY_SIZE(bq24190_ccc_ichg_values),
+#ifdef CONFIG_REGULATOR
+ .vbus_desc = &bq24190_vbus_desc,
+#endif
+ .check_chip = bq24190_check_chip,
+ .set_chg_config = bq24190_battery_set_chg_config,
+ .ntc_fault_mask = BQ24190_REG_F_NTC_FAULT_MASK,
+ .get_ntc_status = bq24190_charger_get_ntc_status,
+ .set_otg_vbus = bq24190_set_otg_vbus,
+ },
[BQ24196] = {
.ichg_array_size = ARRAY_SIZE(bq24190_ccc_ichg_values),
#ifdef CONFIG_REGULATOR
@@ -2084,8 +2088,11 @@ static int bq24190_probe(struct i2c_client *client)
bdi->charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
bdi->f_reg = 0;
bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */
- INIT_DELAYED_WORK(&bdi->input_current_limit_work,
- bq24190_input_current_limit_work);
+
+ ret = devm_delayed_work_autocancel(dev, &bdi->input_current_limit_work,
+ bq24190_input_current_limit_work);
+ if (ret)
+ return ret;
i2c_set_clientdata(client, bdi);
@@ -2117,7 +2124,7 @@ static int bq24190_probe(struct i2c_client *client)
#endif
charger_cfg.drv_data = bdi;
- charger_cfg.of_node = dev->of_node;
+ charger_cfg.fwnode = dev_fwnode(dev);
charger_cfg.supplied_to = bq24190_charger_supplied_to;
charger_cfg.num_supplicants = ARRAY_SIZE(bq24190_charger_supplied_to);
bdi->charger = power_supply_register(dev, &bq24190_charger_desc,
@@ -2174,7 +2181,6 @@ static int bq24190_probe(struct i2c_client *client)
enable_irq_wake(client->irq);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0;
@@ -2196,7 +2202,6 @@ static void bq24190_remove(struct i2c_client *client)
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
int error;
- cancel_delayed_work_sync(&bdi->input_current_limit_work);
error = pm_runtime_resume_and_get(bdi->dev);
if (error < 0)
dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error);
@@ -2261,7 +2266,6 @@ static __maybe_unused int bq24190_pm_suspend(struct device *dev)
bq24190_register_reset(bdi);
if (error >= 0) {
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
}
@@ -2286,7 +2290,6 @@ static __maybe_unused int bq24190_pm_resume(struct device *dev)
bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg);
if (error >= 0) {
- pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
}
@@ -2308,6 +2311,7 @@ static const struct i2c_device_id bq24190_i2c_ids[] = {
{ "bq24190", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24190] },
{ "bq24192", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24192] },
{ "bq24192i", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24192i] },
+ { "bq24193", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24193] },
{ "bq24196", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24196] },
{ "bq24296", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24296] },
{ "bq24297", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24297] },
@@ -2319,6 +2323,7 @@ static const struct of_device_id bq24190_of_match[] = {
{ .compatible = "ti,bq24190", .data = &bq24190_chip_info_tbl[BQ24190] },
{ .compatible = "ti,bq24192", .data = &bq24190_chip_info_tbl[BQ24192] },
{ .compatible = "ti,bq24192i", .data = &bq24190_chip_info_tbl[BQ24192i] },
+ { .compatible = "ti,bq24193", .data = &bq24190_chip_info_tbl[BQ24193] },
{ .compatible = "ti,bq24196", .data = &bq24190_chip_info_tbl[BQ24196] },
{ .compatible = "ti,bq24296", .data = &bq24190_chip_info_tbl[BQ24296] },
{ .compatible = "ti,bq24297", .data = &bq24190_chip_info_tbl[BQ24297] },
diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c
index 1416586f2459..766eecb35694 100644
--- a/drivers/power/supply/bq24257_charger.c
+++ b/drivers/power/supply/bq24257_charger.c
@@ -113,7 +113,7 @@ static const struct regmap_config bq24257_regmap_config = {
.val_bits = 8,
.max_register = BQ24257_REG_7,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = bq24257_is_volatile_reg,
};
diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c
index 73a7fc867b03..637e0da65f87 100644
--- a/drivers/power/supply/bq24735-charger.c
+++ b/drivers/power/supply/bq24735-charger.c
@@ -402,7 +402,7 @@ static int bq24735_charger_probe(struct i2c_client *client)
psy_cfg.supplied_to = charger->pdata->supplied_to;
psy_cfg.num_supplicants = charger->pdata->num_supplicants;
- psy_cfg.of_node = client->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&client->dev);
psy_cfg.drv_data = charger;
i2c_set_clientdata(client, charger);
diff --git a/drivers/power/supply/bq2515x_charger.c b/drivers/power/supply/bq2515x_charger.c
index a3424f67f2b1..437bff5bc420 100644
--- a/drivers/power/supply/bq2515x_charger.c
+++ b/drivers/power/supply/bq2515x_charger.c
@@ -1060,7 +1060,7 @@ static const struct regmap_config bq25150_regmap_config = {
.max_register = BQ2515X_DEVICE_ID,
.reg_defaults = bq25150_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(bq25150_reg_defaults),
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = bq2515x_volatile_register,
};
@@ -1071,7 +1071,7 @@ static const struct regmap_config bq25155_regmap_config = {
.max_register = BQ2515X_DEVICE_ID,
.reg_defaults = bq25155_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(bq25155_reg_defaults),
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = bq2515x_volatile_register,
};
@@ -1102,7 +1102,7 @@ static int bq2515x_probe(struct i2c_client *client)
i2c_set_clientdata(client, bq2515x);
charger_cfg.drv_data = bq2515x;
- charger_cfg.of_node = dev->of_node;
+ charger_cfg.fwnode = dev_fwnode(dev);
ret = bq2515x_read_properties(bq2515x);
if (ret) {
diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c
index 5514d1896bb8..563f512709b3 100644
--- a/drivers/power/supply/bq256xx_charger.c
+++ b/drivers/power/supply/bq256xx_charger.c
@@ -8,7 +8,6 @@
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/gpio/consumer.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/types.h>
@@ -387,7 +386,7 @@ static void bq256xx_usb_work(struct work_struct *data)
}
}
-static struct reg_default bq2560x_reg_defs[] = {
+static const struct reg_default bq2560x_reg_defs[] = {
{BQ256XX_INPUT_CURRENT_LIMIT, 0x17},
{BQ256XX_CHARGER_CONTROL_0, 0x1a},
{BQ256XX_CHARGE_CURRENT_LIMIT, 0xa2},
@@ -398,7 +397,7 @@ static struct reg_default bq2560x_reg_defs[] = {
{BQ256XX_CHARGER_CONTROL_3, 0x4c},
};
-static struct reg_default bq25611d_reg_defs[] = {
+static const struct reg_default bq25611d_reg_defs[] = {
{BQ256XX_INPUT_CURRENT_LIMIT, 0x17},
{BQ256XX_CHARGER_CONTROL_0, 0x1a},
{BQ256XX_CHARGE_CURRENT_LIMIT, 0x91},
@@ -411,7 +410,7 @@ static struct reg_default bq25611d_reg_defs[] = {
{BQ256XX_CHARGER_CONTROL_4, 0x75},
};
-static struct reg_default bq25618_619_reg_defs[] = {
+static const struct reg_default bq25618_619_reg_defs[] = {
{BQ256XX_INPUT_CURRENT_LIMIT, 0x17},
{BQ256XX_CHARGER_CONTROL_0, 0x1a},
{BQ256XX_CHARGE_CURRENT_LIMIT, 0x91},
@@ -1657,7 +1656,7 @@ static int bq256xx_parse_dt(struct bq256xx_device *bq,
int ret = 0;
psy_cfg->drv_data = bq;
- psy_cfg->of_node = dev->of_node;
+ psy_cfg->fwnode = dev_fwnode(dev);
ret = device_property_read_u32(bq->dev, "ti,watchdog-timeout-ms",
&bq->watchdog_timer);
@@ -1741,6 +1740,12 @@ static int bq256xx_probe(struct i2c_client *client)
usb_register_notifier(bq->usb3_phy, &bq->usb_nb);
}
+ ret = bq256xx_power_supply_init(bq, &psy_cfg, dev);
+ if (ret) {
+ dev_err(dev, "Failed to register power supply\n");
+ return ret;
+ }
+
if (client->irq) {
ret = devm_request_threaded_irq(dev, client->irq, NULL,
bq256xx_irq_handler_thread,
@@ -1753,12 +1758,6 @@ static int bq256xx_probe(struct i2c_client *client)
}
}
- ret = bq256xx_power_supply_init(bq, &psy_cfg, dev);
- if (ret) {
- dev_err(dev, "Failed to register power supply\n");
- return ret;
- }
-
ret = bq256xx_hw_init(bq);
if (ret) {
dev_err(dev, "Cannot initialize the chip.\n");
diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c
new file mode 100644
index 000000000000..02c7d8b61e82
--- /dev/null
+++ b/drivers/power/supply/bq257xx_charger.c
@@ -0,0 +1,755 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BQ257XX Battery Charger Driver
+ * Copyright (C) 2025 Chris Morgan <macromorgan@hotmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/bq257xx.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/* Forward declaration of driver data. */
+struct bq257xx_chg;
+
+/**
+ * struct bq257xx_chip_info - chip specific routines
+ * @bq257xx_hw_init: init function for hw
+ * @bq257xx_hw_shutdown: shutdown function for hw
+ * @bq257xx_get_state: get and update state of hardware
+ * @bq257xx_set_ichg: set maximum charge current (in uA)
+ * @bq257xx_set_vbatreg: set maximum charge voltage (in uV)
+ * @bq257xx_set_iindpm: set maximum input current (in uA)
+ */
+struct bq257xx_chip_info {
+ int (*bq257xx_hw_init)(struct bq257xx_chg *pdata);
+ void (*bq257xx_hw_shutdown)(struct bq257xx_chg *pdata);
+ int (*bq257xx_get_state)(struct bq257xx_chg *pdata);
+ int (*bq257xx_set_ichg)(struct bq257xx_chg *pdata, int ichg);
+ int (*bq257xx_set_vbatreg)(struct bq257xx_chg *pdata, int vbatreg);
+ int (*bq257xx_set_iindpm)(struct bq257xx_chg *pdata, int iindpm);
+};
+
+/**
+ * struct bq257xx_chg - driver data for charger
+ * @chip: hw specific functions
+ * @bq: parent MFD device
+ * @charger: power supply device
+ * @online: charger input is present
+ * @fast_charge: charger is in fast charge mode
+ * @pre_charge: charger is in pre-charge mode
+ * @ov_fault: charger reports over voltage fault
+ * @batoc_fault: charger reports battery over current fault
+ * @oc_fault: charger reports over current fault
+ * @usb_type: USB type reported from parent power supply
+ * @supplied: Status of parent power supply
+ * @iindpm_max: maximum input current limit (uA)
+ * @vbat_max: maximum charge voltage (uV)
+ * @ichg_max: maximum charge current (uA)
+ * @vsys_min: minimum system voltage (uV)
+ */
+struct bq257xx_chg {
+ const struct bq257xx_chip_info *chip;
+ struct bq257xx_device *bq;
+ struct power_supply *charger;
+ bool online;
+ bool fast_charge;
+ bool pre_charge;
+ bool ov_fault;
+ bool batoc_fault;
+ bool oc_fault;
+ int usb_type;
+ int supplied;
+ u32 iindpm_max;
+ u32 vbat_max;
+ u32 ichg_max;
+ u32 vsys_min;
+};
+
+/**
+ * bq25703_get_state() - Get the current state of the device
+ * @pdata: driver platform data
+ *
+ * Get the current state of the charger. Check if the charger is
+ * powered, what kind of charge state (if any) the device is in,
+ * and if there are any active faults.
+ *
+ * Return: Returns 0 on success, or error on failure to read device.
+ */
+static int bq25703_get_state(struct bq257xx_chg *pdata)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25703_CHARGER_STATUS, &reg);
+ 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,
+ &reg);
+ 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, &reg);
+ 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, &reg);
+ 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,
+ &reg);
+ 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, &reg);
+ 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, &reg);
+ if (ret)
+ return ret;
+
+ reg = FIELD_GET(BQ25703_ADCVBAT_MASK, reg);
+ *intval = (reg * BQ25703_ADCVSYSVBAT_STEP) + BQ25703_ADCVSYSVBAT_OFFSET_UV;
+
+ return ret;
+}
+
+/**
+ * bq25703_hw_init() - Set all the required registers to init the charger
+ * @pdata: driver platform data
+ *
+ * Initialize the BQ25703 by first disabling the watchdog timer (which
+ * shuts off the charger in the absence of periodic writes). Then, set
+ * the charge current, charge voltage, minimum system voltage, and
+ * input current limit. Disable low power mode to allow ADCs and
+ * interrupts. Enable the ADC, start the ADC, set the ADC scale to
+ * full, and enable each individual ADC channel.
+ *
+ * Return: Returns 0 on success or error code on error.
+ */
+static int bq25703_hw_init(struct bq257xx_chg *pdata)
+{
+ struct regmap *regmap = pdata->bq->regmap;
+ int ret = 0;
+
+ regmap_update_bits(regmap, BQ25703_CHARGE_OPTION_0,
+ BQ25703_WDTMR_ADJ_MASK,
+ FIELD_PREP(BQ25703_WDTMR_ADJ_MASK,
+ BQ25703_WDTMR_DISABLE));
+
+ ret = pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max);
+ if (ret)
+ return ret;
+
+ ret = pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max);
+ if (ret)
+ return ret;
+
+ ret = bq25703_set_min_vsys(pdata, pdata->vsys_min);
+ if (ret)
+ return ret;
+
+ ret = pdata->chip->bq257xx_set_iindpm(pdata, pdata->iindpm_max);
+ if (ret)
+ return ret;
+
+ /* Disable low power mode by writing 0 to the register. */
+ regmap_update_bits(regmap, BQ25703_CHARGE_OPTION_0,
+ BQ25703_EN_LWPWR, 0);
+
+ /* Enable the ADC. */
+ regmap_update_bits(regmap, BQ25703_ADC_OPTION,
+ BQ25703_ADC_CONV_EN, BQ25703_ADC_CONV_EN);
+
+ /* Start the ADC. */
+ regmap_update_bits(regmap, BQ25703_ADC_OPTION,
+ BQ25703_ADC_START, BQ25703_ADC_START);
+
+ /* Set the scale of the ADC. */
+ regmap_update_bits(regmap, BQ25703_ADC_OPTION,
+ BQ25703_ADC_FULL_SCALE, BQ25703_ADC_FULL_SCALE);
+
+ /* Enable each of the ADC channels available. */
+ regmap_update_bits(regmap, BQ25703_ADC_OPTION,
+ BQ25703_ADC_CH_MASK,
+ (BQ25703_ADC_CMPIN_EN | BQ25703_ADC_VBUS_EN |
+ BQ25703_ADC_PSYS_EN | BQ25703_ADC_IIN_EN |
+ BQ25703_ADC_IDCHG_EN | BQ25703_ADC_ICHG_EN |
+ BQ25703_ADC_VSYS_EN | BQ25703_ADC_VBAT_EN));
+
+ return ret;
+}
+
+/**
+ * bq25703_hw_shutdown() - Set registers for shutdown
+ * @pdata: driver platform data
+ *
+ * Enable low power mode for the device while in shutdown.
+ */
+static void bq25703_hw_shutdown(struct bq257xx_chg *pdata)
+{
+ regmap_update_bits(pdata->bq->regmap, BQ25703_CHARGE_OPTION_0,
+ BQ25703_EN_LWPWR, BQ25703_EN_LWPWR);
+}
+
+static int bq257xx_set_charger_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct bq257xx_chg *pdata = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return pdata->chip->bq257xx_set_iindpm(pdata, val->intval);
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ return pdata->chip->bq257xx_set_vbatreg(pdata, val->intval);
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ return pdata->chip->bq257xx_set_ichg(pdata, val->intval);
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int bq257xx_get_charger_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bq257xx_chg *pdata = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ ret = pdata->chip->bq257xx_get_state(pdata);
+ if (ret)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!pdata->online)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (pdata->fast_charge || pdata->pre_charge)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (pdata->ov_fault || pdata->batoc_fault)
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else if (pdata->oc_fault)
+ val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "Texas Instruments";
+ break;
+
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = pdata->online;
+ break;
+
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return bq25703_get_iindpm(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ return bq25703_get_chrg_volt(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return bq25703_get_cur(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return bq25703_get_vbat(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ return bq25703_get_ichg_cur(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ return bq25703_get_min_vsys(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = pdata->usb_type;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property bq257xx_power_supply_props[] = {
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_USB_TYPE,
+};
+
+static int bq257xx_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * bq257xx_external_power_changed() - Handler for external power change
+ * @psy: Power supply data
+ *
+ * When the external power into the charger is changed, check the USB
+ * type so that it can be reported. Additionally, update the max input
+ * current and max charging current to the value reported if it is a
+ * USB PD charger, otherwise use the default value. Note that each time
+ * a charger is removed the max charge current register is erased, so
+ * it must be set again each time the input changes or the device will
+ * not charge.
+ */
+static void bq257xx_external_power_changed(struct power_supply *psy)
+{
+ struct bq257xx_chg *pdata = power_supply_get_drvdata(psy);
+ union power_supply_propval val;
+ int ret;
+ int imax = pdata->iindpm_max;
+
+ pdata->chip->bq257xx_get_state(pdata);
+
+ pdata->supplied = power_supply_am_i_supplied(pdata->charger);
+ if (pdata->supplied < 0)
+ return;
+
+ if (pdata->supplied == 0)
+ goto out;
+
+ ret = power_supply_get_property_from_supplier(psy,
+ POWER_SUPPLY_PROP_USB_TYPE,
+ &val);
+ if (ret)
+ return;
+
+ pdata->usb_type = val.intval;
+
+ if ((pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD) ||
+ (pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD_DRP) ||
+ (pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD_PPS)) {
+ ret = power_supply_get_property_from_supplier(psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ &val);
+ if (ret)
+ return;
+
+ if (val.intval)
+ imax = val.intval;
+ }
+
+ if (pdata->supplied) {
+ pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max);
+ pdata->chip->bq257xx_set_iindpm(pdata, imax);
+ pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max);
+ }
+
+out:
+ power_supply_changed(psy);
+}
+
+static irqreturn_t bq257xx_irq_handler_thread(int irq, void *private)
+{
+ struct bq257xx_chg *pdata = private;
+
+ bq257xx_external_power_changed(pdata->charger);
+ return IRQ_HANDLED;
+}
+
+static const struct power_supply_desc bq257xx_power_supply_desc = {
+ .name = "bq257xx-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD_DRP) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD_PPS) |
+ BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN),
+ .properties = bq257xx_power_supply_props,
+ .num_properties = ARRAY_SIZE(bq257xx_power_supply_props),
+ .get_property = bq257xx_get_charger_property,
+ .set_property = bq257xx_set_charger_property,
+ .property_is_writeable = bq257xx_property_is_writeable,
+ .external_power_changed = bq257xx_external_power_changed,
+};
+
+static const struct bq257xx_chip_info bq25703_chip_info = {
+ .bq257xx_hw_init = &bq25703_hw_init,
+ .bq257xx_hw_shutdown = &bq25703_hw_shutdown,
+ .bq257xx_get_state = &bq25703_get_state,
+ .bq257xx_set_ichg = &bq25703_set_ichg_cur,
+ .bq257xx_set_vbatreg = &bq25703_set_chrg_volt,
+ .bq257xx_set_iindpm = &bq25703_set_iindpm,
+};
+
+/**
+ * bq257xx_parse_dt() - Parse the device tree for required properties
+ * @pdata: driver platform data
+ * @psy_cfg: power supply config data
+ * @dev: device struct
+ *
+ * Read the device tree to identify the minimum system voltage, the
+ * maximum charge current, the maximum charge voltage, and the maximum
+ * input current.
+ *
+ * Return: Returns 0 on success or error code on error.
+ */
+static int bq257xx_parse_dt(struct bq257xx_chg *pdata,
+ struct power_supply_config *psy_cfg, struct device *dev)
+{
+ struct power_supply_battery_info *bat_info;
+ int ret;
+
+ ret = power_supply_get_battery_info(pdata->charger,
+ &bat_info);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Unable to get battery info\n");
+
+ if ((bat_info->voltage_min_design_uv <= 0) ||
+ (bat_info->constant_charge_voltage_max_uv <= 0) ||
+ (bat_info->constant_charge_current_max_ua <= 0))
+ return dev_err_probe(dev, -EINVAL,
+ "Required bat info missing or invalid\n");
+
+ pdata->vsys_min = bat_info->voltage_min_design_uv;
+ pdata->vbat_max = bat_info->constant_charge_voltage_max_uv;
+ pdata->ichg_max = bat_info->constant_charge_current_max_ua;
+
+ power_supply_put_battery_info(pdata->charger, bat_info);
+
+ ret = device_property_read_u32(dev,
+ "input-current-limit-microamp",
+ &pdata->iindpm_max);
+ if (ret)
+ pdata->iindpm_max = BQ25703_IINDPM_DEFAULT_UA;
+
+ return 0;
+}
+
+static int bq257xx_charger_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct bq257xx_device *bq = dev_get_drvdata(pdev->dev.parent);
+ struct bq257xx_chg *pdata;
+ struct power_supply_config psy_cfg = { };
+ int ret;
+
+ device_set_of_node_from_dev(dev, pdev->dev.parent);
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ pdata->bq = bq;
+ pdata->chip = &bq25703_chip_info;
+
+ platform_set_drvdata(pdev, pdata);
+
+ psy_cfg.drv_data = pdata;
+ psy_cfg.fwnode = dev_fwnode(dev);
+
+ pdata->charger = devm_power_supply_register(dev,
+ &bq257xx_power_supply_desc,
+ &psy_cfg);
+ if (IS_ERR(pdata->charger))
+ return dev_err_probe(dev, PTR_ERR(pdata->charger),
+ "Power supply register charger failed\n");
+
+ ret = bq257xx_parse_dt(pdata, &psy_cfg, dev);
+ if (ret)
+ return ret;
+
+ ret = pdata->chip->bq257xx_hw_init(pdata);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot initialize the charger\n");
+
+ platform_set_drvdata(pdev, pdata);
+
+ if (bq->client->irq) {
+ ret = devm_request_threaded_irq(dev, bq->client->irq, NULL,
+ bq257xx_irq_handler_thread,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ dev_name(&bq->client->dev), pdata);
+ if (ret < 0)
+ dev_err_probe(dev, ret, "Charger get irq failed\n");
+ }
+
+ return ret;
+}
+
+static void bq257xx_charger_shutdown(struct platform_device *pdev)
+{
+ struct bq257xx_chg *pdata = platform_get_drvdata(pdev);
+
+ pdata->chip->bq257xx_hw_shutdown(pdata);
+}
+
+static struct platform_driver bq257xx_chg_driver = {
+ .driver = {
+ .name = "bq257xx-charger",
+ },
+ .probe = bq257xx_charger_probe,
+ .shutdown = bq257xx_charger_shutdown,
+};
+module_platform_driver(bq257xx_chg_driver);
+
+MODULE_DESCRIPTION("bq257xx charger driver");
+MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c
index 2f5ceaf00b94..868e86e1749b 100644
--- a/drivers/power/supply/bq25890_charger.c
+++ b/drivers/power/supply/bq25890_charger.c
@@ -164,7 +164,7 @@ static const struct regmap_config bq25890_regmap_config = {
.val_bits = 8,
.max_register = 0x14,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.wr_table = &bq25890_writeable_regs,
.volatile_table = &bq25890_volatile_regs,
diff --git a/drivers/power/supply/bq25980_charger.c b/drivers/power/supply/bq25980_charger.c
index 0c5e2938bb36..44af3a0c92e5 100644
--- a/drivers/power/supply/bq25980_charger.c
+++ b/drivers/power/supply/bq25980_charger.c
@@ -8,7 +8,6 @@
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/gpio/consumer.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/types.h>
@@ -104,7 +103,7 @@ struct bq25980_device {
int watchdog_timer;
};
-static struct reg_default bq25980_reg_defs[] = {
+static const struct reg_default bq25980_reg_defs[] = {
{BQ25980_BATOVP, 0x5A},
{BQ25980_BATOVP_ALM, 0x46},
{BQ25980_BATOCP, 0x51},
@@ -159,7 +158,7 @@ static struct reg_default bq25980_reg_defs[] = {
{BQ25980_CHRGR_CTRL_6, 0x0},
};
-static struct reg_default bq25975_reg_defs[] = {
+static const struct reg_default bq25975_reg_defs[] = {
{BQ25980_BATOVP, 0x5A},
{BQ25980_BATOVP_ALM, 0x46},
{BQ25980_BATOCP, 0x51},
@@ -214,7 +213,7 @@ static struct reg_default bq25975_reg_defs[] = {
{BQ25980_CHRGR_CTRL_6, 0x0},
};
-static struct reg_default bq25960_reg_defs[] = {
+static const struct reg_default bq25960_reg_defs[] = {
{BQ25980_BATOVP, 0x5A},
{BQ25980_BATOVP_ALM, 0x46},
{BQ25980_BATOCP, 0x51},
@@ -932,7 +931,7 @@ static const struct regmap_config bq25980_regmap_config = {
.max_register = BQ25980_CHRGR_CTRL_6,
.reg_defaults = bq25980_reg_defs,
.num_reg_defaults = ARRAY_SIZE(bq25980_reg_defs),
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = bq25980_is_volatile_reg,
};
@@ -943,7 +942,7 @@ static const struct regmap_config bq25975_regmap_config = {
.max_register = BQ25980_CHRGR_CTRL_6,
.reg_defaults = bq25975_reg_defs,
.num_reg_defaults = ARRAY_SIZE(bq25975_reg_defs),
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = bq25980_is_volatile_reg,
};
@@ -954,7 +953,7 @@ static const struct regmap_config bq25960_regmap_config = {
.max_register = BQ25980_CHRGR_CTRL_6,
.reg_defaults = bq25960_reg_defs,
.num_reg_defaults = ARRAY_SIZE(bq25960_reg_defs),
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = bq25980_is_volatile_reg,
};
@@ -1057,7 +1056,7 @@ static int bq25980_power_supply_init(struct bq25980_device *bq,
struct device *dev)
{
struct power_supply_config psy_cfg = { .drv_data = bq,
- .of_node = dev->of_node, };
+ .fwnode = dev_fwnode(dev), };
psy_cfg.supplied_to = bq25980_charger_supplied_to;
psy_cfg.num_supplicants = ARRAY_SIZE(bq25980_charger_supplied_to);
@@ -1241,6 +1240,12 @@ static int bq25980_probe(struct i2c_client *client)
return ret;
}
+ ret = bq25980_power_supply_init(bq, dev);
+ if (ret) {
+ dev_err(dev, "Failed to register power supply\n");
+ return ret;
+ }
+
if (client->irq) {
ret = devm_request_threaded_irq(dev, client->irq, NULL,
bq25980_irq_handler_thread,
@@ -1251,12 +1256,6 @@ static int bq25980_probe(struct i2c_client *client)
return ret;
}
- ret = bq25980_power_supply_init(bq, dev);
- if (ret) {
- dev_err(dev, "Failed to register power supply\n");
- return ret;
- }
-
ret = bq25980_hw_init(bq);
if (ret) {
dev_err(dev, "Cannot initialize the chip.\n");
diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
index 90a5bccfc6b9..45f0e39b8c2d 100644
--- a/drivers/power/supply/bq27xxx_battery.c
+++ b/drivers/power/supply/bq27xxx_battery.c
@@ -124,6 +124,7 @@ enum bq27xxx_reg_index {
BQ27XXX_DM_DATA, /* Block Data */
BQ27XXX_DM_CKSUM, /* Block Data Checksum */
BQ27XXX_REG_SEDVF, /* End-of-discharge Voltage */
+ BQ27XXX_REG_PKCFG, /* Pack Configuration */
BQ27XXX_REG_MAX, /* sentinel */
};
@@ -161,6 +162,7 @@ static u8
[BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
[BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
[BQ27XXX_REG_SEDVF] = 0x77,
+ [BQ27XXX_REG_PKCFG] = 0x7C,
},
bq27010_regs[BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -187,6 +189,7 @@ static u8
[BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
[BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
[BQ27XXX_REG_SEDVF] = 0x77,
+ [BQ27XXX_REG_PKCFG] = 0x7C,
},
bq2750x_regs[BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -583,6 +586,7 @@ static enum power_supply_property bq27000_props[] = {
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
};
static enum power_supply_property bq27010_props[] = {
@@ -604,6 +608,7 @@ static enum power_supply_property bq27010_props[] = {
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
};
#define bq2750x_props bq27510g3_props
@@ -1122,7 +1127,7 @@ static int poll_interval_param_set(const char *val, const struct kernel_param *k
mutex_lock(&bq27xxx_list_lock);
list_for_each_entry(di, &bq27xxx_battery_devices, list)
- mod_delayed_work(system_wq, &di->work, 0);
+ mod_delayed_work(system_percpu_wq, &di->work, 0);
mutex_unlock(&bq27xxx_list_lock);
return ret;
@@ -1167,7 +1172,7 @@ static inline int bq27xxx_write(struct bq27xxx_device_info *di, int reg_index,
return -EINVAL;
if (!di->bus.write)
- return -EPERM;
+ return -EOPNOTSUPP;
ret = di->bus.write(di, di->regs[reg_index], value, single);
if (ret < 0)
@@ -1186,7 +1191,7 @@ static inline int bq27xxx_read_block(struct bq27xxx_device_info *di, int reg_ind
return -EINVAL;
if (!di->bus.read_bulk)
- return -EPERM;
+ return -EOPNOTSUPP;
ret = di->bus.read_bulk(di, di->regs[reg_index], data, len);
if (ret < 0)
@@ -1205,7 +1210,7 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
return -EINVAL;
if (!di->bus.write_bulk)
- return -EPERM;
+ return -EOPNOTSUPP;
ret = di->bus.write_bulk(di, di->regs[reg_index], data, len);
if (ret < 0)
@@ -1914,11 +1919,10 @@ static void bq27xxx_battery_update_unlocked(struct bq27xxx_device_info *di)
bool has_singe_flag = di->opts & BQ27XXX_O_ZERO;
cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag);
- if ((cache.flags & 0xff) == 0xff)
- cache.flags = -1; /* read error */
+ if (di->chip == BQ27000 && (cache.flags & 0xff) == 0xff)
+ cache.flags = -ENODEV; /* bq27000 hdq read error */
if (cache.flags >= 0) {
cache.capacity = bq27xxx_battery_read_soc(di);
- di->cache.flags = cache.flags;
/*
* On gauges with signed current reporting the current must be
@@ -1941,7 +1945,7 @@ static void bq27xxx_battery_update_unlocked(struct bq27xxx_device_info *di)
di->last_update = jiffies;
if (!di->removed && poll_interval > 0)
- mod_delayed_work(system_wq, &di->work, poll_interval * HZ);
+ mod_delayed_work(system_percpu_wq, &di->work, poll_interval * HZ);
}
void bq27xxx_battery_update(struct bq27xxx_device_info *di)
@@ -2045,6 +2049,35 @@ static int bq27xxx_battery_voltage(struct bq27xxx_device_info *di,
}
/*
+ * Return the design maximum battery Voltage in microvolts, or < 0 if something
+ * fails. The programmed value of the maximum battery voltage is determined by
+ * QV0 and QV1 (bits 5 and 6) in the Pack Configuration register.
+ */
+static int bq27xxx_battery_read_dmax_volt(struct bq27xxx_device_info *di,
+ union power_supply_propval *val)
+{
+ int reg_val, qv;
+
+ if (di->voltage_max_design > 0) {
+ val->intval = di->voltage_max_design;
+ return 0;
+ }
+
+ reg_val = bq27xxx_read(di, BQ27XXX_REG_PKCFG, true);
+ if (reg_val < 0) {
+ dev_err(di->dev, "error reading design max voltage\n");
+ return reg_val;
+ }
+
+ qv = (reg_val >> 5) & 0x3;
+ val->intval = 3968000 + 48000 * qv;
+
+ di->voltage_max_design = val->intval;
+
+ return 0;
+}
+
+/*
* Return the design minimum battery Voltage in microvolts
* Or < 0 if something fails.
*/
@@ -2098,7 +2131,7 @@ static int bq27xxx_battery_get_property(struct power_supply *psy,
mutex_unlock(&di->lock);
if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0)
- return -ENODEV;
+ return di->cache.flags;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
@@ -2158,6 +2191,9 @@ static int bq27xxx_battery_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
ret = bq27xxx_battery_read_dmin_volt(di, val);
break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ ret = bq27xxx_battery_read_dmax_volt(di, val);
+ break;
case POWER_SUPPLY_PROP_CYCLE_COUNT:
ret = bq27xxx_battery_read_cyct(di, val);
break;
@@ -2185,30 +2221,21 @@ static void bq27xxx_external_power_changed(struct power_supply *psy)
struct bq27xxx_device_info *di = power_supply_get_drvdata(psy);
/* After charger plug in/out wait 0.5s for things to stabilize */
- mod_delayed_work(system_wq, &di->work, HZ / 2);
-}
-
-static void bq27xxx_battery_mutex_destroy(void *data)
-{
- struct mutex *lock = data;
-
- mutex_destroy(lock);
+ mod_delayed_work(system_percpu_wq, &di->work, HZ / 2);
}
int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
{
struct power_supply_desc *psy_desc;
struct power_supply_config psy_cfg = {
- .of_node = di->dev->of_node,
+ .fwnode = dev_fwnode(di->dev),
.drv_data = di,
.no_wakeup_source = true,
};
int ret;
INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll);
- mutex_init(&di->lock);
- ret = devm_add_action_or_reset(di->dev, bq27xxx_battery_mutex_destroy,
- &di->lock);
+ ret = devm_mutex_init(di->dev, &di->lock);
if (ret)
return ret;
diff --git a/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c
index ba0d22d90429..868e95f0887e 100644
--- a/drivers/power/supply/bq27xxx_battery_i2c.c
+++ b/drivers/power/supply/bq27xxx_battery_i2c.c
@@ -6,6 +6,7 @@
* Andrew F. Davis <afd@ti.com>
*/
+#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/module.h>
@@ -31,6 +32,7 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,
struct i2c_msg msg[2];
u8 data[2];
int ret;
+ int retry = 0;
if (!client->adapter)
return -ENODEV;
@@ -47,7 +49,16 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,
else
msg[1].len = 2;
- ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ do {
+ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret == -EBUSY && ++retry < 3) {
+ /* sleep 10 milliseconds when busy */
+ usleep_range(10000, 11000);
+ continue;
+ }
+ break;
+ } while (1);
+
if (ret < 0)
return ret;
diff --git a/drivers/power/supply/chagall-battery.c b/drivers/power/supply/chagall-battery.c
new file mode 100644
index 000000000000..8b05422aca6f
--- /dev/null
+++ b/drivers/power/supply/chagall-battery.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/array_size.h>
+#include <linux/delay.h>
+#include <linux/devm-helpers.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+
+#define CHAGALL_REG_LED_AMBER 0x60
+#define CHAGALL_REG_LED_WHITE 0x70
+#define CHAGALL_REG_BATTERY_TEMPERATURE 0xa2
+#define CHAGALL_REG_BATTERY_VOLTAGE 0xa4
+#define CHAGALL_REG_BATTERY_CURRENT 0xa6
+#define CHAGALL_REG_BATTERY_CAPACITY 0xa8
+#define CHAGALL_REG_BATTERY_CHARGING_CURRENT 0xaa
+#define CHAGALL_REG_BATTERY_CHARGING_VOLTAGE 0xac
+#define CHAGALL_REG_BATTERY_STATUS 0xae
+#define BATTERY_DISCHARGING BIT(6)
+#define BATTERY_FULL_CHARGED BIT(5)
+#define BATTERY_FULL_DISCHARGED BIT(4)
+#define CHAGALL_REG_BATTERY_REMAIN_CAPACITY 0xb0
+#define CHAGALL_REG_BATTERY_FULL_CAPACITY 0xb2
+#define CHAGALL_REG_MAX_COUNT 0xb4
+
+#define CHAGALL_BATTERY_DATA_REFRESH 5000
+#define TEMP_CELSIUS_OFFSET 2731
+
+static const struct regmap_config chagall_battery_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = CHAGALL_REG_MAX_COUNT,
+ .reg_format_endian = REGMAP_ENDIAN_LITTLE,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+struct chagall_battery_data {
+ struct regmap *regmap;
+ struct led_classdev amber_led;
+ struct led_classdev white_led;
+ struct power_supply *battery;
+ struct delayed_work poll_work;
+ u16 last_state;
+};
+
+static void chagall_led_set_brightness_amber(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ struct chagall_battery_data *cg =
+ container_of(led, struct chagall_battery_data, amber_led);
+
+ regmap_write(cg->regmap, CHAGALL_REG_LED_AMBER, brightness);
+}
+
+static void chagall_led_set_brightness_white(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ struct chagall_battery_data *cg =
+ container_of(led, struct chagall_battery_data, white_led);
+
+ regmap_write(cg->regmap, CHAGALL_REG_LED_WHITE, brightness);
+}
+
+static const enum power_supply_property chagall_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+};
+
+static const unsigned int chagall_battery_prop_offs[] = {
+ [POWER_SUPPLY_PROP_STATUS] = CHAGALL_REG_BATTERY_STATUS,
+ [POWER_SUPPLY_PROP_VOLTAGE_NOW] = CHAGALL_REG_BATTERY_VOLTAGE,
+ [POWER_SUPPLY_PROP_VOLTAGE_MAX] = CHAGALL_REG_BATTERY_CHARGING_VOLTAGE,
+ [POWER_SUPPLY_PROP_CURRENT_NOW] = CHAGALL_REG_BATTERY_CURRENT,
+ [POWER_SUPPLY_PROP_CURRENT_MAX] = CHAGALL_REG_BATTERY_CHARGING_CURRENT,
+ [POWER_SUPPLY_PROP_CAPACITY] = CHAGALL_REG_BATTERY_CAPACITY,
+ [POWER_SUPPLY_PROP_TEMP] = CHAGALL_REG_BATTERY_TEMPERATURE,
+ [POWER_SUPPLY_PROP_CHARGE_FULL] = CHAGALL_REG_BATTERY_FULL_CAPACITY,
+ [POWER_SUPPLY_PROP_CHARGE_NOW] = CHAGALL_REG_BATTERY_REMAIN_CAPACITY,
+};
+
+static int chagall_battery_get_value(struct chagall_battery_data *cg,
+ enum power_supply_property psp, u32 *val)
+{
+ if (psp >= ARRAY_SIZE(chagall_battery_prop_offs))
+ return -EINVAL;
+ if (!chagall_battery_prop_offs[psp])
+ return -EINVAL;
+
+ /* Battery data is stored in 2 consecutive registers with little-endian */
+ return regmap_bulk_read(cg->regmap, chagall_battery_prop_offs[psp], val, 2);
+}
+
+static int chagall_battery_get_status(u32 status_reg)
+{
+ if (status_reg & BATTERY_FULL_CHARGED)
+ return POWER_SUPPLY_STATUS_FULL;
+ else if (status_reg & BATTERY_DISCHARGING)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ return POWER_SUPPLY_STATUS_CHARGING;
+}
+
+static int chagall_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct chagall_battery_data *cg = power_supply_get_drvdata(psy);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+
+ default:
+ ret = chagall_battery_get_value(cg, psp, &val->intval);
+ if (ret)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval -= TEMP_CELSIUS_OFFSET;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval *= 1000;
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = chagall_battery_get_status(val->intval);
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+static void chagall_battery_poll_work(struct work_struct *work)
+{
+ struct chagall_battery_data *cg =
+ container_of(work, struct chagall_battery_data, poll_work.work);
+ u32 state;
+ int ret;
+
+ ret = chagall_battery_get_value(cg, POWER_SUPPLY_PROP_STATUS, &state);
+ if (ret)
+ return;
+
+ state = chagall_battery_get_status(state);
+
+ if (cg->last_state != state) {
+ cg->last_state = state;
+ power_supply_changed(cg->battery);
+ }
+
+ /* continuously send uevent notification */
+ schedule_delayed_work(&cg->poll_work,
+ msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
+}
+
+static const struct power_supply_desc chagall_battery_desc = {
+ .name = "chagall-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = chagall_battery_properties,
+ .num_properties = ARRAY_SIZE(chagall_battery_properties),
+ .get_property = chagall_battery_get_property,
+ .external_power_changed = power_supply_changed,
+};
+
+static int chagall_battery_probe(struct i2c_client *client)
+{
+ struct chagall_battery_data *cg;
+ struct device *dev = &client->dev;
+ struct power_supply_config cfg = { };
+ int ret;
+
+ cg = devm_kzalloc(dev, sizeof(*cg), GFP_KERNEL);
+ if (!cg)
+ return -ENOMEM;
+
+ cfg.drv_data = cg;
+ cfg.fwnode = dev_fwnode(dev);
+
+ i2c_set_clientdata(client, cg);
+
+ cg->regmap = devm_regmap_init_i2c(client, &chagall_battery_regmap_config);
+ if (IS_ERR(cg->regmap))
+ return dev_err_probe(dev, PTR_ERR(cg->regmap), "cannot allocate regmap\n");
+
+ cg->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
+ cg->battery = devm_power_supply_register(dev, &chagall_battery_desc, &cfg);
+ if (IS_ERR(cg->battery))
+ return dev_err_probe(dev, PTR_ERR(cg->battery),
+ "failed to register power supply\n");
+
+ cg->amber_led.name = "power::amber";
+ cg->amber_led.max_brightness = 1;
+ cg->amber_led.flags = LED_CORE_SUSPENDRESUME;
+ cg->amber_led.brightness_set = chagall_led_set_brightness_amber;
+ cg->amber_led.default_trigger = "chagall-battery-charging";
+
+ ret = devm_led_classdev_register(dev, &cg->amber_led);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register amber LED\n");
+
+ cg->white_led.name = "power::white";
+ cg->white_led.max_brightness = 1;
+ cg->white_led.flags = LED_CORE_SUSPENDRESUME;
+ cg->white_led.brightness_set = chagall_led_set_brightness_white;
+ cg->white_led.default_trigger = "chagall-battery-full";
+
+ ret = devm_led_classdev_register(dev, &cg->white_led);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register white LED\n");
+
+ led_set_brightness(&cg->amber_led, LED_OFF);
+ led_set_brightness(&cg->white_led, LED_OFF);
+
+ ret = devm_delayed_work_autocancel(dev, &cg->poll_work, chagall_battery_poll_work);
+ if (ret)
+ return ret;
+
+ schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
+
+ return 0;
+}
+
+static int __maybe_unused chagall_battery_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct chagall_battery_data *cg = i2c_get_clientdata(client);
+
+ cancel_delayed_work_sync(&cg->poll_work);
+
+ return 0;
+}
+
+static int __maybe_unused chagall_battery_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct chagall_battery_data *cg = i2c_get_clientdata(client);
+
+ schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(chagall_battery_pm_ops,
+ chagall_battery_suspend, chagall_battery_resume);
+
+static const struct of_device_id chagall_of_match[] = {
+ { .compatible = "pegatron,chagall-ec" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, chagall_of_match);
+
+static struct i2c_driver chagall_battery_driver = {
+ .driver = {
+ .name = "chagall-battery",
+ .pm = &chagall_battery_pm_ops,
+ .of_match_table = chagall_of_match,
+ },
+ .probe = chagall_battery_probe,
+};
+module_i2c_driver(chagall_battery_driver);
+
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("Pegatron Chagall fuel gauge driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/collie_battery.c b/drivers/power/supply/collie_battery.c
index 68390bd1004f..3daf7befc0bf 100644
--- a/drivers/power/supply/collie_battery.c
+++ b/drivers/power/supply/collie_battery.c
@@ -440,6 +440,7 @@ err_put_gpio_full:
static void collie_bat_remove(struct ucb1x00_dev *dev)
{
+ device_init_wakeup(&ucb->dev, 0);
free_irq(gpiod_to_irq(collie_bat_main.gpio_full), &collie_bat_main);
power_supply_unregister(collie_bat_bu.psy);
power_supply_unregister(collie_bat_main.psy);
diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c
index 813037c00ded..7b7bdce3162f 100644
--- a/drivers/power/supply/cpcap-battery.c
+++ b/drivers/power/supply/cpcap-battery.c
@@ -387,7 +387,7 @@ static const struct cpcap_battery_config cpcap_battery_bw8x_data = {
* Safe values for any lipo battery likely to fit into a mapphone
* battery bay.
*/
-static const struct cpcap_battery_config cpcap_battery_unkown_data = {
+static const struct cpcap_battery_config cpcap_battery_unknown_data = {
.cd_factor = 0x3cc,
.info.technology = POWER_SUPPLY_TECHNOLOGY_LION,
.info.voltage_max_design = 4200000,
@@ -404,6 +404,30 @@ static int cpcap_battery_match_nvmem(struct device *dev, const void *data)
return 0;
}
+static void cpcap_battery_update_battery_data(struct cpcap_battery_ddata *ddata)
+{
+ struct power_supply_battery_info *info;
+
+ if (power_supply_get_battery_info(ddata->psy, &info) < 0)
+ return;
+
+ if (info->technology > 0)
+ ddata->config.info.technology = info->technology;
+
+ if (info->voltage_max_design_uv > 0)
+ ddata->config.info.voltage_max_design = info->voltage_max_design_uv;
+
+ if (info->voltage_min_design_uv > 0)
+ ddata->config.info.voltage_min_design = info->voltage_min_design_uv;
+
+ if (info->charge_full_design_uah > 0)
+ ddata->config.info.charge_full_design = info->charge_full_design_uah;
+
+ if (info->constant_charge_voltage_max_uv > 0)
+ ddata->config.bat.constant_charge_voltage_max_uv =
+ info->constant_charge_voltage_max_uv;
+}
+
static void cpcap_battery_detect_battery_type(struct cpcap_battery_ddata *ddata)
{
struct nvmem_device *nvmem;
@@ -429,8 +453,11 @@ static void cpcap_battery_detect_battery_type(struct cpcap_battery_ddata *ddata)
ddata->config = cpcap_battery_bw8x_data;
break;
default:
- ddata->config = cpcap_battery_unkown_data;
+ ddata->config = cpcap_battery_unknown_data;
}
+
+ if (ddata->psy)
+ cpcap_battery_update_battery_data(ddata);
}
/**
@@ -1122,15 +1149,11 @@ static int cpcap_battery_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, ddata);
- error = cpcap_battery_init_interrupts(pdev, ddata);
- if (error)
- return error;
-
error = cpcap_battery_init_iio(ddata);
if (error)
return error;
- psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&pdev->dev);
psy_cfg.drv_data = ddata;
ddata->psy = devm_power_supply_register(ddata->dev,
@@ -1142,6 +1165,10 @@ static int cpcap_battery_probe(struct platform_device *pdev)
return error;
}
+ error = cpcap_battery_init_interrupts(pdev, ddata);
+ if (error)
+ return error;
+
atomic_set(&ddata->active, 1);
error = cpcap_battery_calibrate(ddata);
diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c
index 6625d539d9ae..d0c3008db534 100644
--- a/drivers/power/supply/cpcap-charger.c
+++ b/drivers/power/supply/cpcap-charger.c
@@ -689,9 +689,8 @@ static void cpcap_usb_detect(struct work_struct *work)
struct power_supply *battery;
battery = power_supply_get_by_name("battery");
- if (IS_ERR_OR_NULL(battery)) {
- dev_err(ddata->dev, "battery power_supply not available %li\n",
- PTR_ERR(battery));
+ if (!battery) {
+ dev_err(ddata->dev, "battery power_supply not available\n");
return;
}
@@ -902,7 +901,7 @@ static int cpcap_charger_probe(struct platform_device *pdev)
atomic_set(&ddata->active, 1);
- psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&pdev->dev);
psy_cfg.drv_data = ddata;
psy_cfg.supplied_to = cpcap_charger_supplied_to;
psy_cfg.num_supplicants = ARRAY_SIZE(cpcap_charger_supplied_to);
diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c
index 02d5bdbe2e8d..53e6a77e03fc 100644
--- a/drivers/power/supply/cros_charge-control.c
+++ b/drivers/power/supply/cros_charge-control.c
@@ -47,29 +47,20 @@ struct cros_chctl_priv {
static int cros_chctl_send_charge_control_cmd(struct cros_ec_device *cros_ec,
u8 cmd_version, struct ec_params_charge_control *req)
{
+ int ret;
static const u8 outsizes[] = {
[1] = offsetof(struct ec_params_charge_control, cmd),
[2] = sizeof(struct ec_params_charge_control),
[3] = sizeof(struct ec_params_charge_control),
};
- struct {
- struct cros_ec_command msg;
- union {
- struct ec_params_charge_control req;
- struct ec_response_charge_control resp;
- } __packed data;
- } __packed buf = {
- .msg = {
- .command = EC_CMD_CHARGE_CONTROL,
- .version = cmd_version,
- .insize = 0,
- .outsize = outsizes[cmd_version],
- },
- .data.req = *req,
- };
+ ret = cros_ec_cmd(cros_ec, cmd_version, EC_CMD_CHARGE_CONTROL, req,
+ outsizes[cmd_version], NULL, 0);
+
+ if (ret < 0)
+ return ret;
- return cros_ec_cmd_xfer_status(cros_ec, &buf.msg);
+ return 0;
}
static int cros_chctl_configure_ec(struct cros_chctl_priv *priv)
diff --git a/drivers/power/supply/cros_peripheral_charger.c b/drivers/power/supply/cros_peripheral_charger.c
index 962a6fd29832..f132fad288cb 100644
--- a/drivers/power/supply/cros_peripheral_charger.c
+++ b/drivers/power/supply/cros_peripheral_charger.c
@@ -64,7 +64,7 @@ static int cros_pchg_ec_command(const struct charger_data *charger,
struct cros_ec_command *msg;
int ret;
- msg = kzalloc(struct_size(msg, data, max(outsize, insize)), GFP_KERNEL);
+ msg = kzalloc_flex(*msg, data, max(outsize, insize));
if (!msg)
return -ENOMEM;
diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c
index 47d3f58aa15c..7d3e676a951c 100644
--- a/drivers/power/supply/cros_usbpd-charger.c
+++ b/drivers/power/supply/cros_usbpd-charger.c
@@ -94,7 +94,7 @@ static int cros_usbpd_charger_ec_command(struct charger_data *charger,
struct cros_ec_command *msg;
int ret;
- msg = kzalloc(struct_size(msg, data, max(outsize, insize)), GFP_KERNEL);
+ msg = kzalloc_flex(*msg, data, max(outsize, insize));
if (!msg)
return -ENOMEM;
diff --git a/drivers/power/supply/cw2015_battery.c b/drivers/power/supply/cw2015_battery.c
index f63c3c410451..286524d2318c 100644
--- a/drivers/power/supply/cw2015_battery.c
+++ b/drivers/power/supply/cw2015_battery.c
@@ -13,7 +13,6 @@
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/gfp.h>
-#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/power_supply.h>
@@ -506,10 +505,7 @@ static int cw_battery_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_CHARGE_FULL:
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
- if (cw_bat->battery->charge_full_design_uah > 0)
- val->intval = cw_bat->battery->charge_full_design_uah;
- else
- val->intval = 0;
+ val->intval = max(cw_bat->battery->charge_full_design_uah, 0);
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
@@ -698,12 +694,18 @@ static int cw_bat_probe(struct i2c_client *client)
"No monitored battery, some properties will be missing\n");
}
- cw_bat->battery_workqueue = create_singlethread_workqueue("rk_battery");
+ cw_bat->battery_workqueue = devm_alloc_ordered_workqueue(&client->dev,
+ "rk_battery", 0);
if (!cw_bat->battery_workqueue)
return -ENOMEM;
- devm_delayed_work_autocancel(&client->dev,
- &cw_bat->battery_delay_work, cw_bat_work);
+ ret = devm_delayed_work_autocancel(&client->dev, &cw_bat->battery_delay_work, cw_bat_work);
+ if (ret) {
+ dev_err_probe(&client->dev, ret,
+ "Failed to register delayed work\n");
+ return ret;
+ }
+
queue_delayed_work(cw_bat->battery_workqueue,
&cw_bat->battery_delay_work, msecs_to_jiffies(10));
return 0;
diff --git a/drivers/power/supply/da9030_battery.c b/drivers/power/supply/da9030_battery.c
index ac2e319e9517..d25279c26030 100644
--- a/drivers/power/supply/da9030_battery.c
+++ b/drivers/power/supply/da9030_battery.c
@@ -502,8 +502,7 @@ static int da9030_battery_probe(struct platform_device *pdev)
/* 10 seconds between monitor runs unless platform defines other
interval */
- charger->interval = msecs_to_jiffies(
- (pdata->batmon_interval ? : 10) * 1000);
+ charger->interval = secs_to_jiffies(pdata->batmon_interval ? : 10);
charger->charge_milliamp = pdata->charge_milliamp;
charger->charge_millivolt = pdata->charge_millivolt;
diff --git a/drivers/power/supply/ds2760_battery.c b/drivers/power/supply/ds2760_battery.c
index 83bdec5a2bda..142c7492c3c2 100644
--- a/drivers/power/supply/ds2760_battery.c
+++ b/drivers/power/supply/ds2760_battery.c
@@ -112,7 +112,6 @@ struct ds2760_device_info {
struct power_supply_desc bat_desc;
struct workqueue_struct *monitor_wqueue;
struct delayed_work monitor_work;
- struct delayed_work set_charged_work;
struct notifier_block pm_notifier;
};
@@ -210,7 +209,7 @@ static const struct bin_attribute *const w1_ds2760_bin_attrs[] = {
};
static const struct attribute_group w1_ds2760_group = {
- .bin_attrs_new = w1_ds2760_bin_attrs,
+ .bin_attrs = w1_ds2760_bin_attrs,
};
static const struct attribute_group *w1_ds2760_groups[] = {
@@ -489,50 +488,6 @@ static void ds2760_battery_external_power_changed(struct power_supply *psy)
}
-static void ds2760_battery_set_charged_work(struct work_struct *work)
-{
- char bias;
- struct ds2760_device_info *di = container_of(work,
- struct ds2760_device_info, set_charged_work.work);
-
- dev_dbg(di->dev, "%s\n", __func__);
-
- ds2760_battery_read_status(di);
-
- /* When we get notified by external circuitry that the battery is
- * considered fully charged now, we know that there is no current
- * flow any more. However, the ds2760's internal current meter is
- * too inaccurate to rely on - spec say something ~15% failure.
- * Hence, we use the current offset bias register to compensate
- * that error.
- */
-
- if (!power_supply_am_i_supplied(di->bat))
- return;
-
- bias = (signed char) di->current_raw +
- (signed char) di->raw[DS2760_CURRENT_OFFSET_BIAS];
-
- dev_dbg(di->dev, "%s: bias = %d\n", __func__, bias);
-
- w1_ds2760_write(di->dev, &bias, DS2760_CURRENT_OFFSET_BIAS, 1);
- w1_ds2760_store_eeprom(di->dev, DS2760_EEPROM_BLOCK1);
- w1_ds2760_recall_eeprom(di->dev, DS2760_EEPROM_BLOCK1);
-
- /* Write to the di->raw[] buffer directly - the CURRENT_OFFSET_BIAS
- * value won't be read back by ds2760_battery_read_status() */
- di->raw[DS2760_CURRENT_OFFSET_BIAS] = bias;
-}
-
-static void ds2760_battery_set_charged(struct power_supply *psy)
-{
- struct ds2760_device_info *di = power_supply_get_drvdata(psy);
-
- /* postpone the actual work by 20 secs. This is for debouncing GPIO
- * signals and to let the current value settle. See AN4188. */
- mod_delayed_work(di->monitor_wqueue, &di->set_charged_work, HZ * 20);
-}
-
static int ds2760_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -692,17 +647,15 @@ static int w1_ds2760_add_slave(struct w1_slave *sl)
di->bat_desc.set_property = ds2760_battery_set_property;
di->bat_desc.property_is_writeable =
ds2760_battery_property_is_writeable;
- di->bat_desc.set_charged = ds2760_battery_set_charged;
di->bat_desc.external_power_changed =
ds2760_battery_external_power_changed;
psy_cfg.drv_data = di;
+ psy_cfg.fwnode = dev_fwnode(dev);
if (dev->of_node) {
u32 tmp;
- psy_cfg.of_node = dev->of_node;
-
if (!of_property_read_bool(dev->of_node, "maxim,pmod-enabled"))
pmod_enabled = true;
@@ -747,8 +700,6 @@ static int w1_ds2760_add_slave(struct w1_slave *sl)
}
INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work);
- INIT_DELAYED_WORK(&di->set_charged_work,
- ds2760_battery_set_charged_work);
di->monitor_wqueue = alloc_ordered_workqueue(name, WQ_MEM_RECLAIM);
if (!di->monitor_wqueue) {
retval = -ESRCH;
@@ -774,7 +725,6 @@ static void w1_ds2760_remove_slave(struct w1_slave *sl)
unregister_pm_notifier(&di->pm_notifier);
cancel_delayed_work_sync(&di->monitor_work);
- cancel_delayed_work_sync(&di->set_charged_work);
destroy_workqueue(di->monitor_wqueue);
}
diff --git a/drivers/power/supply/ds2780_battery.c b/drivers/power/supply/ds2780_battery.c
index dd9ac7a32967..5b57bbba79d4 100644
--- a/drivers/power/supply/ds2780_battery.c
+++ b/drivers/power/supply/ds2780_battery.c
@@ -660,8 +660,8 @@ static const struct bin_attribute ds2780_param_eeprom_bin_attr = {
.mode = S_IRUGO | S_IWUSR,
},
.size = DS2780_PARAM_EEPROM_SIZE,
- .read_new = ds2780_read_param_eeprom_bin,
- .write_new = ds2780_write_param_eeprom_bin,
+ .read = ds2780_read_param_eeprom_bin,
+ .write = ds2780_write_param_eeprom_bin,
};
static ssize_t ds2780_read_user_eeprom_bin(struct file *filp,
@@ -705,8 +705,8 @@ static const struct bin_attribute ds2780_user_eeprom_bin_attr = {
.mode = S_IRUGO | S_IWUSR,
},
.size = DS2780_USER_EEPROM_SIZE,
- .read_new = ds2780_read_user_eeprom_bin,
- .write_new = ds2780_write_user_eeprom_bin,
+ .read = ds2780_read_user_eeprom_bin,
+ .write = ds2780_write_user_eeprom_bin,
};
static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2780_get_pmod_enabled,
@@ -734,7 +734,7 @@ static const struct bin_attribute *const ds2780_sysfs_bin_attrs[] = {
static const struct attribute_group ds2780_sysfs_group = {
.attrs = ds2780_sysfs_attrs,
- .bin_attrs_new = ds2780_sysfs_bin_attrs,
+ .bin_attrs = ds2780_sysfs_bin_attrs,
};
static const struct attribute_group *ds2780_sysfs_groups[] = {
diff --git a/drivers/power/supply/ds2781_battery.c b/drivers/power/supply/ds2781_battery.c
index 8a1f1f9835e0..1319e02f3f95 100644
--- a/drivers/power/supply/ds2781_battery.c
+++ b/drivers/power/supply/ds2781_battery.c
@@ -662,8 +662,8 @@ static const struct bin_attribute ds2781_param_eeprom_bin_attr = {
.mode = S_IRUGO | S_IWUSR,
},
.size = DS2781_PARAM_EEPROM_SIZE,
- .read_new = ds2781_read_param_eeprom_bin,
- .write_new = ds2781_write_param_eeprom_bin,
+ .read = ds2781_read_param_eeprom_bin,
+ .write = ds2781_write_param_eeprom_bin,
};
static ssize_t ds2781_read_user_eeprom_bin(struct file *filp,
@@ -708,8 +708,8 @@ static const struct bin_attribute ds2781_user_eeprom_bin_attr = {
.mode = S_IRUGO | S_IWUSR,
},
.size = DS2781_USER_EEPROM_SIZE,
- .read_new = ds2781_read_user_eeprom_bin,
- .write_new = ds2781_write_user_eeprom_bin,
+ .read = ds2781_read_user_eeprom_bin,
+ .write = ds2781_write_user_eeprom_bin,
};
static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2781_get_pmod_enabled,
@@ -737,7 +737,7 @@ static const struct bin_attribute *const ds2781_sysfs_bin_attrs[] = {
static const struct attribute_group ds2781_sysfs_group = {
.attrs = ds2781_sysfs_attrs,
- .bin_attrs_new = ds2781_sysfs_bin_attrs,
+ .bin_attrs = ds2781_sysfs_bin_attrs,
};
diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c
index d5d215f5ad8b..f5f2566b3a32 100644
--- a/drivers/power/supply/generic-adc-battery.c
+++ b/drivers/power/supply/generic-adc-battery.c
@@ -166,7 +166,7 @@ static int gab_probe(struct platform_device *pdev)
if (!adc_bat)
return -ENOMEM;
- psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&pdev->dev);
psy_cfg.drv_data = adc_bat;
psy_desc = &adc_bat->psy_desc;
psy_desc->name = dev_name(&pdev->dev);
diff --git a/drivers/power/supply/goldfish_battery.c b/drivers/power/supply/goldfish_battery.c
index 479195e35d73..5aa24e4dc445 100644
--- a/drivers/power/supply/goldfish_battery.c
+++ b/drivers/power/supply/goldfish_battery.c
@@ -224,12 +224,6 @@ static int goldfish_battery_probe(struct platform_device *pdev)
if (data->irq < 0)
return -ENODEV;
- ret = devm_request_irq(&pdev->dev, data->irq,
- goldfish_battery_interrupt,
- IRQF_SHARED, pdev->name, data);
- if (ret)
- return ret;
-
psy_cfg.drv_data = data;
data->ac = devm_power_supply_register(&pdev->dev,
@@ -244,6 +238,12 @@ static int goldfish_battery_probe(struct platform_device *pdev)
if (IS_ERR(data->battery))
return PTR_ERR(data->battery);
+ ret = devm_request_irq(&pdev->dev, data->irq,
+ goldfish_battery_interrupt,
+ IRQF_SHARED, pdev->name, data);
+ if (ret)
+ return ret;
+
GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK);
return 0;
}
diff --git a/drivers/power/supply/gpio-charger.c b/drivers/power/supply/gpio-charger.c
index 46d18ce6a739..2504190eba82 100644
--- a/drivers/power/supply/gpio-charger.c
+++ b/drivers/power/supply/gpio-charger.c
@@ -79,7 +79,8 @@ static int set_charge_current_limit(struct gpio_charger *gpio_charger, int val)
for (i = 0; i < ndescs; i++) {
bool val = (mapping.gpiodata >> i) & 1;
- gpiod_set_value_cansleep(gpios[ndescs-i-1], val);
+
+ gpiod_set_value_cansleep(gpios[ndescs - i - 1], val);
}
gpio_charger->charge_current_limit = mapping.limit_ua;
@@ -226,14 +227,14 @@ static int init_charge_current_limit(struct device *dev,
gpio_charger->current_limit_map_size = len / 2;
len = device_property_read_u32_array(dev, "charge-current-limit-mapping",
- (u32*) gpio_charger->current_limit_map, len);
+ (u32 *) gpio_charger->current_limit_map, len);
if (len < 0)
return len;
set_def_limit = !device_property_read_u32(dev,
"charge-current-limit-default-microamp",
&def_limit);
- for (i=0; i < gpio_charger->current_limit_map_size; i++) {
+ for (i = 0; i < gpio_charger->current_limit_map_size; i++) {
if (gpio_charger->current_limit_map[i].limit_ua > cur_limit) {
dev_err(dev, "charge-current-limit-mapping not sorted by current in descending order\n");
return -EINVAL;
@@ -333,7 +334,7 @@ static int gpio_charger_probe(struct platform_device *pdev)
charger_desc->property_is_writeable =
gpio_charger_property_is_writeable;
- psy_cfg.of_node = dev->of_node;
+ psy_cfg.fwnode = dev_fwnode(dev);
psy_cfg.drv_data = gpio_charger;
if (pdata) {
@@ -366,7 +367,9 @@ static int gpio_charger_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, gpio_charger);
- device_init_wakeup(dev, 1);
+ ret = devm_device_init_wakeup(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init wakeup\n");
return 0;
}
diff --git a/drivers/power/supply/huawei-gaokun-battery.c b/drivers/power/supply/huawei-gaokun-battery.c
new file mode 100644
index 000000000000..e4dfec3b4241
--- /dev/null
+++ b/drivers/power/supply/huawei-gaokun-battery.c
@@ -0,0 +1,645 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * huawei-gaokun-battery - A power supply driver for HUAWEI Matebook E Go
+ *
+ * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_data/huawei-gaokun-ec.h>
+#include <linux/power_supply.h>
+#include <linux/sprintf.h>
+
+/* -------------------------------------------------------------------------- */
+/* String Data Reg */
+
+#define EC_BAT_VENDOR 0x01 /* from 0x01 to 0x0F, SUNWODA */
+#define EC_BAT_MODEL 0x11 /* from 0x11 to 0x1F, HB30A8P9ECW-22T */
+
+#define EC_ADP_STATUS 0x81
+#define EC_AC_STATUS BIT(0)
+#define EC_BAT_PRESENT BIT(1) /* BATC._STA */
+
+#define EC_BAT_STATUS 0x82 /* _BST */
+#define EC_BAT_DISCHARGING BIT(0)
+#define EC_BAT_CHARGING BIT(1)
+#define EC_BAT_CRITICAL BIT(2) /* Low Battery Level */
+#define EC_BAT_FULL BIT(3)
+
+/* -------------------------------------------------------------------------- */
+/* Word Data Reg */
+
+/* 0x5A: ?
+ * 0x5C: ?
+ * 0x5E: ?
+ * 0X60: ?
+ * 0x84: ?
+ */
+
+#define EC_BAT_STATUS_START 0x90
+#define EC_BAT_PERCENTAGE 0x90
+#define EC_BAT_VOLTAGE 0x92
+#define EC_BAT_CAPACITY 0x94
+#define EC_BAT_FULL_CAPACITY 0x96
+/* 0x98: ? */
+#define EC_BAT_CURRENT 0x9A
+/* 0x9C: ? */
+
+#define EC_BAT_INFO_START 0xA0
+/* 0xA0: POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT? */
+#define EC_BAT_DESIGN_CAPACITY 0xA2
+#define EC_BAT_DESIGN_VOLTAGE 0xA4
+#define EC_BAT_SERIAL_NUMBER 0xA6
+#define EC_BAT_CYCLE_COUNT 0xAA
+
+/* -------------------------------------------------------------------------- */
+/* Battery Event ID */
+
+#define EC_EVENT_BAT_A0 0xA0
+#define EC_EVENT_BAT_A1 0xA1
+#define EC_EVENT_BAT_A2 0xA2
+#define EC_EVENT_BAT_A3 0xA3
+#define EC_EVENT_BAT_B1 0xB1
+/* EVENT B1 A0 A1 repeat about every 1s 2s 3s respectively */
+
+/* ACPI _BIX field, Min sampling time, the duration between two _BST */
+#define CACHE_TIME 2000 /* cache time in milliseconds */
+
+#define MILLI_TO_MICRO 1000
+
+#define SMART_CHARGE_MODE 0
+#define SMART_CHARGE_DELAY 1
+#define SMART_CHARGE_START 2
+#define SMART_CHARGE_END 3
+
+#define NO_DELAY_MODE 1
+#define DELAY_MODE 4
+
+struct gaokun_psy_bat_status {
+ __le16 percentage_now; /* 0x90 */
+ __le16 voltage_now;
+ __le16 capacity_now;
+ __le16 full_capacity;
+ __le16 unknown1;
+ __le16 rate_now;
+ __le16 unknown2; /* 0x9C */
+} __packed;
+
+struct gaokun_psy_bat_info {
+ __le16 unknown3; /* 0xA0 */
+ __le16 design_capacity;
+ __le16 design_voltage;
+ __le16 serial_number;
+ __le16 padding2;
+ __le16 cycle_count; /* 0xAA */
+} __packed;
+
+struct gaokun_psy {
+ struct gaokun_ec *ec;
+ struct device *dev;
+ struct notifier_block nb;
+
+ struct power_supply *bat_psy;
+ struct power_supply *adp_psy;
+
+ unsigned long update_time;
+ struct gaokun_psy_bat_status status;
+ struct gaokun_psy_bat_info info;
+
+ char battery_model[0x10]; /* HB30A8P9ECW-22T, the real one is XXX-22A */
+ char battery_serial[0x10];
+ char battery_vendor[0x10];
+
+ int charge_now;
+ int online;
+ int bat_present;
+};
+
+/* -------------------------------------------------------------------------- */
+/* Adapter */
+
+static int gaokun_psy_get_adp_status(struct gaokun_psy *ecbat)
+{
+ /* _PSR */
+ int ret;
+ u8 online;
+
+ ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &online);
+ if (ret)
+ return ret;
+
+ ecbat->online = !!(online & EC_AC_STATUS);
+
+ return 0;
+}
+
+static int gaokun_psy_get_adp_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
+ int ret;
+
+ ret = gaokun_psy_get_adp_status(ecbat);
+ if (ret)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = ecbat->online;
+ break;
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = POWER_SUPPLY_USB_TYPE_C;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property gaokun_psy_adp_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_USB_TYPE,
+};
+
+static const struct power_supply_desc gaokun_psy_adp_desc = {
+ .name = "gaokun-ec-adapter",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .usb_types = BIT(POWER_SUPPLY_USB_TYPE_C),
+ .get_property = gaokun_psy_get_adp_property,
+ .properties = gaokun_psy_adp_props,
+ .num_properties = ARRAY_SIZE(gaokun_psy_adp_props),
+};
+
+/* -------------------------------------------------------------------------- */
+/* Battery */
+
+static inline void gaokun_psy_get_bat_present(struct gaokun_psy *ecbat)
+{
+ int ret;
+ u8 present;
+
+ /* Some kind of initialization */
+ gaokun_ec_write(ecbat->ec, (u8 []){0x02, 0xB2, 1, 0x90});
+
+ ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &present);
+
+ ecbat->bat_present = ret ? false : !!(present & EC_BAT_PRESENT);
+}
+
+static inline int gaokun_psy_bat_present(struct gaokun_psy *ecbat)
+{
+ return ecbat->bat_present;
+}
+
+static int gaokun_psy_get_bat_info(struct gaokun_psy *ecbat)
+{
+ /* _BIX */
+ if (!gaokun_psy_bat_present(ecbat))
+ return 0;
+
+ return gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_INFO_START,
+ sizeof(ecbat->info), (u8 *)&ecbat->info);
+}
+
+static void gaokun_psy_update_bat_charge(struct gaokun_psy *ecbat)
+{
+ u8 charge;
+
+ gaokun_ec_psy_read_byte(ecbat->ec, EC_BAT_STATUS, &charge);
+
+ switch (charge) {
+ case EC_BAT_CHARGING:
+ ecbat->charge_now = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case EC_BAT_DISCHARGING:
+ ecbat->charge_now = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case EC_BAT_FULL:
+ ecbat->charge_now = POWER_SUPPLY_STATUS_FULL;
+ break;
+ default:
+ dev_warn(ecbat->dev, "unknown charge state %d\n", charge);
+ }
+}
+
+static int gaokun_psy_get_bat_status(struct gaokun_psy *ecbat)
+{
+ /* _BST */
+ int ret;
+
+ if (time_before(jiffies, ecbat->update_time +
+ msecs_to_jiffies(CACHE_TIME)))
+ return 0;
+
+ gaokun_psy_update_bat_charge(ecbat);
+ ret = gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_STATUS_START,
+ sizeof(ecbat->status), (u8 *)&ecbat->status);
+
+ ecbat->update_time = jiffies;
+
+ return ret;
+}
+
+static void gaokun_psy_init(struct gaokun_psy *ecbat)
+{
+ gaokun_psy_get_bat_present(ecbat);
+ if (!gaokun_psy_bat_present(ecbat))
+ return;
+
+ gaokun_psy_get_bat_info(ecbat);
+
+ snprintf(ecbat->battery_serial, sizeof(ecbat->battery_serial),
+ "%d", le16_to_cpu(ecbat->info.serial_number));
+
+ gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_VENDOR,
+ sizeof(ecbat->battery_vendor) - 1,
+ ecbat->battery_vendor);
+
+ gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_MODEL,
+ sizeof(ecbat->battery_model) - 1,
+ ecbat->battery_model);
+
+ ecbat->battery_model[14] = 'A'; /* FIX UP */
+}
+
+static int gaokun_psy_get_bat_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
+ u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE];
+ int ret;
+
+ if (gaokun_psy_bat_present(ecbat))
+ gaokun_psy_get_bat_status(ecbat);
+ else if (psp != POWER_SUPPLY_PROP_PRESENT)
+ return -ENODEV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = ecbat->charge_now;
+ break;
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = ecbat->bat_present;
+ break;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ val->intval = le16_to_cpu(ecbat->info.cycle_count);
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = le16_to_cpu(ecbat->info.design_voltage) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = le16_to_cpu(ecbat->status.voltage_now) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = (s16)le16_to_cpu(ecbat->status.rate_now) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = le16_to_cpu(ecbat->info.design_capacity) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = le16_to_cpu(ecbat->status.full_capacity) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = le16_to_cpu(ecbat->status.capacity_now) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, buf);
+ if (ret)
+ return ret;
+
+ if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD)
+ val->intval = buf[SMART_CHARGE_START];
+ else
+ val->intval = buf[SMART_CHARGE_END];
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = le16_to_cpu(ecbat->status.percentage_now);
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = ecbat->battery_model;
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = ecbat->battery_vendor;
+ break;
+
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ val->strval = ecbat->battery_serial;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int gaokun_psy_set_bat_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
+ u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE];
+ int ret;
+
+ if (!gaokun_psy_bat_present(ecbat))
+ return -ENODEV;
+
+ ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, buf);
+ if (ret)
+ return ret;
+
+ switch (psp) {
+ /*
+ * Resetting another thershold makes single thersold setting more likely
+ * to succeed. But setting start = end makes thing strange(failure).
+ */
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ buf[SMART_CHARGE_START] = val->intval;
+ if (buf[SMART_CHARGE_START] > buf[SMART_CHARGE_END])
+ buf[SMART_CHARGE_END] = buf[SMART_CHARGE_START] + 1;
+ return gaokun_ec_psy_set_smart_charge(ecbat->ec, buf);
+
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ buf[SMART_CHARGE_END] = val->intval;
+ if (buf[SMART_CHARGE_END] < buf[SMART_CHARGE_START])
+ buf[SMART_CHARGE_START] = buf[SMART_CHARGE_END] - 1;
+ return gaokun_ec_psy_set_smart_charge(ecbat->ec, buf);
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int gaokun_psy_is_bat_property_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD ||
+ psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD;
+}
+
+static enum power_supply_property gaokun_psy_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+};
+
+static const struct power_supply_desc gaokun_psy_bat_desc = {
+ .name = "gaokun-ec-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = gaokun_psy_get_bat_property,
+ .set_property = gaokun_psy_set_bat_property,
+ .property_is_writeable = gaokun_psy_is_bat_property_writeable,
+ .properties = gaokun_psy_bat_props,
+ .num_properties = ARRAY_SIZE(gaokun_psy_bat_props),
+};
+
+/* -------------------------------------------------------------------------- */
+/* Sysfs */
+
+/*
+ * Note that, HUAWEI calls them SBAC/GBAC and SBCM/GBCM in DSDT, they are likely
+ * Set/Get Battery Adaptive Charging and Set/Get Battery Charging Mode.
+ */
+
+/* battery adaptive charge */
+static ssize_t battery_adaptive_charge_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
+ int ret;
+ bool on;
+
+ ret = gaokun_ec_psy_get_smart_charge_enable(ecbat->ec, &on);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", on);
+}
+
+static ssize_t battery_adaptive_charge_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
+ int ret;
+ bool on;
+
+ if (kstrtobool(buf, &on))
+ return -EINVAL;
+
+ ret = gaokun_ec_psy_set_smart_charge_enable(ecbat->ec, on);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static DEVICE_ATTR_RW(battery_adaptive_charge);
+
+static inline int get_charge_delay(u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE])
+{
+ return buf[SMART_CHARGE_MODE] == NO_DELAY_MODE ? 0 : buf[SMART_CHARGE_DELAY];
+}
+
+static inline void
+set_charge_delay(u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE], u8 delay)
+{
+ if (delay) {
+ buf[SMART_CHARGE_DELAY] = delay;
+ buf[SMART_CHARGE_MODE] = DELAY_MODE;
+ } else {
+ /* No writing zero, there is a specific mode for it. */
+ buf[SMART_CHARGE_MODE] = NO_DELAY_MODE;
+ }
+}
+
+/* Smart charge */
+static ssize_t smart_charge_delay_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
+ u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
+ int ret;
+
+ ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, bf);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", get_charge_delay(bf));
+}
+
+static ssize_t smart_charge_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
+ u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
+ u8 delay;
+ int ret;
+
+ if (kstrtou8(buf, 10, &delay))
+ return -EINVAL;
+
+ ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, bf);
+ if (ret)
+ return ret;
+
+ set_charge_delay(bf, delay);
+
+ ret = gaokun_ec_psy_set_smart_charge(ecbat->ec, bf);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static DEVICE_ATTR_RW(smart_charge_delay);
+
+static struct attribute *gaokun_psy_features_attrs[] = {
+ &dev_attr_battery_adaptive_charge.attr,
+ &dev_attr_smart_charge_delay.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(gaokun_psy_features);
+
+static int gaokun_psy_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct gaokun_psy *ecbat = container_of(nb, struct gaokun_psy, nb);
+
+ switch (action) {
+ case EC_EVENT_BAT_A2:
+ case EC_EVENT_BAT_B1:
+ gaokun_psy_get_bat_info(ecbat);
+ return NOTIFY_OK;
+
+ case EC_EVENT_BAT_A0:
+ gaokun_psy_get_adp_status(ecbat);
+ power_supply_changed(ecbat->adp_psy);
+ msleep(10);
+ fallthrough;
+
+ case EC_EVENT_BAT_A1:
+ case EC_EVENT_BAT_A3:
+ if (action == EC_EVENT_BAT_A3) {
+ gaokun_psy_get_bat_info(ecbat);
+ msleep(100);
+ }
+ gaokun_psy_get_bat_status(ecbat);
+ power_supply_changed(ecbat->bat_psy);
+ return NOTIFY_OK;
+
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+static int gaokun_psy_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct gaokun_ec *ec = adev->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ struct device *dev = &adev->dev;
+ struct gaokun_psy *ecbat;
+
+ ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL);
+ if (!ecbat)
+ return -ENOMEM;
+
+ ecbat->ec = ec;
+ ecbat->dev = dev;
+ ecbat->nb.notifier_call = gaokun_psy_notify;
+
+ auxiliary_set_drvdata(adev, ecbat);
+
+ psy_cfg.drv_data = ecbat;
+ ecbat->adp_psy = devm_power_supply_register(dev, &gaokun_psy_adp_desc,
+ &psy_cfg);
+ if (IS_ERR(ecbat->adp_psy))
+ return dev_err_probe(dev, PTR_ERR(ecbat->adp_psy),
+ "Failed to register AC power supply\n");
+
+ psy_cfg.supplied_to = (char **)&gaokun_psy_bat_desc.name;
+ psy_cfg.num_supplicants = 1;
+ psy_cfg.no_wakeup_source = true;
+ psy_cfg.attr_grp = gaokun_psy_features_groups;
+ ecbat->bat_psy = devm_power_supply_register(dev, &gaokun_psy_bat_desc,
+ &psy_cfg);
+ if (IS_ERR(ecbat->bat_psy))
+ return dev_err_probe(dev, PTR_ERR(ecbat->bat_psy),
+ "Failed to register battery power supply\n");
+ gaokun_psy_init(ecbat);
+
+ return gaokun_ec_register_notify(ec, &ecbat->nb);
+}
+
+static void gaokun_psy_remove(struct auxiliary_device *adev)
+{
+ struct gaokun_psy *ecbat = auxiliary_get_drvdata(adev);
+
+ gaokun_ec_unregister_notify(ecbat->ec, &ecbat->nb);
+}
+
+static const struct auxiliary_device_id gaokun_psy_id_table[] = {
+ { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_PSY, },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, gaokun_psy_id_table);
+
+static struct auxiliary_driver gaokun_psy_driver = {
+ .name = GAOKUN_DEV_PSY,
+ .id_table = gaokun_psy_id_table,
+ .probe = gaokun_psy_probe,
+ .remove = gaokun_psy_remove,
+};
+
+module_auxiliary_driver(gaokun_psy_driver);
+
+MODULE_DESCRIPTION("HUAWEI Matebook E Go psy driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/ingenic-battery.c b/drivers/power/supply/ingenic-battery.c
index 0a40f425c277..b111c7ce2be3 100644
--- a/drivers/power/supply/ingenic-battery.c
+++ b/drivers/power/supply/ingenic-battery.c
@@ -146,7 +146,7 @@ static int ingenic_battery_probe(struct platform_device *pdev)
desc->num_properties = ARRAY_SIZE(ingenic_battery_properties);
desc->get_property = ingenic_battery_get_property;
psy_cfg.drv_data = bat;
- psy_cfg.of_node = dev->of_node;
+ psy_cfg.fwnode = dev_fwnode(dev);
bat->battery = devm_power_supply_register(dev, desc, &psy_cfg);
if (IS_ERR(bat->battery))
diff --git a/drivers/power/supply/intel_dc_ti_battery.c b/drivers/power/supply/intel_dc_ti_battery.c
new file mode 100644
index 000000000000..67a75281b0ac
--- /dev/null
+++ b/drivers/power/supply/intel_dc_ti_battery.c
@@ -0,0 +1,391 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Battery driver for the coulomb-counter of the Intel Dollar Cove TI PMIC
+ *
+ * Note the Intel Dollar Cove TI PMIC coulomb-counter is not a full-featured
+ * autonomous fuel-gauge. It is intended to work together with an always on
+ * micro-controller monitoring it.
+ *
+ * Since Linux does not monitor coulomb-counter changes while the device
+ * is off or suspended, voltage based capacity estimation from
+ * the adc-battery-helper code is used.
+ *
+ * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
+ *
+ * Register definitions and calibration code was taken from
+ * kernel/drivers/platform/x86/dc_ti_cc.c from the Acer A1-840 Android kernel
+ * which has the following copyright header:
+ *
+ * Copyright (C) 2014 Intel Corporation
+ * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
+ *
+ * dc_ti_cc.c is part of the Acer A1-840 Android kernel source-code archive
+ * named: "App. Guide_Acer_20151221_A_A.zip"
+ * which is distributed by Acer from the Acer A1-840 support page:
+ * https://www.acer.com/us-en/support/product-support/A1-840/downloads
+ */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/consumer.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/timekeeping.h>
+
+#include "adc-battery-helper.h"
+
+#define DC_TI_PMIC_VERSION_REG 0x00
+#define PMIC_VERSION_A0 0xC0
+#define PMIC_VERSION_A1 0xC1
+
+#define DC_TI_CC_CNTL_REG 0x60
+#define CC_CNTL_CC_CTR_EN BIT(0)
+#define CC_CNTL_CC_CLR_EN BIT(1)
+#define CC_CNTL_CC_CAL_EN BIT(2)
+#define CC_CNTL_CC_OFFSET_EN BIT(3)
+#define CC_CNTL_SMPL_INTVL GENMASK(5, 4)
+#define CC_CNTL_SMPL_INTVL_15MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 0)
+#define CC_CNTL_SMPL_INTVL_62MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 1)
+#define CC_CNTL_SMPL_INTVL_125MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 2)
+#define CC_CNTL_SMPL_INTVL_250MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 3)
+
+#define DC_TI_SMPL_CTR0_REG 0x69
+#define DC_TI_SMPL_CTR1_REG 0x68
+#define DC_TI_SMPL_CTR2_REG 0x67
+
+#define DC_TI_CC_OFFSET_HI_REG 0x61
+#define CC_OFFSET_HI_MASK 0x3F
+#define DC_TI_CC_OFFSET_LO_REG 0x62
+
+#define DC_TI_SW_OFFSET_REG 0x6C
+
+#define DC_TI_CC_ACC3_REG 0x63
+#define DC_TI_CC_ACC2_REG 0x64
+#define DC_TI_CC_ACC1_REG 0x65
+#define DC_TI_CC_ACC0_REG 0x66
+
+#define DC_TI_CC_INTG1_REG 0x6A
+#define DC_TI_CC_INTG1_MASK 0x3F
+#define DC_TI_CC_INTG0_REG 0x6B
+
+#define DC_TI_EEPROM_ACCESS_CONTROL 0x88
+#define EEPROM_UNLOCK 0xDA
+#define EEPROM_LOCK 0x00
+
+#define DC_TI_EEPROM_CC_GAIN_REG 0xF4
+#define CC_TRIM_REVISION GENMASK(3, 0)
+#define CC_GAIN_CORRECTION GENMASK(7, 4)
+
+#define PMIC_VERSION_A0_TRIM_REV 3
+#define PMIC_VERSION_A1_MIN_TRIM_REV 1
+
+#define DC_TI_EEPROM_CC_OFFSET_REG 0xFD
+
+#define DC_TI_EEPROM_CTRL 0xFE
+#define EEPROM_BANK0_SEL 0x01
+#define EEPROM_BANK1_SEL 0x02
+
+#define SMPL_INTVL_US 15000
+#define SMPL_INTVL_MS (SMPL_INTVL_US / USEC_PER_MSEC)
+#define CALIBRATION_TIME_US (10 * SMPL_INTVL_US)
+#define SLEEP_SLACK_US 2500
+
+/* CC gain correction is in 0.0025 increments */
+#define CC_GAIN_STEP 25
+#define CC_GAIN_DIV 10000
+
+/* CC offset is in 0.5 units per 250ms (default sample interval) */
+#define CC_OFFSET_DIV 2
+#define CC_OFFSET_SMPL_INTVL_MS 250
+
+/* CC accumulator scale is 366.2 ųCoulumb / unit */
+#define CC_ACC_TO_UA(acc, smpl_ctr) \
+ ((acc) * (3662 * MSEC_PER_SEC / 10) / ((smpl_ctr) * SMPL_INTVL_MS))
+
+#define DEV_NAME "chtdc_ti_battery"
+
+struct dc_ti_battery_chip {
+ /* Must be the first member see adc-battery-helper documentation */
+ struct adc_battery_helper helper;
+ struct device *dev;
+ struct regmap *regmap;
+ struct iio_channel *vbat_channel;
+ struct power_supply *psy;
+ int cc_gain;
+ int cc_offset;
+};
+
+static int dc_ti_battery_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr)
+{
+ struct dc_ti_battery_chip *chip = power_supply_get_drvdata(psy);
+ ktime_t ktime;
+ s64 sleep_usec;
+ unsigned int reg_val;
+ s32 acc, smpl_ctr;
+ int ret;
+
+ /*
+ * Enable coulomb-counter before reading Vbat from ADC, so that the CC
+ * samples are from the same time period as the Vbat reading.
+ */
+ ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
+ CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN | CC_CNTL_CC_CTR_EN);
+ if (ret)
+ goto out_err;
+
+ ktime = ktime_get();
+
+ /* Read Vbat, convert IIO mV to power-supply ųV */
+ ret = iio_read_channel_processed_scale(chip->vbat_channel, volt, 1000);
+ if (ret < 0)
+ goto out_err;
+
+ ktime = ktime_sub(ktime_get(), ktime);
+
+ /* Sleep at least 3 sample-times + slack to get 3+ CC samples */
+ sleep_usec = 3 * SMPL_INTVL_US + SLEEP_SLACK_US - ktime_to_us(ktime);
+ if (sleep_usec > 0 && sleep_usec < 1000000)
+ usleep_range(sleep_usec, sleep_usec + SLEEP_SLACK_US);
+
+ /*
+ * The PMIC latches the coulomb- and sample-counters upon reading the
+ * CC_ACC0 register. Reading multiple registers at once is not supported.
+ *
+ * Step 1: Read CC_ACC0 - CC_ACC3
+ */
+ ret = regmap_read(chip->regmap, DC_TI_CC_ACC0_REG, &reg_val);
+ if (ret)
+ goto out_err;
+
+ acc = reg_val;
+
+ ret = regmap_read(chip->regmap, DC_TI_CC_ACC1_REG, &reg_val);
+ if (ret)
+ goto out_err;
+
+ acc |= reg_val << 8;
+
+ ret = regmap_read(chip->regmap, DC_TI_CC_ACC2_REG, &reg_val);
+ if (ret)
+ goto out_err;
+
+ acc |= reg_val << 16;
+
+ ret = regmap_read(chip->regmap, DC_TI_CC_ACC3_REG, &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, &reg_val);
+ if (ret)
+ goto out_err;
+
+ smpl_ctr = reg_val;
+
+ ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR1_REG, &reg_val);
+ if (ret)
+ goto out_err;
+
+ smpl_ctr |= reg_val << 8;
+
+ ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR2_REG, &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, &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, &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, &reg_val);
+ if (ret)
+ goto out_relock;
+
+ chip->cc_offset = (s8)reg_val;
+
+ dev_dbg(chip->dev, "cc-offset %d cc-gain %d\n", chip->cc_offset, chip->cc_gain);
+
+out_relock:
+ /* Re-lock the EEPROM Access */
+ regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_LOCK);
+out:
+ if (ret)
+ dev_err(chip->dev, "IO-error %d initializing PMIC\n", ret);
+
+ return ret;
+}
+
+static int dc_ti_battery_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
+ struct power_supply_config psy_cfg = {};
+ struct fwnode_reference_args args;
+ struct gpio_desc *charge_finished;
+ struct dc_ti_battery_chip *chip;
+ int ret;
+
+ /* On most devices with a Dollar Cove TI the battery is handled by ACPI */
+ if (!acpi_quirk_skip_acpi_ac_and_battery())
+ return -ENODEV;
+
+ /* ACPI glue code adds a "monitored-battery" fwnode, wait for this */
+ ret = fwnode_property_get_reference_args(dev_fwnode(dev), "monitored-battery",
+ NULL, 0, 0, &args);
+ if (ret) {
+ dev_dbg(dev, "fwnode_property_get_ref() ret %d\n", ret);
+ return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for monitored-battery fwnode\n");
+ }
+
+ fwnode_handle_put(args.fwnode);
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = dev;
+ chip->regmap = pmic->regmap;
+
+ chip->vbat_channel = devm_iio_channel_get(dev, "VBAT");
+ if (IS_ERR(chip->vbat_channel)) {
+ dev_dbg(dev, "devm_iio_channel_get() ret %ld\n", PTR_ERR(chip->vbat_channel));
+ return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for VBAT IIO channel\n");
+ }
+
+ charge_finished = devm_gpiod_get_optional(dev, "charged", GPIOD_IN);
+ if (IS_ERR(charge_finished))
+ return dev_err_probe(dev, PTR_ERR(charge_finished), "Getting charged GPIO\n");
+
+ ret = dc_ti_battery_hw_init(chip);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, chip);
+
+ psy_cfg.drv_data = chip;
+ chip->psy = devm_power_supply_register(dev, &dc_ti_battery_psy_desc, &psy_cfg);
+ if (IS_ERR(chip->psy))
+ return PTR_ERR(chip->psy);
+
+ return adc_battery_helper_init(&chip->helper, chip->psy,
+ dc_ti_battery_get_voltage_and_current_now,
+ charge_finished);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(dc_ti_battery_pm_ops, adc_battery_helper_suspend,
+ adc_battery_helper_resume, NULL);
+
+static struct platform_driver dc_ti_battery_driver = {
+ .driver = {
+ .name = DEV_NAME,
+ .pm = pm_sleep_ptr(&dc_ti_battery_pm_ops),
+ },
+ .probe = dc_ti_battery_probe,
+};
+module_platform_driver(dc_ti_battery_driver);
+
+MODULE_ALIAS("platform:" DEV_NAME);
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_DESCRIPTION("Intel Dollar Cove (TI) battery driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/ip5xxx_power.c b/drivers/power/supply/ip5xxx_power.c
index c448e0ac0dfa..24eea7a91b30 100644
--- a/drivers/power/supply/ip5xxx_power.c
+++ b/drivers/power/supply/ip5xxx_power.c
@@ -828,10 +828,9 @@ static void ip5xxx_setup_regs(struct device *dev, struct ip5xxx *ip5xxx,
static int ip5xxx_power_probe(struct i2c_client *client)
{
- const struct ip5xxx_regfield_config *fields = &ip51xx_fields;
+ const struct ip5xxx_regfield_config *fields;
struct power_supply_config psy_cfg = {};
struct device *dev = &client->dev;
- const struct of_device_id *of_id;
struct power_supply *psy;
struct ip5xxx *ip5xxx;
@@ -843,12 +842,10 @@ static int ip5xxx_power_probe(struct i2c_client *client)
if (IS_ERR(ip5xxx->regmap))
return PTR_ERR(ip5xxx->regmap);
- of_id = i2c_of_match_device(dev->driver->of_match_table, client);
- if (of_id)
- fields = (const struct ip5xxx_regfield_config *)of_id->data;
+ fields = i2c_get_match_data(client) ?: &ip51xx_fields;
ip5xxx_setup_regs(dev, ip5xxx, fields);
- psy_cfg.of_node = dev->of_node;
+ psy_cfg.fwnode = dev_fwnode(dev);
psy_cfg.drv_data = ip5xxx;
psy = devm_power_supply_register(dev, &ip5xxx_battery_desc, &psy_cfg);
diff --git a/drivers/power/supply/ipaq_micro_battery.c b/drivers/power/supply/ipaq_micro_battery.c
index 7e0568a5353f..5e3fe3852d0e 100644
--- a/drivers/power/supply/ipaq_micro_battery.c
+++ b/drivers/power/supply/ipaq_micro_battery.c
@@ -7,6 +7,7 @@
* Author : Linus Walleij <linus.walleij@linaro.org>
*/
+#include <linux/devm-helpers.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
@@ -232,48 +233,31 @@ static int micro_batt_probe(struct platform_device *pdev)
return -ENOMEM;
mb->micro = dev_get_drvdata(pdev->dev.parent);
- mb->wq = alloc_workqueue("ipaq-battery-wq", WQ_MEM_RECLAIM, 0);
+ mb->wq = devm_alloc_workqueue(&pdev->dev, "ipaq-battery-wq",
+ WQ_MEM_RECLAIM | WQ_PERCPU, 0);
if (!mb->wq)
return -ENOMEM;
- INIT_DELAYED_WORK(&mb->update, micro_battery_work);
+ ret = devm_delayed_work_autocancel(&pdev->dev, &mb->update, micro_battery_work);
+ if (ret)
+ return ret;
+
platform_set_drvdata(pdev, mb);
queue_delayed_work(mb->wq, &mb->update, 1);
- micro_batt_power = power_supply_register(&pdev->dev,
- &micro_batt_power_desc, NULL);
- if (IS_ERR(micro_batt_power)) {
- ret = PTR_ERR(micro_batt_power);
- goto batt_err;
- }
+ micro_batt_power = devm_power_supply_register(&pdev->dev,
+ &micro_batt_power_desc,
+ NULL);
+ if (IS_ERR(micro_batt_power))
+ return PTR_ERR(micro_batt_power);
- micro_ac_power = power_supply_register(&pdev->dev,
- &micro_ac_power_desc, NULL);
- if (IS_ERR(micro_ac_power)) {
- ret = PTR_ERR(micro_ac_power);
- goto ac_err;
- }
+ micro_ac_power = devm_power_supply_register(&pdev->dev,
+ &micro_ac_power_desc, NULL);
+ if (IS_ERR(micro_ac_power))
+ return PTR_ERR(micro_ac_power);
dev_info(&pdev->dev, "iPAQ micro battery driver\n");
return 0;
-
-ac_err:
- power_supply_unregister(micro_batt_power);
-batt_err:
- cancel_delayed_work_sync(&mb->update);
- destroy_workqueue(mb->wq);
- return ret;
-}
-
-static void micro_batt_remove(struct platform_device *pdev)
-
-{
- struct micro_battery *mb = platform_get_drvdata(pdev);
-
- power_supply_unregister(micro_ac_power);
- power_supply_unregister(micro_batt_power);
- cancel_delayed_work_sync(&mb->update);
- destroy_workqueue(mb->wq);
}
static int __maybe_unused micro_batt_suspend(struct device *dev)
@@ -302,7 +286,6 @@ static struct platform_driver micro_batt_device_driver = {
.pm = &micro_batt_dev_pm_ops,
},
.probe = micro_batt_probe,
- .remove = micro_batt_remove,
};
module_platform_driver(micro_batt_device_driver);
diff --git a/drivers/power/supply/lego_ev3_battery.c b/drivers/power/supply/lego_ev3_battery.c
index 9085de0ae1b2..28454de05761 100644
--- a/drivers/power/supply/lego_ev3_battery.c
+++ b/drivers/power/supply/lego_ev3_battery.c
@@ -23,6 +23,7 @@
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
+#include <linux/property.h>
struct lego_ev3_battery {
struct iio_channel *iio_v;
@@ -198,7 +199,7 @@ static int lego_ev3_battery_probe(struct platform_device *pdev)
batt->v_min = 48000000;
}
- psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&pdev->dev);
psy_cfg.drv_data = batt;
batt->psy = devm_power_supply_register(dev, &lego_ev3_battery_desc,
diff --git a/drivers/power/supply/lt3651-charger.c b/drivers/power/supply/lt3651-charger.c
index 8de500ffad95..ebfbdbcb7683 100644
--- a/drivers/power/supply/lt3651-charger.c
+++ b/drivers/power/supply/lt3651-charger.c
@@ -131,7 +131,7 @@ static int lt3651_charger_probe(struct platform_device *pdev)
charger_desc->properties = lt3651_charger_properties;
charger_desc->num_properties = ARRAY_SIZE(lt3651_charger_properties);
charger_desc->get_property = lt3651_charger_get_property;
- psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&pdev->dev);
psy_cfg.drv_data = lt3651_charger;
lt3651_charger->charger = devm_power_supply_register(&pdev->dev,
diff --git a/drivers/power/supply/ltc4162-l-charger.c b/drivers/power/supply/ltc4162-l-charger.c
index 23eb426295db..9dd74fa9552d 100644
--- a/drivers/power/supply/ltc4162-l-charger.c
+++ b/drivers/power/supply/ltc4162-l-charger.c
@@ -1119,7 +1119,7 @@ static const struct regmap_config ltc4162l_regmap_config = {
.writeable_reg = ltc4162l_is_writeable_reg,
.volatile_reg = ltc4162l_is_volatile_reg,
.max_register = LTC4162L_INPUT_UNDERVOLTAGE_DAC,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
};
static void ltc4162l_clear_interrupts(struct ltc4162l_info *info)
@@ -1185,7 +1185,7 @@ static int ltc4162l_probe(struct i2c_client *client)
if (!device_property_read_u32(dev, "lltc,cell-count", &value))
info->cell_count = value;
- ltc4162l_config.of_node = dev->of_node;
+ ltc4162l_config.fwnode = dev_fwnode(dev);
ltc4162l_config.drv_data = info;
ltc4162l_config.attr_grp = ltc4162l_attr_groups;
diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c
new file mode 100644
index 000000000000..33ca07460f3a
--- /dev/null
+++ b/drivers/power/supply/macsmc-power.c
@@ -0,0 +1,855 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC Power/Battery Management Driver
+ *
+ * This driver exposes battery telemetry (voltage, current, temperature, health)
+ * and AC adapter status provided by the Apple SMC (System Management Controller)
+ * on Apple Silicon systems.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/devm-helpers.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/reboot.h>
+#include <linux/workqueue.h>
+
+#define MAX_STRING_LENGTH 256
+
+/*
+ * The SMC reports charge in mAh (Coulombs) but energy in mWh (Joules).
+ * We lack a register for "Nominal Voltage" or "Energy Accumulator".
+ * We use a fixed 3.8V/cell constant to approximate energy stats for userspace,
+ * derived from empirical data across supported MacBook models.
+ */
+#define MACSMC_NOMINAL_CELL_VOLTAGE_MV 3800
+
+/* SMC Key Flags */
+#define CHNC_BATTERY_FULL BIT(0)
+#define CHNC_NO_CHARGER BIT(7)
+#define CHNC_NOCHG_CH0C BIT(14)
+#define CHNC_NOCHG_CH0B_CH0K BIT(15)
+#define CHNC_BATTERY_FULL_2 BIT(18)
+#define CHNC_BMS_BUSY BIT(23)
+#define CHNC_CHLS_LIMIT BIT(24)
+#define CHNC_NOAC_CH0J BIT(53)
+#define CHNC_NOAC_CH0I BIT(54)
+
+#define CH0R_LOWER_FLAGS GENMASK(15, 0)
+#define CH0R_NOAC_CH0I BIT(0)
+#define CH0R_NOAC_DISCONNECTED BIT(4)
+#define CH0R_NOAC_CH0J BIT(5)
+#define CH0R_BMS_BUSY BIT(8)
+#define CH0R_NOAC_CH0K BIT(9)
+#define CH0R_NOAC_CHWA BIT(11)
+
+#define CH0X_CH0C BIT(0)
+#define CH0X_CH0B BIT(1)
+
+#define ACSt_CAN_BOOT_AP BIT(2)
+#define ACSt_CAN_BOOT_IBOOT BIT(1)
+
+#define CHWA_CHLS_FIXED_START_OFFSET 5
+#define CHLS_MIN_END_THRESHOLD 10
+#define CHLS_FORCE_DISCHARGE 0x100
+#define CHWA_FIXED_END_THRESHOLD 80
+#define CHWA_PROP_WRITE_THRESHOLD 95
+
+#define MACSMC_MAX_BATT_PROPS 50
+#define MACSMC_MAX_AC_PROPS 10
+
+struct macsmc_power {
+ struct device *dev;
+ struct apple_smc *smc;
+
+ struct power_supply_desc ac_desc;
+ struct power_supply_desc batt_desc;
+
+ struct power_supply *batt;
+ struct power_supply *ac;
+
+ char model_name[MAX_STRING_LENGTH];
+ char serial_number[MAX_STRING_LENGTH];
+ char mfg_date[MAX_STRING_LENGTH];
+
+ /* Supported feature flags based on SMC key presence */
+ bool has_chwa; /* Charge limit (Modern firmware) */
+ bool has_chls; /* Charge limit (Older firmware) */
+ bool has_ch0i; /* Force discharge (Older firmware) */
+ bool has_ch0c; /* Inhibit charge (Older firmware) */
+ bool has_chte; /* Inhibit charge (Modern firmware) */
+
+ u8 num_cells;
+ int nominal_voltage_mv;
+
+ struct notifier_block nb;
+ struct work_struct critical_work;
+ bool emergency_shutdown_triggered;
+ bool orderly_shutdown_triggered;
+};
+
+static int macsmc_battery_get_status(struct macsmc_power *power)
+{
+ u64 nocharge_flags;
+ u32 nopower_flags;
+ u16 ac_current;
+ int charge_limit = 0;
+ bool limited = false;
+ bool flag;
+ int ret;
+
+ /*
+ * B0AV (Voltage) is fundamental. If we can't read it, we assume the
+ * battery is gone. CHCE (Hardware charger present) / CHCC (Hardware
+ * charger capable) are fundamental status flags.
+ * BSFC (System full charge) / CHSC (System charging) are fundamental
+ * status flags.
+ */
+
+ /* Check if power input is inhibited (e.g. BMS balancing cycle) */
+ ret = apple_smc_read_u32(power->smc, SMC_KEY(CH0R), &nopower_flags);
+ if (!ret && (nopower_flags & CH0R_LOWER_FLAGS & ~CH0R_BMS_BUSY))
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ /* Check if charger is present */
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCE), &flag);
+ if (ret < 0)
+ return ret;
+ if (!flag)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ /* Check if AC is charge capable */
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCC), &flag);
+ if (ret < 0)
+ return ret;
+ if (!flag)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ /* Check if AC input limit is too low */
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &ac_current);
+ if (!ret && ac_current < 100)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ /* Check if battery is full */
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC), &flag);
+ if (ret < 0)
+ return ret;
+ if (flag)
+ return POWER_SUPPLY_STATUS_FULL;
+
+ /* Check for user-defined charge limits */
+ if (power->has_chls) {
+ u16 vu16;
+
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16);
+ if (ret == 0 && (vu16 & 0xff) >= CHLS_MIN_END_THRESHOLD)
+ charge_limit = (vu16 & 0xff) - CHWA_CHLS_FIXED_START_OFFSET;
+ } else if (power->has_chwa) {
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag);
+ if (ret == 0 && flag)
+ charge_limit = CHWA_FIXED_END_THRESHOLD - CHWA_CHLS_FIXED_START_OFFSET;
+ }
+
+ if (charge_limit > 0) {
+ u8 buic = 0;
+
+ if (apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &buic) >= 0 &&
+ buic >= charge_limit)
+ limited = true;
+ }
+
+ /* Check charging inhibitors */
+ ret = apple_smc_read_u64(power->smc, SMC_KEY(CHNC), &nocharge_flags);
+ if (!ret) {
+ if (nocharge_flags & CHNC_BATTERY_FULL)
+ return POWER_SUPPLY_STATUS_FULL;
+ /* BMS busy shows up as inhibit, but we treat it as charging */
+ else if (nocharge_flags == CHNC_BMS_BUSY && !limited)
+ return POWER_SUPPLY_STATUS_CHARGING;
+ else if (nocharge_flags)
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ return POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ /* Fallback: System charging flag */
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(CHSC), &flag);
+ if (ret < 0)
+ return ret;
+ if (!flag)
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ return POWER_SUPPLY_STATUS_CHARGING;
+}
+
+static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power)
+{
+ int ret;
+ u8 val8;
+ u8 chte_buf[4];
+
+ if (power->has_ch0i) {
+ ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val8);
+ if (ret)
+ return ret;
+ if (val8 & CH0R_NOAC_CH0I)
+ return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
+ }
+
+ if (power->has_chte) {
+ ret = apple_smc_read(power->smc, SMC_KEY(CHTE), chte_buf, 4);
+ if (ret < 0)
+ return ret;
+
+ if (chte_buf[0] == 0x01)
+ return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ } else if (power->has_ch0c) {
+ ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val8);
+ if (ret)
+ return ret;
+ if (val8 & CH0X_CH0C)
+ return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ }
+
+ return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+}
+
+static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int val)
+{
+ int ret;
+
+ switch (val) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ /* Reset all inhibitors to a known-good 'auto' state */
+ if (power->has_ch0i) {
+ ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0);
+ if (ret)
+ return ret;
+ }
+
+ if (power->has_chte) {
+ ret = apple_smc_write_u32(power->smc, SMC_KEY(CHTE), 0);
+ if (ret)
+ return ret;
+ } else if (power->has_ch0c) {
+ ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0);
+ if (ret)
+ return ret;
+ }
+ return 0;
+
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+ if (power->has_chte)
+ return apple_smc_write_u32(power->smc, SMC_KEY(CHTE), 1);
+ else if (power->has_ch0c)
+ return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 1);
+ else
+ return -EOPNOTSUPP;
+
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
+ if (!power->has_ch0i)
+ return -EOPNOTSUPP;
+ return apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 1);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int macsmc_battery_get_date(const char *s, int *out)
+{
+ if (!isdigit(s[0]) || !isdigit(s[1]))
+ return -EOPNOTSUPP;
+
+ *out = (s[0] - '0') * 10 + s[1] - '0';
+ return 0;
+}
+
+static int macsmc_battery_get_capacity_level(struct macsmc_power *power)
+{
+ bool flag;
+ u32 val;
+ int ret;
+
+ /* Check for emergency shutdown condition */
+ if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val) >= 0 && val)
+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+
+ /* Check AC status for whether we could boot in this state */
+ if (apple_smc_read_u32(power->smc, SMC_KEY(ACSt), &val) >= 0) {
+ if (!(val & ACSt_CAN_BOOT_IBOOT))
+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+
+ if (!(val & ACSt_CAN_BOOT_AP))
+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ }
+
+ /* BSFC = Battery System Full Charge */
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC), &flag);
+ if (ret < 0)
+ return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+
+ if (flag)
+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else
+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+}
+
+static int macsmc_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct macsmc_power *power = power_supply_get_drvdata(psy);
+ int ret = 0;
+ u8 vu8;
+ u16 vu16;
+ s16 vs16;
+ s32 vs32;
+ s64 vs64;
+ bool flag;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = macsmc_battery_get_status(power);
+ ret = val->intval < 0 ? val->intval : 0;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ val->intval = macsmc_battery_get_charge_behaviour(power);
+ ret = val->intval < 0 ? val->intval : 0;
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16);
+ val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TF), &vu16);
+ val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8);
+ val->intval = vu8;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ val->intval = macsmc_battery_get_capacity_level(power);
+ ret = val->intval < 0 ? val->intval : 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = apple_smc_read_s16(power->smc, SMC_KEY(B0AC), &vs16);
+ val->intval = vs16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_POWER_NOW:
+ ret = apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &vs32);
+ val->intval = vs32 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(BITV), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ /* Calculate total max design voltage from per-cell maximum voltage */
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(BVVN), &vu16);
+ val->intval = vu16 * 1000 * power->num_cells;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ /* Lifetime min */
+ ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPM), &vs16);
+ val->intval = vs16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ /* Lifetime max */
+ ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPX), &vs16);
+ val->intval = vs16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RV), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
+ /* B0RM is Big Endian, likely pass through from TI gas gauge */
+ val->intval = (s16)swab16(vu16) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
+ val->intval = vu16 * power->nominal_voltage_mv;
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
+ val->intval = vu16 * power->nominal_voltage_mv;
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
+ /* B0RM is Big Endian, likely pass through from TI gas gauge */
+ val->intval = (s16)swab16(vu16) * power->nominal_voltage_mv;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16);
+ val->intval = vu16 - 2732; /* Kelvin x10 to Celsius x10 */
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ ret = apple_smc_read_s64(power->smc, SMC_KEY(BAAC), &vs64);
+ val->intval = vs64;
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0CT), &vu16);
+ val->intval = vu16;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ flag = false;
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(BBAD), &flag);
+ val->intval = flag ? POWER_SUPPLY_HEALTH_DEAD : POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = power->model_name;
+ break;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ val->strval = power->serial_number;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
+ ret = macsmc_battery_get_date(&power->mfg_date[0], &val->intval);
+ /* The SMC reports the manufacture year as an offset from 1992. */
+ val->intval += 1992;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURE_MONTH:
+ ret = macsmc_battery_get_date(&power->mfg_date[2], &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURE_DAY:
+ ret = macsmc_battery_get_date(&power->mfg_date[4], &val->intval);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int macsmc_battery_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct macsmc_power *power = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ return macsmc_battery_set_charge_behaviour(power, val->intval);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int macsmc_battery_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct power_supply_desc macsmc_battery_desc_template = {
+ .name = "macsmc-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = macsmc_battery_get_property,
+ .set_property = macsmc_battery_set_property,
+ .property_is_writeable = macsmc_battery_property_is_writeable,
+};
+
+static int macsmc_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct macsmc_power *power = power_supply_get_drvdata(psy);
+ int ret = 0;
+ u16 vu16;
+ u32 vu32;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = apple_smc_read_u32(power->smc, SMC_KEY(CHIS), &vu32);
+ val->intval = !!vu32;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT:
+ ret = apple_smc_read_u32(power->smc, SMC_KEY(ACPW), &vu32);
+ val->intval = vu32 * 1000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct power_supply_desc macsmc_ac_desc_template = {
+ .name = "macsmc-ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .get_property = macsmc_ac_get_property,
+};
+
+static void macsmc_power_critical_work(struct work_struct *wrk)
+{
+ struct macsmc_power *power = container_of(wrk, struct macsmc_power, critical_work);
+ u16 bitv, b0av;
+ u32 bcf0;
+
+ if (!power->batt)
+ return;
+
+ /*
+ * Avoid duplicate atempts at emergency shutdown
+ */
+ if (power->emergency_shutdown_triggered || system_state > SYSTEM_RUNNING)
+ return;
+
+ /*
+ * EMERGENCY: Check voltage vs design minimum.
+ * If we are below BITV, the battery is physically exhausted.
+ * We must shut down NOW to protect the filesystem.
+ */
+ if (apple_smc_read_u16(power->smc, SMC_KEY(BITV), &bitv) >= 0 &&
+ apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &b0av) >= 0 &&
+ b0av < bitv) {
+ power->emergency_shutdown_triggered = true;
+ dev_emerg(power->dev,
+ "Battery voltage (%d mV) below design minimum (%d mV)! Emergency shutdown.\n",
+ b0av, bitv);
+
+ /*
+ * Shutdown is now imminent. Kick userspace again and give it some
+ * brief time to (hopefully) flush what's needed, before forcing.
+ */
+ hw_protection_trigger("Battery voltage below design minimum", 1500);
+ }
+
+ /*
+ * Avoid duplicate attempts at orderly shutdown.
+ * Voltage check is above this as we may want to
+ * "upgrade" an orderly shutdown to a critical power
+ * off if voltage drops.
+ */
+ if (power->orderly_shutdown_triggered || system_state > SYSTEM_RUNNING)
+ return;
+
+ /*
+ * Check if SMC flagged the battery as empty.
+ * We trigger a graceful shutdown to let the OS save data.
+ */
+ if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &bcf0) == 0 && bcf0 != 0) {
+ power->orderly_shutdown_triggered = true;
+ dev_crit(power->dev, "Battery critical (empty flag set). Triggering orderly shutdown.\n");
+ orderly_poweroff(true);
+ }
+}
+
+static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data)
+{
+ struct macsmc_power *power = container_of(nb, struct macsmc_power, nb);
+
+ /*
+ * SMC Event IDs are correlated to physical events (e.g. charger
+ * connect/disconnect) but the exact meaning of each ID is predicted.
+ * 0x71... indicates power/battery events.
+ */
+ if ((event & 0xffffff00) == 0x71010100 || /* Charger status change */
+ (event & 0xffff0000) == 0x71060000 || /* Port charge state change */
+ (event & 0xffff0000) == 0x71130000) { /* Connector insert/remove event */
+ if (power->batt)
+ power_supply_changed(power->batt);
+ if (power->ac)
+ power_supply_changed(power->ac);
+ return NOTIFY_OK;
+ } else if (event == 0x71020000) {
+ /* Critical battery warning */
+ if (power->batt)
+ schedule_work(&power->critical_work);
+ return NOTIFY_OK;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int macsmc_power_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+ struct power_supply_config psy_cfg = {};
+ struct macsmc_power *power;
+ bool has_battery = false;
+ bool has_ac_adapter = false;
+ int ret = -ENODEV;
+ bool flag;
+ u16 vu16;
+ u32 val32;
+ enum power_supply_property *props;
+ size_t nprops;
+
+ if (!smc)
+ return -ENODEV;
+
+ power = devm_kzalloc(dev, sizeof(*power), GFP_KERNEL);
+ if (!power)
+ return -ENOMEM;
+
+ power->dev = dev;
+ power->smc = smc;
+ dev_set_drvdata(dev, power);
+
+ INIT_WORK(&power->critical_work, macsmc_power_critical_work);
+ ret = devm_work_autocancel(dev, &power->critical_work, macsmc_power_critical_work);
+ if (ret)
+ return ret;
+
+ /*
+ * Check for battery presence.
+ * B0AV is a fundamental key.
+ */
+ if (apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16) == 0 &&
+ macsmc_battery_get_status(power) > POWER_SUPPLY_STATUS_UNKNOWN)
+ has_battery = true;
+
+ /*
+ * Check for AC adapter presence.
+ * CHIS is a fundamental key.
+ */
+ if (apple_smc_key_exists(smc, SMC_KEY(CHIS)))
+ has_ac_adapter = true;
+
+ if (!has_battery && !has_ac_adapter)
+ return -ENODEV;
+
+ if (has_battery) {
+ power->batt_desc = macsmc_battery_desc_template;
+ props = devm_kcalloc(dev, MACSMC_MAX_BATT_PROPS,
+ sizeof(enum power_supply_property),
+ GFP_KERNEL);
+ if (!props)
+ return -ENOMEM;
+
+ nprops = 0;
+
+ /* Fundamental properties */
+ props[nprops++] = POWER_SUPPLY_PROP_STATUS;
+ props[nprops++] = POWER_SUPPLY_PROP_PRESENT;
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_CURRENT_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_POWER_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_CAPACITY;
+ props[nprops++] = POWER_SUPPLY_PROP_CAPACITY_LEVEL;
+ props[nprops++] = POWER_SUPPLY_PROP_TEMP;
+ props[nprops++] = POWER_SUPPLY_PROP_CYCLE_COUNT;
+ props[nprops++] = POWER_SUPPLY_PROP_HEALTH;
+ props[nprops++] = POWER_SUPPLY_PROP_SCOPE;
+ props[nprops++] = POWER_SUPPLY_PROP_MODEL_NAME;
+ props[nprops++] = POWER_SUPPLY_PROP_SERIAL_NUMBER;
+ props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_YEAR;
+ props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_MONTH;
+ props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_DAY;
+
+ /* Extended properties usually present */
+ props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_FULL_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN;
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN;
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT;
+ props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
+ props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
+ props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL;
+ props[nprops++] = POWER_SUPPLY_PROP_ENERGY_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_COUNTER;
+
+ /* Detect features based on key availability */
+ if (apple_smc_key_exists(smc, SMC_KEY(CHTE)))
+ power->has_chte = true;
+ if (apple_smc_key_exists(smc, SMC_KEY(CH0C)))
+ power->has_ch0c = true;
+ if (apple_smc_key_exists(smc, SMC_KEY(CH0I)))
+ power->has_ch0i = true;
+
+ /* Reset "Optimised Battery Charging" flags to default state */
+ if (power->has_chte)
+ apple_smc_write_u32(smc, SMC_KEY(CHTE), 0);
+ else if (power->has_ch0c)
+ apple_smc_write_u8(smc, SMC_KEY(CH0C), 0);
+
+ if (power->has_ch0i)
+ apple_smc_write_u8(smc, SMC_KEY(CH0I), 0);
+
+ apple_smc_write_u8(smc, SMC_KEY(CH0K), 0);
+ apple_smc_write_u8(smc, SMC_KEY(CH0B), 0);
+
+ /* Configure charge behaviour if supported */
+ if (power->has_ch0i || power->has_ch0c || power->has_chte) {
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR;
+
+ power->batt_desc.charge_behaviours =
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO);
+
+ if (power->has_ch0i)
+ power->batt_desc.charge_behaviours |=
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE);
+
+ if (power->has_chte || power->has_ch0c)
+ power->batt_desc.charge_behaviours |=
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE);
+ }
+
+ /* Detect charge limit method (CHWA vs CHLS) */
+ if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0)
+ power->has_chwa = true;
+ else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0)
+ power->has_chls = true;
+
+ if (nprops > MACSMC_MAX_BATT_PROPS)
+ return -ENOMEM;
+
+ power->batt_desc.properties = props;
+ power->batt_desc.num_properties = nprops;
+
+ /* Fetch identity strings */
+ apple_smc_read(smc, SMC_KEY(BMDN), power->model_name,
+ sizeof(power->model_name) - 1);
+ apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number,
+ sizeof(power->serial_number) - 1);
+ apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date,
+ sizeof(power->mfg_date) - 1);
+
+ apple_smc_read_u8(power->smc, SMC_KEY(BNCB), &power->num_cells);
+ power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells;
+
+ /* Enable critical shutdown notifications by reading status once */
+ apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val32);
+
+ psy_cfg.drv_data = power;
+ power->batt = devm_power_supply_register(dev, &power->batt_desc, &psy_cfg);
+ if (IS_ERR(power->batt)) {
+ dev_err_probe(dev, PTR_ERR(power->batt),
+ "Failed to register battery\n");
+ /* Don't return failure yet; try AC registration first */
+ power->batt = NULL;
+ }
+ }
+
+ if (has_ac_adapter) {
+ power->ac_desc = macsmc_ac_desc_template;
+ props = devm_kcalloc(dev, MACSMC_MAX_AC_PROPS,
+ sizeof(enum power_supply_property),
+ GFP_KERNEL);
+ if (!props)
+ return -ENOMEM;
+
+ nprops = 0;
+
+ /* Online status is fundamental */
+ props[nprops++] = POWER_SUPPLY_PROP_ONLINE;
+
+ /* Input power limits are usually available */
+ if (apple_smc_key_exists(power->smc, SMC_KEY(ACPW)))
+ props[nprops++] = POWER_SUPPLY_PROP_INPUT_POWER_LIMIT;
+
+ /* macOS 15.4+ firmware dropped legacy AC keys (AC-n, AC-i) */
+ if (apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16) >= 0) {
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT;
+ }
+
+ if (nprops > MACSMC_MAX_AC_PROPS)
+ return -ENOMEM;
+
+ power->ac_desc.properties = props;
+ power->ac_desc.num_properties = nprops;
+
+ psy_cfg.drv_data = power;
+ power->ac = devm_power_supply_register(dev, &power->ac_desc, &psy_cfg);
+ if (IS_ERR(power->ac)) {
+ dev_err_probe(dev, PTR_ERR(power->ac),
+ "Failed to register AC adapter\n");
+ power->ac = NULL;
+ }
+ }
+
+ /* Final check: did we register anything? */
+ if (!power->batt && !power->ac)
+ return -ENODEV;
+
+ power->nb.notifier_call = macsmc_power_event;
+ blocking_notifier_chain_register(&smc->event_handlers, &power->nb);
+
+ return 0;
+}
+
+static void macsmc_power_remove(struct platform_device *pdev)
+{
+ struct macsmc_power *power = dev_get_drvdata(&pdev->dev);
+
+ blocking_notifier_chain_unregister(&power->smc->event_handlers, &power->nb);
+}
+
+static const struct platform_device_id macsmc_power_id[] = {
+ { "macsmc-power" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, macsmc_power_id);
+
+static struct platform_driver macsmc_power_driver = {
+ .driver = {
+ .name = "macsmc-power",
+ },
+ .id_table = macsmc_power_id,
+ .probe = macsmc_power_probe,
+ .remove = macsmc_power_remove,
+};
+module_platform_driver(macsmc_power_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC battery and power management driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_AUTHOR("Michael Reeves <michael.reeves077@gmail.com>");
diff --git a/drivers/power/supply/max14577_charger.c b/drivers/power/supply/max14577_charger.c
index 1cef2f860b5f..63077d38ea30 100644
--- a/drivers/power/supply/max14577_charger.c
+++ b/drivers/power/supply/max14577_charger.c
@@ -501,7 +501,7 @@ static struct max14577_charger_platform_data *max14577_charger_dt_init(
static struct max14577_charger_platform_data *max14577_charger_dt_init(
struct platform_device *pdev)
{
- return NULL;
+ return ERR_PTR(-ENODATA);
}
#endif /* CONFIG_OF */
@@ -572,7 +572,7 @@ static int max14577_charger_probe(struct platform_device *pdev)
chg->max14577 = max14577;
chg->pdata = max14577_charger_dt_init(pdev);
- if (IS_ERR_OR_NULL(chg->pdata))
+ if (IS_ERR(chg->pdata))
return PTR_ERR(chg->pdata);
ret = max14577_charger_reg_init(chg);
diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c
index 51310f6e4803..48453508688a 100644
--- a/drivers/power/supply/max17040_battery.c
+++ b/drivers/power/supply/max17040_battery.c
@@ -388,6 +388,7 @@ static int max17040_get_property(struct power_supply *psy,
union power_supply_propval *val)
{
struct max17040_chip *chip = power_supply_get_drvdata(psy);
+ int ret;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
@@ -410,8 +411,12 @@ static int max17040_get_property(struct power_supply *psy,
if (!chip->channel_temp)
return -ENODATA;
- iio_read_channel_processed_scale(chip->channel_temp,
- &val->intval, 10);
+ ret = iio_read_channel_processed(chip->channel_temp, &val->intval);
+ if (ret)
+ return ret;
+
+ val->intval /= 100; /* Convert from milli- to deci-degree */
+
break;
default:
return -EINVAL;
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 655b3f25dbd7..167fb3fb3732 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -61,6 +61,7 @@ struct max17042_chip {
struct work_struct work;
int init_complete;
int irq;
+ int task_period;
};
static enum power_supply_property max17042_battery_props[] = {
@@ -88,6 +89,7 @@ static enum power_supply_property max17042_battery_props[] = {
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
// these two have to be at the end on the list
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
@@ -131,7 +133,7 @@ static int max17042_get_status(struct max17042_chip *chip, int *status)
* FullCAP to match RepCap when it detects end of charging.
*
* When this cycle the battery gets charged to a higher (calculated)
- * capacity then the previous cycle then FullCAP will get updated
+ * capacity than the previous cycle then FullCAP will get updated
* continuously once end-of-charge detection kicks in, so allow the
* 2 to differ a bit.
*/
@@ -201,7 +203,7 @@ static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
goto out;
}
- if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) {
+ if (vbatt > size_add(chip->pdata->vmax, MAX17042_VMAX_TOLERANCE)) {
*health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
goto out;
}
@@ -331,6 +333,8 @@ static int max17042_get_property(struct power_supply *psy,
return ret;
data64 = data * 5000000ll;
+ data64 *= chip->task_period;
+ do_div(data64, MAX17042_DEFAULT_TASK_PERIOD);
do_div(data64, chip->pdata->r_sns);
val->intval = data64;
break;
@@ -340,6 +344,8 @@ static int max17042_get_property(struct power_supply *psy,
return ret;
data64 = data * 5000000ll;
+ data64 *= chip->task_period;
+ do_div(data64, MAX17042_DEFAULT_TASK_PERIOD);
do_div(data64, chip->pdata->r_sns);
val->intval = data64;
break;
@@ -349,6 +355,8 @@ static int max17042_get_property(struct power_supply *psy,
return ret;
data64 = data * 5000000ll;
+ data64 *= chip->task_period;
+ do_div(data64, MAX17042_DEFAULT_TASK_PERIOD);
do_div(data64, chip->pdata->r_sns);
val->intval = data64;
break;
@@ -358,6 +366,8 @@ static int max17042_get_property(struct power_supply *psy,
return ret;
data64 = sign_extend64(data, 15) * 5000000ll;
+ data64 *= chip->task_period;
+ data64 = div_s64(data64, MAX17042_DEFAULT_TASK_PERIOD);
val->intval = div_s64(data64, chip->pdata->r_sns);
break;
case POWER_SUPPLY_PROP_TEMP:
@@ -430,6 +440,25 @@ static int max17042_get_property(struct power_supply *psy,
if (ret < 0)
return ret;
+ /* when charging, the value is not meaningful */
+ if (data == U16_MAX)
+ return -ENODATA;
+
+ val->intval = data * 5625 / 1000;
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055 &&
+ chip->chip_type != MAXIM_DEVICE_TYPE_MAX77759)
+ return -EINVAL;
+
+ ret = regmap_read(map, MAX17055_TTF, &data);
+ if (ret < 0)
+ return ret;
+
+ /* when discharging, the value is not meaningful */
+ if (data == U16_MAX)
+ return -ENODATA;
+
val->intval = data * 5625 / 1000;
break;
default:
@@ -646,7 +675,8 @@ static void max17042_write_config_regs(struct max17042_chip *chip)
regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg);
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 ||
chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050 ||
- chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+ chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 ||
+ chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759)
regmap_write(map, MAX17047_FullSOCThr,
config->full_soc_thresh);
}
@@ -783,7 +813,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) ||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
- (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)) {
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050) ||
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759)) {
max17042_override_por(map, MAX17042_IAvg_empty, config->iavg_empty);
max17042_override_por(map, MAX17042_TempNom, config->temp_nom);
max17042_override_por(map, MAX17042_TempLim, config->temp_lim);
@@ -792,7 +823,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050) ||
- (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)) {
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) ||
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759)) {
max17042_override_por(map, MAX17047_V_empty, config->vempty);
}
}
@@ -921,8 +953,12 @@ max17042_get_of_pdata(struct max17042_chip *chip)
/*
* Require current sense resistor value to be specified for
* current-sense functionality to be enabled at all.
+ * maxim,rsns-microohm is the property name used by older DTs and kept
+ * for compatibility.
*/
- if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) {
+ if ((of_property_read_u32(np, "shunt-resistor-micro-ohms",
+ &prop) == 0) ||
+ (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0)) {
pdata->r_sns = prop;
pdata->enable_current_sense = true;
}
@@ -1011,6 +1047,45 @@ static const struct regmap_config max17042_regmap_config = {
.val_format_endian = REGMAP_ENDIAN_NATIVE,
};
+static const struct regmap_range max77759_fg_registers[] = {
+ regmap_reg_range(MAX17042_STATUS, MAX77759_MixAtFull),
+ regmap_reg_range(MAX17042_VFSOC0Enable, MAX17042_VFSOC0Enable),
+ regmap_reg_range(MAX17042_MLOCKReg1, MAX17042_MLOCKReg2),
+ regmap_reg_range(MAX17042_MODELChrTbl, MAX17055_TimerH),
+ regmap_reg_range(MAX77759_IIn, MAX77759_IIn),
+ regmap_reg_range(MAX17055_AtQResidual, MAX17055_AtAvCap),
+ regmap_reg_range(MAX17042_OCVInternal, MAX17042_OCVInternal),
+ regmap_reg_range(MAX17042_VFSOC, MAX17042_VFSOC),
+};
+
+static const struct regmap_range max77759_fg_ro_registers[] = {
+ regmap_reg_range(MAX17042_FSTAT, MAX17042_FSTAT),
+ regmap_reg_range(MAX17042_OCVInternal, MAX17042_OCVInternal),
+ regmap_reg_range(MAX17042_VFSOC, MAX17042_VFSOC),
+};
+
+static const struct regmap_access_table max77759_fg_write_table = {
+ .yes_ranges = max77759_fg_registers,
+ .n_yes_ranges = ARRAY_SIZE(max77759_fg_registers),
+ .no_ranges = max77759_fg_ro_registers,
+ .n_no_ranges = ARRAY_SIZE(max77759_fg_ro_registers),
+};
+
+static const struct regmap_access_table max77759_fg_rd_table = {
+ .yes_ranges = max77759_fg_registers,
+ .n_yes_ranges = ARRAY_SIZE(max77759_fg_registers),
+};
+
+static const struct regmap_config max77759_fg_regmap_cfg = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = 0xff,
+ .wr_table = &max77759_fg_write_table,
+ .rd_table = &max77759_fg_rd_table,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+ .cache_type = REGCACHE_NONE,
+};
+
static const struct power_supply_desc max17042_psy_desc = {
.name = "max170xx_battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
@@ -1037,6 +1112,7 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
{
struct i2c_adapter *adapter = client->adapter;
const struct power_supply_desc *max17042_desc = &max17042_psy_desc;
+ const struct regmap_config *regmap_config;
struct power_supply_config psy_cfg = {};
struct max17042_chip *chip;
int ret;
@@ -1052,21 +1128,24 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
chip->dev = dev;
chip->chip_type = chip_type;
- chip->regmap = devm_regmap_init_i2c(client, &max17042_regmap_config);
- if (IS_ERR(chip->regmap)) {
- dev_err(dev, "Failed to initialize regmap\n");
- return -EINVAL;
- }
+
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759)
+ regmap_config = &max77759_fg_regmap_cfg;
+ else
+ regmap_config = &max17042_regmap_config;
+ chip->regmap = devm_regmap_init_i2c(client, regmap_config);
+ if (IS_ERR(chip->regmap))
+ return dev_err_probe(dev, PTR_ERR(chip->regmap),
+ "Failed to initialize regmap\n");
chip->pdata = max17042_get_pdata(chip);
- if (!chip->pdata) {
- dev_err(dev, "no platform data provided\n");
- return -EINVAL;
- }
+ if (!chip->pdata)
+ return dev_err_probe(dev, -EINVAL,
+ "no platform data provided\n");
dev_set_drvdata(dev, chip);
psy_cfg.drv_data = chip;
- psy_cfg.of_node = dev->of_node;
+ psy_cfg.fwnode = dev_fwnode(dev);
/* When current is not measured,
* CURRENT_NOW and CURRENT_AVG properties should be invisible. */
@@ -1088,12 +1167,22 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007);
}
+ chip->task_period = MAX17042_DEFAULT_TASK_PERIOD;
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759) {
+ ret = regmap_read(chip->regmap, MAX17042_TaskPeriod, &val);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read task period\n");
+ chip->task_period = val;
+ }
+ dev_dbg(dev, "task period: %#.4x (%d)\n", chip->task_period,
+ chip->task_period);
+
chip->battery = devm_power_supply_register(dev, max17042_desc,
&psy_cfg);
- if (IS_ERR(chip->battery)) {
- dev_err(dev, "failed: power supply register\n");
- return PTR_ERR(chip->battery);
- }
+ if (IS_ERR(chip->battery))
+ return dev_err_probe(dev, PTR_ERR(chip->battery),
+ "failed: power supply register\n");
if (irq) {
unsigned int flags = IRQF_ONESHOT | IRQF_SHARED | IRQF_PROBE_SHARED;
@@ -1236,6 +1325,8 @@ static const struct of_device_id max17042_dt_match[] __used = {
.data = (void *) MAXIM_DEVICE_TYPE_MAX17055 },
{ .compatible = "maxim,max77705-battery",
.data = (void *) MAXIM_DEVICE_TYPE_MAX17047 },
+ { .compatible = "maxim,max77759-fg",
+ .data = (void *) MAXIM_DEVICE_TYPE_MAX77759 },
{ .compatible = "maxim,max77849-battery",
.data = (void *) MAXIM_DEVICE_TYPE_MAX17047 },
{ },
@@ -1248,6 +1339,7 @@ static const struct i2c_device_id max17042_id[] = {
{ "max17047", MAXIM_DEVICE_TYPE_MAX17047 },
{ "max17050", MAXIM_DEVICE_TYPE_MAX17050 },
{ "max17055", MAXIM_DEVICE_TYPE_MAX17055 },
+ { "max77759-fg", MAXIM_DEVICE_TYPE_MAX77759 },
{ "max77849-battery", MAXIM_DEVICE_TYPE_MAX17047 },
{ }
};
diff --git a/drivers/power/supply/max1720x_battery.c b/drivers/power/supply/max1720x_battery.c
index 11580e414713..e2bd54ee3970 100644
--- a/drivers/power/supply/max1720x_battery.c
+++ b/drivers/power/supply/max1720x_battery.c
@@ -29,6 +29,11 @@
/* ModelGauge m5 */
#define MAX172XX_STATUS 0x00 /* Status */
#define MAX172XX_STATUS_BAT_ABSENT BIT(3) /* Battery absent */
+#define MAX172XX_STATUS_IMX BIT(6) /* Maximum Current Alert Threshold Exceeded */
+#define MAX172XX_STATUS_VMN BIT(8) /* Minimum Voltage Alert Threshold Exceeded */
+#define MAX172XX_STATUS_TMN BIT(9) /* Minimum Temperature Alert Threshold Exceeded */
+#define MAX172XX_STATUS_VMX BIT(12) /* Maximum Voltage Alert Threshold Exceeded */
+#define MAX172XX_STATUS_TMX BIT(13) /* Maximum Temperature Alert Threshold Exceeded */
#define MAX172XX_REPCAP 0x05 /* Average capacity */
#define MAX172XX_REPSOC 0x06 /* Percentage of charge */
#define MAX172XX_TEMP 0x08 /* Temperature */
@@ -114,7 +119,7 @@ static const struct regmap_config max1720x_regmap_cfg = {
.val_format_endian = REGMAP_ENDIAN_LITTLE,
.rd_table = &max1720x_readable_regs,
.volatile_table = &max1720x_volatile_regs,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
};
static const struct regmap_range max1720x_nvmem_allow[] = {
@@ -250,6 +255,7 @@ static const struct nvmem_cell_info max1720x_nvmem_cells[] = {
};
static const enum power_supply_property max1720x_battery_props[] = {
+ POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
@@ -282,9 +288,10 @@ static int max172xx_voltage_to_ps(unsigned int reg)
return reg * 1250; /* in uV */
}
-static int max172xx_capacity_to_ps(unsigned int reg)
+static int max172xx_capacity_to_ps(unsigned int reg,
+ struct max1720x_device_info *info)
{
- return reg * 500; /* in uAh */
+ return reg * (500000 / info->rsense); /* in uAh */
}
/*
@@ -302,7 +309,7 @@ static int max172xx_temperature_to_ps(unsigned int reg)
/*
* Calculating current registers resolution:
*
- * RSense stored in 10^-5 Ohm, so mesaurment voltage must be
+ * RSense stored in 10^-5 Ohm, so measurement voltage must be
* in 10^-11 Volts for get current in uA.
* 16 bit current reg fullscale +/-51.2mV is 102400 uV.
* So: 102400 / 65535 * 10^5 = 156252
@@ -314,6 +321,43 @@ static int max172xx_current_to_voltage(unsigned int reg)
return val * 156252;
}
+static int max172xx_battery_health(struct max1720x_device_info *info,
+ unsigned int *health)
+{
+ unsigned int status;
+ int ret;
+
+ ret = regmap_read(info->regmap, MAX172XX_STATUS, &status);
+ if (ret < 0)
+ return ret;
+
+ if (status & MAX172XX_STATUS_VMN)
+ *health = POWER_SUPPLY_HEALTH_DEAD;
+ else if (status & MAX172XX_STATUS_VMX)
+ *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else if (status & MAX172XX_STATUS_TMN)
+ *health = POWER_SUPPLY_HEALTH_COLD;
+ else if (status & MAX172XX_STATUS_TMX)
+ *health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (status & MAX172XX_STATUS_IMX)
+ *health = POWER_SUPPLY_HEALTH_OVERCURRENT;
+ else
+ *health = POWER_SUPPLY_HEALTH_GOOD;
+
+ /* Clear events which are not self-clearing to detect next events */
+ if (status > 0 && status != MAX172XX_STATUS_IMX) {
+ ret = regmap_set_bits(info->regmap, MAX172XX_STATUS,
+ MAX172XX_STATUS_VMN |
+ MAX172XX_STATUS_VMX |
+ MAX172XX_STATUS_TMN |
+ MAX172XX_STATUS_TMX);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
static int max1720x_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -323,6 +367,10 @@ static int max1720x_battery_get_property(struct power_supply *psy,
int ret = 0;
switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = max172xx_battery_health(info, &reg_val);
+ val->intval = reg_val;
+ break;
case POWER_SUPPLY_PROP_PRESENT:
/*
* POWER_SUPPLY_PROP_PRESENT will always readable via
@@ -347,11 +395,11 @@ static int max1720x_battery_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
ret = regmap_read(info->regmap, MAX172XX_DESIGN_CAP, &reg_val);
- val->intval = max172xx_capacity_to_ps(reg_val);
+ val->intval = max172xx_capacity_to_ps(reg_val, info);
break;
case POWER_SUPPLY_PROP_CHARGE_AVG:
ret = regmap_read(info->regmap, MAX172XX_REPCAP, &reg_val);
- val->intval = max172xx_capacity_to_ps(reg_val);
+ val->intval = max172xx_capacity_to_ps(reg_val, info);
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
ret = regmap_read(info->regmap, MAX172XX_TTE, &reg_val);
@@ -375,10 +423,12 @@ static int max1720x_battery_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
ret = regmap_read(info->regmap, MAX172XX_FULL_CAP, &reg_val);
- val->intval = max172xx_capacity_to_ps(reg_val);
+ val->intval = max172xx_capacity_to_ps(reg_val, info);
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
ret = regmap_read(info->regmap, MAX172XX_DEV_NAME, &reg_val);
+ if (ret)
+ return ret;
reg_val = FIELD_GET(MAX172XX_DEV_NAME_TYPE_MASK, reg_val);
if (reg_val == MAX172XX_DEV_NAME_TYPE_MAX17201)
val->strval = max17201_model;
diff --git a/drivers/power/supply/max77650-charger.c b/drivers/power/supply/max77650-charger.c
index 5f58c0c24b4d..4ae43668992e 100644
--- a/drivers/power/supply/max77650-charger.c
+++ b/drivers/power/supply/max77650-charger.c
@@ -298,7 +298,7 @@ static int max77650_charger_probe(struct platform_device *pdev)
chg->dev = dev;
- pscfg.of_node = dev->of_node;
+ pscfg.fwnode = dev_fwnode(dev);
pscfg.drv_data = chg;
chg_irq = platform_get_irq_byname(pdev, "CHG");
diff --git a/drivers/power/supply/max77693_charger.c b/drivers/power/supply/max77693_charger.c
index cdea35c0d1de..027d6a539b65 100644
--- a/drivers/power/supply/max77693_charger.c
+++ b/drivers/power/supply/max77693_charger.c
@@ -608,7 +608,7 @@ static int max77693_set_charge_input_threshold_volt(struct max77693_charger *chg
case 4700000:
case 4800000:
case 4900000:
- data = (uvolt - 4700000) / 100000;
+ data = ((uvolt - 4700000) / 100000) + 1;
break;
default:
dev_err(chg->dev, "Wrong value for charge input voltage regulation threshold\n");
diff --git a/drivers/power/supply/max77705_charger.c b/drivers/power/supply/max77705_charger.c
new file mode 100644
index 000000000000..63b0b4f0cd21
--- /dev/null
+++ b/drivers/power/supply/max77705_charger.c
@@ -0,0 +1,699 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Based on max77650-charger.c
+ *
+ * Copyright (C) 2025 Dzmitry Sankouski <dsankouski@gmail.org>
+ *
+ * Battery charger driver for MAXIM 77705 charger/power-supply.
+ */
+
+#include <linux/devm-helpers.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max77693-common.h>
+#include <linux/mfd/max77705-private.h>
+#include <linux/power/max77705_charger.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+
+static const char *max77705_charger_model = "max77705";
+static const char *max77705_charger_manufacturer = "Maxim Integrated";
+
+static const struct regmap_config max77705_chg_regmap_config = {
+ .reg_base = MAX77705_CHG_REG_BASE,
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX77705_CHG_REG_SAFEOUT_CTRL,
+};
+
+static enum power_supply_property max77705_charger_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+};
+
+static irqreturn_t max77705_aicl_irq(int irq, void *irq_drv_data)
+{
+ struct max77705_charger_data *chg = irq_drv_data;
+ unsigned int regval, irq_status;
+ int err;
+
+ err = regmap_read(chg->regmap, MAX77705_CHG_REG_INT_OK, &irq_status);
+ if (err < 0)
+ return IRQ_HANDLED;
+
+ // irq is fiered at the end of current decrease sequence too
+ // early check AICL_I bit to guard against that excess irq call
+ while (!(irq_status & BIT(MAX77705_AICL_I))) {
+ err = regmap_field_read(chg->rfield[MAX77705_CHG_CHGIN_LIM], &regval);
+ 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, &reg_data);
+
+ dev_dbg(chg->dev, "CHG_INT_OK(0x%x)\n", reg_data);
+
+ regmap_read(regmap, MAX77705_CHG_REG_DETAILS_00, &reg_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, &reg_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, &reg_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], &reg_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], &reg_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], &reg_data);
+ voltage_mv = reg_data <= 0x04 ? reg_data * 50 + 4000 :
+ (reg_data - 4) * 10 + 4200;
+ *val = voltage_mv * 1000;
+
+ return 0;
+}
+
+static int max77705_chg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max77705_charger_data *chg = power_supply_get_drvdata(psy);
+ struct regmap *regmap = chg->regmap;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ return max77705_get_online(regmap, &val->intval);
+ case POWER_SUPPLY_PROP_PRESENT:
+ return max77705_check_battery(chg, &val->intval);
+ case POWER_SUPPLY_PROP_STATUS:
+ return max77705_get_status(chg, &val->intval);
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ return max77705_get_charge_type(chg, &val->intval);
+ case POWER_SUPPLY_PROP_HEALTH:
+ return max77705_get_health(chg, &val->intval);
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return max77705_get_input_current(chg, &val->intval);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ return max77705_get_charge_current(chg, &val->intval);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ return max77705_get_float_voltage(chg, &val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = chg->bat_info->voltage_max_design_uv;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = max77705_charger_model;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = max77705_charger_manufacturer;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max77705_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max77705_charger_data *chg = power_supply_get_drvdata(psy);
+ int err = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ err = max77705_set_integer(chg, MAX77705_CHG_CC_LIM,
+ MAX77705_CURRENT_CHGIN_MIN,
+ MAX77705_CURRENT_CHGIN_MAX,
+ MAX77705_CURRENT_CHG_STEP,
+ val->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ err = max77705_set_integer(chg, MAX77705_CHG_CHGIN_LIM,
+ MAX77705_CURRENT_CHGIN_MIN,
+ MAX77705_CURRENT_CHGIN_MAX,
+ MAX77705_CURRENT_CHGIN_STEP,
+ val->intval);
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+};
+
+static int max77705_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct power_supply_desc max77705_charger_psy_desc = {
+ .name = "max77705-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = max77705_charger_props,
+ .property_is_writeable = max77705_property_is_writeable,
+ .num_properties = ARRAY_SIZE(max77705_charger_props),
+ .get_property = max77705_chg_get_property,
+ .set_property = max77705_set_property,
+};
+
+static void max77705_chgin_isr_work(struct work_struct *work)
+{
+ struct max77705_charger_data *chg =
+ container_of(work, struct max77705_charger_data, chgin_work);
+
+ power_supply_changed(chg->psy_chg);
+}
+
+static int max77705_charger_initialize(struct max77705_charger_data *chg)
+{
+ struct power_supply_battery_info *info;
+ struct regmap *regmap = chg->regmap;
+ int err;
+
+ err = power_supply_get_battery_info(chg->psy_chg, &info);
+ if (err)
+ return dev_err_probe(chg->dev, err, "error on getting battery info");
+
+ chg->bat_info = info;
+
+ /* unlock charger setting protect */
+ /* slowest LX slope */
+ err = regmap_field_write(chg->rfield[MAX77705_CHGPROT], MAX77705_CHGPROT_UNLOCKED);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_LX_SLOPE], MAX77705_SLOWEST_LX_SLOPE);
+ if (err)
+ goto err;
+
+ /* fast charge timer disable */
+ /* restart threshold disable */
+ /* pre-qual charge disable */
+ err = regmap_field_write(chg->rfield[MAX77705_FCHGTIME], MAX77705_FCHGTIME_DISABLE);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_CHG_RSTRT], MAX77705_CHG_RSTRT_DISABLE);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_CHG_PQEN], MAX77705_CHG_PQEN_DISABLE);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_MODE],
+ MAX77705_CHG_MASK | MAX77705_BUCK_MASK);
+ if (err)
+ goto err;
+
+ /* charge current 450mA(default) */
+ /* otg current limit 900mA */
+ err = regmap_field_write(chg->rfield[MAX77705_OTG_ILIM], MAX77705_OTG_ILIM_900);
+ if (err)
+ goto err;
+
+ /* BAT to SYS OCP 4.80A */
+ err = regmap_field_write(chg->rfield[MAX77705_REG_B2SOVRC], MAX77705_B2SOVRC_4_8A);
+ if (err)
+ goto err;
+
+ /* top off current 150mA */
+ /* top off timer 30min */
+ err = regmap_field_write(chg->rfield[MAX77705_TO], MAX77705_TO_ITH_150MA);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_TO_TIME], MAX77705_TO_TIME_30M);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_SYS_TRACK], MAX77705_SYS_TRACK_DISABLE);
+ if (err)
+ goto err;
+
+ /* cv voltage 4.2V or 4.35V */
+ /* MINVSYS 3.6V(default) */
+ if (info->voltage_max_design_uv < 0) {
+ dev_warn(chg->dev, "missing battery:voltage-max-design-microvolt\n");
+ max77705_set_float_voltage(chg, 4200000);
+ } else {
+ max77705_set_float_voltage(chg, info->voltage_max_design_uv);
+ }
+
+ err = regmap_field_write(chg->rfield[MAX77705_VCHGIN], MAX77705_VCHGIN_4_5);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_WCIN], MAX77705_WCIN_4_5);
+ if (err)
+ goto err;
+
+ /* Watchdog timer */
+ regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_00,
+ MAX77705_WDTEN_MASK, 0);
+
+ /* VBYPSET=5.0V */
+ err = regmap_field_write(chg->rfield[MAX77705_VBYPSET], 0);
+ if (err)
+ goto err;
+
+ /* Switching Frequency : 1.5MHz */
+ err = regmap_field_write(chg->rfield[MAX77705_REG_FSW], MAX77705_CHG_FSW_1_5MHz);
+ if (err)
+ goto err;
+
+ /* Auto skip mode */
+ err = regmap_field_write(chg->rfield[MAX77705_REG_DISKIP], MAX77705_AUTO_SKIP);
+ if (err)
+ goto err;
+
+ return 0;
+
+err:
+ return dev_err_probe(chg->dev, err, "error while configuring");
+
+}
+
+static int max77705_charger_probe(struct i2c_client *i2c)
+{
+ struct power_supply_config pscfg = {};
+ struct max77705_charger_data *chg;
+ struct regmap_irq_chip *chip_desc;
+ struct device *dev;
+ struct regmap_irq_chip_data *irq_data;
+ int ret;
+
+ dev = &i2c->dev;
+
+ chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ chg->dev = dev;
+ i2c_set_clientdata(i2c, chg);
+
+ chip_desc = devm_kmemdup(dev, &max77705_charger_irq_chip,
+ sizeof(max77705_charger_irq_chip),
+ GFP_KERNEL);
+ if (!chip_desc)
+ return -ENOMEM;
+ chip_desc->irq_drv_data = chg;
+
+ chg->regmap = devm_regmap_init_i2c(i2c, &max77705_chg_regmap_config);
+ if (IS_ERR(chg->regmap))
+ return PTR_ERR(chg->regmap);
+
+ for (int i = 0; i < MAX77705_N_REGMAP_FIELDS; i++) {
+ chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap,
+ max77705_reg_field[i]);
+ if (IS_ERR(chg->rfield[i]))
+ return dev_err_probe(dev, PTR_ERR(chg->rfield[i]),
+ "cannot allocate regmap field\n");
+ }
+
+ pscfg.fwnode = dev_fwnode(dev);
+ pscfg.drv_data = chg;
+
+ chg->psy_chg = devm_power_supply_register(dev, &max77705_charger_psy_desc, &pscfg);
+ if (IS_ERR(chg->psy_chg))
+ return PTR_ERR(chg->psy_chg);
+
+ ret = devm_regmap_add_irq_chip(chg->dev, chg->regmap, i2c->irq,
+ IRQF_ONESHOT, 0,
+ chip_desc, &irq_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add irq chip\n");
+
+ chg->wqueue = devm_alloc_ordered_workqueue(dev, "%s", 0, dev_name(dev));
+ if (!chg->wqueue)
+ return -ENOMEM;
+
+ ret = devm_work_autocancel(dev, &chg->chgin_work, max77705_chgin_isr_work);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to initialize interrupt work\n");
+
+ ret = max77705_charger_initialize(chg);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to initialize charger IC\n");
+
+ ret = devm_request_threaded_irq(dev, regmap_irq_get_virq(irq_data, MAX77705_CHGIN_I),
+ NULL, max77705_chgin_irq,
+ IRQF_TRIGGER_NONE,
+ "chgin-irq", chg);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, regmap_irq_get_virq(irq_data, MAX77705_AICL_I),
+ NULL, max77705_aicl_irq,
+ IRQF_TRIGGER_NONE,
+ "aicl-irq", chg);
+ if (ret)
+ return ret;
+
+ ret = max77705_charger_enable(chg);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable charge\n");
+
+ return devm_add_action_or_reset(dev, max77705_charger_disable, chg);
+}
+
+static const struct of_device_id max77705_charger_of_match[] = {
+ { .compatible = "maxim,max77705-charger" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max77705_charger_of_match);
+
+static struct i2c_driver max77705_charger_driver = {
+ .driver = {
+ .name = "max77705-charger",
+ .of_match_table = max77705_charger_of_match,
+ },
+ .probe = max77705_charger_probe,
+};
+module_i2c_driver(max77705_charger_driver);
+
+MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>");
+MODULE_DESCRIPTION("Maxim MAX77705 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/max77759_charger.c b/drivers/power/supply/max77759_charger.c
new file mode 100644
index 000000000000..9bb414599f16
--- /dev/null
+++ b/drivers/power/supply/max77759_charger.c
@@ -0,0 +1,774 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * max77759_charger.c - Battery charger driver for MAX77759 charger device.
+ *
+ * Copyright 2025 Google LLC.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/devm-helpers.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/linear_range.h>
+#include <linux/mfd/max77759.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/string_choices.h>
+#include <linux/workqueue.h>
+
+/* Default values for Fast Charge Current & Float Voltage */
+#define CHG_CC_DEFAULT_UA 2266770
+#define CHG_FV_DEFAULT_MV 4300
+
+#define MAX_NUM_RETRIES 3
+#define PSY_WORK_RETRY_DELAY_MS 10
+
+#define FOREACH_IRQ(S) \
+ S(AICL), \
+ S(CHGIN), \
+ S(CHG), \
+ S(INLIM), \
+ S(BAT_OILO), \
+ S(CHG_STA_CC), \
+ S(CHG_STA_CV), \
+ S(CHG_STA_TO), \
+ S(CHG_STA_DONE)
+
+#define GENERATE_ENUM(e) e
+#define GENERATE_STRING(s) #s
+
+enum {
+ FOREACH_IRQ(GENERATE_ENUM)
+};
+
+static const char *const chgr_irqs_str[] = {
+ FOREACH_IRQ(GENERATE_STRING)
+};
+
+#define NUM_IRQS ARRAY_SIZE(chgr_irqs_str)
+
+/* Fast charge current limits (in uA) */
+static const struct linear_range chgcc_limit_ranges[] = {
+ LINEAR_RANGE(133330, 0x0, 0x2, 0),
+ LINEAR_RANGE(200000, 0x3, 0x3C, 66670),
+};
+
+/* Charge Termination Voltage Limits (in mV) */
+static const struct linear_range chg_cv_prm_ranges[] = {
+ LINEAR_RANGE(3800, 0x38, 0x39, 100),
+ LINEAR_RANGE(4000, 0x0, 0x32, 10),
+};
+
+/* USB input current limits (in uA) */
+static const struct linear_range chgin_ilim_ranges[] = {
+ LINEAR_RANGE(100000, 0x3, 0x7F, 25000),
+};
+
+struct max77759_charger {
+ struct device *dev;
+ struct regmap *regmap;
+ struct power_supply *psy;
+ struct regulator_dev *chgin_otg_rdev;
+ struct notifier_block nb;
+ struct power_supply *tcpm_psy;
+ struct delayed_work psy_work;
+ struct mutex retry_lock; /* Protects psy_work_retry_cnt */
+ u32 psy_work_retry_cnt;
+ int irqs[NUM_IRQS];
+ struct mutex lock; /* protects the state below */
+ enum max77759_chgr_mode mode;
+};
+
+static inline int unlock_prot_regs(struct max77759_charger *chg, bool unlock)
+{
+ return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_06,
+ MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT, unlock
+ ? MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT : 0);
+}
+
+static int charger_input_valid(struct max77759_charger *chg)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_INT_OK, &val);
+ if (ret)
+ return ret;
+
+ return (val & MAX77759_CHGR_REG_CHG_INT_CHG) &&
+ (val & MAX77759_CHGR_REG_CHG_INT_CHGIN);
+}
+
+static int get_online(struct max77759_charger *chg)
+{
+ u32 val;
+ int ret;
+
+ ret = charger_input_valid(chg);
+ if (ret <= 0)
+ return ret;
+
+ ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_02, &val);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&chg->lock);
+
+ return (val & MAX77759_CHGR_REG_CHG_DETAILS_02_CHGIN_STS) &&
+ (chg->mode == MAX77759_CHGR_MODE_CHG_BUCK_ON);
+}
+
+static int get_status(struct max77759_charger *chg)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val);
+ if (ret)
+ return ret;
+
+ switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) {
+ case MAX77759_CHGR_CHG_DTLS_PREQUAL:
+ case MAX77759_CHGR_CHG_DTLS_CC:
+ case MAX77759_CHGR_CHG_DTLS_CV:
+ case MAX77759_CHGR_CHG_DTLS_TO:
+ return POWER_SUPPLY_STATUS_CHARGING;
+ case MAX77759_CHGR_CHG_DTLS_DONE:
+ return POWER_SUPPLY_STATUS_FULL;
+ case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT:
+ case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM:
+ case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER:
+ case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA:
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+ case MAX77759_CHGR_CHG_DTLS_OFF:
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+ default:
+ break;
+ }
+
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+}
+
+static int get_charge_type(struct max77759_charger *chg)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val);
+ if (ret)
+ return ret;
+
+ switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) {
+ case MAX77759_CHGR_CHG_DTLS_PREQUAL:
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ case MAX77759_CHGR_CHG_DTLS_CC:
+ case MAX77759_CHGR_CHG_DTLS_CV:
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ case MAX77759_CHGR_CHG_DTLS_TO:
+ return POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+ case MAX77759_CHGR_CHG_DTLS_DONE:
+ case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT:
+ case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM:
+ case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER:
+ case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA:
+ case MAX77759_CHGR_CHG_DTLS_OFF:
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+ default:
+ break;
+ }
+
+ return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+}
+
+static int get_chg_health(struct max77759_charger *chg)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_00, &val);
+ if (ret)
+ return ret;
+
+ switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_00_CHGIN_DTLS, val)) {
+ case MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE:
+ case MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE:
+ return POWER_SUPPLY_HEALTH_UNDERVOLTAGE;
+ case MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE:
+ return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ case MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID:
+ return POWER_SUPPLY_HEALTH_GOOD;
+ default:
+ break;
+ }
+
+ return POWER_SUPPLY_HEALTH_UNKNOWN;
+}
+
+static int get_batt_health(struct max77759_charger *chg)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val);
+ if (ret)
+ return ret;
+
+ switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_BAT_DTLS, val)) {
+ case MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP:
+ return POWER_SUPPLY_HEALTH_NO_BATTERY;
+ case MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY:
+ return POWER_SUPPLY_HEALTH_DEAD;
+ case MAX77759_CHGR_BAT_DTLS_BAT_CHG_TIMER_FAULT:
+ return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+ case MAX77759_CHGR_BAT_DTLS_BAT_OKAY:
+ case MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE:
+ return POWER_SUPPLY_HEALTH_GOOD;
+ case MAX77759_CHGR_BAT_DTLS_BAT_UNDERVOLTAGE:
+ return POWER_SUPPLY_HEALTH_UNDERVOLTAGE;
+ case MAX77759_CHGR_BAT_DTLS_BAT_OVERVOLTAGE:
+ return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ case MAX77759_CHGR_BAT_DTLS_BAT_OVERCURRENT:
+ return POWER_SUPPLY_HEALTH_OVERCURRENT;
+ default:
+ break;
+ }
+
+ return POWER_SUPPLY_HEALTH_UNKNOWN;
+}
+
+static int get_health(struct max77759_charger *chg)
+{
+ int ret;
+
+ ret = get_online(chg);
+ if (ret < 0)
+ return ret;
+
+ if (ret) {
+ ret = get_chg_health(chg);
+ if (ret < 0 || ret != POWER_SUPPLY_HEALTH_GOOD)
+ return ret;
+ }
+
+ return get_batt_health(chg);
+}
+
+static int get_fast_charge_current(struct max77759_charger *chg)
+{
+ u32 regval, val;
+ int ret;
+
+ ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, &regval);
+ if (ret)
+ return ret;
+
+ regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval);
+ ret = linear_range_get_value_array(chgcc_limit_ranges,
+ ARRAY_SIZE(chgcc_limit_ranges),
+ regval, &val);
+ return ret ? ret : val;
+}
+
+static int set_fast_charge_current_limit(struct max77759_charger *chg,
+ u32 cc_max_ua)
+{
+ bool found;
+ u32 regval;
+
+ linear_range_get_selector_high_array(chgcc_limit_ranges,
+ ARRAY_SIZE(chgcc_limit_ranges),
+ cc_max_ua, &regval, &found);
+ if (!found)
+ return -EINVAL;
+
+ return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02,
+ MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval);
+}
+
+static int get_float_voltage(struct max77759_charger *chg)
+{
+ u32 regval, val;
+ int ret;
+
+ ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, &regval);
+ if (ret)
+ return ret;
+
+ regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, regval);
+ ret = linear_range_get_value_array(chg_cv_prm_ranges,
+ ARRAY_SIZE(chg_cv_prm_ranges),
+ regval, &val);
+
+ return ret ? ret : val;
+}
+
+static int set_float_voltage_limit(struct max77759_charger *chg, u32 fv_mv)
+{
+ u32 regval;
+ bool found;
+
+ linear_range_get_selector_high_array(chg_cv_prm_ranges,
+ ARRAY_SIZE(chg_cv_prm_ranges),
+ fv_mv, &regval, &found);
+ if (!found)
+ return -EINVAL;
+
+ return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04,
+ MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM,
+ regval);
+}
+
+static int get_input_current_limit(struct max77759_charger *chg)
+{
+ u32 regval, val;
+ int ret;
+
+ ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, &regval);
+ if (ret)
+ return ret;
+
+ regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, regval);
+ regval = umax(regval, chgin_ilim_ranges[0].min_sel);
+
+ ret = linear_range_get_value_array(chgin_ilim_ranges,
+ ARRAY_SIZE(chgin_ilim_ranges),
+ regval, &val);
+
+ return ret ? ret : val;
+}
+
+static int set_input_current_limit(struct max77759_charger *chg, int ilim_ua)
+{
+ u32 regval;
+
+ if (ilim_ua < 0)
+ return -EINVAL;
+
+ linear_range_get_selector_within(chgin_ilim_ranges, ilim_ua, &regval);
+
+ return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09,
+ MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM,
+ regval);
+}
+
+static const enum power_supply_property max77759_charger_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+};
+
+static int max77759_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *pval)
+{
+ struct max77759_charger *chg = power_supply_get_drvdata(psy);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = get_online(chg);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = charger_input_valid(chg);
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = get_status(chg);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = get_charge_type(chg);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = get_health(chg);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ret = get_fast_charge_current(chg);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ ret = get_float_voltage(chg);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ ret = get_input_current_limit(chg);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ pval->intval = ret;
+ return ret < 0 ? ret : 0;
+}
+
+static const struct power_supply_desc max77759_charger_desc = {
+ .name = "max77759-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = max77759_charger_props,
+ .num_properties = ARRAY_SIZE(max77759_charger_props),
+ .get_property = max77759_charger_get_property,
+};
+
+static int charger_set_mode(struct max77759_charger *chg,
+ enum max77759_chgr_mode mode)
+{
+ int ret;
+
+ guard(mutex)(&chg->lock);
+
+ if (chg->mode == mode)
+ return 0;
+
+ if ((mode == MAX77759_CHGR_MODE_CHG_BUCK_ON ||
+ mode == MAX77759_CHGR_MODE_OTG_BOOST_ON) &&
+ chg->mode != MAX77759_CHGR_MODE_OFF) {
+ dev_err(chg->dev, "Invalid mode transition from %d to %d\n",
+ chg->mode, mode);
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00,
+ MAX77759_CHGR_REG_CHG_CNFG_00_MODE, mode);
+ if (ret)
+ return ret;
+
+ chg->mode = mode;
+ return 0;
+}
+
+static int enable_chgin_otg(struct regulator_dev *rdev)
+{
+ struct max77759_charger *chg = rdev_get_drvdata(rdev);
+
+ return charger_set_mode(chg, MAX77759_CHGR_MODE_OTG_BOOST_ON);
+}
+
+static int disable_chgin_otg(struct regulator_dev *rdev)
+{
+ struct max77759_charger *chg = rdev_get_drvdata(rdev);
+
+ return charger_set_mode(chg, MAX77759_CHGR_MODE_OFF);
+}
+
+static int chgin_otg_status(struct regulator_dev *rdev)
+{
+ struct max77759_charger *chg = rdev_get_drvdata(rdev);
+
+ guard(mutex)(&chg->lock);
+
+ return chg->mode == MAX77759_CHGR_MODE_OTG_BOOST_ON;
+}
+
+static const struct regulator_ops chgin_otg_reg_ops = {
+ .enable = enable_chgin_otg,
+ .disable = disable_chgin_otg,
+ .is_enabled = chgin_otg_status,
+};
+
+static const struct regulator_desc chgin_otg_reg_desc = {
+ .name = "chgin-otg",
+ .of_match = of_match_ptr("chgin-otg-regulator"),
+ .owner = THIS_MODULE,
+ .ops = &chgin_otg_reg_ops,
+ .fixed_uV = 5000000,
+ .n_voltages = 1,
+};
+
+static irqreturn_t irq_handler(int irq, void *data)
+{
+ struct max77759_charger *chg = data;
+
+ power_supply_changed(chg->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bat_oilo_irq_handler(int irq, void *data)
+{
+ struct max77759_charger *chg = data;
+
+ dev_warn_ratelimited(chg->dev,
+ "Battery over-current threshold crossed\n");
+
+ return irq_handler(irq, data);
+}
+
+static int max77759_init_irqhandler(struct max77759_charger *chg)
+{
+ struct device *dev = chg->dev;
+ irq_handler_t thread_fn;
+ char *name;
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(chgr_irqs_str); i++) {
+ ret = platform_get_irq_byname(to_platform_device(dev),
+ chgr_irqs_str[i]);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to get irq resource for %s\n",
+ chgr_irqs_str[i]);
+
+ chg->irqs[i] = ret;
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", dev_name(dev),
+ chgr_irqs_str[i]);
+ if (!name)
+ return dev_err_probe(dev, -ENOMEM,
+ "Failed to allocate space for irqname: %s\n",
+ chgr_irqs_str[i]);
+
+ if (i == BAT_OILO)
+ thread_fn = bat_oilo_irq_handler;
+ else
+ thread_fn = irq_handler;
+
+ ret = devm_request_threaded_irq(dev, chg->irqs[i], NULL,
+ thread_fn, 0, name, chg);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Unable to register irq handler for %s\n",
+ chgr_irqs_str[i]);
+ }
+
+ return 0;
+}
+
+static int max77759_charger_init(struct max77759_charger *chg)
+{
+ struct power_supply_battery_info *info;
+ u32 regval, fast_chg_curr, fv;
+ int ret;
+
+ ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, &regval);
+ if (ret)
+ return ret;
+
+ chg->mode = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_00_MODE, regval);
+ ret = charger_set_mode(chg, MAX77759_CHGR_MODE_OFF);
+ if (ret)
+ return ret;
+
+ if (power_supply_get_battery_info(chg->psy, &info)) {
+ fv = CHG_FV_DEFAULT_MV;
+ fast_chg_curr = CHG_CC_DEFAULT_UA;
+ } else {
+ fv = info->constant_charge_voltage_max_uv / 1000;
+ fast_chg_curr = info->constant_charge_current_max_ua;
+ }
+
+ ret = set_fast_charge_current_limit(chg, fast_chg_curr);
+ if (ret)
+ return ret;
+
+ ret = set_float_voltage_limit(chg, fv);
+ if (ret)
+ return ret;
+
+ ret = unlock_prot_regs(chg, true);
+ if (ret)
+ return ret;
+
+ /* Disable wireless charging input */
+ ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_12,
+ MAX77759_CHGR_REG_CHG_CNFG_12_WCINSEL, 0);
+ if (ret)
+ goto relock;
+
+ ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_18,
+ MAX77759_CHGR_REG_CHG_CNFG_18_WDTEN, 0);
+ if (ret)
+ goto relock;
+
+ return unlock_prot_regs(chg, false);
+
+relock:
+ (void)unlock_prot_regs(chg, false);
+ return ret;
+}
+
+static void psy_work_item(struct work_struct *work)
+{
+ struct max77759_charger *chg =
+ container_of(work, struct max77759_charger, psy_work.work);
+ union power_supply_propval current_limit, online;
+ int ret;
+
+ ret = power_supply_get_property(chg->tcpm_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ &current_limit);
+ if (ret) {
+ dev_err(chg->dev,
+ "Failed to get CURRENT_MAX psy property, ret=%d\n",
+ ret);
+ goto err;
+ }
+
+ ret = power_supply_get_property(chg->tcpm_psy, POWER_SUPPLY_PROP_ONLINE,
+ &online);
+ if (ret) {
+ dev_err(chg->dev,
+ "Failed to get ONLINE psy property, ret=%d\n",
+ ret);
+ goto err;
+ }
+
+ if (online.intval && current_limit.intval) {
+ ret = set_input_current_limit(chg, current_limit.intval);
+ if (ret) {
+ dev_err(chg->dev,
+ "Unable to set current limit, ret=%d\n", ret);
+ goto err;
+ }
+
+ charger_set_mode(chg, MAX77759_CHGR_MODE_CHG_BUCK_ON);
+ } else {
+ charger_set_mode(chg, MAX77759_CHGR_MODE_OFF);
+ }
+
+ scoped_guard(mutex, &chg->retry_lock) {
+ if (chg->psy_work_retry_cnt)
+ dev_dbg(chg->dev,
+ "chg psy_work succeeded after %u tries\n",
+ chg->psy_work_retry_cnt);
+ chg->psy_work_retry_cnt = 0;
+ }
+
+ return;
+
+err:
+ charger_set_mode(chg, MAX77759_CHGR_MODE_OFF);
+ scoped_guard(mutex, &chg->retry_lock) {
+ if (chg->psy_work_retry_cnt >= MAX_NUM_RETRIES) {
+ dev_err(chg->dev, "chg psy work failed, giving up\n");
+ return;
+ }
+
+ ++chg->psy_work_retry_cnt;
+ dev_dbg(chg->dev, "Retrying %u/%u chg psy_work\n",
+ chg->psy_work_retry_cnt, MAX_NUM_RETRIES);
+ schedule_delayed_work(&chg->psy_work,
+ msecs_to_jiffies(PSY_WORK_RETRY_DELAY_MS));
+ }
+}
+
+static int psy_changed(struct notifier_block *nb, unsigned long evt, void *data)
+{
+ struct max77759_charger *chg = container_of(nb, struct max77759_charger,
+ nb);
+ static const char *psy_name = "tcpm-source";
+ struct power_supply *psy = data;
+
+ if (!strnstr(psy->desc->name, psy_name, strlen(psy_name)) ||
+ evt != PSY_EVENT_PROP_CHANGED)
+ return NOTIFY_OK;
+
+ chg->tcpm_psy = psy;
+ scoped_guard(mutex, &chg->retry_lock)
+ chg->psy_work_retry_cnt = 0;
+
+ schedule_delayed_work(&chg->psy_work, 0);
+
+ return NOTIFY_OK;
+}
+
+static void max_tcpci_unregister_psy_notifier(void *nb)
+{
+ power_supply_unreg_notifier(nb);
+}
+
+static int max77759_charger_probe(struct platform_device *pdev)
+{
+ struct regulator_config chgin_otg_reg_cfg;
+ struct power_supply_config psy_cfg;
+ struct device *dev = &pdev->dev;
+ struct max77759_charger *chg;
+ int ret;
+
+ device_set_of_node_from_dev(dev, dev->parent);
+ chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, chg);
+ chg->dev = dev;
+ chg->regmap = dev_get_regmap(dev->parent, "charger");
+ if (!chg->regmap)
+ return dev_err_probe(dev, -ENODEV, "Missing regmap\n");
+
+ ret = devm_mutex_init(dev, &chg->lock);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to initialize lock\n");
+
+ ret = devm_mutex_init(dev, &chg->retry_lock);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to initialize retry_lock\n");
+
+ psy_cfg.fwnode = dev_fwnode(dev);
+ psy_cfg.drv_data = chg;
+ chg->psy = devm_power_supply_register(dev, &max77759_charger_desc,
+ &psy_cfg);
+ if (IS_ERR(chg->psy))
+ return dev_err_probe(dev, PTR_ERR(chg->psy),
+ "Failed to register psy\n");
+
+ ret = max77759_charger_init(chg);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to initialize max77759 charger\n");
+
+ chgin_otg_reg_cfg.dev = dev;
+ chgin_otg_reg_cfg.driver_data = chg;
+ chgin_otg_reg_cfg.of_node = dev_of_node(dev);
+ chg->chgin_otg_rdev = devm_regulator_register(dev, &chgin_otg_reg_desc,
+ &chgin_otg_reg_cfg);
+ if (IS_ERR(chg->chgin_otg_rdev))
+ return dev_err_probe(dev, PTR_ERR(chg->chgin_otg_rdev),
+ "Failed to register chgin otg regulator\n");
+
+ ret = devm_delayed_work_autocancel(dev, &chg->psy_work, psy_work_item);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to initialize psy work\n");
+
+ chg->nb.notifier_call = psy_changed;
+ ret = power_supply_reg_notifier(&chg->nb);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Unable to register psy notifier\n");
+
+ ret = devm_add_action_or_reset(dev, max_tcpci_unregister_psy_notifier,
+ &chg->nb);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to add devm action to unregister psy notifier\n");
+
+ return max77759_init_irqhandler(chg);
+}
+
+static const struct platform_device_id max77759_charger_id[] = {
+ { .name = "max77759-charger", },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, max77759_charger_id);
+
+static struct platform_driver max77759_charger_driver = {
+ .driver = {
+ .name = "max77759-charger",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = max77759_charger_probe,
+ .id_table = max77759_charger_id,
+};
+module_platform_driver(max77759_charger_driver);
+
+MODULE_AUTHOR("Amit Sunil Dhamne <amitsd@google.com>");
+MODULE_DESCRIPTION("Maxim MAX77759 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/max77976_charger.c b/drivers/power/supply/max77976_charger.c
index e6fe68cebc32..3d6ff4005533 100644
--- a/drivers/power/supply/max77976_charger.c
+++ b/drivers/power/supply/max77976_charger.c
@@ -292,10 +292,10 @@ static int max77976_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_ONLINE:
err = max77976_get_online(chg, &val->intval);
break;
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
val->intval = MAX77976_CHG_CC_MAX;
break;
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
err = max77976_get_integer(chg, CHG_CC,
MAX77976_CHG_CC_MIN,
MAX77976_CHG_CC_MAX,
@@ -330,7 +330,7 @@ static int max77976_set_property(struct power_supply *psy,
int err = 0;
switch (psp) {
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
err = max77976_set_integer(chg, CHG_CC,
MAX77976_CHG_CC_MIN,
MAX77976_CHG_CC_MAX,
@@ -355,7 +355,7 @@ static int max77976_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return true;
default:
@@ -368,8 +368,8 @@ static enum power_supply_property max77976_psy_props[] = {
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
- POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
- POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
diff --git a/drivers/power/supply/max8903_charger.c b/drivers/power/supply/max8903_charger.c
index e65d0141f260..45fbaad6c647 100644
--- a/drivers/power/supply/max8903_charger.c
+++ b/drivers/power/supply/max8903_charger.c
@@ -349,7 +349,7 @@ static int max8903_probe(struct platform_device *pdev)
data->psy_desc.properties = max8903_charger_props;
data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props);
- psy_cfg.of_node = dev->of_node;
+ psy_cfg.fwnode = dev_fwnode(dev);
psy_cfg.drv_data = data;
data->psy = devm_power_supply_register(dev, &data->psy_desc, &psy_cfg);
diff --git a/drivers/power/supply/max8971_charger.c b/drivers/power/supply/max8971_charger.c
new file mode 100644
index 000000000000..26416d26f235
--- /dev/null
+++ b/drivers/power/supply/max8971_charger.c
@@ -0,0 +1,752 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/devm-helpers.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/extcon.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of_graph.h>
+#include <linux/property.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#define MAX8971_REG_CHGINT 0x0f
+#define MAX8971_REG_CHG_RST BIT(0)
+#define MAX8971_REG_CHGINT_MASK 0x01
+#define MAX8971_AICL_MASK BIT(7)
+#define MAX8971_REG_CHG_STAT 0x02
+#define MAX8971_CHG_MASK BIT(3)
+#define MAX8971_REG_DETAILS1 0x03
+#define MAX8971_REG_DETAILS2 0x04
+#define MAX8971_REG_CHGCNTL1 0x05
+#define MAX8971_REG_FCHGCRNT 0x06
+#define MAX8971_REG_DCCRNT 0x07
+#define MAX8971_CHGRSTRT_MASK BIT(6)
+#define MAX8971_REG_TOPOFF 0x08
+#define MAX8971_REG_TEMPREG 0x09
+#define MAX8971_REG_PROTCMD 0x0a
+#define MAX8971_CHGPROT_LOCKED 0x00
+#define MAX8971_CHGPROT_UNLOCKED 0x03
+
+#define MAX8971_FCHGT_DEFAULT 2
+#define MAX8971_TOPOFFT_DEFAULT 3
+
+static const char *max8971_manufacturer = "Maxim Integrated";
+static const char *max8971_model = "MAX8971";
+
+enum max8971_charging_state {
+ MAX8971_CHARGING_DEAD_BATTERY,
+ MAX8971_CHARGING_PREQUALIFICATION,
+ MAX8971_CHARGING_FAST_CONST_CURRENT,
+ MAX8971_CHARGING_FAST_CONST_VOLTAGE,
+ MAX8971_CHARGING_TOP_OFF,
+ MAX8971_CHARGING_DONE,
+ MAX8971_CHARGING_TIMER_FAULT,
+ MAX8971_CHARGING_SUSPENDED_THERMAL,
+ MAX8971_CHARGING_OFF,
+ MAX8971_CHARGING_THERMAL_LOOP,
+};
+
+enum max8971_health_state {
+ MAX8971_HEALTH_UNKNOWN,
+ MAX8971_HEALTH_COLD,
+ MAX8971_HEALTH_COOL,
+ MAX8971_HEALTH_WARM,
+ MAX8971_HEALTH_HOT,
+ MAX8971_HEALTH_OVERHEAT,
+};
+
+/* Fast-Charge current limit, 250..1550 mA, 50 mA steps */
+#define MAX8971_CHG_CC_STEP 50000U
+#define MAX8971_CHG_CC_MIN 250000U
+#define MAX8971_CHG_CC_MAX 1550000U
+
+/* Input current limit, 250..1500 mA, 25 mA steps */
+#define MAX8971_DCILMT_STEP 25000U
+#define MAX8971_DCILMT_MIN 250000U
+#define MAX8971_DCILMT_MAX 1500000U
+
+enum max8971_field_idx {
+ THM_DTLS, /* DETAILS1 */
+ BAT_DTLS, CHG_DTLS, /* DETAILS2 */
+ CHG_CC, FCHG_T, /* FCHGCRNT */
+ DCI_LMT, /* DCCRNT */
+ TOPOFF_T, TOPOFF_S, /* TOPOFF */
+ CPROT, /* PROTCMD */
+ MAX8971_N_REGMAP_FIELDS
+};
+
+static const struct reg_field max8971_reg_field[MAX8971_N_REGMAP_FIELDS] = {
+ [THM_DTLS] = REG_FIELD(MAX8971_REG_DETAILS1, 0, 2),
+ [BAT_DTLS] = REG_FIELD(MAX8971_REG_DETAILS2, 4, 5),
+ [CHG_DTLS] = REG_FIELD(MAX8971_REG_DETAILS2, 0, 3),
+ [CHG_CC] = REG_FIELD(MAX8971_REG_FCHGCRNT, 0, 4),
+ [FCHG_T] = REG_FIELD(MAX8971_REG_FCHGCRNT, 5, 7),
+ [DCI_LMT] = REG_FIELD(MAX8971_REG_DCCRNT, 0, 5),
+ [TOPOFF_T] = REG_FIELD(MAX8971_REG_TOPOFF, 5, 7),
+ [TOPOFF_S] = REG_FIELD(MAX8971_REG_TOPOFF, 2, 3),
+ [CPROT] = REG_FIELD(MAX8971_REG_PROTCMD, 2, 3),
+};
+
+static const struct regmap_config max8971_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX8971_REG_CHGINT,
+};
+
+struct max8971_data {
+ struct device *dev;
+ struct power_supply *psy_mains;
+
+ struct extcon_dev *edev;
+ struct notifier_block extcon_nb;
+ struct delayed_work extcon_work;
+
+ struct regmap *regmap;
+ struct regmap_field *rfield[MAX8971_N_REGMAP_FIELDS];
+
+ enum power_supply_usb_type usb_type;
+
+ u32 fchgt;
+ u32 tofft;
+ u32 toffs;
+
+ bool present;
+};
+
+static int max8971_get_status(struct max8971_data *priv, int *val)
+{
+ u32 regval;
+ int err;
+
+ err = regmap_field_read(priv->rfield[CHG_DTLS], &regval);
+ 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], &regval);
+ 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], &regval);
+ 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, &regval);
+ 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], &regval);
+ 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], &regval);
+ 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], &regval);
+ 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], &regval);
+ if (err)
+ return err;
+
+ /* 10 min intervals */
+ regval *= 10;
+
+ return sysfs_emit(buf, "%u\n", regval);
+}
+
+static ssize_t top_off_timer_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct max8971_data *priv = power_supply_get_drvdata(psy);
+ unsigned long minutes;
+ int err;
+
+ err = kstrtoul(buf, 10, &minutes);
+ if (err)
+ return err;
+
+ if (minutes > 70)
+ return -EINVAL;
+
+ priv->tofft = minutes / 10;
+
+ max8971_update_config(priv);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(fast_charge_timer);
+static DEVICE_ATTR_RW(top_off_threshold_current);
+static DEVICE_ATTR_RW(top_off_timer);
+
+static struct attribute *max8971_attrs[] = {
+ &dev_attr_fast_charge_timer.attr,
+ &dev_attr_top_off_threshold_current.attr,
+ &dev_attr_top_off_timer.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(max8971);
+
+static void max8971_extcon_evt_worker(struct work_struct *work)
+{
+ struct max8971_data *priv =
+ container_of(work, struct max8971_data, extcon_work.work);
+ struct device *dev = priv->dev;
+ struct extcon_dev *edev = priv->edev;
+ u32 chgcc, dcilmt;
+
+ if (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0) {
+ dev_dbg(dev, "USB SDP charger is connected\n");
+ priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
+ chgcc = 500000;
+ dcilmt = 500000;
+ } else if (extcon_get_state(edev, EXTCON_USB) > 0) {
+ dev_dbg(dev, "USB charger is connected\n");
+ priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
+ chgcc = 500000;
+ dcilmt = 500000;
+ } else if (extcon_get_state(edev, EXTCON_DISP_MHL) > 0) {
+ dev_dbg(dev, "MHL plug is connected\n");
+ priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
+ chgcc = 500000;
+ dcilmt = 500000;
+ } else if (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0) {
+ dev_dbg(dev, "USB DCP charger is connected\n");
+ priv->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
+ chgcc = 900000;
+ dcilmt = 1200000;
+ } else if (extcon_get_state(edev, EXTCON_CHG_USB_FAST) > 0) {
+ dev_dbg(dev, "USB FAST charger is connected\n");
+ priv->usb_type = POWER_SUPPLY_USB_TYPE_ACA;
+ chgcc = 900000;
+ dcilmt = 1200000;
+ } else if (extcon_get_state(edev, EXTCON_CHG_USB_SLOW) > 0) {
+ dev_dbg(dev, "USB SLOW charger is connected\n");
+ priv->usb_type = POWER_SUPPLY_USB_TYPE_ACA;
+ chgcc = 900000;
+ dcilmt = 1200000;
+ } else if (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0) {
+ dev_dbg(dev, "USB CDP charger is connected\n");
+ priv->usb_type = POWER_SUPPLY_USB_TYPE_CDP;
+ chgcc = 900000;
+ dcilmt = 1200000;
+ } else {
+ dev_dbg(dev, "USB state is unknown\n");
+ priv->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+ return;
+ }
+
+ regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_UNLOCKED);
+
+ max8971_set_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX,
+ MAX8971_CHG_CC_STEP, chgcc);
+ max8971_set_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX,
+ MAX8971_DCILMT_STEP, dcilmt);
+
+ regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_LOCKED);
+}
+
+static int extcon_get_charger_type(struct notifier_block *nb,
+ unsigned long state, void *data)
+{
+ struct max8971_data *priv =
+ container_of(nb, struct max8971_data, extcon_nb);
+ schedule_delayed_work(&priv->extcon_work, 0);
+
+ return NOTIFY_OK;
+}
+
+static irqreturn_t max8971_interrupt(int irq, void *dev_id)
+{
+ struct max8971_data *priv = dev_id;
+ struct device *dev = priv->dev;
+ int err, state;
+
+ err = regmap_read(priv->regmap, MAX8971_REG_CHGINT, &state);
+ if (err)
+ dev_err(dev, "interrupt reg read failed %d\n", err);
+
+ err = regmap_write_bits(priv->regmap, MAX8971_REG_CHGINT_MASK,
+ MAX8971_AICL_MASK, MAX8971_AICL_MASK);
+ if (err)
+ dev_err(dev, "failed to mask IRQ\n");
+
+ /* set presence prop */
+ priv->present = state & MAX8971_REG_CHG_RST;
+
+ /* on every plug chip resets to default */
+ if (priv->present)
+ max8971_update_config(priv);
+
+ /* update supply status */
+ power_supply_changed(priv->psy_mains);
+
+ return IRQ_HANDLED;
+}
+
+static int max8971_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct max8971_data *priv;
+ struct device_node *extcon;
+ struct power_supply_config cfg = { };
+ int err, i;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+
+ i2c_set_clientdata(client, priv);
+
+ priv->regmap = devm_regmap_init_i2c(client, &max8971_regmap_config);
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap), "cannot allocate regmap\n");
+
+ for (i = 0; i < MAX8971_N_REGMAP_FIELDS; i++) {
+ priv->rfield[i] = devm_regmap_field_alloc(dev, priv->regmap, max8971_reg_field[i]);
+ if (IS_ERR(priv->rfield[i]))
+ return dev_err_probe(dev, PTR_ERR(priv->rfield[i]),
+ "cannot allocate regmap field\n");
+ }
+
+ cfg.attr_grp = max8971_groups;
+ cfg.drv_data = priv;
+ cfg.fwnode = dev_fwnode(dev);
+
+ priv->psy_mains = devm_power_supply_register(dev, &max8971_charger_desc, &cfg);
+ if (IS_ERR(priv->psy_mains))
+ return dev_err_probe(dev, PTR_ERR(priv->psy_mains),
+ "failed to register mains supply\n");
+
+ err = regmap_write_bits(priv->regmap, MAX8971_REG_CHGINT_MASK, MAX8971_AICL_MASK,
+ MAX8971_AICL_MASK);
+ if (err)
+ return dev_err_probe(dev, err, "failed to mask IRQ\n");
+
+ err = devm_request_threaded_irq(dev, client->irq, NULL, &max8971_interrupt,
+ IRQF_ONESHOT | IRQF_SHARED, client->name, priv);
+ if (err)
+ return dev_err_probe(dev, err, "failed to register IRQ %d\n", client->irq);
+
+ extcon = of_graph_get_remote_node(dev->of_node, -1, -1);
+ if (!extcon)
+ return 0;
+
+ priv->edev = extcon_find_edev_by_node(extcon);
+ of_node_put(extcon);
+ if (IS_ERR(priv->edev))
+ return dev_err_probe(dev, PTR_ERR(priv->edev), "failed to find extcon\n");
+
+ err = devm_delayed_work_autocancel(dev, &priv->extcon_work,
+ max8971_extcon_evt_worker);
+ if (err)
+ return dev_err_probe(dev, err, "failed to add extcon evt stop action\n");
+
+ priv->extcon_nb.notifier_call = extcon_get_charger_type;
+
+ err = devm_extcon_register_notifier_all(dev, priv->edev, &priv->extcon_nb);
+ if (err)
+ return dev_err_probe(dev, err, "failed to register notifier\n");
+
+ /* Initial configuration work with 1 sec delay */
+ schedule_delayed_work(&priv->extcon_work, msecs_to_jiffies(1000));
+
+ return 0;
+}
+
+static int __maybe_unused max8971_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct max8971_data *priv = i2c_get_clientdata(client);
+
+ irq_wake_thread(client->irq, priv);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(max8971_pm_ops, NULL, max8971_resume);
+
+static const struct of_device_id max8971_match_ids[] = {
+ { .compatible = "maxim,max8971" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, max8971_match_ids);
+
+static const struct i2c_device_id max8971_i2c_id[] = {
+ { "max8971" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max8971_i2c_id);
+
+static struct i2c_driver max8971_driver = {
+ .driver = {
+ .name = "max8971-charger",
+ .of_match_table = max8971_match_ids,
+ .pm = &max8971_pm_ops,
+ },
+ .probe = max8971_probe,
+ .id_table = max8971_i2c_id,
+};
+module_i2c_driver(max8971_driver);
+
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("MAX8971 Charger Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/mm8013.c b/drivers/power/supply/mm8013.c
index 4adf2acc2779..93c50cff31bc 100644
--- a/drivers/power/supply/mm8013.c
+++ b/drivers/power/supply/mm8013.c
@@ -274,7 +274,7 @@ static int mm8013_probe(struct i2c_client *client)
return dev_err_probe(dev, ret, "MM8013 not found\n");
psy_cfg.drv_data = chip;
- psy_cfg.of_node = dev->of_node;
+ psy_cfg.fwnode = dev_fwnode(dev);
psy = devm_power_supply_register(dev, &mm8013_desc, &psy_cfg);
if (IS_ERR(psy))
diff --git a/drivers/power/supply/mt6360_charger.c b/drivers/power/supply/mt6360_charger.c
index e99e55148976..77747eb51667 100644
--- a/drivers/power/supply/mt6360_charger.c
+++ b/drivers/power/supply/mt6360_charger.c
@@ -810,7 +810,7 @@ static int mt6360_charger_probe(struct platform_device *pdev)
memcpy(&mci->psy_desc, &mt6360_charger_desc, sizeof(mci->psy_desc));
mci->psy_desc.name = dev_name(&pdev->dev);
charger_cfg.drv_data = mci;
- charger_cfg.of_node = pdev->dev.of_node;
+ charger_cfg.fwnode = dev_fwnode(&pdev->dev);
mci->psy = devm_power_supply_register(&pdev->dev,
&mci->psy_desc, &charger_cfg);
if (IS_ERR(mci->psy))
diff --git a/drivers/power/supply/mt6370-charger.c b/drivers/power/supply/mt6370-charger.c
index ad8793bf997e..916556baa854 100644
--- a/drivers/power/supply/mt6370-charger.c
+++ b/drivers/power/supply/mt6370-charger.c
@@ -752,7 +752,7 @@ static int mt6370_chg_init_psy(struct mt6370_priv *priv)
{
struct power_supply_config cfg = {
.drv_data = priv,
- .of_node = dev_of_node(priv->dev),
+ .fwnode = dev_fwnode(priv->dev),
};
priv->psy = devm_power_supply_register(priv->dev, &mt6370_chg_psy_desc,
@@ -761,21 +761,6 @@ static int mt6370_chg_init_psy(struct mt6370_priv *priv)
return PTR_ERR_OR_ZERO(priv->psy);
}
-static void mt6370_chg_destroy_attach_lock(void *data)
-{
- struct mutex *attach_lock = data;
-
- mutex_destroy(attach_lock);
-}
-
-static void mt6370_chg_destroy_wq(void *data)
-{
- struct workqueue_struct *wq = data;
-
- flush_workqueue(wq);
- destroy_workqueue(wq);
-}
-
static irqreturn_t mt6370_attach_i_handler(int irq, void *data)
{
struct mt6370_priv *priv = data;
@@ -895,22 +880,15 @@ static int mt6370_chg_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "Failed to init psy\n");
- mutex_init(&priv->attach_lock);
- ret = devm_add_action_or_reset(dev, mt6370_chg_destroy_attach_lock,
- &priv->attach_lock);
+ ret = devm_mutex_init(dev, &priv->attach_lock);
if (ret)
- return dev_err_probe(dev, ret, "Failed to init attach lock\n");
+ return ret;
priv->attach = MT6370_ATTACH_STAT_DETACH;
- priv->wq = create_singlethread_workqueue(dev_name(priv->dev));
+ priv->wq = devm_alloc_ordered_workqueue(dev, "%s", 0, dev_name(priv->dev));
if (!priv->wq)
- return dev_err_probe(dev, -ENOMEM,
- "Failed to create workqueue\n");
-
- ret = devm_add_action_or_reset(dev, mt6370_chg_destroy_wq, priv->wq);
- if (ret)
- return dev_err_probe(dev, ret, "Failed to init wq\n");
+ return -ENOMEM;
ret = devm_work_autocancel(dev, &priv->bc12_work, mt6370_chg_bc12_work_func);
if (ret)
diff --git a/drivers/power/supply/olpc_battery.c b/drivers/power/supply/olpc_battery.c
index 849f63e89ba0..202c4fa9b903 100644
--- a/drivers/power/supply/olpc_battery.c
+++ b/drivers/power/supply/olpc_battery.c
@@ -553,7 +553,7 @@ static const struct bin_attribute olpc_bat_eeprom = {
.mode = S_IRUGO,
},
.size = EEPROM_SIZE,
- .read_new = olpc_bat_eeprom_read,
+ .read = olpc_bat_eeprom_read,
};
/* Allow userspace to see the specific error value pulled from the EC */
@@ -591,7 +591,7 @@ static const struct bin_attribute *const olpc_bat_sysfs_bin_attrs[] = {
static const struct attribute_group olpc_bat_sysfs_group = {
.attrs = olpc_bat_sysfs_attrs,
- .bin_attrs_new = olpc_bat_sysfs_bin_attrs,
+ .bin_attrs = olpc_bat_sysfs_bin_attrs,
};
static const struct attribute_group *olpc_bat_sysfs_groups[] = {
@@ -674,7 +674,7 @@ static int olpc_battery_probe(struct platform_device *pdev)
/* Ignore the status. It doesn't actually matter */
- ac_psy_cfg.of_node = pdev->dev.of_node;
+ ac_psy_cfg.fwnode = dev_fwnode(&pdev->dev);
ac_psy_cfg.drv_data = data;
data->olpc_ac = devm_power_supply_register(&pdev->dev, &olpc_ac_desc,
@@ -692,7 +692,7 @@ static int olpc_battery_probe(struct platform_device *pdev)
olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo1_bat_props);
}
- bat_psy_cfg.of_node = pdev->dev.of_node;
+ bat_psy_cfg.fwnode = dev_fwnode(&pdev->dev);
bat_psy_cfg.drv_data = data;
bat_psy_cfg.attr_grp = olpc_bat_sysfs_groups;
diff --git a/drivers/power/supply/pcf50633-charger.c b/drivers/power/supply/pcf50633-charger.c
deleted file mode 100644
index 0136bc87b105..000000000000
--- a/drivers/power/supply/pcf50633-charger.c
+++ /dev/null
@@ -1,466 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/* NXP PCF50633 Main Battery Charger Driver
- *
- * (C) 2006-2008 by Openmoko, Inc.
- * Author: Balaji Rao <balajirrao@openmoko.org>
- * All rights reserved.
- *
- * Broken down from monstrous PCF50633 driver mainly by
- * Harald Welte, Andy Green and Werner Almesberger
- */
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-#include <linux/init.h>
-#include <linux/types.h>
-#include <linux/device.h>
-#include <linux/sysfs.h>
-#include <linux/platform_device.h>
-#include <linux/power_supply.h>
-
-#include <linux/mfd/pcf50633/core.h>
-#include <linux/mfd/pcf50633/mbc.h>
-
-struct pcf50633_mbc {
- struct pcf50633 *pcf;
-
- int adapter_online;
- int usb_online;
-
- struct power_supply *usb;
- struct power_supply *adapter;
- struct power_supply *ac;
-};
-
-int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma)
-{
- struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
- int ret = 0;
- u8 bits;
- u8 mbcs2, chgmod;
- unsigned int mbcc5;
-
- if (ma >= 1000) {
- bits = PCF50633_MBCC7_USB_1000mA;
- ma = 1000;
- } else if (ma >= 500) {
- bits = PCF50633_MBCC7_USB_500mA;
- ma = 500;
- } else if (ma >= 100) {
- bits = PCF50633_MBCC7_USB_100mA;
- ma = 100;
- } else {
- bits = PCF50633_MBCC7_USB_SUSPEND;
- ma = 0;
- }
-
- ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7,
- PCF50633_MBCC7_USB_MASK, bits);
- if (ret)
- dev_err(pcf->dev, "error setting usb curlim to %d mA\n", ma);
- else
- dev_info(pcf->dev, "usb curlim to %d mA\n", ma);
-
- /*
- * We limit the charging current to be the USB current limit.
- * The reason is that on pcf50633, when it enters PMU Standby mode,
- * which it does when the device goes "off", the USB current limit
- * reverts to the variant default. In at least one common case, that
- * default is 500mA. By setting the charging current to be the same
- * as the USB limit we set here before PMU standby, we enforce it only
- * using the correct amount of current even when the USB current limit
- * gets reset to the wrong thing
- */
-
- if (mbc->pcf->pdata->charger_reference_current_ma) {
- mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma;
- if (mbcc5 > 255)
- mbcc5 = 255;
- pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5);
- }
-
- mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2);
- chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK);
-
- /* If chgmod == BATFULL, setting chgena has no effect.
- * Datasheet says we need to set resume instead but when autoresume is
- * used resume doesn't work. Clear and set chgena instead.
- */
- if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL)
- pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1,
- PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA);
- else {
- pcf50633_reg_clear_bits(pcf, PCF50633_REG_MBCC1,
- PCF50633_MBCC1_CHGENA);
- pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1,
- PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA);
- }
-
- power_supply_changed(mbc->usb);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(pcf50633_mbc_usb_curlim_set);
-
-int pcf50633_mbc_get_status(struct pcf50633 *pcf)
-{
- struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
- int status = 0;
- u8 chgmod;
-
- if (!mbc)
- return 0;
-
- chgmod = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2)
- & PCF50633_MBCS2_MBC_MASK;
-
- if (mbc->usb_online)
- status |= PCF50633_MBC_USB_ONLINE;
- if (chgmod == PCF50633_MBCS2_MBC_USB_PRE ||
- chgmod == PCF50633_MBCS2_MBC_USB_PRE_WAIT ||
- chgmod == PCF50633_MBCS2_MBC_USB_FAST ||
- chgmod == PCF50633_MBCS2_MBC_USB_FAST_WAIT)
- status |= PCF50633_MBC_USB_ACTIVE;
- if (mbc->adapter_online)
- status |= PCF50633_MBC_ADAPTER_ONLINE;
- if (chgmod == PCF50633_MBCS2_MBC_ADP_PRE ||
- chgmod == PCF50633_MBCS2_MBC_ADP_PRE_WAIT ||
- chgmod == PCF50633_MBCS2_MBC_ADP_FAST ||
- chgmod == PCF50633_MBCS2_MBC_ADP_FAST_WAIT)
- status |= PCF50633_MBC_ADAPTER_ACTIVE;
-
- return status;
-}
-EXPORT_SYMBOL_GPL(pcf50633_mbc_get_status);
-
-int pcf50633_mbc_get_usb_online_status(struct pcf50633 *pcf)
-{
- struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
-
- if (!mbc)
- return 0;
-
- return mbc->usb_online;
-}
-EXPORT_SYMBOL_GPL(pcf50633_mbc_get_usb_online_status);
-
-static ssize_t
-show_chgmode(struct device *dev, struct device_attribute *attr, char *buf)
-{
- struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
-
- u8 mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2);
- u8 chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK);
-
- return sysfs_emit(buf, "%d\n", chgmod);
-}
-static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, NULL);
-
-static ssize_t
-show_usblim(struct device *dev, struct device_attribute *attr, char *buf)
-{
- struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
- u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) &
- PCF50633_MBCC7_USB_MASK;
- unsigned int ma;
-
- if (usblim == PCF50633_MBCC7_USB_1000mA)
- ma = 1000;
- else if (usblim == PCF50633_MBCC7_USB_500mA)
- ma = 500;
- else if (usblim == PCF50633_MBCC7_USB_100mA)
- ma = 100;
- else
- ma = 0;
-
- return sysfs_emit(buf, "%u\n", ma);
-}
-
-static ssize_t set_usblim(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t count)
-{
- struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
- unsigned long ma;
- int ret;
-
- ret = kstrtoul(buf, 10, &ma);
- if (ret)
- return ret;
-
- pcf50633_mbc_usb_curlim_set(mbc->pcf, ma);
-
- return count;
-}
-
-static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim);
-
-static ssize_t
-show_chglim(struct device *dev, struct device_attribute *attr, char *buf)
-{
- struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
- u8 mbcc5 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC5);
- unsigned int ma;
-
- if (!mbc->pcf->pdata->charger_reference_current_ma)
- return -ENODEV;
-
- ma = (mbc->pcf->pdata->charger_reference_current_ma * mbcc5) >> 8;
-
- return sysfs_emit(buf, "%u\n", ma);
-}
-
-static ssize_t set_chglim(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t count)
-{
- struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
- unsigned long ma;
- unsigned int mbcc5;
- int ret;
-
- if (!mbc->pcf->pdata->charger_reference_current_ma)
- return -ENODEV;
-
- ret = kstrtoul(buf, 10, &ma);
- if (ret)
- return ret;
-
- mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma;
- if (mbcc5 > 255)
- mbcc5 = 255;
- pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5);
-
- return count;
-}
-
-/*
- * This attribute allows to change MBC charging limit on the fly
- * independently of usb current limit. It also gets set automatically every
- * time usb current limit is changed.
- */
-static DEVICE_ATTR(chg_curlim, S_IRUGO | S_IWUSR, show_chglim, set_chglim);
-
-static struct attribute *pcf50633_mbc_sysfs_attrs[] = {
- &dev_attr_chgmode.attr,
- &dev_attr_usb_curlim.attr,
- &dev_attr_chg_curlim.attr,
- NULL,
-};
-
-ATTRIBUTE_GROUPS(pcf50633_mbc_sysfs);
-
-static void
-pcf50633_mbc_irq_handler(int irq, void *data)
-{
- struct pcf50633_mbc *mbc = data;
-
- /* USB */
- if (irq == PCF50633_IRQ_USBINS) {
- mbc->usb_online = 1;
- } else if (irq == PCF50633_IRQ_USBREM) {
- mbc->usb_online = 0;
- pcf50633_mbc_usb_curlim_set(mbc->pcf, 0);
- }
-
- /* Adapter */
- if (irq == PCF50633_IRQ_ADPINS)
- mbc->adapter_online = 1;
- else if (irq == PCF50633_IRQ_ADPREM)
- mbc->adapter_online = 0;
-
- power_supply_changed(mbc->ac);
- power_supply_changed(mbc->usb);
- power_supply_changed(mbc->adapter);
-
- if (mbc->pcf->pdata->mbc_event_callback)
- mbc->pcf->pdata->mbc_event_callback(mbc->pcf, irq);
-}
-
-static int adapter_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
-{
- struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy);
- int ret = 0;
-
- switch (psp) {
- case POWER_SUPPLY_PROP_ONLINE:
- val->intval = mbc->adapter_online;
- break;
- default:
- ret = -EINVAL;
- break;
- }
- return ret;
-}
-
-static int usb_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
-{
- struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy);
- int ret = 0;
- u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) &
- PCF50633_MBCC7_USB_MASK;
-
- switch (psp) {
- case POWER_SUPPLY_PROP_ONLINE:
- val->intval = mbc->usb_online &&
- (usblim <= PCF50633_MBCC7_USB_500mA);
- break;
- default:
- ret = -EINVAL;
- break;
- }
- return ret;
-}
-
-static int ac_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
-{
- struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy);
- int ret = 0;
- u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) &
- PCF50633_MBCC7_USB_MASK;
-
- switch (psp) {
- case POWER_SUPPLY_PROP_ONLINE:
- val->intval = mbc->usb_online &&
- (usblim == PCF50633_MBCC7_USB_1000mA);
- break;
- default:
- ret = -EINVAL;
- break;
- }
- return ret;
-}
-
-static enum power_supply_property power_props[] = {
- POWER_SUPPLY_PROP_ONLINE,
-};
-
-static const u8 mbc_irq_handlers[] = {
- PCF50633_IRQ_ADPINS,
- PCF50633_IRQ_ADPREM,
- PCF50633_IRQ_USBINS,
- PCF50633_IRQ_USBREM,
- PCF50633_IRQ_BATFULL,
- PCF50633_IRQ_CHGHALT,
- PCF50633_IRQ_THLIMON,
- PCF50633_IRQ_THLIMOFF,
- PCF50633_IRQ_USBLIMON,
- PCF50633_IRQ_USBLIMOFF,
- PCF50633_IRQ_LOWSYS,
- PCF50633_IRQ_LOWBAT,
-};
-
-static const struct power_supply_desc pcf50633_mbc_adapter_desc = {
- .name = "adapter",
- .type = POWER_SUPPLY_TYPE_MAINS,
- .properties = power_props,
- .num_properties = ARRAY_SIZE(power_props),
- .get_property = &adapter_get_property,
-};
-
-static const struct power_supply_desc pcf50633_mbc_usb_desc = {
- .name = "usb",
- .type = POWER_SUPPLY_TYPE_USB,
- .properties = power_props,
- .num_properties = ARRAY_SIZE(power_props),
- .get_property = usb_get_property,
-};
-
-static const struct power_supply_desc pcf50633_mbc_ac_desc = {
- .name = "ac",
- .type = POWER_SUPPLY_TYPE_MAINS,
- .properties = power_props,
- .num_properties = ARRAY_SIZE(power_props),
- .get_property = ac_get_property,
-};
-
-static int pcf50633_mbc_probe(struct platform_device *pdev)
-{
- struct power_supply_config psy_cfg = {};
- struct power_supply_config usb_psy_cfg;
- struct pcf50633_mbc *mbc;
- int i;
- u8 mbcs1;
-
- mbc = devm_kzalloc(&pdev->dev, sizeof(*mbc), GFP_KERNEL);
- if (!mbc)
- return -ENOMEM;
-
- platform_set_drvdata(pdev, mbc);
- mbc->pcf = dev_to_pcf50633(pdev->dev.parent);
-
- /* Set up IRQ handlers */
- for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
- pcf50633_register_irq(mbc->pcf, mbc_irq_handlers[i],
- pcf50633_mbc_irq_handler, mbc);
-
- psy_cfg.supplied_to = mbc->pcf->pdata->batteries;
- psy_cfg.num_supplicants = mbc->pcf->pdata->num_batteries;
- psy_cfg.drv_data = mbc;
-
- /* Create power supplies */
- mbc->adapter = devm_power_supply_register(&pdev->dev,
- &pcf50633_mbc_adapter_desc,
- &psy_cfg);
- if (IS_ERR(mbc->adapter)) {
- dev_err(mbc->pcf->dev, "failed to register adapter\n");
- return PTR_ERR(mbc->adapter);
- }
-
- usb_psy_cfg = psy_cfg;
- usb_psy_cfg.attr_grp = pcf50633_mbc_sysfs_groups;
-
- mbc->usb = devm_power_supply_register(&pdev->dev,
- &pcf50633_mbc_usb_desc,
- &usb_psy_cfg);
- if (IS_ERR(mbc->usb)) {
- dev_err(mbc->pcf->dev, "failed to register usb\n");
- return PTR_ERR(mbc->usb);
- }
-
- mbc->ac = devm_power_supply_register(&pdev->dev,
- &pcf50633_mbc_ac_desc,
- &psy_cfg);
- if (IS_ERR(mbc->ac)) {
- dev_err(mbc->pcf->dev, "failed to register ac\n");
- return PTR_ERR(mbc->ac);
- }
-
- mbcs1 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS1);
- if (mbcs1 & PCF50633_MBCS1_USBPRES)
- pcf50633_mbc_irq_handler(PCF50633_IRQ_USBINS, mbc);
- if (mbcs1 & PCF50633_MBCS1_ADAPTPRES)
- pcf50633_mbc_irq_handler(PCF50633_IRQ_ADPINS, mbc);
-
- return 0;
-}
-
-static void pcf50633_mbc_remove(struct platform_device *pdev)
-{
- struct pcf50633_mbc *mbc = platform_get_drvdata(pdev);
- int i;
-
- /* Remove IRQ handlers */
- for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
- pcf50633_free_irq(mbc->pcf, mbc_irq_handlers[i]);
-}
-
-static struct platform_driver pcf50633_mbc_driver = {
- .driver = {
- .name = "pcf50633-mbc",
- },
- .probe = pcf50633_mbc_probe,
- .remove = pcf50633_mbc_remove,
-};
-
-module_platform_driver(pcf50633_mbc_driver);
-
-MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
-MODULE_DESCRIPTION("PCF50633 mbc driver");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:pcf50633-mbc");
diff --git a/drivers/power/supply/pf1550-charger.c b/drivers/power/supply/pf1550-charger.c
new file mode 100644
index 000000000000..a457862ef461
--- /dev/null
+++ b/drivers/power/supply/pf1550-charger.c
@@ -0,0 +1,641 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * charger driver for the PF1550
+ *
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Robin Gong <yibin.gong@freescale.com>
+ *
+ * Portions Copyright (c) 2025 Savoir-faire Linux Inc.
+ * Samuel Kayode <samuel.kayode@savoirfairelinux.com>
+ */
+
+#include <linux/devm-helpers.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/pf1550.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#define PF1550_DEFAULT_CONSTANT_VOLT 4200000
+#define PF1550_DEFAULT_MIN_SYSTEM_VOLT 3500000
+#define PF1550_DEFAULT_THERMAL_TEMP 95
+#define PF1550_CHARGER_IRQ_NR 5
+
+struct pf1550_charger {
+ struct device *dev;
+ const struct pf1550_ddata *pf1550;
+ struct power_supply *charger;
+ struct power_supply *battery;
+ struct delayed_work vbus_sense_work;
+ struct delayed_work chg_sense_work;
+ struct delayed_work bat_sense_work;
+ int virqs[PF1550_CHARGER_IRQ_NR];
+
+ u32 constant_volt;
+ u32 min_system_volt;
+ u32 thermal_regulation_temp;
+};
+
+static int pf1550_get_charger_state(struct regmap *regmap, int *val)
+{
+ unsigned int data;
+ int ret;
+
+ ret = regmap_read(regmap, PF1550_CHARG_REG_CHG_SNS, &data);
+ if (ret < 0)
+ return ret;
+
+ data &= PF1550_CHG_SNS_MASK;
+
+ switch (data) {
+ case PF1550_CHG_PRECHARGE:
+ case PF1550_CHG_CONSTANT_CURRENT:
+ case PF1550_CHG_CONSTANT_VOL:
+ case PF1550_CHG_EOC:
+ *val = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case PF1550_CHG_DONE:
+ *val = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case PF1550_CHG_TIMER_FAULT:
+ case PF1550_CHG_SUSPEND:
+ *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case PF1550_CHG_OFF_INV:
+ case PF1550_CHG_OFF_TEMP:
+ case PF1550_CHG_LINEAR_ONLY:
+ *val = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ default:
+ *val = POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static int pf1550_get_charge_type(struct regmap *regmap, int *val)
+{
+ unsigned int data;
+ int ret;
+
+ ret = regmap_read(regmap, PF1550_CHARG_REG_CHG_SNS, &data);
+ if (ret < 0)
+ return ret;
+
+ data &= PF1550_CHG_SNS_MASK;
+
+ switch (data) {
+ case PF1550_CHG_SNS_MASK:
+ *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case PF1550_CHG_CONSTANT_CURRENT:
+ case PF1550_CHG_CONSTANT_VOL:
+ case PF1550_CHG_EOC:
+ *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case PF1550_CHG_DONE:
+ case PF1550_CHG_TIMER_FAULT:
+ case PF1550_CHG_SUSPEND:
+ case PF1550_CHG_OFF_INV:
+ case PF1550_CHG_BAT_OVER:
+ case PF1550_CHG_OFF_TEMP:
+ case PF1550_CHG_LINEAR_ONLY:
+ *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ default:
+ *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ }
+
+ return 0;
+}
+
+/*
+ * Supported health statuses:
+ * - POWER_SUPPLY_HEALTH_DEAD
+ * - POWER_SUPPLY_HEALTH_GOOD
+ * - POWER_SUPPLY_HEALTH_OVERVOLTAGE
+ * - POWER_SUPPLY_HEALTH_UNKNOWN
+ */
+static int pf1550_get_battery_health(struct regmap *regmap, int *val)
+{
+ unsigned int data;
+ int ret;
+
+ ret = regmap_read(regmap, PF1550_CHARG_REG_BATT_SNS, &data);
+ if (ret < 0)
+ return ret;
+
+ data &= PF1550_BAT_SNS_MASK;
+
+ switch (data) {
+ case PF1550_BAT_NO_DETECT:
+ *val = POWER_SUPPLY_HEALTH_NO_BATTERY;
+ break;
+ case PF1550_BAT_NO_VBUS:
+ case PF1550_BAT_LOW_THAN_PRECHARG:
+ case PF1550_BAT_CHARG_FAIL:
+ case PF1550_BAT_HIGH_THAN_PRECHARG:
+ *val = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case PF1550_BAT_OVER_VOL:
+ *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ break;
+ default:
+ *val = POWER_SUPPLY_HEALTH_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static int pf1550_get_present(struct regmap *regmap, int *val)
+{
+ unsigned int data;
+ int ret;
+
+ ret = regmap_read(regmap, PF1550_CHARG_REG_BATT_SNS, &data);
+ if (ret < 0)
+ return ret;
+
+ data &= PF1550_BAT_SNS_MASK;
+ *val = (data == PF1550_BAT_NO_DETECT) ? 0 : 1;
+
+ return 0;
+}
+
+static int pf1550_get_online(struct regmap *regmap, int *val)
+{
+ unsigned int data;
+ int ret;
+
+ ret = regmap_read(regmap, PF1550_CHARG_REG_VBUS_SNS, &data);
+ if (ret < 0)
+ return ret;
+
+ *val = (data & PF1550_VBUS_VALID) ? 1 : 0;
+
+ return 0;
+}
+
+static void pf1550_chg_bat_work(struct work_struct *work)
+{
+ struct pf1550_charger *chg = container_of(to_delayed_work(work),
+ struct pf1550_charger,
+ bat_sense_work);
+ unsigned int data;
+
+ if (regmap_read(chg->pf1550->regmap, PF1550_CHARG_REG_BATT_SNS, &data)) {
+ dev_err(chg->dev, "Read BATT_SNS error.\n");
+ return;
+ }
+
+ switch (data & PF1550_BAT_SNS_MASK) {
+ case PF1550_BAT_NO_VBUS:
+ dev_dbg(chg->dev, "No valid VBUS input.\n");
+ break;
+ case PF1550_BAT_LOW_THAN_PRECHARG:
+ dev_dbg(chg->dev, "VBAT < VPRECHG.LB.\n");
+ break;
+ case PF1550_BAT_CHARG_FAIL:
+ dev_dbg(chg->dev, "Battery charging failed.\n");
+ break;
+ case PF1550_BAT_HIGH_THAN_PRECHARG:
+ dev_dbg(chg->dev, "VBAT > VPRECHG.LB.\n");
+ break;
+ case PF1550_BAT_OVER_VOL:
+ dev_dbg(chg->dev, "VBAT > VBATOV.\n");
+ break;
+ case PF1550_BAT_NO_DETECT:
+ dev_dbg(chg->dev, "Battery not detected.\n");
+ break;
+ default:
+ dev_err(chg->dev, "Unknown value read:%x\n",
+ data & PF1550_CHG_SNS_MASK);
+ }
+}
+
+static void pf1550_chg_chg_work(struct work_struct *work)
+{
+ struct pf1550_charger *chg = container_of(to_delayed_work(work),
+ struct pf1550_charger,
+ chg_sense_work);
+ unsigned int data;
+
+ if (regmap_read(chg->pf1550->regmap, PF1550_CHARG_REG_CHG_SNS, &data)) {
+ dev_err(chg->dev, "Read CHG_SNS error.\n");
+ return;
+ }
+
+ switch (data & PF1550_CHG_SNS_MASK) {
+ case PF1550_CHG_PRECHARGE:
+ dev_dbg(chg->dev, "In pre-charger mode.\n");
+ break;
+ case PF1550_CHG_CONSTANT_CURRENT:
+ dev_dbg(chg->dev, "In fast-charge constant current mode.\n");
+ break;
+ case PF1550_CHG_CONSTANT_VOL:
+ dev_dbg(chg->dev, "In fast-charge constant voltage mode.\n");
+ break;
+ case PF1550_CHG_EOC:
+ dev_dbg(chg->dev, "In EOC mode.\n");
+ break;
+ case PF1550_CHG_DONE:
+ dev_dbg(chg->dev, "In DONE mode.\n");
+ break;
+ case PF1550_CHG_TIMER_FAULT:
+ dev_info(chg->dev, "In timer fault mode.\n");
+ break;
+ case PF1550_CHG_SUSPEND:
+ dev_info(chg->dev, "In thermistor suspend mode.\n");
+ break;
+ case PF1550_CHG_OFF_INV:
+ dev_info(chg->dev, "Input invalid, charger off.\n");
+ break;
+ case PF1550_CHG_BAT_OVER:
+ dev_warn(chg->dev, "Battery over-voltage.\n");
+ break;
+ case PF1550_CHG_OFF_TEMP:
+ dev_info(chg->dev, "Temp high, charger off.\n");
+ break;
+ case PF1550_CHG_LINEAR_ONLY:
+ dev_dbg(chg->dev, "In Linear mode, not charging.\n");
+ break;
+ default:
+ dev_err(chg->dev, "Unknown value read:%x\n",
+ data & PF1550_CHG_SNS_MASK);
+ }
+}
+
+static void pf1550_chg_vbus_work(struct work_struct *work)
+{
+ struct pf1550_charger *chg = container_of(to_delayed_work(work),
+ struct pf1550_charger,
+ vbus_sense_work);
+ unsigned int data;
+
+ if (regmap_read(chg->pf1550->regmap, PF1550_CHARG_REG_VBUS_SNS, &data)) {
+ dev_err(chg->dev, "Read VBUS_SNS error.\n");
+ return;
+ }
+
+ if (data & PF1550_VBUS_UVLO) {
+ dev_dbg(chg->dev, "VBUS detached.\n");
+ power_supply_changed(chg->battery);
+ }
+ if (data & PF1550_VBUS_IN2SYS)
+ dev_dbg(chg->dev, "VBUS_IN2SYS_SNS.\n");
+ if (data & PF1550_VBUS_OVLO)
+ dev_dbg(chg->dev, "VBUS_OVLO_SNS.\n");
+ if (data & PF1550_VBUS_VALID) {
+ dev_dbg(chg->dev, "VBUS attached.\n");
+ power_supply_changed(chg->charger);
+ }
+}
+
+static irqreturn_t pf1550_charger_irq_handler(int irq, void *data)
+{
+ struct pf1550_charger *chg = data;
+ struct device *dev = chg->dev;
+ int i, irq_type = -1;
+
+ for (i = 0; i < PF1550_CHARGER_IRQ_NR; i++)
+ if (irq == chg->virqs[i])
+ irq_type = i;
+
+ switch (irq_type) {
+ case PF1550_CHARG_IRQ_BAT2SOCI:
+ dev_info(dev, "BAT to SYS Overcurrent interrupt.\n");
+ break;
+ case PF1550_CHARG_IRQ_BATI:
+ schedule_delayed_work(&chg->bat_sense_work,
+ msecs_to_jiffies(10));
+ break;
+ case PF1550_CHARG_IRQ_CHGI:
+ schedule_delayed_work(&chg->chg_sense_work,
+ msecs_to_jiffies(10));
+ break;
+ case PF1550_CHARG_IRQ_VBUSI:
+ schedule_delayed_work(&chg->vbus_sense_work,
+ msecs_to_jiffies(10));
+ break;
+ case PF1550_CHARG_IRQ_THMI:
+ dev_info(dev, "Thermal interrupt.\n");
+ break;
+ default:
+ dev_err(dev, "unknown interrupt occurred.\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+static enum power_supply_property pf1550_charger_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static enum power_supply_property pf1550_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static int pf1550_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pf1550_charger *chg = power_supply_get_drvdata(psy);
+ struct regmap *regmap = chg->pf1550->regmap;
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = pf1550_get_charger_state(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = pf1550_get_charge_type(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = pf1550_get_battery_health(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = pf1550_get_present(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = pf1550_get_online(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = "PF1550";
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "NXP";
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct power_supply_desc pf1550_charger_desc = {
+ .name = "pf1550-charger",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = pf1550_charger_props,
+ .num_properties = ARRAY_SIZE(pf1550_charger_props),
+ .get_property = pf1550_charger_get_property,
+};
+
+static const struct power_supply_desc pf1550_battery_desc = {
+ .name = "pf1550-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = pf1550_battery_props,
+ .num_properties = ARRAY_SIZE(pf1550_battery_props),
+ .get_property = pf1550_charger_get_property,
+};
+
+static int pf1550_set_constant_volt(struct pf1550_charger *chg,
+ unsigned int uvolt)
+{
+ unsigned int data;
+
+ if (uvolt >= 3500000 && uvolt <= 4440000)
+ data = 8 + (uvolt - 3500000) / 20000;
+ else
+ return dev_err_probe(chg->dev, -EINVAL,
+ "Wrong value for constant voltage\n");
+
+ dev_dbg(chg->dev, "Charging constant voltage: %u (0x%x)\n", uvolt,
+ data);
+
+ return regmap_update_bits(chg->pf1550->regmap,
+ PF1550_CHARG_REG_BATT_REG,
+ PF1550_CHARG_REG_BATT_REG_CHGCV_MASK, data);
+}
+
+static int pf1550_set_min_system_volt(struct pf1550_charger *chg,
+ unsigned int uvolt)
+{
+ unsigned int data;
+
+ switch (uvolt) {
+ case 3500000:
+ data = 0x0;
+ break;
+ case 3700000:
+ data = 0x1;
+ break;
+ case 4300000:
+ data = 0x2;
+ break;
+ default:
+ return dev_err_probe(chg->dev, -EINVAL,
+ "Wrong value for minimum system voltage\n");
+ }
+
+ data <<= PF1550_CHARG_REG_BATT_REG_VMINSYS_SHIFT;
+
+ dev_dbg(chg->dev, "Minimum system regulation voltage: %u (0x%x)\n",
+ uvolt, data);
+
+ return regmap_update_bits(chg->pf1550->regmap,
+ PF1550_CHARG_REG_BATT_REG,
+ PF1550_CHARG_REG_BATT_REG_VMINSYS_MASK, data);
+}
+
+static int pf1550_set_thermal_regulation_temp(struct pf1550_charger *chg,
+ unsigned int cells)
+{
+ unsigned int data;
+
+ switch (cells) {
+ case 80:
+ data = 0x0;
+ break;
+ case 95:
+ data = 0x1;
+ break;
+ case 110:
+ data = 0x2;
+ break;
+ case 125:
+ data = 0x3;
+ break;
+ default:
+ return dev_err_probe(chg->dev, -EINVAL,
+ "Wrong value for thermal temperature\n");
+ }
+
+ data <<= PF1550_CHARG_REG_THM_REG_CNFG_REGTEMP_SHIFT;
+
+ dev_dbg(chg->dev, "Thermal regulation loop temperature: %u (0x%x)\n",
+ cells, data);
+
+ return regmap_update_bits(chg->pf1550->regmap,
+ PF1550_CHARG_REG_THM_REG_CNFG,
+ PF1550_CHARG_REG_THM_REG_CNFG_REGTEMP_MASK,
+ data);
+}
+
+/*
+ * Sets charger registers to proper and safe default values.
+ */
+static int pf1550_reg_init(struct pf1550_charger *chg)
+{
+ struct power_supply_battery_info *info;
+ struct device *dev = chg->dev;
+ int ret;
+
+ /* Unmask charger interrupt, mask DPMI and reserved bit */
+ ret = regmap_write(chg->pf1550->regmap, PF1550_CHARG_REG_CHG_INT_MASK,
+ PF1550_CHG_INT_MASK);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Error unmask charger interrupt\n");
+
+ ret = pf1550_set_constant_volt(chg, chg->constant_volt);
+ if (ret)
+ return ret;
+
+ ret = pf1550_set_min_system_volt(chg, chg->min_system_volt);
+ if (ret)
+ return ret;
+
+ ret = pf1550_set_thermal_regulation_temp(chg,
+ chg->thermal_regulation_temp);
+ if (ret)
+ return ret;
+
+ /*
+ * The PF1550 charger has 3 modes of operation. By default, the charger
+ * is in mode 1; it remains off. Appropriate for applications not using
+ * a battery. The other supported mode is mode 2, the charger is turned
+ * on to charge a battery when present.
+ */
+ if (power_supply_get_battery_info(chg->charger, &info)) {
+ ret = regmap_write(chg->pf1550->regmap,
+ PF1550_CHARG_REG_CHG_OPER,
+ PF1550_CHG_BAT_ON);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Error turn on charger\n");
+ }
+
+ return 0;
+}
+
+static void pf1550_dt_parse_dev_info(struct pf1550_charger *chg)
+{
+ struct power_supply_battery_info *info;
+ struct device *dev = chg->dev;
+
+ if (device_property_read_u32(dev->parent, "nxp,min-system-microvolt",
+ &chg->min_system_volt))
+ chg->min_system_volt = PF1550_DEFAULT_MIN_SYSTEM_VOLT;
+
+ if (device_property_read_u32(dev->parent,
+ "nxp,thermal-regulation-celsius",
+ &chg->thermal_regulation_temp))
+ chg->thermal_regulation_temp = PF1550_DEFAULT_THERMAL_TEMP;
+
+ if (power_supply_get_battery_info(chg->charger, &info))
+ chg->constant_volt = PF1550_DEFAULT_CONSTANT_VOLT;
+ else
+ chg->constant_volt = info->constant_charge_voltage_max_uv;
+}
+
+static int pf1550_charger_probe(struct platform_device *pdev)
+{
+ const struct pf1550_ddata *pf1550 = dev_get_drvdata(pdev->dev.parent);
+ struct power_supply_config psy_cfg = {};
+ struct pf1550_charger *chg;
+ int i, irq, ret;
+
+ chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ chg->dev = &pdev->dev;
+ chg->pf1550 = pf1550;
+
+ if (!chg->pf1550->regmap)
+ return dev_err_probe(&pdev->dev, -ENODEV,
+ "failed to get regmap\n");
+
+ platform_set_drvdata(pdev, chg);
+
+ ret = devm_delayed_work_autocancel(chg->dev, &chg->vbus_sense_work,
+ pf1550_chg_vbus_work);
+ if (ret)
+ return dev_err_probe(chg->dev, ret,
+ "failed to add vbus sense work\n");
+
+ ret = devm_delayed_work_autocancel(chg->dev, &chg->chg_sense_work,
+ pf1550_chg_chg_work);
+ if (ret)
+ return dev_err_probe(chg->dev, ret,
+ "failed to add charger sense work\n");
+
+ ret = devm_delayed_work_autocancel(chg->dev, &chg->bat_sense_work,
+ pf1550_chg_bat_work);
+ if (ret)
+ return dev_err_probe(chg->dev, ret,
+ "failed to add battery sense work\n");
+
+ psy_cfg.drv_data = chg;
+
+ chg->charger = devm_power_supply_register(&pdev->dev,
+ &pf1550_charger_desc,
+ &psy_cfg);
+ if (IS_ERR(chg->charger))
+ return dev_err_probe(&pdev->dev, PTR_ERR(chg->charger),
+ "failed: power supply register\n");
+
+ chg->battery = devm_power_supply_register(&pdev->dev,
+ &pf1550_battery_desc,
+ &psy_cfg);
+ if (IS_ERR(chg->battery))
+ return dev_err_probe(&pdev->dev, PTR_ERR(chg->battery),
+ "failed: power supply register\n");
+
+ for (i = 0; i < PF1550_CHARGER_IRQ_NR; i++) {
+ irq = platform_get_irq(pdev, i);
+ if (irq < 0)
+ return irq;
+
+ chg->virqs[i] = irq;
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ pf1550_charger_irq_handler,
+ IRQF_NO_SUSPEND,
+ "pf1550-charger", chg);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed irq request\n");
+ }
+
+ pf1550_dt_parse_dev_info(chg);
+
+ return pf1550_reg_init(chg);
+}
+
+static const struct platform_device_id pf1550_charger_id[] = {
+ { "pf1550-charger", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, pf1550_charger_id);
+
+static struct platform_driver pf1550_charger_driver = {
+ .driver = {
+ .name = "pf1550-charger",
+ },
+ .probe = pf1550_charger_probe,
+ .id_table = pf1550_charger_id,
+};
+module_platform_driver(pf1550_charger_driver);
+
+MODULE_AUTHOR("Robin Gong <yibin.gong@freescale.com>");
+MODULE_DESCRIPTION("PF1550 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/pm8916_bms_vm.c b/drivers/power/supply/pm8916_bms_vm.c
index 5d0dd842509c..de5d571c03e2 100644
--- a/drivers/power/supply/pm8916_bms_vm.c
+++ b/drivers/power/supply/pm8916_bms_vm.c
@@ -167,15 +167,6 @@ static int pm8916_bms_vm_battery_probe(struct platform_device *pdev)
if (ret < 0)
return -EINVAL;
- irq = platform_get_irq_byname(pdev, "fifo");
- if (irq < 0)
- return irq;
-
- ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_bms_vm_fifo_update_done_irq,
- IRQF_ONESHOT, "pm8916_vm_bms", bat);
- if (ret)
- return ret;
-
ret = regmap_bulk_read(bat->regmap, bat->reg + PM8916_PERPH_TYPE, &tmp, 2);
if (ret)
goto comm_error;
@@ -210,7 +201,7 @@ static int pm8916_bms_vm_battery_probe(struct platform_device *pdev)
bat->vbat_now = bat->last_ocv;
psy_cfg.drv_data = bat;
- psy_cfg.of_node = dev->of_node;
+ psy_cfg.fwnode = dev_fwnode(dev);
bat->battery = devm_power_supply_register(dev, &pm8916_bms_vm_battery_psy_desc, &psy_cfg);
if (IS_ERR(bat->battery))
@@ -220,6 +211,15 @@ static int pm8916_bms_vm_battery_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "Unable to get battery info\n");
+ irq = platform_get_irq_byname(pdev, "fifo");
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_bms_vm_fifo_update_done_irq,
+ IRQF_ONESHOT, "pm8916_vm_bms", bat);
+ if (ret)
+ return ret;
+
platform_set_drvdata(pdev, bat);
return 0;
diff --git a/drivers/power/supply/pm8916_lbc.c b/drivers/power/supply/pm8916_lbc.c
index 6d92e98cbecc..6b631012a795 100644
--- a/drivers/power/supply/pm8916_lbc.c
+++ b/drivers/power/supply/pm8916_lbc.c
@@ -274,15 +274,6 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev)
return dev_err_probe(dev, -EINVAL,
"Wrong amount of reg values: %d (4 expected)\n", len);
- irq = platform_get_irq_byname(pdev, "usb_vbus");
- if (irq < 0)
- return irq;
-
- ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq,
- IRQF_ONESHOT, "pm8916_lbc", chg);
- if (ret)
- return ret;
-
ret = device_property_read_u32_array(dev, "reg", chg->reg, len);
if (ret)
return ret;
@@ -322,7 +313,7 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev)
dev_err_probe(dev, ret, "Error while parsing device tree\n");
psy_cfg.drv_data = chg;
- psy_cfg.of_node = dev->of_node;
+ psy_cfg.fwnode = dev_fwnode(dev);
chg->charger = devm_power_supply_register(dev, &pm8916_lbc_charger_psy_desc, &psy_cfg);
if (IS_ERR(chg->charger))
@@ -332,6 +323,10 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "Unable to get battery info\n");
+ irq = platform_get_irq_byname(pdev, "usb_vbus");
+ if (irq < 0)
+ return irq;
+
chg->edev = devm_extcon_dev_allocate(dev, pm8916_lbc_charger_cable);
if (IS_ERR(chg->edev))
return PTR_ERR(chg->edev);
@@ -340,6 +335,11 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev)
if (ret < 0)
return dev_err_probe(dev, ret, "failed to register extcon device\n");
+ ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq,
+ IRQF_ONESHOT, "pm8916_lbc", chg);
+ if (ret)
+ return ret;
+
ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp);
if (ret)
goto comm_error;
diff --git a/drivers/power/supply/pmu_battery.c b/drivers/power/supply/pmu_battery.c
index ed83c5e05ca3..b5ffe2da14dc 100644
--- a/drivers/power/supply/pmu_battery.c
+++ b/drivers/power/supply/pmu_battery.c
@@ -160,8 +160,7 @@ static int __init pmu_bat_init(void)
for (i = 0; i < pmu_battery_count; i++) {
struct power_supply_config psy_cfg = {};
- struct pmu_battery_dev *pbat = kzalloc(sizeof(*pbat),
- GFP_KERNEL);
+ struct pmu_battery_dev *pbat = kzalloc_obj(*pbat);
if (!pbat)
break;
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 76c340b38015..a446d3d086fc 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -18,7 +18,6 @@
#include <linux/device.h>
#include <linux/notifier.h>
#include <linux/err.h>
-#include <linux/of.h>
#include <linux/power_supply.h>
#include <linux/property.h>
#include <linux/thermal.h>
@@ -196,24 +195,24 @@ static int __power_supply_populate_supplied_from(struct power_supply *epsy,
void *data)
{
struct power_supply *psy = data;
- struct device_node *np;
+ struct fwnode_handle *np;
int i = 0;
do {
- np = of_parse_phandle(psy->of_node, "power-supplies", i++);
- if (!np)
+ np = fwnode_find_reference(psy->dev.fwnode, "power-supplies", i++);
+ if (IS_ERR(np))
break;
- if (np == epsy->of_node) {
+ if (np == epsy->dev.fwnode) {
dev_dbg(&psy->dev, "%s: Found supply : %s\n",
psy->desc->name, epsy->desc->name);
psy->supplied_from[i-1] = (char *)epsy->desc->name;
psy->num_supplies++;
- of_node_put(np);
+ fwnode_handle_put(np);
break;
}
- of_node_put(np);
- } while (np);
+ fwnode_handle_put(np);
+ } while (true);
return 0;
}
@@ -232,16 +231,16 @@ static int power_supply_populate_supplied_from(struct power_supply *psy)
static int __power_supply_find_supply_from_node(struct power_supply *epsy,
void *data)
{
- struct device_node *np = data;
+ struct fwnode_handle *fwnode = data;
/* returning non-zero breaks out of power_supply_for_each_psy loop */
- if (epsy->of_node == np)
+ if (epsy->dev.fwnode == fwnode)
return 1;
return 0;
}
-static int power_supply_find_supply_from_node(struct device_node *supply_node)
+static int power_supply_find_supply_from_fwnode(struct fwnode_handle *supply_node)
{
int error;
@@ -249,7 +248,7 @@ static int power_supply_find_supply_from_node(struct device_node *supply_node)
* power_supply_for_each_psy() either returns its own errors or values
* returned by __power_supply_find_supply_from_node().
*
- * __power_supply_find_supply_from_node() will return 0 (no match)
+ * __power_supply_find_supply_from_fwnode() will return 0 (no match)
* or 1 (match).
*
* We return 0 if power_supply_for_each_psy() returned 1, -EPROBE_DEFER if
@@ -262,7 +261,7 @@ static int power_supply_find_supply_from_node(struct device_node *supply_node)
static int power_supply_check_supplies(struct power_supply *psy)
{
- struct device_node *np;
+ struct fwnode_handle *np;
int cnt = 0;
/* If there is already a list honor it */
@@ -270,24 +269,24 @@ static int power_supply_check_supplies(struct power_supply *psy)
return 0;
/* No device node found, nothing to do */
- if (!psy->of_node)
+ if (!psy->dev.fwnode)
return 0;
do {
int ret;
- np = of_parse_phandle(psy->of_node, "power-supplies", cnt++);
- if (!np)
+ np = fwnode_find_reference(psy->dev.fwnode, "power-supplies", cnt++);
+ if (IS_ERR(np))
break;
- ret = power_supply_find_supply_from_node(np);
- of_node_put(np);
+ ret = power_supply_find_supply_from_fwnode(np);
+ fwnode_handle_put(np);
if (ret) {
dev_dbg(&psy->dev, "Failed to find supply!\n");
return ret;
}
- } while (np);
+ } while (!IS_ERR(np));
/* Missing valid "power-supplies" entries */
if (cnt == 1)
@@ -449,19 +448,6 @@ int power_supply_get_property_from_supplier(struct power_supply *psy,
}
EXPORT_SYMBOL_GPL(power_supply_get_property_from_supplier);
-int power_supply_set_battery_charged(struct power_supply *psy)
-{
- if (atomic_read(&psy->use_cnt) >= 0 &&
- psy->desc->type == POWER_SUPPLY_TYPE_BATTERY &&
- psy->desc->set_charged) {
- psy->desc->set_charged(psy);
- return 0;
- }
-
- return -EINVAL;
-}
-EXPORT_SYMBOL_GPL(power_supply_set_battery_charged);
-
static int power_supply_match_device_by_name(struct device *dev, const void *data)
{
const char *name = data;
@@ -510,15 +496,14 @@ void power_supply_put(struct power_supply *psy)
}
EXPORT_SYMBOL_GPL(power_supply_put);
-#ifdef CONFIG_OF
-static int power_supply_match_device_node(struct device *dev, const void *data)
+static int power_supply_match_device_fwnode(struct device *dev, const void *data)
{
- return dev->parent && dev->parent->of_node == data;
+ return dev->parent && dev_fwnode(dev->parent) == data;
}
/**
- * power_supply_get_by_phandle() - Search for a power supply and returns its ref
- * @np: Pointer to device node holding phandle property
+ * power_supply_get_by_reference() - Search for a power supply and returns its ref
+ * @fwnode: Pointer to fwnode holding phandle property
* @property: Name of property holding a power supply name
*
* If power supply was found, it increases reference count for the
@@ -528,21 +513,21 @@ static int power_supply_match_device_node(struct device *dev, const void *data)
* Return: On success returns a reference to a power supply with
* matching name equals to value under @property, NULL or ERR_PTR otherwise.
*/
-struct power_supply *power_supply_get_by_phandle(struct device_node *np,
- const char *property)
+struct power_supply *power_supply_get_by_reference(struct fwnode_handle *fwnode,
+ const char *property)
{
- struct device_node *power_supply_np;
+ struct fwnode_handle *power_supply_fwnode;
struct power_supply *psy = NULL;
struct device *dev;
- power_supply_np = of_parse_phandle(np, property, 0);
- if (!power_supply_np)
- return ERR_PTR(-ENODEV);
+ power_supply_fwnode = fwnode_find_reference(fwnode, property, 0);
+ if (IS_ERR(power_supply_fwnode))
+ return ERR_CAST(power_supply_fwnode);
- dev = class_find_device(&power_supply_class, NULL, power_supply_np,
- power_supply_match_device_node);
+ dev = class_find_device(&power_supply_class, NULL, power_supply_fwnode,
+ power_supply_match_device_fwnode);
- of_node_put(power_supply_np);
+ fwnode_handle_put(power_supply_fwnode);
if (dev) {
psy = dev_to_psy(dev);
@@ -551,7 +536,7 @@ struct power_supply *power_supply_get_by_phandle(struct device_node *np,
return psy;
}
-EXPORT_SYMBOL_GPL(power_supply_get_by_phandle);
+EXPORT_SYMBOL_GPL(power_supply_get_by_reference);
static void devm_power_supply_put(struct device *dev, void *res)
{
@@ -561,27 +546,27 @@ static void devm_power_supply_put(struct device *dev, void *res)
}
/**
- * devm_power_supply_get_by_phandle() - Resource managed version of
- * power_supply_get_by_phandle()
+ * devm_power_supply_get_by_reference() - Resource managed version of
+ * power_supply_get_by_reference()
* @dev: Pointer to device holding phandle property
* @property: Name of property holding a power supply phandle
*
* Return: On success returns a reference to a power supply with
* matching name equals to value under @property, NULL or ERR_PTR otherwise.
*/
-struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
- const char *property)
+struct power_supply *devm_power_supply_get_by_reference(struct device *dev,
+ const char *property)
{
struct power_supply **ptr, *psy;
- if (!dev->of_node)
+ if (!dev_fwnode(dev))
return ERR_PTR(-ENODEV);
ptr = devres_alloc(devm_power_supply_put, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
- psy = power_supply_get_by_phandle(dev->of_node, property);
+ psy = power_supply_get_by_reference(dev_fwnode(dev), property);
if (IS_ERR_OR_NULL(psy)) {
devres_free(ptr);
} else {
@@ -590,40 +575,26 @@ struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
}
return psy;
}
-EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
-#endif /* CONFIG_OF */
+EXPORT_SYMBOL_GPL(devm_power_supply_get_by_reference);
int power_supply_get_battery_info(struct power_supply *psy,
struct power_supply_battery_info **info_out)
{
struct power_supply_resistance_temp_table *resist_table;
struct power_supply_battery_info *info;
- struct device_node *battery_np = NULL;
- struct fwnode_reference_args args;
- struct fwnode_handle *fwnode = NULL;
+ struct fwnode_handle *srcnode, *fwnode;
const char *value;
- int err, len, index;
- const __be32 *list;
+ int err, len, index, proplen;
+ u32 *propdata __free(kfree) = NULL;
u32 min_max[2];
- if (psy->of_node) {
- battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0);
- if (!battery_np)
- return -ENODEV;
-
- fwnode = fwnode_handle_get(of_fwnode_handle(battery_np));
- } else if (psy->dev.parent) {
- err = fwnode_property_get_reference_args(
- dev_fwnode(psy->dev.parent),
- "monitored-battery", NULL, 0, 0, &args);
- if (err)
- return err;
-
- fwnode = args.fwnode;
- }
+ srcnode = dev_fwnode(&psy->dev);
+ if (!srcnode && psy->dev.parent)
+ srcnode = dev_fwnode(psy->dev.parent);
- if (!fwnode)
- return -ENOENT;
+ fwnode = fwnode_find_reference(srcnode, "monitored-battery", 0);
+ if (IS_ERR(fwnode))
+ return PTR_ERR(fwnode);
err = fwnode_property_read_string(fwnode, "compatible", &value);
if (err)
@@ -753,15 +724,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
info->temp_max = min_max[1];
}
- /*
- * The below code uses raw of-data parsing to parse
- * /schemas/types.yaml#/definitions/uint32-matrix
- * data, so for now this is only support with of.
- */
- if (!battery_np)
- goto out_ret_pointer;
-
- len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius");
+ len = fwnode_property_count_u32(fwnode, "ocv-capacity-celsius");
if (len < 0 && len != -EINVAL) {
err = len;
goto out_put_node;
@@ -770,13 +733,13 @@ int power_supply_get_battery_info(struct power_supply *psy,
err = -EINVAL;
goto out_put_node;
} else if (len > 0) {
- of_property_read_u32_array(battery_np, "ocv-capacity-celsius",
+ fwnode_property_read_u32_array(fwnode, "ocv-capacity-celsius",
info->ocv_temp, len);
}
for (index = 0; index < len; index++) {
struct power_supply_battery_ocv_table *table;
- int i, tab_len, size;
+ int i, tab_len;
char *propname __free(kfree) = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d",
index);
@@ -785,15 +748,28 @@ int power_supply_get_battery_info(struct power_supply *psy,
err = -ENOMEM;
goto out_put_node;
}
- list = of_get_property(battery_np, propname, &size);
- if (!list || !size) {
+ proplen = fwnode_property_count_u32(fwnode, propname);
+ if (proplen < 0 || proplen % 2 != 0) {
dev_err(&psy->dev, "failed to get %s\n", propname);
power_supply_put_battery_info(psy, info);
err = -EINVAL;
goto out_put_node;
}
- tab_len = size / (2 * sizeof(__be32));
+ u32 *propdata __free(kfree) = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
+ if (!propdata) {
+ power_supply_put_battery_info(psy, info);
+ err = -EINVAL;
+ goto out_put_node;
+ }
+ err = fwnode_property_read_u32_array(fwnode, propname, propdata, proplen);
+ if (err < 0) {
+ dev_err(&psy->dev, "failed to get %s\n", propname);
+ power_supply_put_battery_info(psy, info);
+ goto out_put_node;
+ }
+
+ tab_len = proplen / 2;
info->ocv_table_size[index] = tab_len;
info->ocv_table[index] = table =
@@ -805,18 +781,36 @@ int power_supply_get_battery_info(struct power_supply *psy,
}
for (i = 0; i < tab_len; i++) {
- table[i].ocv = be32_to_cpu(*list);
- list++;
- table[i].capacity = be32_to_cpu(*list);
- list++;
+ table[i].ocv = propdata[i*2];
+ table[i].capacity = propdata[i*2+1];
}
}
- list = of_get_property(battery_np, "resistance-temp-table", &len);
- if (!list || !len)
+ proplen = fwnode_property_count_u32(fwnode, "resistance-temp-table");
+ if (proplen == 0 || proplen == -EINVAL) {
+ err = 0;
goto out_ret_pointer;
+ } else if (proplen < 0 || proplen % 2 != 0) {
+ power_supply_put_battery_info(psy, info);
+ err = (proplen < 0) ? proplen : -EINVAL;
+ goto out_put_node;
+ }
- info->resist_table_size = len / (2 * sizeof(__be32));
+ propdata = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
+ if (!propdata) {
+ power_supply_put_battery_info(psy, info);
+ err = -ENOMEM;
+ goto out_put_node;
+ }
+
+ err = fwnode_property_read_u32_array(fwnode, "resistance-temp-table",
+ propdata, proplen);
+ if (err < 0) {
+ power_supply_put_battery_info(psy, info);
+ goto out_put_node;
+ }
+
+ info->resist_table_size = proplen / 2;
info->resist_table = resist_table = devm_kcalloc(&psy->dev,
info->resist_table_size,
sizeof(*resist_table),
@@ -828,8 +822,8 @@ int power_supply_get_battery_info(struct power_supply *psy,
}
for (index = 0; index < info->resist_table_size; index++) {
- resist_table[index].temp = be32_to_cpu(*list++);
- resist_table[index].resistance = be32_to_cpu(*list++);
+ resist_table[index].temp = propdata[index*2];
+ resist_table[index].resistance = propdata[index*2+1];
}
out_ret_pointer:
@@ -838,7 +832,6 @@ out_ret_pointer:
out_put_node:
fwnode_handle_put(fwnode);
- of_node_put(battery_np);
return err;
}
EXPORT_SYMBOL_GPL(power_supply_get_battery_info);
@@ -1248,9 +1241,8 @@ bool power_supply_has_property(struct power_supply *psy,
return false;
}
-int power_supply_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
+static int __power_supply_get_property(struct power_supply *psy, enum power_supply_property psp,
+ union power_supply_propval *val, bool use_extensions)
{
struct power_supply_ext_registration *reg;
@@ -1260,10 +1252,14 @@ int power_supply_get_property(struct power_supply *psy,
return -ENODEV;
}
- scoped_guard(rwsem_read, &psy->extensions_sem) {
- power_supply_for_each_extension(reg, psy) {
- if (power_supply_ext_has_property(reg->ext, psp))
+ if (use_extensions) {
+ scoped_guard(rwsem_read, &psy->extensions_sem) {
+ power_supply_for_each_extension(reg, psy) {
+ if (!power_supply_ext_has_property(reg->ext, psp))
+ continue;
+
return reg->ext->get_property(psy, reg->ext, reg->data, psp, val);
+ }
}
}
@@ -1274,20 +1270,49 @@ int power_supply_get_property(struct power_supply *psy,
else
return -EINVAL;
}
+
+int power_supply_get_property(struct power_supply *psy, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ return __power_supply_get_property(psy, psp, val, true);
+}
EXPORT_SYMBOL_GPL(power_supply_get_property);
-int power_supply_set_property(struct power_supply *psy,
- enum power_supply_property psp,
- const union power_supply_propval *val)
+/**
+ * power_supply_get_property_direct - Read a power supply property without checking for extensions
+ * @psy: The power supply
+ * @psp: The power supply property to read
+ * @val: The resulting value of the power supply property
+ *
+ * Read a power supply property without taking into account any power supply extensions registered
+ * on the given power supply. This is mostly useful for power supply extensions that want to access
+ * their own power supply as using power_supply_get_property() directly will result in a potential
+ * deadlock.
+ *
+ * Return: 0 on success or negative error code on failure.
+ */
+int power_supply_get_property_direct(struct power_supply *psy, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ return __power_supply_get_property(psy, psp, val, false);
+}
+EXPORT_SYMBOL_GPL(power_supply_get_property_direct);
+
+
+static int __power_supply_set_property(struct power_supply *psy, enum power_supply_property psp,
+ const union power_supply_propval *val, bool use_extensions)
{
struct power_supply_ext_registration *reg;
if (atomic_read(&psy->use_cnt) <= 0)
return -ENODEV;
- scoped_guard(rwsem_read, &psy->extensions_sem) {
- power_supply_for_each_extension(reg, psy) {
- if (power_supply_ext_has_property(reg->ext, psp)) {
+ if (use_extensions) {
+ scoped_guard(rwsem_read, &psy->extensions_sem) {
+ power_supply_for_each_extension(reg, psy) {
+ if (!power_supply_ext_has_property(reg->ext, psp))
+ continue;
+
if (reg->ext->set_property)
return reg->ext->set_property(psy, reg->ext, reg->data,
psp, val);
@@ -1302,8 +1327,34 @@ int power_supply_set_property(struct power_supply *psy,
return psy->desc->set_property(psy, psp, val);
}
+
+int power_supply_set_property(struct power_supply *psy, enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ return __power_supply_set_property(psy, psp, val, true);
+}
EXPORT_SYMBOL_GPL(power_supply_set_property);
+/**
+ * power_supply_set_property_direct - Write a power supply property without checking for extensions
+ * @psy: The power supply
+ * @psp: The power supply property to write
+ * @val: The value to write to the power supply property
+ *
+ * Write a power supply property without taking into account any power supply extensions registered
+ * on the given power supply. This is mostly useful for power supply extensions that want to access
+ * their own power supply as using power_supply_set_property() directly will result in a potential
+ * deadlock.
+ *
+ * Return: 0 on success or negative error code on failure.
+ */
+int power_supply_set_property_direct(struct power_supply *psy, enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ return __power_supply_set_property(psy, psp, val, false);
+}
+EXPORT_SYMBOL_GPL(power_supply_set_property_direct);
+
int power_supply_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
@@ -1375,7 +1426,7 @@ int power_supply_register_extension(struct power_supply *psy, const struct power
if (power_supply_has_property(psy, ext->properties[i]))
return -EEXIST;
- reg = kmalloc(sizeof(*reg), GFP_KERNEL);
+ reg = kmalloc_obj(*reg);
if (!reg)
return -ENOMEM;
@@ -1542,11 +1593,9 @@ __power_supply_register(struct device *parent,
dev_set_drvdata(dev, psy);
psy->desc = desc;
if (cfg) {
+ device_set_node(dev, cfg->fwnode);
dev->groups = cfg->attr_grp;
psy->drv_data = cfg->drv_data;
- psy->of_node =
- cfg->fwnode ? to_of_node(cfg->fwnode) : cfg->of_node;
- dev->of_node = psy->of_node;
psy->supplied_to = cfg->supplied_to;
psy->num_supplicants = cfg->num_supplicants;
}
diff --git a/drivers/power/supply/power_supply_leds.c b/drivers/power/supply/power_supply_leds.c
index f4a7e566bea1..1548aaba3362 100644
--- a/drivers/power/supply/power_supply_leds.c
+++ b/drivers/power/supply/power_supply_leds.c
@@ -48,7 +48,7 @@ static int power_supply_register_led_trigger(struct power_supply *psy,
if (err && *err)
return *err;
- psy_trig = kzalloc(sizeof(*psy_trig), GFP_KERNEL);
+ psy_trig = kzalloc_obj(*psy_trig);
if (!psy_trig)
goto err_free_trigger;
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index edb058c19c9c..f30a7b9ccd5e 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -33,7 +33,7 @@ struct power_supply_attr {
[POWER_SUPPLY_PROP_ ## _name] = \
{ \
.prop_name = #_name, \
- .attr_name = #_name "\0", \
+ .attr_name = #_name, \
.text_values = _text, \
.text_values_len = _len, \
}
@@ -70,6 +70,8 @@ static const char * const POWER_SUPPLY_USB_TYPE_TEXT[] = {
[POWER_SUPPLY_USB_TYPE_PD] = "PD",
[POWER_SUPPLY_USB_TYPE_PD_DRP] = "PD_DRP",
[POWER_SUPPLY_USB_TYPE_PD_PPS] = "PD_PPS",
+ [POWER_SUPPLY_USB_TYPE_PD_SPR_AVS] = "PD_SPR_AVS",
+ [POWER_SUPPLY_USB_TYPE_PD_PPS_SPR_AVS] = "PD_PPS_SPR_AVS",
[POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID] = "BrickID",
};
@@ -110,6 +112,8 @@ static const char * const POWER_SUPPLY_HEALTH_TEXT[] = {
[POWER_SUPPLY_HEALTH_COOL] = "Cool",
[POWER_SUPPLY_HEALTH_HOT] = "Hot",
[POWER_SUPPLY_HEALTH_NO_BATTERY] = "No battery",
+ [POWER_SUPPLY_HEALTH_BLOWN_FUSE] = "Blown fuse",
+ [POWER_SUPPLY_HEALTH_CELL_IMBALANCE] = "Cell imbalance",
};
static const char * const POWER_SUPPLY_TECHNOLOGY_TEXT[] = {
@@ -138,9 +142,10 @@ static const char * const POWER_SUPPLY_SCOPE_TEXT[] = {
};
static const char * const POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[] = {
- [POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO] = "auto",
- [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE] = "inhibit-charge",
- [POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge",
+ [POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO] = "auto",
+ [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE] = "inhibit-charge",
+ [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE] = "inhibit-charge-awake",
+ [POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge",
};
static struct power_supply_attr power_supply_attrs[] __ro_after_init = {
@@ -220,6 +225,8 @@ static struct power_supply_attr power_supply_attrs[] __ro_after_init = {
POWER_SUPPLY_ATTR(MANUFACTURE_YEAR),
POWER_SUPPLY_ATTR(MANUFACTURE_MONTH),
POWER_SUPPLY_ATTR(MANUFACTURE_DAY),
+ POWER_SUPPLY_ATTR(INTERNAL_RESISTANCE),
+ POWER_SUPPLY_ATTR(STATE_OF_HEALTH),
/* Properties of type `const char *' */
POWER_SUPPLY_ATTR(MODEL_NAME),
POWER_SUPPLY_ATTR(MANUFACTURER),
@@ -230,12 +237,12 @@ static struct power_supply_attr power_supply_attrs[] __ro_after_init = {
static struct attribute *
__power_supply_attrs[POWER_SUPPLY_ATTR_CNT + 1] __ro_after_init;
-static const struct power_supply_attr *to_ps_attr(struct device_attribute *attr)
+static const struct power_supply_attr *to_ps_attr(const struct device_attribute *attr)
{
- return container_of(attr, struct power_supply_attr, dev_attr);
+ return container_of_const(attr, struct power_supply_attr, dev_attr);
}
-static enum power_supply_property dev_attr_psp(struct device_attribute *attr)
+static enum power_supply_property dev_attr_psp(const struct device_attribute *attr)
{
return to_ps_attr(attr) - power_supply_attrs;
}
@@ -321,6 +328,27 @@ static ssize_t power_supply_show_charge_behaviour(struct device *dev,
value->intval, buf);
}
+static ssize_t power_supply_show_charge_types(struct device *dev,
+ struct power_supply *psy,
+ enum power_supply_charge_type current_type,
+ char *buf)
+{
+ struct power_supply_ext_registration *reg;
+
+ scoped_guard(rwsem_read, &psy->extensions_sem) {
+ power_supply_for_each_extension(reg, psy) {
+ if (power_supply_ext_has_property(reg->ext,
+ POWER_SUPPLY_PROP_CHARGE_TYPES))
+ return power_supply_charge_types_show(dev,
+ reg->ext->charge_types,
+ current_type, buf);
+ }
+ }
+
+ return power_supply_charge_types_show(dev, psy->desc->charge_types,
+ current_type, buf);
+}
+
static ssize_t power_supply_format_property(struct device *dev,
bool uevent,
struct device_attribute *attr,
@@ -365,7 +393,7 @@ static ssize_t power_supply_format_property(struct device *dev,
case POWER_SUPPLY_PROP_CHARGE_TYPES:
if (uevent) /* no possible values in uevents */
goto default_format;
- ret = power_supply_charge_types_show(dev, psy->desc->charge_types,
+ ret = power_supply_show_charge_types(dev, psy,
value.intval, buf);
break;
case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER:
diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c
index 47d29271ddf4..490137a23d00 100644
--- a/drivers/power/supply/qcom_battmgr.c
+++ b/drivers/power/supply/qcom_battmgr.c
@@ -2,12 +2,15 @@
/*
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2022, Linaro Ltd
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/auxiliary_bus.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/nvmem-consumer.h>
#include <linux/of_device.h>
#include <linux/power_supply.h>
+#include <linux/property.h>
#include <linux/soc/qcom/pdr.h>
#include <linux/soc/qcom/pmic_glink.h>
#include <linux/math.h>
@@ -17,8 +20,10 @@
#define BATTMGR_STRING_LEN 128
enum qcom_battmgr_variant {
- QCOM_BATTMGR_SM8350,
QCOM_BATTMGR_SC8280XP,
+ QCOM_BATTMGR_SM8350,
+ QCOM_BATTMGR_SM8550,
+ QCOM_BATTMGR_X1E80100,
};
#define BATTMGR_BAT_STATUS 0x1
@@ -29,8 +34,9 @@ enum qcom_battmgr_variant {
#define NOTIF_BAT_PROPERTY 0x30
#define NOTIF_USB_PROPERTY 0x32
#define NOTIF_WLS_PROPERTY 0x34
-#define NOTIF_BAT_INFO 0x81
#define NOTIF_BAT_STATUS 0x80
+#define NOTIF_BAT_INFO 0x81
+#define NOTIF_BAT_CHARGING_STATE 0x83
#define BATTMGR_BAT_INFO 0x9
@@ -64,6 +70,9 @@ enum qcom_battmgr_variant {
#define BATT_RESISTANCE 21
#define BATT_POWER_NOW 22
#define BATT_POWER_AVG 23
+#define BATT_CHG_CTRL_EN 24
+#define BATT_CHG_CTRL_START_THR 25
+#define BATT_CHG_CTRL_END_THR 26
#define BATTMGR_USB_PROPERTY_GET 0x32
#define BATTMGR_USB_PROPERTY_SET 0x33
@@ -88,6 +97,13 @@ enum qcom_battmgr_variant {
#define WLS_TYPE 5
#define WLS_BOOST_EN 6
+#define BATTMGR_CHG_CTRL_LIMIT_EN 0x48
+#define CHARGE_CTRL_START_THR_MIN 50
+#define CHARGE_CTRL_START_THR_MAX 95
+#define CHARGE_CTRL_END_THR_MIN 55
+#define CHARGE_CTRL_END_THR_MAX 100
+#define CHARGE_CTRL_DELTA_SOC 5
+
struct qcom_battmgr_enable_request {
struct pmic_glink_hdr hdr;
__le32 battery_id;
@@ -122,6 +138,13 @@ struct qcom_battmgr_discharge_time_request {
__le32 reserved;
};
+struct qcom_battmgr_charge_ctrl_request {
+ struct pmic_glink_hdr hdr;
+ __le32 enable;
+ __le32 target_soc;
+ __le32 delta_soc;
+};
+
struct qcom_battmgr_message {
struct pmic_glink_hdr hdr;
union {
@@ -234,6 +257,8 @@ struct qcom_battmgr_info {
unsigned int capacity_warning;
unsigned int cycle_count;
unsigned int charge_count;
+ unsigned int charge_ctrl_start;
+ unsigned int charge_ctrl_end;
char model_number[BATTMGR_STRING_LEN];
char serial_number[BATTMGR_STRING_LEN];
char oem_info[BATTMGR_STRING_LEN];
@@ -253,6 +278,8 @@ struct qcom_battmgr_status {
unsigned int voltage_now;
unsigned int voltage_ocv;
unsigned int temperature;
+ unsigned int resistance;
+ unsigned int soh_percent;
unsigned int discharge_time;
unsigned int charge_time;
@@ -417,7 +444,11 @@ static const u8 sm8350_bat_prop_map[] = {
[POWER_SUPPLY_PROP_MODEL_NAME] = BATT_MODEL_NAME,
[POWER_SUPPLY_PROP_TIME_TO_FULL_AVG] = BATT_TTF_AVG,
[POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG] = BATT_TTE_AVG,
+ [POWER_SUPPLY_PROP_INTERNAL_RESISTANCE] = BATT_RESISTANCE,
+ [POWER_SUPPLY_PROP_STATE_OF_HEALTH] = BATT_SOH,
[POWER_SUPPLY_PROP_POWER_NOW] = BATT_POWER_NOW,
+ [POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD] = BATT_CHG_CTRL_START_THR,
+ [POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD] = BATT_CHG_CTRL_END_THR,
};
static int qcom_battmgr_bat_sm8350_update(struct qcom_battmgr *battmgr,
@@ -488,7 +519,8 @@ static int qcom_battmgr_bat_get_property(struct power_supply *psy,
if (!battmgr->service_up)
return -EAGAIN;
- if (battmgr->variant == QCOM_BATTMGR_SC8280XP)
+ if (battmgr->variant == QCOM_BATTMGR_SC8280XP ||
+ battmgr->variant == QCOM_BATTMGR_X1E80100)
ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp);
else
ret = qcom_battmgr_bat_sm8350_update(battmgr, psp);
@@ -576,17 +608,31 @@ static int qcom_battmgr_bat_get_property(struct power_supply *psy,
val->intval = battmgr->status.capacity;
break;
case POWER_SUPPLY_PROP_CAPACITY:
+ if (battmgr->status.percent == (unsigned int)-1)
+ return -ENODATA;
val->intval = battmgr->status.percent;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = battmgr->status.temperature;
break;
+ case POWER_SUPPLY_PROP_INTERNAL_RESISTANCE:
+ val->intval = battmgr->status.resistance;
+ break;
+ case POWER_SUPPLY_PROP_STATE_OF_HEALTH:
+ val->intval = battmgr->status.soh_percent;
+ break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
val->intval = battmgr->status.discharge_time;
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
val->intval = battmgr->status.charge_time;
break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ val->intval = battmgr->info.charge_ctrl_start;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ val->intval = battmgr->info.charge_ctrl_end;
+ break;
case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
val->intval = battmgr->info.year;
break;
@@ -612,10 +658,144 @@ static int qcom_battmgr_bat_get_property(struct power_supply *psy,
return 0;
}
+static int qcom_battmgr_set_charge_control(struct qcom_battmgr *battmgr,
+ u32 target_soc, u32 delta_soc)
+{
+ struct qcom_battmgr_charge_ctrl_request request = {
+ .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_BATTMGR),
+ .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP),
+ .hdr.opcode = cpu_to_le32(BATTMGR_CHG_CTRL_LIMIT_EN),
+ .enable = cpu_to_le32(1),
+ .target_soc = cpu_to_le32(target_soc),
+ .delta_soc = cpu_to_le32(delta_soc),
+ };
+
+ return qcom_battmgr_request(battmgr, &request, sizeof(request));
+}
+
+static int qcom_battmgr_set_charge_start_threshold(struct qcom_battmgr *battmgr, int start_soc)
+{
+ u32 target_soc, delta_soc;
+ int ret;
+
+ start_soc = clamp(start_soc, CHARGE_CTRL_START_THR_MIN, CHARGE_CTRL_START_THR_MAX);
+
+ /*
+ * If the new start threshold is larger than the old end threshold,
+ * move the end threshold one step (DELTA_SOC) after the new start
+ * threshold.
+ */
+ if (start_soc > battmgr->info.charge_ctrl_end) {
+ target_soc = start_soc + CHARGE_CTRL_DELTA_SOC;
+ target_soc = min_t(u32, target_soc, CHARGE_CTRL_END_THR_MAX);
+ delta_soc = target_soc - start_soc;
+ delta_soc = min_t(u32, delta_soc, CHARGE_CTRL_DELTA_SOC);
+ } else {
+ target_soc = battmgr->info.charge_ctrl_end;
+ delta_soc = battmgr->info.charge_ctrl_end - start_soc;
+ }
+
+ mutex_lock(&battmgr->lock);
+ ret = qcom_battmgr_set_charge_control(battmgr, target_soc, delta_soc);
+ mutex_unlock(&battmgr->lock);
+ if (!ret) {
+ battmgr->info.charge_ctrl_start = start_soc;
+ battmgr->info.charge_ctrl_end = target_soc;
+ }
+
+ return 0;
+}
+
+static int qcom_battmgr_set_charge_end_threshold(struct qcom_battmgr *battmgr, int end_soc)
+{
+ u32 delta_soc = CHARGE_CTRL_DELTA_SOC;
+ int ret;
+
+ end_soc = clamp(end_soc, CHARGE_CTRL_END_THR_MIN, CHARGE_CTRL_END_THR_MAX);
+
+ if (battmgr->info.charge_ctrl_start && end_soc > battmgr->info.charge_ctrl_start)
+ delta_soc = end_soc - battmgr->info.charge_ctrl_start;
+
+ mutex_lock(&battmgr->lock);
+ ret = qcom_battmgr_set_charge_control(battmgr, end_soc, delta_soc);
+ mutex_unlock(&battmgr->lock);
+ if (!ret) {
+ battmgr->info.charge_ctrl_start = end_soc - delta_soc;
+ battmgr->info.charge_ctrl_end = end_soc;
+ }
+
+ return 0;
+}
+
+static int qcom_battmgr_charge_control_thresholds_init(struct qcom_battmgr *battmgr)
+{
+ int ret;
+ u8 en, end_soc, start_soc, delta_soc;
+
+ ret = nvmem_cell_read_u8(battmgr->dev->parent, "charge_limit_en", &en);
+ if (!ret && en != 0) {
+ ret = nvmem_cell_read_u8(battmgr->dev->parent, "charge_limit_end", &end_soc);
+ if (ret < 0)
+ return ret;
+
+ ret = nvmem_cell_read_u8(battmgr->dev->parent, "charge_limit_delta", &delta_soc);
+ if (ret < 0)
+ return ret;
+
+ if (delta_soc >= end_soc)
+ return -EINVAL;
+
+ start_soc = end_soc - delta_soc;
+ end_soc = clamp(end_soc, CHARGE_CTRL_END_THR_MIN, CHARGE_CTRL_END_THR_MAX);
+ start_soc = clamp(start_soc, CHARGE_CTRL_START_THR_MIN, CHARGE_CTRL_START_THR_MAX);
+
+ battmgr->info.charge_ctrl_start = start_soc;
+ battmgr->info.charge_ctrl_end = end_soc;
+ }
+
+ return 0;
+}
+
+static int qcom_battmgr_bat_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ return 1;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static int qcom_battmgr_bat_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *pval)
+{
+ struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy);
+
+ if (!battmgr->service_up)
+ return -EAGAIN;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ return qcom_battmgr_set_charge_start_threshold(battmgr, pval->intval);
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ return qcom_battmgr_set_charge_end_threshold(battmgr, pval->intval);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static const enum power_supply_property sc8280xp_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
@@ -645,6 +825,43 @@ static const struct power_supply_desc sc8280xp_bat_psy_desc = {
.get_property = qcom_battmgr_bat_get_property,
};
+static const enum power_supply_property x1e80100_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_POWER_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_EMPTY,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+ POWER_SUPPLY_PROP_ENERGY_EMPTY,
+ POWER_SUPPLY_PROP_ENERGY_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_MANUFACTURE_YEAR,
+ POWER_SUPPLY_PROP_MANUFACTURE_MONTH,
+ POWER_SUPPLY_PROP_MANUFACTURE_DAY,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const struct power_supply_desc x1e80100_bat_psy_desc = {
+ .name = "qcom-battmgr-bat",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = x1e80100_bat_props,
+ .num_properties = ARRAY_SIZE(x1e80100_bat_props),
+ .get_property = qcom_battmgr_bat_get_property,
+ .set_property = qcom_battmgr_bat_set_property,
+ .property_is_writeable = qcom_battmgr_bat_is_writeable,
+};
+
static const enum power_supply_property sm8350_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
@@ -664,6 +881,8 @@ static const enum power_supply_property sm8350_bat_props[] = {
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_INTERNAL_RESISTANCE,
+ POWER_SUPPLY_PROP_STATE_OF_HEALTH,
POWER_SUPPLY_PROP_POWER_NOW,
};
@@ -675,6 +894,42 @@ static const struct power_supply_desc sm8350_bat_psy_desc = {
.get_property = qcom_battmgr_bat_get_property,
};
+static const enum power_supply_property sm8550_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_INTERNAL_RESISTANCE,
+ POWER_SUPPLY_PROP_STATE_OF_HEALTH,
+ POWER_SUPPLY_PROP_POWER_NOW,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const struct power_supply_desc sm8550_bat_psy_desc = {
+ .name = "qcom-battmgr-bat",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = sm8550_bat_props,
+ .num_properties = ARRAY_SIZE(sm8550_bat_props),
+ .get_property = qcom_battmgr_bat_get_property,
+ .set_property = qcom_battmgr_bat_set_property,
+ .property_is_writeable = qcom_battmgr_bat_is_writeable,
+};
+
static int qcom_battmgr_ac_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -750,7 +1005,8 @@ static int qcom_battmgr_usb_get_property(struct power_supply *psy,
if (!battmgr->service_up)
return -EAGAIN;
- if (battmgr->variant == QCOM_BATTMGR_SC8280XP)
+ if (battmgr->variant == QCOM_BATTMGR_SC8280XP ||
+ battmgr->variant == QCOM_BATTMGR_X1E80100)
ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp);
else
ret = qcom_battmgr_usb_sm8350_update(battmgr, psp);
@@ -872,7 +1128,8 @@ static int qcom_battmgr_wls_get_property(struct power_supply *psy,
if (!battmgr->service_up)
return -EAGAIN;
- if (battmgr->variant == QCOM_BATTMGR_SC8280XP)
+ if (battmgr->variant == QCOM_BATTMGR_SC8280XP ||
+ battmgr->variant == QCOM_BATTMGR_X1E80100)
ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp);
else
ret = qcom_battmgr_wls_sm8350_update(battmgr, psp);
@@ -943,12 +1200,14 @@ static void qcom_battmgr_notification(struct qcom_battmgr *battmgr,
}
notification = le32_to_cpu(msg->notification);
+ notification &= 0xff;
switch (notification) {
case NOTIF_BAT_INFO:
battmgr->info.valid = false;
fallthrough;
case NOTIF_BAT_STATUS:
case NOTIF_BAT_PROPERTY:
+ case NOTIF_BAT_CHARGING_STATE:
power_supply_changed(battmgr->bat_psy);
break;
case NOTIF_USB_PROPERTY:
@@ -978,8 +1237,12 @@ static void qcom_battmgr_sc8280xp_strcpy(char *dest, const char *src)
static unsigned int qcom_battmgr_sc8280xp_parse_technology(const char *chemistry)
{
- if (!strncmp(chemistry, "LIO", BATTMGR_CHEMISTRY_LEN))
+ if ((!strncmp(chemistry, "LIO", BATTMGR_CHEMISTRY_LEN)) ||
+ (!strncmp(chemistry, "OOI", BATTMGR_CHEMISTRY_LEN)))
return POWER_SUPPLY_TECHNOLOGY_LION;
+ if (!strncmp(chemistry, "LIP", BATTMGR_CHEMISTRY_LEN) ||
+ !strncmp(chemistry, "LiP", BATTMGR_CHEMISTRY_LEN))
+ return POWER_SUPPLY_TECHNOLOGY_LIPO;
pr_err("Unknown battery technology '%s'\n", chemistry);
return POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
@@ -1062,6 +1325,26 @@ static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr,
battmgr->ac.online = source == BATTMGR_CHARGING_SOURCE_AC;
battmgr->usb.online = source == BATTMGR_CHARGING_SOURCE_USB;
battmgr->wireless.online = source == BATTMGR_CHARGING_SOURCE_WIRELESS;
+ if (battmgr->info.last_full_capacity != 0) {
+ /*
+ * 100 * battmgr->status.capacity can overflow a 32bit
+ * unsigned integer. FW readings are in m{W/A}h, which
+ * are multiplied by 1000 converting them to u{W/A}h,
+ * the format the power_supply API expects.
+ * To avoid overflow use the original value for dividend
+ * and convert the divider back to m{W/A}h, which can be
+ * done without any loss of precision.
+ */
+ battmgr->status.percent =
+ (100 * le32_to_cpu(resp->status.capacity)) /
+ (battmgr->info.last_full_capacity / 1000);
+ } else {
+ /*
+ * Let the sysfs handler know no data is available at
+ * this time.
+ */
+ battmgr->status.percent = (unsigned int)-1;
+ }
break;
case BATTMGR_BAT_DISCHARGE_TIME:
battmgr->status.discharge_time = le32_to_cpu(resp->time);
@@ -1069,6 +1352,9 @@ static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr,
case BATTMGR_BAT_CHARGE_TIME:
battmgr->status.charge_time = le32_to_cpu(resp->time);
break;
+ case BATTMGR_CHG_CTRL_LIMIT_EN:
+ battmgr->error = 0;
+ break;
default:
dev_warn(battmgr->dev, "unknown message %#x\n", opcode);
break;
@@ -1133,6 +1419,9 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr,
case BATT_CAPACITY:
battmgr->status.percent = le32_to_cpu(resp->intval.value) / 100;
break;
+ case BATT_SOH:
+ battmgr->status.soh_percent = le32_to_cpu(resp->intval.value);
+ break;
case BATT_VOLT_OCV:
battmgr->status.voltage_ocv = le32_to_cpu(resp->intval.value);
break;
@@ -1173,9 +1462,18 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr,
case BATT_TTE_AVG:
battmgr->status.discharge_time = le32_to_cpu(resp->intval.value);
break;
+ case BATT_RESISTANCE:
+ battmgr->status.resistance = le32_to_cpu(resp->intval.value);
+ break;
case BATT_POWER_NOW:
battmgr->status.power_now = le32_to_cpu(resp->intval.value);
break;
+ case BATT_CHG_CTRL_START_THR:
+ battmgr->info.charge_ctrl_start = le32_to_cpu(resp->intval.value);
+ break;
+ case BATT_CHG_CTRL_END_THR:
+ battmgr->info.charge_ctrl_end = le32_to_cpu(resp->intval.value);
+ break;
default:
dev_warn(battmgr->dev, "unknown property %#x\n", property);
break;
@@ -1258,6 +1556,7 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr,
}
break;
case BATTMGR_REQUEST_NOTIFICATION:
+ case BATTMGR_CHG_CTRL_LIMIT_EN:
battmgr->error = 0;
break;
default:
@@ -1277,7 +1576,8 @@ static void qcom_battmgr_callback(const void *data, size_t len, void *priv)
if (opcode == BATTMGR_NOTIFICATION)
qcom_battmgr_notification(battmgr, data, len);
- else if (battmgr->variant == QCOM_BATTMGR_SC8280XP)
+ else if (battmgr->variant == QCOM_BATTMGR_SC8280XP ||
+ battmgr->variant == QCOM_BATTMGR_X1E80100)
qcom_battmgr_sc8280xp_callback(battmgr, data, len);
else
qcom_battmgr_sm8350_callback(battmgr, data, len);
@@ -1311,9 +1611,12 @@ static void qcom_battmgr_pdr_notify(void *priv, int state)
}
static const struct of_device_id qcom_battmgr_of_variants[] = {
+ { .compatible = "qcom,glymur-pmic-glink", .data = (void *)QCOM_BATTMGR_X1E80100 },
+ { .compatible = "qcom,kaanapali-pmic-glink", .data = (void *)QCOM_BATTMGR_SM8550 },
{ .compatible = "qcom,sc8180x-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP },
{ .compatible = "qcom,sc8280xp-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP },
- { .compatible = "qcom,x1e80100-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP },
+ { .compatible = "qcom,sm8550-pmic-glink", .data = (void *)QCOM_BATTMGR_SM8550 },
+ { .compatible = "qcom,x1e80100-pmic-glink", .data = (void *)QCOM_BATTMGR_X1E80100 },
/* Unmatched devices falls back to QCOM_BATTMGR_SM8350 */
{}
};
@@ -1323,11 +1626,13 @@ static char *qcom_battmgr_battery[] = { "battery" };
static int qcom_battmgr_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
+ const struct power_supply_desc *psy_desc;
struct power_supply_config psy_cfg_supply = {};
struct power_supply_config psy_cfg = {};
const struct of_device_id *match;
struct qcom_battmgr *battmgr;
struct device *dev = &adev->dev;
+ int ret;
battmgr = devm_kzalloc(dev, sizeof(*battmgr), GFP_KERNEL);
if (!battmgr)
@@ -1336,10 +1641,10 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev,
battmgr->dev = dev;
psy_cfg.drv_data = battmgr;
- psy_cfg.of_node = adev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&adev->dev);
psy_cfg_supply.drv_data = battmgr;
- psy_cfg_supply.of_node = adev->dev.of_node;
+ psy_cfg_supply.fwnode = dev_fwnode(&adev->dev);
psy_cfg_supply.supplied_to = qcom_battmgr_battery;
psy_cfg_supply.num_supplicants = 1;
@@ -1353,8 +1658,19 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev,
else
battmgr->variant = QCOM_BATTMGR_SM8350;
- if (battmgr->variant == QCOM_BATTMGR_SC8280XP) {
- battmgr->bat_psy = devm_power_supply_register(dev, &sc8280xp_bat_psy_desc, &psy_cfg);
+ ret = qcom_battmgr_charge_control_thresholds_init(battmgr);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "failed to init battery charge control thresholds\n");
+
+ if (battmgr->variant == QCOM_BATTMGR_SC8280XP ||
+ battmgr->variant == QCOM_BATTMGR_X1E80100) {
+ if (battmgr->variant == QCOM_BATTMGR_X1E80100)
+ psy_desc = &x1e80100_bat_psy_desc;
+ else
+ psy_desc = &sc8280xp_bat_psy_desc;
+
+ battmgr->bat_psy = devm_power_supply_register(dev, psy_desc, &psy_cfg);
if (IS_ERR(battmgr->bat_psy))
return dev_err_probe(dev, PTR_ERR(battmgr->bat_psy),
"failed to register battery power supply\n");
@@ -1374,7 +1690,12 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev,
return dev_err_probe(dev, PTR_ERR(battmgr->wls_psy),
"failed to register wireless charing power supply\n");
} else {
- battmgr->bat_psy = devm_power_supply_register(dev, &sm8350_bat_psy_desc, &psy_cfg);
+ if (battmgr->variant == QCOM_BATTMGR_SM8550)
+ psy_desc = &sm8550_bat_psy_desc;
+ else
+ psy_desc = &sm8350_bat_psy_desc;
+
+ battmgr->bat_psy = devm_power_supply_register(dev, psy_desc, &psy_cfg);
if (IS_ERR(battmgr->bat_psy))
return dev_err_probe(dev, PTR_ERR(battmgr->bat_psy),
"failed to register battery power supply\n");
diff --git a/drivers/power/supply/qcom_smbb.c b/drivers/power/supply/qcom_smbb.c
index a79563f6ff7a..28afe758a2da 100644
--- a/drivers/power/supply/qcom_smbb.c
+++ b/drivers/power/supply/qcom_smbb.c
@@ -880,7 +880,7 @@ static int smbb_charger_probe(struct platform_device *pdev)
}
bat_cfg.drv_data = chg;
- bat_cfg.of_node = pdev->dev.of_node;
+ bat_cfg.fwnode = dev_fwnode(&pdev->dev);
chg->bat_psy = devm_power_supply_register(&pdev->dev,
&bat_psy_desc,
&bat_cfg);
diff --git a/drivers/power/supply/qcom_pmi8998_charger.c b/drivers/power/supply/qcom_smbx.c
index 3b4132376649..bf2e2ccc454a 100644
--- a/drivers/power/supply/qcom_pmi8998_charger.c
+++ b/drivers/power/supply/qcom_smbx.c
@@ -2,7 +2,7 @@
/*
* Copyright (c) 2016-2019 The Linux Foundation. All rights reserved.
* Copyright (c) 2023, Linaro Ltd.
- * Author: Caleb Connolly <caleb.connolly@linaro.org>
+ * Author: Casey Connolly <casey.connolly@linaro.org>
*
* This driver is for the switch-mode battery charger and boost
* hardware found in pmi8998 and related PMICs.
@@ -134,6 +134,9 @@
#define OCP_CHARGER_BIT BIT(1)
#define SDP_CHARGER_BIT BIT(0)
+#define USBIN_CMD_IL 0x340
+#define USBIN_SUSPEND_BIT BIT(0)
+
#define TYPE_C_STATUS_1 0x30B
#define UFP_TYPEC_MASK GENMASK(7, 5)
#define UFP_TYPEC_RDSTD_BIT BIT(7)
@@ -362,17 +365,17 @@ enum charger_status {
DISABLE_CHARGE,
};
-struct smb2_register {
+struct smb_init_register {
u16 addr;
u8 mask;
u8 val;
};
/**
- * struct smb2_chip - smb2 chip structure
+ * struct smb_chip - smb chip structure
* @dev: Device reference for power_supply
* @name: The platform device name
- * @base: Base address for smb2 registers
+ * @base: Base address for smb registers
* @regmap: Register map
* @batt_info: Battery data from DT
* @status_change_work: Worker to handle plug/unplug events
@@ -382,7 +385,7 @@ struct smb2_register {
* @usb_in_v_chan: USB_IN voltage measurement channel
* @chg_psy: Charger power supply instance
*/
-struct smb2_chip {
+struct smb_chip {
struct device *dev;
const char *name;
unsigned int base;
@@ -399,7 +402,7 @@ struct smb2_chip {
struct power_supply *chg_psy;
};
-static enum power_supply_property smb2_properties[] = {
+static enum power_supply_property smb_properties[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_CURRENT_MAX,
@@ -411,7 +414,7 @@ static enum power_supply_property smb2_properties[] = {
POWER_SUPPLY_PROP_USB_TYPE,
};
-static int smb2_get_prop_usb_online(struct smb2_chip *chip, int *val)
+static int smb_get_prop_usb_online(struct smb_chip *chip, int *val)
{
unsigned int stat;
int rc;
@@ -431,13 +434,13 @@ static int smb2_get_prop_usb_online(struct smb2_chip *chip, int *val)
* Qualcomm "automatic power source detection" aka APSD
* tells us what type of charger we're connected to.
*/
-static int smb2_apsd_get_charger_type(struct smb2_chip *chip, int *val)
+static int smb_apsd_get_charger_type(struct smb_chip *chip, int *val)
{
unsigned int apsd_stat, stat;
int usb_online = 0;
int rc;
- rc = smb2_get_prop_usb_online(chip, &usb_online);
+ rc = smb_get_prop_usb_online(chip, &usb_online);
if (!usb_online) {
*val = POWER_SUPPLY_USB_TYPE_UNKNOWN;
return rc;
@@ -471,13 +474,13 @@ static int smb2_apsd_get_charger_type(struct smb2_chip *chip, int *val)
return 0;
}
-static int smb2_get_prop_status(struct smb2_chip *chip, int *val)
+static int smb_get_prop_status(struct smb_chip *chip, int *val)
{
unsigned char stat[2];
int usb_online = 0;
int rc;
- rc = smb2_get_prop_usb_online(chip, &usb_online);
+ rc = smb_get_prop_usb_online(chip, &usb_online);
if (!usb_online) {
*val = POWER_SUPPLY_STATUS_DISCHARGING;
return rc;
@@ -519,7 +522,7 @@ static int smb2_get_prop_status(struct smb2_chip *chip, int *val)
}
}
-static inline int smb2_get_current_limit(struct smb2_chip *chip,
+static inline int smb_get_current_limit(struct smb_chip *chip,
unsigned int *val)
{
int rc = regmap_read(chip->regmap, chip->base + ICL_STATUS, val);
@@ -529,7 +532,7 @@ static inline int smb2_get_current_limit(struct smb2_chip *chip,
return rc;
}
-static int smb2_set_current_limit(struct smb2_chip *chip, unsigned int val)
+static int smb_set_current_limit(struct smb_chip *chip, unsigned int val)
{
unsigned char val_raw;
@@ -544,22 +547,22 @@ static int smb2_set_current_limit(struct smb2_chip *chip, unsigned int val)
val_raw);
}
-static void smb2_status_change_work(struct work_struct *work)
+static void smb_status_change_work(struct work_struct *work)
{
unsigned int charger_type, current_ua;
int usb_online = 0;
int count, rc;
- struct smb2_chip *chip;
+ struct smb_chip *chip;
- chip = container_of(work, struct smb2_chip, status_change_work.work);
+ chip = container_of(work, struct smb_chip, status_change_work.work);
- smb2_get_prop_usb_online(chip, &usb_online);
+ smb_get_prop_usb_online(chip, &usb_online);
if (!usb_online)
return;
for (count = 0; count < 3; count++) {
dev_dbg(chip->dev, "get charger type retry %d\n", count);
- rc = smb2_apsd_get_charger_type(chip, &charger_type);
+ rc = smb_apsd_get_charger_type(chip, &charger_type);
if (rc != -EAGAIN)
break;
msleep(100);
@@ -592,11 +595,11 @@ static void smb2_status_change_work(struct work_struct *work)
break;
}
- smb2_set_current_limit(chip, current_ua);
+ smb_set_current_limit(chip, current_ua);
power_supply_changed(chip->chg_psy);
}
-static int smb2_get_iio_chan(struct smb2_chip *chip, struct iio_channel *chan,
+static int smb_get_iio_chan(struct smb_chip *chip, struct iio_channel *chan,
int *val)
{
int rc;
@@ -617,7 +620,7 @@ static int smb2_get_iio_chan(struct smb2_chip *chip, struct iio_channel *chan,
return iio_read_channel_processed(chan, val);
}
-static int smb2_get_prop_health(struct smb2_chip *chip, int *val)
+static int smb_get_prop_health(struct smb_chip *chip, int *val)
{
int rc;
unsigned int stat;
@@ -651,11 +654,11 @@ static int smb2_get_prop_health(struct smb2_chip *chip, int *val)
}
}
-static int smb2_get_property(struct power_supply *psy,
+static int smb_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
- struct smb2_chip *chip = power_supply_get_drvdata(psy);
+ struct smb_chip *chip = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_MANUFACTURER:
@@ -665,46 +668,50 @@ static int smb2_get_property(struct power_supply *psy,
val->strval = chip->name;
return 0;
case POWER_SUPPLY_PROP_CURRENT_MAX:
- return smb2_get_current_limit(chip, &val->intval);
+ return smb_get_current_limit(chip, &val->intval);
case POWER_SUPPLY_PROP_CURRENT_NOW:
- return smb2_get_iio_chan(chip, chip->usb_in_i_chan,
+ return smb_get_iio_chan(chip, chip->usb_in_i_chan,
&val->intval);
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- return smb2_get_iio_chan(chip, chip->usb_in_v_chan,
+ return smb_get_iio_chan(chip, chip->usb_in_v_chan,
&val->intval);
case POWER_SUPPLY_PROP_ONLINE:
- return smb2_get_prop_usb_online(chip, &val->intval);
+ return smb_get_prop_usb_online(chip, &val->intval);
case POWER_SUPPLY_PROP_STATUS:
- return smb2_get_prop_status(chip, &val->intval);
+ return smb_get_prop_status(chip, &val->intval);
case POWER_SUPPLY_PROP_HEALTH:
- return smb2_get_prop_health(chip, &val->intval);
+ return smb_get_prop_health(chip, &val->intval);
case POWER_SUPPLY_PROP_USB_TYPE:
- return smb2_apsd_get_charger_type(chip, &val->intval);
+ return smb_apsd_get_charger_type(chip, &val->intval);
default:
dev_err(chip->dev, "invalid property: %d\n", psp);
return -EINVAL;
}
}
-static int smb2_set_property(struct power_supply *psy,
+static int smb_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
- struct smb2_chip *chip = power_supply_get_drvdata(psy);
+ struct smb_chip *chip = power_supply_get_drvdata(psy);
switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ return regmap_update_bits(chip->regmap, chip->base + USBIN_CMD_IL,
+ USBIN_SUSPEND_BIT, !val->intval);
case POWER_SUPPLY_PROP_CURRENT_MAX:
- return smb2_set_current_limit(chip, val->intval);
+ return smb_set_current_limit(chip, val->intval);
default:
dev_err(chip->dev, "No setter for property: %d\n", psp);
return -EINVAL;
}
}
-static int smb2_property_is_writable(struct power_supply *psy,
+static int smb_property_is_writable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
case POWER_SUPPLY_PROP_CURRENT_MAX:
return 1;
default:
@@ -712,9 +719,9 @@ static int smb2_property_is_writable(struct power_supply *psy,
}
}
-static irqreturn_t smb2_handle_batt_overvoltage(int irq, void *data)
+static irqreturn_t smb_handle_batt_overvoltage(int irq, void *data)
{
- struct smb2_chip *chip = data;
+ struct smb_chip *chip = data;
unsigned int status;
regmap_read(chip->regmap, chip->base + BATTERY_CHARGER_STATUS_2,
@@ -729,9 +736,9 @@ static irqreturn_t smb2_handle_batt_overvoltage(int irq, void *data)
return IRQ_HANDLED;
}
-static irqreturn_t smb2_handle_usb_plugin(int irq, void *data)
+static irqreturn_t smb_handle_usb_plugin(int irq, void *data)
{
- struct smb2_chip *chip = data;
+ struct smb_chip *chip = data;
power_supply_changed(chip->chg_psy);
@@ -741,18 +748,18 @@ static irqreturn_t smb2_handle_usb_plugin(int irq, void *data)
return IRQ_HANDLED;
}
-static irqreturn_t smb2_handle_usb_icl_change(int irq, void *data)
+static irqreturn_t smb_handle_usb_icl_change(int irq, void *data)
{
- struct smb2_chip *chip = data;
+ struct smb_chip *chip = data;
power_supply_changed(chip->chg_psy);
return IRQ_HANDLED;
}
-static irqreturn_t smb2_handle_wdog_bark(int irq, void *data)
+static irqreturn_t smb_handle_wdog_bark(int irq, void *data)
{
- struct smb2_chip *chip = data;
+ struct smb_chip *chip = data;
int rc;
power_supply_changed(chip->chg_psy);
@@ -765,22 +772,22 @@ static irqreturn_t smb2_handle_wdog_bark(int irq, void *data)
return IRQ_HANDLED;
}
-static const struct power_supply_desc smb2_psy_desc = {
+static const struct power_supply_desc smb_psy_desc = {
.name = "pmi8998_charger",
.type = POWER_SUPPLY_TYPE_USB,
.usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) |
BIT(POWER_SUPPLY_USB_TYPE_CDP) |
BIT(POWER_SUPPLY_USB_TYPE_DCP) |
BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN),
- .properties = smb2_properties,
- .num_properties = ARRAY_SIZE(smb2_properties),
- .get_property = smb2_get_property,
- .set_property = smb2_set_property,
- .property_is_writeable = smb2_property_is_writable,
+ .properties = smb_properties,
+ .num_properties = ARRAY_SIZE(smb_properties),
+ .get_property = smb_get_property,
+ .set_property = smb_set_property,
+ .property_is_writeable = smb_property_is_writable,
};
/* Init sequence derived from vendor downstream driver */
-static const struct smb2_register smb2_init_seq[] = {
+static const struct smb_init_register smb_init_seq[] = {
{ .addr = AICL_RERUN_TIME_CFG, .mask = AICL_RERUN_TIME_MASK, .val = 0 },
/*
* By default configure us as an upstream facing port
@@ -882,17 +889,17 @@ static const struct smb2_register smb2_init_seq[] = {
.val = 1000000 / CURRENT_SCALE_FACTOR },
};
-static int smb2_init_hw(struct smb2_chip *chip)
+static int smb_init_hw(struct smb_chip *chip)
{
int rc, i;
- for (i = 0; i < ARRAY_SIZE(smb2_init_seq); i++) {
+ for (i = 0; i < ARRAY_SIZE(smb_init_seq); i++) {
dev_dbg(chip->dev, "%d: Writing 0x%02x to 0x%02x\n", i,
- smb2_init_seq[i].val, smb2_init_seq[i].addr);
+ smb_init_seq[i].val, smb_init_seq[i].addr);
rc = regmap_update_bits(chip->regmap,
- chip->base + smb2_init_seq[i].addr,
- smb2_init_seq[i].mask,
- smb2_init_seq[i].val);
+ chip->base + smb_init_seq[i].addr,
+ smb_init_seq[i].mask,
+ smb_init_seq[i].val);
if (rc < 0)
return dev_err_probe(chip->dev, rc,
"%s: init command %d failed\n",
@@ -902,7 +909,7 @@ static int smb2_init_hw(struct smb2_chip *chip)
return 0;
}
-static int smb2_init_irq(struct smb2_chip *chip, int *irq, const char *name,
+static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name,
irqreturn_t (*handler)(int irq, void *data))
{
int irqnum;
@@ -924,11 +931,11 @@ static int smb2_init_irq(struct smb2_chip *chip, int *irq, const char *name,
return 0;
}
-static int smb2_probe(struct platform_device *pdev)
+static int smb_probe(struct platform_device *pdev)
{
struct power_supply_config supply_config = {};
struct power_supply_desc *desc;
- struct smb2_chip *chip;
+ struct smb_chip *chip;
int rc, irq;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
@@ -959,17 +966,17 @@ static int smb2_probe(struct platform_device *pdev)
"Couldn't get usbin_i IIO channel\n");
}
- rc = smb2_init_hw(chip);
+ rc = smb_init_hw(chip);
if (rc < 0)
return rc;
supply_config.drv_data = chip;
- supply_config.of_node = pdev->dev.of_node;
+ supply_config.fwnode = dev_fwnode(&pdev->dev);
- desc = devm_kzalloc(chip->dev, sizeof(smb2_psy_desc), GFP_KERNEL);
+ desc = devm_kzalloc(chip->dev, sizeof(smb_psy_desc), GFP_KERNEL);
if (!desc)
return -ENOMEM;
- memcpy(desc, &smb2_psy_desc, sizeof(smb2_psy_desc));
+ memcpy(desc, &smb_psy_desc, sizeof(smb_psy_desc));
desc->name =
devm_kasprintf(chip->dev, GFP_KERNEL, "%s-charger",
(const char *)device_get_match_data(chip->dev));
@@ -988,7 +995,7 @@ static int smb2_probe(struct platform_device *pdev)
"Failed to get battery info\n");
rc = devm_delayed_work_autocancel(chip->dev, &chip->status_change_work,
- smb2_status_change_work);
+ smb_status_change_work);
if (rc)
return dev_err_probe(chip->dev, rc,
"Failed to init status change work\n");
@@ -999,24 +1006,26 @@ static int smb2_probe(struct platform_device *pdev)
if (rc < 0)
return dev_err_probe(chip->dev, rc, "Couldn't set vbat max\n");
- rc = smb2_init_irq(chip, &irq, "bat-ov", smb2_handle_batt_overvoltage);
+ rc = smb_init_irq(chip, &irq, "bat-ov", smb_handle_batt_overvoltage);
if (rc < 0)
return rc;
- rc = smb2_init_irq(chip, &chip->cable_irq, "usb-plugin",
- smb2_handle_usb_plugin);
+ rc = smb_init_irq(chip, &chip->cable_irq, "usb-plugin",
+ smb_handle_usb_plugin);
if (rc < 0)
return rc;
- rc = smb2_init_irq(chip, &irq, "usbin-icl-change",
- smb2_handle_usb_icl_change);
+ rc = smb_init_irq(chip, &irq, "usbin-icl-change",
+ smb_handle_usb_icl_change);
if (rc < 0)
return rc;
- rc = smb2_init_irq(chip, &irq, "wdog-bark", smb2_handle_wdog_bark);
+ rc = smb_init_irq(chip, &irq, "wdog-bark", smb_handle_wdog_bark);
if (rc < 0)
return rc;
- rc = dev_pm_set_wake_irq(chip->dev, chip->cable_irq);
+ devm_device_init_wakeup(chip->dev);
+
+ rc = devm_pm_set_wake_irq(chip->dev, chip->cable_irq);
if (rc < 0)
return dev_err_probe(chip->dev, rc, "Couldn't set wake irq\n");
@@ -1028,23 +1037,23 @@ static int smb2_probe(struct platform_device *pdev)
return 0;
}
-static const struct of_device_id smb2_match_id_table[] = {
+static const struct of_device_id smb_match_id_table[] = {
{ .compatible = "qcom,pmi8998-charger", .data = "pmi8998" },
{ .compatible = "qcom,pm660-charger", .data = "pm660" },
{ /* sentinal */ }
};
-MODULE_DEVICE_TABLE(of, smb2_match_id_table);
+MODULE_DEVICE_TABLE(of, smb_match_id_table);
-static struct platform_driver qcom_spmi_smb2 = {
- .probe = smb2_probe,
+static struct platform_driver qcom_spmi_smb = {
+ .probe = smb_probe,
.driver = {
- .name = "qcom-pmi8998/pm660-charger",
- .of_match_table = smb2_match_id_table,
+ .name = "qcom-smbx-charger",
+ .of_match_table = smb_match_id_table,
},
};
-module_platform_driver(qcom_spmi_smb2);
+module_platform_driver(qcom_spmi_smb);
-MODULE_AUTHOR("Caleb Connolly <caleb.connolly@linaro.org>");
+MODULE_AUTHOR("Casey Connolly <casey.connolly@linaro.org>");
MODULE_DESCRIPTION("Qualcomm SMB2 Charger Driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/rk817_charger.c b/drivers/power/supply/rk817_charger.c
index e5f35d083c23..9436c6bbf51f 100644
--- a/drivers/power/supply/rk817_charger.c
+++ b/drivers/power/supply/rk817_charger.c
@@ -1046,7 +1046,7 @@ static void rk817_charging_monitor(struct work_struct *work)
rk817_read_props(charger);
/* Run every 8 seconds like the BSP driver did. */
- queue_delayed_work(system_wq, &charger->work, msecs_to_jiffies(8000));
+ queue_delayed_work(system_percpu_wq, &charger->work, msecs_to_jiffies(8000));
}
static void rk817_cleanup_node(void *data)
@@ -1088,7 +1088,7 @@ static int rk817_charger_probe(struct platform_device *pdev)
rk817_bat_calib_vol(charger);
pscfg.drv_data = charger;
- pscfg.of_node = node;
+ pscfg.fwnode = &node->fwnode;
/*
* Get sample resistor value. Note only values of 10000 or 20000
@@ -1206,7 +1206,7 @@ static int rk817_charger_probe(struct platform_device *pdev)
return ret;
/* Force the first update immediately. */
- mod_delayed_work(system_wq, &charger->work, 0);
+ mod_delayed_work(system_percpu_wq, &charger->work, 0);
return 0;
}
@@ -1226,7 +1226,7 @@ static int __maybe_unused rk817_resume(struct device *dev)
struct rk817_charger *charger = dev_get_drvdata(dev);
/* force an immediate update */
- mod_delayed_work(system_wq, &charger->work, 0);
+ mod_delayed_work(system_percpu_wq, &charger->work, 0);
return 0;
}
diff --git a/drivers/power/supply/rt5033_battery.c b/drivers/power/supply/rt5033_battery.c
index 7a27b262fb84..b2674adfa30b 100644
--- a/drivers/power/supply/rt5033_battery.c
+++ b/drivers/power/supply/rt5033_battery.c
@@ -160,7 +160,7 @@ static int rt5033_battery_probe(struct i2c_client *client)
}
i2c_set_clientdata(client, battery);
- psy_cfg.of_node = client->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&client->dev);
psy_cfg.drv_data = battery;
battery->psy = devm_power_supply_register(&client->dev,
diff --git a/drivers/power/supply/rt5033_charger.c b/drivers/power/supply/rt5033_charger.c
index d19c7e80a92a..de724f23e453 100644
--- a/drivers/power/supply/rt5033_charger.c
+++ b/drivers/power/supply/rt5033_charger.c
@@ -16,6 +16,7 @@
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/mfd/rt5033-private.h>
+#include <linux/property.h>
struct rt5033_charger_data {
unsigned int pre_uamp;
@@ -675,7 +676,7 @@ static int rt5033_charger_probe(struct platform_device *pdev)
charger->regmap = dev_get_regmap(pdev->dev.parent, NULL);
mutex_init(&charger->lock);
- psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&pdev->dev);
psy_cfg.drv_data = charger;
charger->psy = devm_power_supply_register(charger->dev,
@@ -700,6 +701,8 @@ static int rt5033_charger_probe(struct platform_device *pdev)
np_conn = of_parse_phandle(pdev->dev.of_node, "richtek,usb-connector", 0);
np_edev = of_get_parent(np_conn);
charger->edev = extcon_find_edev_by_node(np_edev);
+ of_node_put(np_edev);
+ of_node_put(np_conn);
if (IS_ERR(charger->edev)) {
dev_warn(charger->dev, "no extcon device found in device-tree\n");
goto out;
diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c
index 64a23e3d7bb0..5130d2395e88 100644
--- a/drivers/power/supply/rt9455_charger.c
+++ b/drivers/power/supply/rt9455_charger.c
@@ -1579,7 +1579,7 @@ static const struct regmap_config rt9455_regmap_config = {
.writeable_reg = rt9455_is_writeable_reg,
.volatile_reg = rt9455_is_volatile_reg,
.max_register = RT9455_REG_MASK3,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
};
static int rt9455_probe(struct i2c_client *client)
@@ -1658,11 +1658,20 @@ static int rt9455_probe(struct i2c_client *client)
INIT_DEFERRABLE_WORK(&info->batt_presence_work,
rt9455_batt_presence_work_callback);
- rt9455_charger_config.of_node = dev->of_node;
+ rt9455_charger_config.fwnode = dev_fwnode(dev);
rt9455_charger_config.drv_data = info;
rt9455_charger_config.supplied_to = rt9455_charger_supplied_to;
rt9455_charger_config.num_supplicants =
ARRAY_SIZE(rt9455_charger_supplied_to);
+
+ info->charger = devm_power_supply_register(dev, &rt9455_charger_desc,
+ &rt9455_charger_config);
+ if (IS_ERR(info->charger)) {
+ dev_err(dev, "Failed to register charger\n");
+ ret = PTR_ERR(info->charger);
+ goto put_usb_notifier;
+ }
+
ret = devm_request_threaded_irq(dev, client->irq, NULL,
rt9455_irq_handler_thread,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
@@ -1678,14 +1687,6 @@ static int rt9455_probe(struct i2c_client *client)
goto put_usb_notifier;
}
- info->charger = devm_power_supply_register(dev, &rt9455_charger_desc,
- &rt9455_charger_config);
- if (IS_ERR(info->charger)) {
- dev_err(dev, "Failed to register charger\n");
- ret = PTR_ERR(info->charger);
- goto put_usb_notifier;
- }
-
return 0;
put_usb_notifier:
diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c
index 235169c85c5d..44c26fb37a77 100644
--- a/drivers/power/supply/rt9467-charger.c
+++ b/drivers/power/supply/rt9467-charger.c
@@ -376,7 +376,7 @@ static int rt9467_set_value_from_ranges(struct rt9467_chg_data *data,
if (rsel == RT9467_RANGE_VMIVR) {
ret = linear_range_get_selector_high(range, value, &sel, &found);
if (ret)
- value = range->max_sel;
+ sel = range->max_sel;
} else {
linear_range_get_selector_within(range, value, &sel);
}
@@ -588,6 +588,10 @@ static int rt9467_run_aicl(struct rt9467_chg_data *data)
aicl_vth = mivr_vth + RT9467_AICLVTH_GAP_uV;
ret = rt9467_set_value_from_ranges(data, F_AICL_VTH,
RT9467_RANGE_AICL_VTH, aicl_vth);
+ if (ret) {
+ dev_err(data->dev, "Failed to set AICL VTH\n");
+ return ret;
+ }
/* Trigger AICL function */
ret = regmap_field_write(data->rm_field[F_AICL_MEAS], 1);
@@ -633,7 +637,9 @@ out:
static const enum power_supply_property rt9467_chg_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
@@ -656,6 +662,8 @@ static int rt9467_psy_get_property(struct power_supply *psy,
return rt9467_psy_get_status(data, &val->intval);
case POWER_SUPPLY_PROP_ONLINE:
return regmap_field_read(data->rm_field[F_PWR_RDY], &val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return rt9467_get_adc(data, RT9467_ADC_VBUS_DIV5, &val->intval);
case POWER_SUPPLY_PROP_CURRENT_MAX:
mutex_lock(&data->attach_lock);
if (data->psy_usb_type == POWER_SUPPLY_USB_TYPE_UNKNOWN ||
@@ -665,6 +673,8 @@ static int rt9467_psy_get_property(struct power_supply *psy,
val->intval = 1500000;
mutex_unlock(&data->attach_lock);
return 0;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return rt9467_get_adc(data, RT9467_ADC_IBUS, &val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
mutex_lock(&data->ichg_ieoc_lock);
val->intval = data->ichg_ua;
@@ -826,7 +836,7 @@ static int rt9467_register_psy(struct rt9467_chg_data *data)
{
struct power_supply_config cfg = {
.drv_data = data,
- .of_node = dev_of_node(data->dev),
+ .fwnode = dev_fwnode(data->dev),
.attr_grp = rt9467_sysfs_groups,
};
@@ -1141,27 +1151,6 @@ static int rt9467_reset_chip(struct rt9467_chg_data *data)
return regmap_field_write(data->rm_field[F_RST], 1);
}
-static void rt9467_chg_destroy_adc_lock(void *data)
-{
- struct mutex *adc_lock = data;
-
- mutex_destroy(adc_lock);
-}
-
-static void rt9467_chg_destroy_attach_lock(void *data)
-{
- struct mutex *attach_lock = data;
-
- mutex_destroy(attach_lock);
-}
-
-static void rt9467_chg_destroy_ichg_ieoc_lock(void *data)
-{
- struct mutex *ichg_ieoc_lock = data;
-
- mutex_destroy(ichg_ieoc_lock);
-}
-
static void rt9467_chg_complete_aicl_done(void *data)
{
struct completion *aicl_done = data;
@@ -1214,29 +1203,23 @@ static int rt9467_charger_probe(struct i2c_client *i2c)
if (ret)
return dev_err_probe(dev, ret, "Failed to add irq chip\n");
- mutex_init(&data->adc_lock);
- ret = devm_add_action_or_reset(dev, rt9467_chg_destroy_adc_lock,
- &data->adc_lock);
+ ret = devm_mutex_init(dev, &data->adc_lock);
if (ret)
- return dev_err_probe(dev, ret, "Failed to init ADC lock\n");
+ return ret;
- mutex_init(&data->attach_lock);
- ret = devm_add_action_or_reset(dev, rt9467_chg_destroy_attach_lock,
- &data->attach_lock);
+ ret = devm_mutex_init(dev, &data->attach_lock);
if (ret)
- return dev_err_probe(dev, ret, "Failed to init attach lock\n");
+ return ret;
- mutex_init(&data->ichg_ieoc_lock);
- ret = devm_add_action_or_reset(dev, rt9467_chg_destroy_ichg_ieoc_lock,
- &data->ichg_ieoc_lock);
+ ret = devm_mutex_init(dev, &data->ichg_ieoc_lock);
if (ret)
- return dev_err_probe(dev, ret, "Failed to init ICHG/IEOC lock\n");
+ return ret;
init_completion(&data->aicl_done);
ret = devm_add_action_or_reset(dev, rt9467_chg_complete_aicl_done,
&data->aicl_done);
if (ret)
- return dev_err_probe(dev, ret, "Failed to init AICL done completion\n");
+ return ret;
ret = rt9467_do_charger_init(data);
if (ret)
diff --git a/drivers/power/supply/rt9471.c b/drivers/power/supply/rt9471.c
index 67b86ac91a21..e7f843f12c98 100644
--- a/drivers/power/supply/rt9471.c
+++ b/drivers/power/supply/rt9471.c
@@ -192,12 +192,12 @@ static const struct reg_field rt9471_reg_fields[F_MAX_FIELDS] = {
};
static const struct linear_range rt9471_chg_ranges[RT9471_MAX_RANGES] = {
- [RT9471_RANGE_AICR] = { .min = 50000, .min_sel = 1, .max_sel = 63, .step = 50000 },
- [RT9471_RANGE_MIVR] = { .min = 3900000, .min_sel = 0, .max_sel = 15, .step = 100000 },
- [RT9471_RANGE_IPRE] = { .min = 50000, .min_sel = 0, .max_sel = 15, .step = 50000 },
- [RT9471_RANGE_VCHG] = { .min = 3900000, .min_sel = 0, .max_sel = 80, .step = 10000 },
- [RT9471_RANGE_ICHG] = { .min = 0, .min_sel = 0, .max_sel = 63, .step = 50000 },
- [RT9471_RANGE_IEOC] = { .min = 50000, .min_sel = 0, .max_sel = 15, .step = 50000 },
+ [RT9471_RANGE_AICR] = LINEAR_RANGE(50000, 1, 63, 50000),
+ [RT9471_RANGE_MIVR] = LINEAR_RANGE(3900000, 0, 15, 100000),
+ [RT9471_RANGE_IPRE] = LINEAR_RANGE(50000, 0, 15, 50000),
+ [RT9471_RANGE_VCHG] = LINEAR_RANGE(3900000, 0, 80, 10000),
+ [RT9471_RANGE_ICHG] = LINEAR_RANGE(0, 0, 63, 50000),
+ [RT9471_RANGE_IEOC] = LINEAR_RANGE(50000, 0, 15, 50000),
};
static int rt9471_set_value_by_field_range(struct rt9471_chip *chip,
@@ -723,7 +723,7 @@ static int rt9471_register_psy(struct rt9471_chip *chip)
char *psy_name;
cfg.drv_data = chip;
- cfg.of_node = dev->of_node;
+ cfg.fwnode = dev_fwnode(dev);
cfg.attr_grp = rt9471_sysfs_groups;
psy_name = devm_kasprintf(dev, GFP_KERNEL, "rt9471-%s", dev_name(dev));
diff --git a/drivers/power/supply/rt9756.c b/drivers/power/supply/rt9756.c
new file mode 100644
index 000000000000..f254527be653
--- /dev/null
+++ b/drivers/power/supply/rt9756.c
@@ -0,0 +1,955 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright (C) 2025 Richtek Technology Corp.
+//
+// Authors: ChiYuan Huang <cy_huang@richtek.com>
+
+#include <linux/atomic.h>
+#include <linux/cleanup.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/linear_range.h>
+#include <linux/interrupt.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/sysfs.h>
+#include <linux/util_macros.h>
+
+#define RT9756_REG_INTFLAG1 0x0B
+#define RT9756_REG_INTFLAG2 0x0D
+#define RT9756_REG_INTFLAG3 0x0F
+#define RT9756_REG_ADCCTL 0x11
+#define RT9756_REG_VBUSADC 0x12
+#define RT9756_REG_BC12FLAG 0x45
+#define RT9756_REG_INTFLAG4 0x49
+
+/* Flag1 */
+#define RT9756_EVT_BUSOVP BIT(3)
+#define RT9756_EVT_BUSOCP BIT(2)
+#define RT9756_EVT_BUSUCP BIT(0)
+/* Flag2 */
+#define RT9756_EVT_BATOVP BIT(7)
+#define RT9756_EVT_BATOCP BIT(6)
+#define RT9756_EVT_TDIEOTP BIT(3)
+#define RT9756_EVT_VBUSLOW_ERR BIT(2)
+#define RT9756_EVT_VAC_INSERT BIT(0)
+/* Flag3 */
+#define RT9756_EVT_WDT BIT(5)
+#define RT9756_EVT_VAC_UVLO BIT(4)
+/* ADCCTL */
+#define RT9756_ADCEN_MASK BIT(7)
+#define RT9756_ADCONCE_MASK BIT(6)
+/* Bc12_flag */
+#define RT9756_EVT_BC12_DONE BIT(3)
+/* Flag4 */
+#define RT9756_EVT_OUTOVP BIT(0)
+
+#define RICHTEK_DEVID 7
+#define RT9756_REVID 0
+#define RT9756A_REVID 1
+#define RT9757_REVID 2
+#define RT9757A_REVID 3
+#define RT9756_ADC_CONVTIME 1200
+#define RT9756_ADC_MAXWAIT 16000
+
+enum rt9756_model {
+ MODEL_RT9756 = 0,
+ MODEL_RT9757,
+ MODEL_RT9770,
+ MODEL_MAX
+};
+
+enum rt9756_adc_chan {
+ ADC_VBUS = 0,
+ ADC_IBUS,
+ ADC_VBAT,
+ ADC_IBAT,
+ ADC_TDIE,
+ ADC_MAX_CHANNEL
+};
+
+enum rt9756_usb_type {
+ USB_NO_VBUS = 0,
+ USB_SDP = 2,
+ USB_NSTD,
+ USB_DCP,
+ USB_CDP,
+ MAX_USB_TYPE
+};
+
+enum rt9756_fields {
+ F_VBATOVP = 0,
+ F_VBATOVP_EN,
+ F_IBATOCP,
+ F_IBATOCP_EN,
+ F_VBUSOVP,
+ F_VBUSOVP_EN,
+ F_IBUSOCP,
+ F_IBUSOCP_EN,
+ F_SWITCHING,
+ F_REG_RST,
+ F_CHG_EN,
+ F_OP_MODE,
+ F_WDT_DIS,
+ F_WDT_TMR,
+ F_DEV_ID,
+ F_BC12_EN,
+ F_USB_STATE,
+ F_VBUS_STATE,
+ F_IBAT_RSEN,
+ F_REVISION,
+ F_MAX_FIELD
+};
+
+enum rt9756_ranges {
+ R_VBATOVP = 0,
+ R_IBATOCP,
+ R_VBUSOVP,
+ R_IBUSOCP,
+ R_MAX_RANGE
+};
+
+static const struct reg_field rt9756_chg_fields[F_MAX_FIELD] = {
+ [F_VBATOVP] = REG_FIELD(0x08, 0, 4),
+ [F_VBATOVP_EN] = REG_FIELD(0x08, 7, 7),
+ [F_IBATOCP] = REG_FIELD(0x09, 0, 5),
+ [F_IBATOCP_EN] = REG_FIELD(0x09, 7, 7),
+ [F_VBUSOVP] = REG_FIELD(0x06, 0, 5),
+ [F_VBUSOVP_EN] = REG_FIELD(0x06, 7, 7),
+ [F_IBUSOCP] = REG_FIELD(0x07, 0, 4),
+ [F_IBUSOCP_EN] = REG_FIELD(0x07, 5, 5),
+ [F_SWITCHING] = REG_FIELD(0x5c, 7, 7),
+ [F_REG_RST] = REG_FIELD(0x00, 7, 7),
+ [F_CHG_EN] = REG_FIELD(0x00, 6, 6),
+ [F_OP_MODE] = REG_FIELD(0x00, 5, 5),
+ [F_WDT_DIS] = REG_FIELD(0x00, 3, 3),
+ [F_WDT_TMR] = REG_FIELD(0x00, 0, 2),
+ [F_DEV_ID] = REG_FIELD(0x03, 0, 3),
+ [F_BC12_EN] = REG_FIELD(0x44, 7, 7),
+ [F_USB_STATE] = REG_FIELD(0x46, 5, 7),
+ [F_VBUS_STATE] = REG_FIELD(0x4c, 0, 0),
+ [F_IBAT_RSEN] = REG_FIELD(0x5e, 0, 1),
+ [F_REVISION] = REG_FIELD(0x62, 0, 1),
+};
+
+static const struct reg_field rt9770_chg_fields[F_MAX_FIELD] = {
+ [F_VBATOVP] = REG_FIELD(0x08, 0, 4),
+ [F_VBATOVP_EN] = REG_FIELD(0x08, 7, 7),
+ [F_IBATOCP] = REG_FIELD(0x09, 0, 5),
+ [F_IBATOCP_EN] = REG_FIELD(0x09, 7, 7),
+ [F_VBUSOVP] = REG_FIELD(0x06, 0, 5),
+ [F_VBUSOVP_EN] = REG_FIELD(0x06, 7, 7),
+ [F_IBUSOCP] = REG_FIELD(0x07, 0, 4),
+ [F_IBUSOCP_EN] = REG_FIELD(0x07, 5, 5),
+ [F_SWITCHING] = REG_FIELD(0x5c, 7, 7),
+ [F_REG_RST] = REG_FIELD(0x00, 7, 7),
+ [F_CHG_EN] = REG_FIELD(0x00, 6, 6),
+ [F_OP_MODE] = REG_FIELD(0x00, 5, 5),
+ [F_WDT_DIS] = REG_FIELD(0x00, 3, 3),
+ [F_WDT_TMR] = REG_FIELD(0x00, 0, 2),
+ [F_DEV_ID] = REG_FIELD(0x60, 0, 3),
+ [F_BC12_EN] = REG_FIELD(0x03, 7, 7),
+ [F_USB_STATE] = REG_FIELD(0x02, 5, 7),
+ [F_VBUS_STATE] = REG_FIELD(0x4c, 0, 0),
+ [F_IBAT_RSEN] = REG_FIELD(0x5e, 0, 1),
+ [F_REVISION] = REG_FIELD(0x62, 3, 7),
+};
+
+/* All converted to microvolt or microamp */
+static const struct linear_range rt9756_chg_ranges[R_MAX_RANGE] = {
+ LINEAR_RANGE_IDX(R_VBATOVP, 4200000, 0, 31, 25000),
+ LINEAR_RANGE_IDX(R_IBATOCP, 2000000, 0, 63, 100000),
+ LINEAR_RANGE_IDX(R_VBUSOVP, 3000000, 0, 63, 50000),
+ LINEAR_RANGE_IDX(R_IBUSOCP, 1000000, 0, 31, 250000),
+};
+
+struct charger_event {
+ unsigned int flag1;
+ unsigned int flag2;
+ unsigned int flag3;
+ unsigned int flag4;
+};
+
+struct rt9756_data {
+ struct device *dev;
+ struct regmap *regmap;
+ struct regmap_field *rm_fields[F_MAX_FIELD];
+ struct power_supply *psy;
+ struct power_supply *bat_psy;
+ struct mutex adc_lock;
+ struct power_supply_desc psy_desc;
+ struct power_supply_desc bat_psy_desc;
+ struct charger_event chg_evt;
+ unsigned int rg_resistor;
+ unsigned int real_resistor;
+ enum rt9756_model model;
+ atomic_t usb_type;
+};
+
+struct rt975x_dev_data {
+ const struct regmap_config *regmap_config;
+ const struct reg_field *reg_fields;
+ const struct reg_sequence *init_regs;
+ size_t num_init_regs;
+ int (*check_device_model)(struct rt9756_data *data);
+};
+
+static int rt9756_get_value_field_range(struct rt9756_data *data, enum rt9756_fields en_field,
+ enum rt9756_fields field, enum rt9756_ranges rsel, int *val)
+{
+ const struct linear_range *range = rt9756_chg_ranges + rsel;
+ unsigned int enable, selector, value;
+ int ret;
+
+ ret = regmap_field_read(data->rm_fields[en_field], &enable);
+ if (ret)
+ return ret;
+
+ if (!enable) {
+ *val = 0;
+ return 0;
+ }
+
+ ret = regmap_field_read(data->rm_fields[field], &selector);
+ if (ret)
+ return ret;
+
+ ret = linear_range_get_value(range, selector, &value);
+ if (ret)
+ return ret;
+
+ *val = (int)value;
+
+ return 0;
+}
+
+static int rt9756_set_value_field_range(struct rt9756_data *data, enum rt9756_fields en_field,
+ enum rt9756_fields field, enum rt9756_ranges rsel, int val)
+{
+ const struct linear_range *range = rt9756_chg_ranges + rsel;
+ unsigned int selector, value;
+ int ret;
+
+ if (!val)
+ return regmap_field_write(data->rm_fields[en_field], 0);
+
+ value = (unsigned int)val;
+ linear_range_get_selector_within(range, value, &selector);
+ ret = regmap_field_write(data->rm_fields[field], selector);
+ if (ret)
+ return ret;
+
+ return regmap_field_write(data->rm_fields[en_field], 1);
+}
+
+static int rt9756_get_adc(struct rt9756_data *data, enum rt9756_adc_chan chan,
+ int *val)
+{
+ struct regmap *regmap = data->regmap;
+ unsigned int reg_addr = RT9756_REG_VBUSADC + chan * 2;
+ unsigned int mask = RT9756_ADCEN_MASK | RT9756_ADCONCE_MASK;
+ unsigned int shift = 0, adc_cntl;
+ __be16 raws;
+ int scale, offset = 0, ret;
+
+ guard(mutex)(&data->adc_lock);
+
+ ret = regmap_update_bits(regmap, RT9756_REG_ADCCTL, mask, mask);
+ if (ret)
+ return ret;
+
+ ret = regmap_read_poll_timeout(regmap, RT9756_REG_ADCCTL, adc_cntl,
+ !(adc_cntl & RT9756_ADCEN_MASK),
+ RT9756_ADC_CONVTIME, RT9756_ADC_MAXWAIT);
+ if (ret && ret != -ETIMEDOUT)
+ return ret;
+
+ ret = regmap_raw_read(regmap, reg_addr, &raws, sizeof(raws));
+ if (ret)
+ return ret;
+
+ /*
+ * TDIE LSB 1'c, others LSB 1000uV or 1000uA.
+ * Rsense ratio is needed for IBAT channel
+ */
+ if (chan == ADC_TDIE) {
+ scale = 10;
+ shift = 8;
+ offset = -40;
+ } else if (chan == ADC_IBAT)
+ scale = 1000 * data->rg_resistor / data->real_resistor;
+ else
+ scale = 1000;
+
+ *val = ((be16_to_cpu(raws) >> shift) + offset) * scale;
+
+ return regmap_update_bits(regmap, RT9756_REG_ADCCTL, mask, 0);
+}
+
+static int rt9756_get_switching_state(struct rt9756_data *data, int *status)
+{
+ unsigned int switching_state;
+ int ret;
+
+ ret = regmap_field_read(data->rm_fields[F_SWITCHING], &switching_state);
+ if (ret)
+ return ret;
+
+ if (switching_state)
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ *status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ return 0;
+}
+
+static int rt9756_get_charger_health(struct rt9756_data *data)
+{
+ struct charger_event *evt = &data->chg_evt;
+
+ if (evt->flag2 & RT9756_EVT_VBUSLOW_ERR)
+ return POWER_SUPPLY_HEALTH_UNDERVOLTAGE;
+
+ if (evt->flag1 & RT9756_EVT_BUSOVP || evt->flag2 & RT9756_EVT_BATOVP ||
+ evt->flag4 & RT9756_EVT_OUTOVP)
+ return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+
+ if (evt->flag1 & RT9756_EVT_BUSOCP || evt->flag2 & RT9756_EVT_BATOCP)
+ return POWER_SUPPLY_HEALTH_OVERCURRENT;
+
+ if (evt->flag1 & RT9756_EVT_BUSUCP)
+ return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+
+ if (evt->flag2 & RT9756_EVT_TDIEOTP)
+ return POWER_SUPPLY_HEALTH_OVERHEAT;
+
+ if (evt->flag3 & RT9756_EVT_WDT)
+ return POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE;
+
+ return POWER_SUPPLY_HEALTH_GOOD;
+}
+
+static int rt9756_get_charger_online(struct rt9756_data *data, int *val)
+{
+ unsigned int online;
+ int ret;
+
+ ret = regmap_field_read(data->rm_fields[F_VBUS_STATE], &online);
+ if (ret)
+ return ret;
+
+ *val = !!online;
+ return 0;
+}
+
+static int rt9756_get_vbus_ovp(struct rt9756_data *data, int *val)
+{
+ unsigned int opmode;
+ int ovpval, ret;
+
+ /* operating mode -> 0 bypass, 1 div2 */
+ ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode);
+ if (ret)
+ return ret;
+
+ ret = rt9756_get_value_field_range(data, F_VBUSOVP_EN, F_VBUSOVP, R_VBUSOVP, &ovpval);
+ if (ret)
+ return ret;
+
+ *val = opmode ? ovpval * 2 : ovpval;
+ return 0;
+}
+
+static int rt9756_set_vbus_ovp(struct rt9756_data *data, int val)
+{
+ unsigned int opmode;
+ int ret;
+
+ /* operating mode -> 0 bypass, 1 div2 */
+ ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode);
+ if (ret)
+ return ret;
+
+ return rt9756_set_value_field_range(data, F_VBUSOVP_EN, F_VBUSOVP, R_VBUSOVP,
+ opmode ? val / 2 : val);
+}
+
+static const char * const rt9756_manufacturer = "Richtek Technology Corp.";
+static const char * const rt9756_model[MODEL_MAX] = { "RT9756", "RT9757", "RT9770" };
+
+static int rt9756_psy_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct rt9756_data *data = power_supply_get_drvdata(psy);
+ int *pval = &val->intval;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ return rt9756_get_switching_state(data, pval);
+ case POWER_SUPPLY_PROP_HEALTH:
+ *pval = rt9756_get_charger_health(data);
+ return 0;
+ case POWER_SUPPLY_PROP_ONLINE:
+ return rt9756_get_charger_online(data, pval);
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ return rt9756_get_vbus_ovp(data, pval);
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return rt9756_get_adc(data, ADC_VBUS, pval);
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ return rt9756_get_value_field_range(data, F_IBUSOCP_EN, F_IBUSOCP, R_IBUSOCP, pval);
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return rt9756_get_adc(data, ADC_IBUS, pval);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ return rt9756_get_value_field_range(data, F_VBATOVP_EN, F_VBATOVP, R_VBATOVP, pval);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ return rt9756_get_value_field_range(data, F_IBATOCP_EN, F_IBATOCP, R_IBATOCP, pval);
+ case POWER_SUPPLY_PROP_TEMP:
+ return rt9756_get_adc(data, ADC_TDIE, pval);
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ *pval = atomic_read(&data->usb_type);
+ return 0;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = rt9756_model[data->model];
+ return 0;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = rt9756_manufacturer;
+ return 0;
+ default:
+ return -ENODATA;
+ }
+}
+
+static int rt9756_psy_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct rt9756_data *data = power_supply_get_drvdata(psy);
+ int intval = val->intval;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ memset(&data->chg_evt, 0, sizeof(data->chg_evt));
+ return regmap_field_write(data->rm_fields[F_CHG_EN], !!intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ return rt9756_set_vbus_ovp(data, intval);
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ return rt9756_set_value_field_range(data, F_IBUSOCP_EN, F_IBUSOCP, R_IBUSOCP,
+ intval);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ return rt9756_set_value_field_range(data, F_VBATOVP_EN, F_VBATOVP, R_VBATOVP,
+ intval);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ return rt9756_set_value_field_range(data, F_IBATOCP_EN, F_IBATOCP, R_IBATOCP,
+ intval);
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ return regmap_field_write(data->rm_fields[F_BC12_EN], !!intval);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const enum power_supply_property rt9756_psy_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_USB_TYPE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static int rt9756_bat_psy_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct rt9756_data *data = power_supply_get_drvdata(psy);
+ int *pval = &val->intval;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ *pval = POWER_SUPPLY_TECHNOLOGY_LION;
+ return 0;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return rt9756_get_adc(data, ADC_VBAT, pval);
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return rt9756_get_adc(data, ADC_IBAT, pval);
+ default:
+ return -ENODATA;
+ }
+}
+
+static const enum power_supply_property rt9756_bat_psy_properties[] = {
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static int rt9756_psy_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ case POWER_SUPPLY_PROP_ONLINE:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static const unsigned int rt9756_wdt_millisecond[] = {
+ 500, 1000, 5000, 30000, 40000, 80000, 128000, 255000
+};
+
+static ssize_t watchdog_timer_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct rt9756_data *data = power_supply_get_drvdata(psy);
+ unsigned int wdt_tmr_now = 0, wdt_sel, wdt_dis;
+ int ret;
+
+ ret = regmap_field_read(data->rm_fields[F_WDT_DIS], &wdt_dis);
+ if (ret)
+ return ret;
+
+ if (!wdt_dis) {
+ ret = regmap_field_read(data->rm_fields[F_WDT_TMR], &wdt_sel);
+ if (ret)
+ return ret;
+
+ wdt_tmr_now = rt9756_wdt_millisecond[wdt_sel];
+ }
+
+ return sysfs_emit(buf, "%d\n", wdt_tmr_now);
+}
+
+static ssize_t watchdog_timer_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct rt9756_data *data = power_supply_get_drvdata(psy);
+ unsigned int wdt_set, wdt_sel;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &wdt_set);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(data->rm_fields[F_WDT_DIS], 1);
+ if (ret)
+ return ret;
+
+ wdt_sel = find_closest(wdt_set, rt9756_wdt_millisecond,
+ ARRAY_SIZE(rt9756_wdt_millisecond));
+
+ ret = regmap_field_write(data->rm_fields[F_WDT_TMR], wdt_sel);
+ if (ret)
+ return ret;
+
+ if (wdt_set) {
+ ret = regmap_field_write(data->rm_fields[F_WDT_DIS], 0);
+ if (ret)
+ return ret;
+ }
+
+ return count;
+}
+
+static const char * const rt9756_opmode_str[] = { "bypass", "div2" };
+
+static ssize_t operation_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct rt9756_data *data = power_supply_get_drvdata(psy);
+ unsigned int opmode;
+ int ret;
+
+ ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", rt9756_opmode_str[opmode]);
+}
+
+static ssize_t operation_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct rt9756_data *data = power_supply_get_drvdata(psy);
+ int index, ret;
+
+ index = sysfs_match_string(rt9756_opmode_str, buf);
+ if (index < 0)
+ return index;
+
+ ret = regmap_field_write(data->rm_fields[F_OP_MODE], index);
+
+ return ret ?: count;
+}
+
+static DEVICE_ATTR_RW(watchdog_timer);
+static DEVICE_ATTR_RW(operation_mode);
+
+static struct attribute *rt9756_sysfs_attrs[] = {
+ &dev_attr_watchdog_timer.attr,
+ &dev_attr_operation_mode.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(rt9756_sysfs);
+
+static int rt9756_register_psy(struct rt9756_data *data)
+{
+ struct power_supply_desc *desc = &data->psy_desc;
+ struct power_supply_desc *bat_desc = &data->bat_psy_desc;
+ struct power_supply_config cfg = {}, bat_cfg = {};
+ struct device *dev = data->dev;
+ char *psy_name, *bat_psy_name, **supplied_to;
+
+ bat_cfg.drv_data = data;
+ bat_cfg.fwnode = dev_fwnode(dev);
+
+ bat_psy_name = devm_kasprintf(dev, GFP_KERNEL, "rt9756-%s-battery", dev_name(dev));
+ if (!bat_psy_name)
+ return -ENOMEM;
+
+ bat_desc->name = bat_psy_name;
+ bat_desc->type = POWER_SUPPLY_TYPE_BATTERY;
+ bat_desc->properties = rt9756_bat_psy_properties;
+ bat_desc->num_properties = ARRAY_SIZE(rt9756_bat_psy_properties);
+ bat_desc->get_property = rt9756_bat_psy_get_property;
+
+ data->bat_psy = devm_power_supply_register(dev, bat_desc, &bat_cfg);
+ if (IS_ERR(data->bat_psy))
+ return dev_err_probe(dev, PTR_ERR(data->bat_psy), "Failed to register battery\n");
+
+ supplied_to = devm_kzalloc(dev, sizeof(*supplied_to), GFP_KERNEL);
+ if (!supplied_to)
+ return -ENOMEM;
+
+ /* Link charger psy to battery psy */
+ supplied_to[0] = bat_psy_name;
+
+ cfg.drv_data = data;
+ cfg.fwnode = dev_fwnode(dev);
+ cfg.attr_grp = rt9756_sysfs_groups;
+ cfg.supplied_to = supplied_to;
+ cfg.num_supplicants = 1;
+
+ psy_name = devm_kasprintf(dev, GFP_KERNEL, "rt9756-%s", dev_name(dev));
+ if (!psy_name)
+ return -ENOMEM;
+
+ desc->name = psy_name;
+ desc->type = POWER_SUPPLY_TYPE_USB;
+ desc->usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | BIT(POWER_SUPPLY_USB_TYPE_SDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_DCP) | BIT(POWER_SUPPLY_USB_TYPE_CDP);
+ desc->properties = rt9756_psy_properties;
+ desc->num_properties = ARRAY_SIZE(rt9756_psy_properties);
+ desc->property_is_writeable = rt9756_psy_property_is_writeable;
+ desc->get_property = rt9756_psy_get_property;
+ desc->set_property = rt9756_psy_set_property;
+
+ data->psy = devm_power_supply_register(dev, desc, &cfg);
+
+ return PTR_ERR_OR_ZERO(data->psy);
+}
+
+static int rt9756_get_usb_type(struct rt9756_data *data)
+{
+ unsigned int type;
+ int report_type, ret;
+
+ ret = regmap_field_read(data->rm_fields[F_USB_STATE], &type);
+ if (ret)
+ return ret;
+
+ switch (type) {
+ case USB_SDP:
+ case USB_NSTD:
+ report_type = POWER_SUPPLY_USB_TYPE_SDP;
+ break;
+ case USB_DCP:
+ report_type = POWER_SUPPLY_USB_TYPE_DCP;
+ break;
+ case USB_CDP:
+ report_type = POWER_SUPPLY_USB_TYPE_CDP;
+ break;
+ case USB_NO_VBUS:
+ default:
+ report_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+ break;
+ }
+
+ atomic_set(&data->usb_type, report_type);
+ return 0;
+}
+
+static irqreturn_t rt9756_irq_handler(int irq, void *devid)
+{
+ struct rt9756_data *data = devid;
+ struct regmap *regmap = data->regmap;
+ struct charger_event *evt = &data->chg_evt;
+ unsigned int bc12_flag = 0;
+ int ret;
+
+ ret = regmap_read(regmap, RT9756_REG_INTFLAG1, &evt->flag1);
+ if (ret)
+ return IRQ_NONE;
+
+ ret = regmap_read(regmap, RT9756_REG_INTFLAG2, &evt->flag2);
+ if (ret)
+ return IRQ_NONE;
+
+ ret = regmap_read(regmap, RT9756_REG_INTFLAG3, &evt->flag3);
+ if (ret)
+ return IRQ_NONE;
+
+ if (data->model != MODEL_RT9770) {
+ ret = regmap_read(regmap, RT9756_REG_INTFLAG4, &evt->flag4);
+ if (ret)
+ return IRQ_NONE;
+
+ ret = regmap_read(regmap, RT9756_REG_BC12FLAG, &bc12_flag);
+ if (ret)
+ return IRQ_NONE;
+ }
+
+ dev_dbg(data->dev, "events: 0x%02x,%02x,%02x,%02x,%02x\n", evt->flag1, evt->flag2,
+ evt->flag3, evt->flag4, bc12_flag);
+
+ if (evt->flag2 & RT9756_EVT_VAC_INSERT) {
+ ret = regmap_field_write(data->rm_fields[F_BC12_EN], 1);
+ if (ret)
+ return IRQ_NONE;
+ }
+
+ if (evt->flag3 & RT9756_EVT_VAC_UVLO)
+ atomic_set(&data->usb_type, POWER_SUPPLY_USB_TYPE_UNKNOWN);
+
+ if (bc12_flag & RT9756_EVT_BC12_DONE) {
+ ret = rt9756_get_usb_type(data);
+ if (ret)
+ return IRQ_NONE;
+ }
+
+ power_supply_changed(data->psy);
+
+ return IRQ_HANDLED;
+}
+
+static int rt9756_config_batsense_resistor(struct rt9756_data *data)
+{
+ unsigned int shunt_resistor_uohms = 2000, rsense_sel;
+
+ device_property_read_u32(data->dev, "shunt-resistor-micro-ohms", &shunt_resistor_uohms);
+
+ if (!shunt_resistor_uohms || shunt_resistor_uohms > 5000)
+ return -EINVAL;
+
+ data->real_resistor = shunt_resistor_uohms;
+
+ /* Always choose the larger or equal one to prevent false ocp alarm */
+ if (shunt_resistor_uohms <= 1000) {
+ rsense_sel = 0;
+ data->rg_resistor = 1000;
+ } else if (shunt_resistor_uohms <= 2000) {
+ rsense_sel = 1;
+ data->rg_resistor = 2000;
+ } else {
+ rsense_sel = 2;
+ data->rg_resistor = 5000;
+ }
+
+ return regmap_field_write(data->rm_fields[F_IBAT_RSEN], rsense_sel);
+}
+
+static const struct reg_sequence rt9756_init_regs[] = {
+ REG_SEQ(0x00, 0x80, 1000), /* REG_RESET */
+ REG_SEQ0(0x04, 0x13), /* VACOVP/OVPGATE 12V */
+ REG_SEQ0(0x00, 0x28), /* WDT_DIS = 1 */
+ REG_SEQ0(0x0c, 0x02), /* MASK FLAG1 */
+ REG_SEQ0(0x0e, 0x06), /* MASK FLAG2 */
+ REG_SEQ0(0x10, 0xca), /* MASK FLAG3 */
+ REG_SEQ0(0x44, 0xa0), /* BC12_EN */
+ REG_SEQ0(0x47, 0x07), /* MASK BC12FLAG */
+ REG_SEQ0(0x4a, 0xfe), /* MASK FLAG4 */
+ REG_SEQ0(0x5c, 0x40), /* MASK CON_SWITCHING */
+ REG_SEQ0(0x63, 0x01), /* MASK VDDA_UVLO */
+};
+
+static const struct reg_sequence rt9770_init_regs[] = {
+ REG_SEQ(0x00, 0x80, 1000), /* REG_RESET */
+ REG_SEQ0(0x04, 0x13), /* VACOVP/OVPGATE 12V */
+ REG_SEQ0(0x00, 0x28), /* WDT_DIS = 1 */
+ REG_SEQ0(0x0c, 0x02), /* MASK FLAG1 */
+ REG_SEQ0(0x0e, 0x06), /* MASK FLAG2 */
+ REG_SEQ0(0x10, 0xca), /* MASK FLAG3 */
+ REG_SEQ0(0x5c, 0x40), /* MASK CON_SWITCHING */
+ REG_SEQ0(0x63, 0x01), /* MASK VDDA_UVLO */
+};
+
+static const struct regmap_config rt9756_regmap_config = {
+ .name = "rt9756",
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = 0x1ff,
+};
+
+static const struct regmap_config rt9770_regmap_config = {
+ .name = "rt9770",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+};
+
+static int rt9756_check_device_model(struct rt9756_data *data)
+{
+ struct device *dev = data->dev;
+ unsigned int revid;
+ int ret;
+
+ ret = regmap_field_read(data->rm_fields[F_REVISION], &revid);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read revid\n");
+
+ if (revid == RT9757_REVID || revid == RT9757A_REVID)
+ data->model = MODEL_RT9757;
+ else if (revid == RT9756_REVID || revid == RT9756A_REVID)
+ data->model = MODEL_RT9756;
+ else
+ return dev_err_probe(dev, -EINVAL, "Unknown revision %d\n", revid);
+
+ return 0;
+}
+
+static int rt9770_check_device_model(struct rt9756_data *data)
+{
+ data->model = MODEL_RT9770;
+ return 0;
+}
+
+static int rt9756_probe(struct i2c_client *i2c)
+{
+ const struct rt975x_dev_data *dev_data;
+ struct device *dev = &i2c->dev;
+ struct rt9756_data *data;
+ struct regmap *regmap;
+ unsigned int devid;
+ int ret;
+
+ dev_data = device_get_match_data(dev);
+ if (!dev_data)
+ return dev_err_probe(dev, -EINVAL, "No device data found\n");
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = dev;
+ mutex_init(&data->adc_lock);
+ atomic_set(&data->usb_type, POWER_SUPPLY_USB_TYPE_UNKNOWN);
+ i2c_set_clientdata(i2c, data);
+
+ regmap = devm_regmap_init_i2c(i2c, dev_data->regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to init regmap\n");
+
+ data->regmap = regmap;
+
+ ret = devm_regmap_field_bulk_alloc(dev, regmap, data->rm_fields, dev_data->reg_fields,
+ F_MAX_FIELD);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to alloc regmap fields\n");
+
+ /* Richtek Device ID check */
+ ret = regmap_field_read(data->rm_fields[F_DEV_ID], &devid);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read devid\n");
+
+ if (devid != RICHTEK_DEVID)
+ return dev_err_probe(dev, -ENODEV, "Incorrect VID 0x%02x\n", devid);
+
+ /* Get specific model */
+ ret = dev_data->check_device_model(data);
+ if (ret)
+ return ret;
+
+ ret = regmap_register_patch(regmap, dev_data->init_regs, dev_data->num_init_regs);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init registers\n");
+
+ ret = rt9756_config_batsense_resistor(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to config batsense resistor\n");
+
+ ret = rt9756_register_psy(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init power supply\n");
+
+ return devm_request_threaded_irq(dev, i2c->irq, NULL, rt9756_irq_handler, IRQF_ONESHOT,
+ dev_name(dev), data);
+}
+
+static void rt9756_shutdown(struct i2c_client *i2c)
+{
+ struct rt9756_data *data = i2c_get_clientdata(i2c);
+
+ regmap_field_write(data->rm_fields[F_REG_RST], 1);
+}
+
+static const struct rt975x_dev_data rt9756_dev_data = {
+ .regmap_config = &rt9756_regmap_config,
+ .reg_fields = rt9756_chg_fields,
+ .init_regs = rt9756_init_regs,
+ .num_init_regs = ARRAY_SIZE(rt9756_init_regs),
+ .check_device_model = rt9756_check_device_model,
+};
+
+static const struct rt975x_dev_data rt9770_dev_data = {
+ .regmap_config = &rt9770_regmap_config,
+ .reg_fields = rt9770_chg_fields,
+ .init_regs = rt9770_init_regs,
+ .num_init_regs = ARRAY_SIZE(rt9770_init_regs),
+ .check_device_model = rt9770_check_device_model,
+};
+
+static const struct of_device_id rt9756_device_match_table[] = {
+ { .compatible = "richtek,rt9756", .data = &rt9756_dev_data },
+ { .compatible = "richtek,rt9770", .data = &rt9770_dev_data },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rt9756_device_match_table);
+
+static struct i2c_driver rt9756_charger_driver = {
+ .driver = {
+ .name = "rt9756",
+ .of_match_table = rt9756_device_match_table,
+ },
+ .probe = rt9756_probe,
+ .shutdown = rt9756_shutdown,
+};
+module_i2c_driver(rt9756_charger_driver);
+
+MODULE_DESCRIPTION("Richtek RT9756 charger driver");
+MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/rx51_battery.c b/drivers/power/supply/rx51_battery.c
index 7cdcd415e868..b0220ec2d926 100644
--- a/drivers/power/supply/rx51_battery.c
+++ b/drivers/power/supply/rx51_battery.c
@@ -116,7 +116,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di)
int mid = (max + min) / 2;
if (rx51_temp_table2[mid] <= raw)
min = mid;
- else if (rx51_temp_table2[mid] > raw)
+ else
max = mid;
if (rx51_temp_table2[mid] == raw)
break;
diff --git a/drivers/power/supply/s2mu005-battery.c b/drivers/power/supply/s2mu005-battery.c
new file mode 100644
index 000000000000..64c57a14ef7d
--- /dev/null
+++ b/drivers/power/supply/s2mu005-battery.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Battery Fuel Gauge Driver for Samsung S2MU005 PMIC.
+ *
+ * Copyright (C) 2015 Samsung Electronics
+ * Copyright (C) 2023 Yassine Oudjana <y.oudjana@protonmail.com>
+ * Copyright (C) 2025 Kaustabh Chakraborty <kauschluss@disroot.org>
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#define S2MU005_FG_REG_STATUS 0x00
+#define S2MU005_FG_REG_IRQ 0x02
+#define S2MU005_FG_REG_RVBAT 0x04
+#define S2MU005_FG_REG_RCURCC 0x06
+#define S2MU005_FG_REG_RSOC 0x08
+#define S2MU005_FG_REG_MONOUT 0x0a
+#define S2MU005_FG_REG_MONOUTSEL 0x0c
+#define S2MU005_FG_REG_RBATCAP 0x0e
+#define S2MU005_FG_REG_RZADJ 0x12
+#define S2MU005_FG_REG_RBATZ0 0x16
+#define S2MU005_FG_REG_RBATZ1 0x18
+#define S2MU005_FG_REG_IRQLVL 0x1a
+#define S2MU005_FG_REG_START 0x1e
+
+#define S2MU005_FG_MONOUTSEL_AVGCURRENT 0x26
+#define S2MU005_FG_MONOUTSEL_AVGVOLTAGE 0x27
+
+struct s2mu005_fg {
+ struct device *dev;
+ struct regmap *regmap;
+ struct power_supply *psy;
+ struct mutex monout_mutex;
+};
+
+static const struct regmap_config s2mu005_fg_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static irqreturn_t s2mu005_handle_irq(int irq, void *data)
+{
+ struct s2mu005_fg *priv = data;
+
+ msleep(100);
+ power_supply_changed(priv->psy);
+
+ return IRQ_HANDLED;
+}
+
+static int s2mu005_fg_get_voltage_now(struct s2mu005_fg *priv, int *value)
+{
+ struct regmap *regmap = priv->regmap;
+ u32 val;
+ int ret;
+
+ ret = regmap_read(regmap, S2MU005_FG_REG_RVBAT, &val);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to read voltage register (%d)\n", ret);
+ return ret;
+ }
+
+ *value = (val * MICRO) >> 13;
+
+ return 0;
+}
+
+static int s2mu005_fg_get_voltage_avg(struct s2mu005_fg *priv, int *value)
+{
+ struct regmap *regmap = priv->regmap;
+ u32 val;
+ int ret;
+
+ mutex_lock(&priv->monout_mutex);
+
+ ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL,
+ S2MU005_FG_MONOUTSEL_AVGVOLTAGE);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to enable average voltage monitoring (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to read current register (%d)\n", ret);
+ goto unlock;
+ }
+
+ *value = (val * MICRO) >> 12;
+
+unlock:
+ mutex_unlock(&priv->monout_mutex);
+
+ return ret;
+}
+static int s2mu005_fg_get_current_now(struct s2mu005_fg *priv, int *value)
+{
+ struct regmap *regmap = priv->regmap;
+ u32 val;
+ int ret;
+
+ ret = regmap_read(regmap, S2MU005_FG_REG_RCURCC, &val);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to read current register (%d)\n", ret);
+ return ret;
+ }
+
+ *value = -((s16)val * MICRO) >> 12;
+
+ return 0;
+}
+
+static int s2mu005_fg_get_current_avg(struct s2mu005_fg *priv, int *value)
+{
+ struct regmap *regmap = priv->regmap;
+ u32 val;
+ int ret;
+
+ mutex_lock(&priv->monout_mutex);
+
+ ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL,
+ S2MU005_FG_MONOUTSEL_AVGCURRENT);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to enable average current monitoring (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to read current register (%d)\n", ret);
+ goto unlock;
+ }
+
+ *value = -((s16)val * MICRO) >> 12;
+
+unlock:
+ mutex_unlock(&priv->monout_mutex);
+
+ return ret;
+}
+
+static int s2mu005_fg_get_capacity(struct s2mu005_fg *priv, int *value)
+{
+ struct regmap *regmap = priv->regmap;
+ u32 val;
+ int ret;
+
+ ret = regmap_read(regmap, S2MU005_FG_REG_RSOC, &val);
+ if (ret < 0) {
+ dev_err(priv->dev, "failed to read capacity register (%d)\n", ret);
+ return ret;
+ }
+
+ *value = (val * CENTI) >> 14;
+
+ return 0;
+}
+
+static int s2mu005_fg_get_status(struct s2mu005_fg *priv, int *value)
+{
+ int current_now, current_avg, capacity;
+ int ret;
+
+ ret = s2mu005_fg_get_current_now(priv, &current_now);
+ if (ret < 0)
+ return ret;
+
+ ret = s2mu005_fg_get_current_avg(priv, &current_avg);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Verify both current values reported to reduce inaccuracies due to
+ * internal hysteresis.
+ */
+ if (current_now < 0 && current_avg < 0) {
+ *value = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else if (current_now == 0) {
+ *value = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ *value = POWER_SUPPLY_STATUS_CHARGING;
+
+ ret = s2mu005_fg_get_capacity(priv, &capacity);
+ if (!ret && capacity > 98)
+ *value = POWER_SUPPLY_STATUS_FULL;
+ return ret;
+ }
+
+ return 0;
+}
+
+static const enum power_supply_property s2mu005_fg_properties[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_STATUS,
+};
+
+static int s2mu005_fg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct s2mu005_fg *priv = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return s2mu005_fg_get_voltage_now(priv, &val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ return s2mu005_fg_get_voltage_avg(priv, &val->intval);
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return s2mu005_fg_get_current_now(priv, &val->intval);
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ return s2mu005_fg_get_current_avg(priv, &val->intval);
+ case POWER_SUPPLY_PROP_CAPACITY:
+ return s2mu005_fg_get_capacity(priv, &val->intval);
+ case POWER_SUPPLY_PROP_STATUS:
+ return s2mu005_fg_get_status(priv, &val->intval);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct power_supply_desc s2mu005_fg_desc = {
+ .name = "s2mu005-fuel-gauge",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = s2mu005_fg_properties,
+ .num_properties = ARRAY_SIZE(s2mu005_fg_properties),
+ .get_property = s2mu005_fg_get_property,
+};
+
+static int s2mu005_fg_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct s2mu005_fg *priv;
+ struct power_supply_config psy_cfg = {};
+ const struct power_supply_desc *psy_desc;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, priv);
+ priv->dev = dev;
+
+ priv->regmap = devm_regmap_init_i2c(client, &s2mu005_fg_regmap_config);
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap),
+ "failed to initialize regmap\n");
+
+ ret = devm_mutex_init(dev, &priv->monout_mutex);
+ if (ret)
+ dev_err_probe(dev, ret, "failed to initialize MONOUT mutex\n");
+
+ psy_desc = device_get_match_data(dev);
+
+ psy_cfg.drv_data = priv;
+ psy_cfg.fwnode = dev_fwnode(dev);
+ priv->psy = devm_power_supply_register(priv->dev, psy_desc, &psy_cfg);
+ if (IS_ERR(priv->psy))
+ return dev_err_probe(dev, PTR_ERR(priv->psy),
+ "failed to register power supply subsystem\n");
+
+ ret = devm_request_threaded_irq(priv->dev, client->irq, NULL,
+ s2mu005_handle_irq, IRQF_ONESHOT,
+ psy_desc->name, priv);
+ if (ret)
+ dev_err_probe(dev, ret, "failed to request IRQ\n");
+
+ return 0;
+}
+
+static const struct of_device_id s2mu005_fg_of_match_table[] = {
+ {
+ .compatible = "samsung,s2mu005-fuel-gauge",
+ .data = &s2mu005_fg_desc,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, s2mu005_fg_of_match_table);
+
+static struct i2c_driver s2mu005_fg_i2c_driver = {
+ .probe = s2mu005_fg_i2c_probe,
+ .driver = {
+ .name = "s2mu005-fuel-gauge",
+ .of_match_table = s2mu005_fg_of_match_table,
+ },
+};
+module_i2c_driver(s2mu005_fg_i2c_driver);
+
+MODULE_DESCRIPTION("Samsung S2MU005 PMIC Battery Fuel Gauge Driver");
+MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>");
+MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c
index 6f3d0413b1c1..43c48196c167 100644
--- a/drivers/power/supply/sbs-battery.c
+++ b/drivers/power/supply/sbs-battery.c
@@ -1138,7 +1138,7 @@ static int sbs_probe(struct i2c_client *client)
chip->flags = (uintptr_t)i2c_get_match_data(client);
chip->client = client;
- psy_cfg.of_node = client->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&client->dev);
psy_cfg.drv_data = chip;
chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
sbs_invalidate_cached_props(chip);
@@ -1174,24 +1174,6 @@ static int sbs_probe(struct i2c_client *client)
i2c_set_clientdata(client, chip);
- if (!chip->gpio_detect)
- goto skip_gpio;
-
- irq = gpiod_to_irq(chip->gpio_detect);
- if (irq <= 0) {
- dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq);
- goto skip_gpio;
- }
-
- rc = devm_request_threaded_irq(&client->dev, irq, NULL, sbs_irq,
- IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
- dev_name(&client->dev), chip);
- if (rc) {
- dev_warn(&client->dev, "Failed to request irq: %d\n", rc);
- goto skip_gpio;
- }
-
-skip_gpio:
/*
* Before we register, we might need to make sure we can actually talk
* to the battery.
@@ -1217,6 +1199,24 @@ skip_gpio:
return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply),
"Failed to register power supply\n");
+ if (!chip->gpio_detect)
+ goto out;
+
+ irq = gpiod_to_irq(chip->gpio_detect);
+ if (irq <= 0) {
+ dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq);
+ goto out;
+ }
+
+ rc = devm_request_threaded_irq(&client->dev, irq, NULL, sbs_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ dev_name(&client->dev), chip);
+ if (rc) {
+ dev_warn(&client->dev, "Failed to request irq: %d\n", rc);
+ goto out;
+ }
+
+out:
dev_info(&client->dev,
"%s: battery gas gauge device registered\n", client->name);
diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c
index ab3f095d90ea..7d5e67620580 100644
--- a/drivers/power/supply/sbs-charger.c
+++ b/drivers/power/supply/sbs-charger.c
@@ -154,8 +154,7 @@ static const struct regmap_config sbs_regmap = {
.val_format_endian = REGMAP_ENDIAN_LITTLE, /* since based on SMBus */
};
-static const struct power_supply_desc sbs_desc = {
- .name = "sbs-charger",
+static const struct power_supply_desc sbs_default_desc = {
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = sbs_properties,
.num_properties = ARRAY_SIZE(sbs_properties),
@@ -165,15 +164,26 @@ static const struct power_supply_desc sbs_desc = {
static int sbs_probe(struct i2c_client *client)
{
struct power_supply_config psy_cfg = {};
+ struct power_supply_desc *sbs_desc;
struct sbs_info *chip;
int ret, val;
+ sbs_desc = devm_kmemdup(&client->dev, &sbs_default_desc,
+ sizeof(*sbs_desc), GFP_KERNEL);
+ if (!sbs_desc)
+ return -ENOMEM;
+
+ sbs_desc->name = devm_kasprintf(&client->dev, GFP_KERNEL, "sbs-%s",
+ dev_name(&client->dev));
+ if (!sbs_desc->name)
+ return -ENOMEM;
+
chip = devm_kzalloc(&client->dev, sizeof(struct sbs_info), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->client = client;
- psy_cfg.of_node = client->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&client->dev);
psy_cfg.drv_data = chip;
i2c_set_clientdata(client, chip);
@@ -191,7 +201,7 @@ static int sbs_probe(struct i2c_client *client)
return dev_err_probe(&client->dev, ret, "Failed to get device status\n");
chip->last_state = val;
- chip->power_supply = devm_power_supply_register(&client->dev, &sbs_desc, &psy_cfg);
+ chip->power_supply = devm_power_supply_register(&client->dev, sbs_desc, &psy_cfg);
if (IS_ERR(chip->power_supply))
return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply),
"Failed to register power supply\n");
diff --git a/drivers/power/supply/sbs-manager.c b/drivers/power/supply/sbs-manager.c
index 7d2f39f19acb..343ad4ab4082 100644
--- a/drivers/power/supply/sbs-manager.c
+++ b/drivers/power/supply/sbs-manager.c
@@ -199,7 +199,7 @@ static int sbsm_gpio_get_value(struct gpio_chip *gc, unsigned int off)
if (ret < 0)
return ret;
- return ret & BIT(off);
+ return !!(ret & BIT(off));
}
/*
@@ -348,7 +348,7 @@ static int sbsm_probe(struct i2c_client *client)
data->muxc = i2c_mux_alloc(adapter, dev, SBSM_MAX_BATS, 0,
I2C_MUX_LOCKED, &sbsm_select, NULL);
if (!data->muxc)
- return dev_err_probe(dev, -ENOMEM, "failed to alloc i2c mux\n");
+ return -ENOMEM;
data->muxc->priv = data;
ret = devm_add_action_or_reset(dev, sbsm_del_mux_adapter, data);
@@ -379,7 +379,7 @@ static int sbsm_probe(struct i2c_client *client)
return ret;
psy_cfg.drv_data = data;
- psy_cfg.of_node = dev->of_node;
+ psy_cfg.fwnode = dev_fwnode(dev);
data->psy = devm_power_supply_register(dev, psy_desc, &psy_cfg);
if (IS_ERR(data->psy))
return dev_err_probe(dev, PTR_ERR(data->psy),
diff --git a/drivers/power/supply/sc2731_charger.c b/drivers/power/supply/sc2731_charger.c
index 50d5157af927..58b86fd78771 100644
--- a/drivers/power/supply/sc2731_charger.c
+++ b/drivers/power/supply/sc2731_charger.c
@@ -480,7 +480,7 @@ static int sc2731_charger_probe(struct platform_device *pdev)
}
charger_cfg.drv_data = info;
- charger_cfg.of_node = np;
+ charger_cfg.fwnode = dev_fwnode(&pdev->dev);
info->psy_usb = devm_power_supply_register(&pdev->dev,
&sc2731_charger_desc,
&charger_cfg);
diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c
index f36edc2ba708..a7ed9de8a289 100644
--- a/drivers/power/supply/sc27xx_fuel_gauge.c
+++ b/drivers/power/supply/sc27xx_fuel_gauge.c
@@ -1014,9 +1014,8 @@ static int sc27xx_fgu_hw_init(struct sc27xx_fgu_data *data)
if (!table)
return -EINVAL;
- data->cap_table = devm_kmemdup(data->dev, table,
- data->table_len * sizeof(*table),
- GFP_KERNEL);
+ data->cap_table = devm_kmemdup_array(data->dev, table, data->table_len,
+ sizeof(*table), GFP_KERNEL);
if (!data->cap_table) {
power_supply_put_battery_info(data->battery, info);
return -ENOMEM;
@@ -1141,7 +1140,6 @@ disable_fgu:
static int sc27xx_fgu_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct device_node *np = dev->of_node;
struct power_supply_config fgu_cfg = { };
struct sc27xx_fgu_data *data;
int ret, irq;
@@ -1205,7 +1203,7 @@ static int sc27xx_fgu_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, data);
fgu_cfg.drv_data = data;
- fgu_cfg.of_node = np;
+ fgu_cfg.fwnode = dev_fwnode(dev);
data->battery = devm_power_supply_register(dev, &sc27xx_fgu_desc,
&fgu_cfg);
if (IS_ERR(data->battery)) {
diff --git a/drivers/power/supply/smb347-charger.c b/drivers/power/supply/smb347-charger.c
index c8392933ee28..8b95f7e8712f 100644
--- a/drivers/power/supply/smb347-charger.c
+++ b/drivers/power/supply/smb347-charger.c
@@ -1488,7 +1488,7 @@ static const struct regmap_config smb347_regmap = {
.max_register = SMB347_MAX_REGISTER,
.volatile_reg = smb347_volatile_reg,
.readable_reg = smb347_readable_reg,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
};
static const struct regulator_ops smb347_usb_vbus_regulator_ops = {
@@ -1553,7 +1553,7 @@ static int smb347_probe(struct i2c_client *client)
return PTR_ERR(smb->regmap);
mains_usb_cfg.drv_data = smb;
- mains_usb_cfg.of_node = dev->of_node;
+ mains_usb_cfg.fwnode = dev_fwnode(dev);
if (smb->use_mains) {
smb->mains = devm_power_supply_register(dev, &smb347_mains_desc,
&mains_usb_cfg);
diff --git a/drivers/power/supply/test_power.c b/drivers/power/supply/test_power.c
index 2a975a110f48..2c0e9ad820c0 100644
--- a/drivers/power/supply/test_power.c
+++ b/drivers/power/supply/test_power.c
@@ -37,6 +37,8 @@ static int battery_charge_counter = -1000;
static int battery_current = -1600;
static enum power_supply_charge_behaviour battery_charge_behaviour =
POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+static enum power_supply_charge_type battery_charge_types =
+ POWER_SUPPLY_CHARGE_TYPE_STANDARD;
static bool battery_extension;
static bool module_initialized;
@@ -87,7 +89,7 @@ static int test_power_get_battery_property(struct power_supply *psy,
val->intval = battery_status;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
- val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ val->intval = battery_charge_types;
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = battery_health;
@@ -129,6 +131,9 @@ static int test_power_get_battery_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
val->intval = battery_charge_behaviour;
break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPES:
+ val->intval = battery_charge_types;
+ break;
default:
pr_info("%s: some properties deliberately report errors.\n",
__func__);
@@ -140,7 +145,7 @@ static int test_power_get_battery_property(struct power_supply *psy,
static int test_power_battery_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
- return psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR;
+ return psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR || psp == POWER_SUPPLY_PROP_CHARGE_TYPES;
}
static int test_power_set_battery_property(struct power_supply *psy,
@@ -156,6 +161,14 @@ static int test_power_set_battery_property(struct power_supply *psy,
}
battery_charge_behaviour = val->intval;
break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPES:
+ if (val->intval < 0 ||
+ val->intval >= BITS_PER_TYPE(typeof(psy->desc->charge_types)) ||
+ !(BIT(val->intval) & psy->desc->charge_types)) {
+ return -EINVAL;
+ }
+ battery_charge_types = val->intval;
+ break;
default:
return -EINVAL;
}
@@ -188,6 +201,7 @@ static enum power_supply_property test_power_battery_props[] = {
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+ POWER_SUPPLY_PROP_CHARGE_TYPES,
};
static char *test_power_ac_supplied_to[] = {
@@ -214,7 +228,10 @@ static const struct power_supply_desc test_power_desc[] = {
.property_is_writeable = test_power_battery_property_is_writeable,
.charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO)
| BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE)
+ | BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE)
| BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE),
+ .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD)
+ | BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)
},
[TEST_USB] = {
.name = "test_usb",
@@ -242,6 +259,7 @@ static const struct power_supply_config test_power_configs[] = {
static int test_power_battery_extmanufacture_year = 1234;
static int test_power_battery_exttemp_max = 1000;
static const enum power_supply_property test_power_battery_extprops[] = {
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_MANUFACTURE_YEAR,
POWER_SUPPLY_PROP_TEMP_MAX,
};
@@ -253,6 +271,9 @@ static int test_power_battery_extget_property(struct power_supply *psy,
union power_supply_propval *val)
{
switch (psp) {
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ return power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ val);
case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
val->intval = test_power_battery_extmanufacture_year;
break;
diff --git a/drivers/power/supply/tps65090-charger.c b/drivers/power/supply/tps65090-charger.c
index d65193e410a6..d010f013af8c 100644
--- a/drivers/power/supply/tps65090-charger.c
+++ b/drivers/power/supply/tps65090-charger.c
@@ -259,7 +259,7 @@ static int tps65090_charger_probe(struct platform_device *pdev)
psy_cfg.supplied_to = pdata->supplied_to;
psy_cfg.num_supplicants = pdata->num_supplicants;
- psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.fwnode = dev_fwnode(&pdev->dev);
psy_cfg.drv_data = cdata;
cdata->ac = devm_power_supply_register(&pdev->dev, &tps65090_charger_desc,
diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c
index 6fff44e1ecac..6af17ce0b204 100644
--- a/drivers/power/supply/tps65217_charger.c
+++ b/drivers/power/supply/tps65217_charger.c
@@ -198,7 +198,7 @@ static int tps65217_charger_probe(struct platform_device *pdev)
charger->tps = tps;
charger->dev = &pdev->dev;
- cfg.of_node = pdev->dev.of_node;
+ cfg.fwnode = dev_fwnode(&pdev->dev);
cfg.drv_data = charger;
charger->psy = devm_power_supply_register(&pdev->dev,
diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c
index 9dcb5457bef4..04216b2bfb6c 100644
--- a/drivers/power/supply/twl4030_charger.c
+++ b/drivers/power/supply/twl4030_charger.c
@@ -512,7 +512,6 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
ret |= twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a,
TWL4030_BCIMDKEY);
if (bci->usb_enabled) {
- pm_runtime_mark_last_busy(bci->transceiver->dev);
pm_runtime_put_autosuspend(bci->transceiver->dev);
bci->usb_enabled = 0;
}
diff --git a/drivers/power/supply/twl4030_madc_battery.c b/drivers/power/supply/twl4030_madc_battery.c
index 3935162e350b..a99b3ff26929 100644
--- a/drivers/power/supply/twl4030_madc_battery.c
+++ b/drivers/power/supply/twl4030_madc_battery.c
@@ -11,9 +11,7 @@
*/
#include <linux/module.h>
-#include <linux/param.h>
#include <linux/delay.h>
-#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c
index 7382bec6a43c..3f44cc902f84 100644
--- a/drivers/power/supply/ucs1002_power.c
+++ b/drivers/power/supply/ucs1002_power.c
@@ -6,7 +6,6 @@
*/
#include <linux/bits.h>
#include <linux/freezer.h>
-#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
@@ -493,7 +492,7 @@ static irqreturn_t ucs1002_alert_irq(int irq, void *data)
{
struct ucs1002_info *info = data;
- mod_delayed_work(system_wq, &info->health_poll, 0);
+ mod_delayed_work(system_percpu_wq, &info->health_poll, 0);
return IRQ_HANDLED;
}
@@ -560,7 +559,7 @@ static int ucs1002_probe(struct i2c_client *client)
irq_a_det = of_irq_get_byname(dev->of_node, "a_det");
irq_alert = of_irq_get_byname(dev->of_node, "alert");
- charger_config.of_node = dev->of_node;
+ charger_config.fwnode = dev_fwnode(dev);
charger_config.drv_data = info;
ret = regmap_read(info->regmap, UCS1002_REG_PRODUCT_ID, &regval);
diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c
index 38e23bdd4603..210e0f9aa5e0 100644
--- a/drivers/power/supply/ug3105_battery.c
+++ b/drivers/power/supply/ug3105_battery.c
@@ -10,7 +10,22 @@
* is off or suspended, the coulomb counter is not used atm.
*
* Possible improvements:
- * 1. Activate commented out total_coulomb_count code
+ * 1. Add coulumb counter reading, e.g. something like this:
+ * Read + reset coulomb counter every 10 polls (every 300 seconds)
+ *
+ * if ((chip->poll_count % 10) == 0) {
+ * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT);
+ * if (val < 0)
+ * goto out;
+ *
+ * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
+ * UG3105_CTRL1_RESET_COULOMB_CNT);
+ *
+ * chip->total_coulomb_count += (s16)val;
+ * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n",
+ * (s16)val, chip->total_coulomb_count);
+ * }
+ *
* 2. Reset total_coulomb_count val to 0 when the battery is as good as empty
* and remember that we did this (and clear the flag for this on susp/resume)
* 3. When the battery is full check if the flag that we set total_coulomb_count
@@ -31,24 +46,16 @@
* has shown that an estimated 7404mWh increase of the battery's energy results
* in a total_coulomb_count increase of 3277 units with a 5 milli-ohm sense R.
*
- * Copyright (C) 2021 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021 - 2025 Hans de Goede <hansg@kernel.org>
*/
-#include <linux/devm-helpers.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/power_supply.h>
-#include <linux/workqueue.h>
-
-#define UG3105_MOV_AVG_WINDOW 8
-#define UG3105_INIT_POLL_TIME (5 * HZ)
-#define UG3105_POLL_TIME (30 * HZ)
-#define UG3105_SETTLE_TIME (1 * HZ)
-#define UG3105_INIT_POLL_COUNT 30
+#include "adc-battery-helper.h"
#define UG3105_REG_MODE 0x00
#define UG3105_REG_CTRL1 0x01
@@ -61,33 +68,13 @@
#define UG3105_CTRL1_RESET_COULOMB_CNT 0x03
-#define UG3105_CURR_HYST_UA 65000
-
-#define UG3105_LOW_BAT_UV 3700000
-#define UG3105_FULL_BAT_HYST_UV 38000
-
struct ug3105_chip {
+ /* Must be the first member see adc-battery-helper documentation */
+ struct adc_battery_helper helper;
struct i2c_client *client;
struct power_supply *psy;
- struct power_supply_battery_info *info;
- struct delayed_work work;
- struct mutex lock;
- int ocv[UG3105_MOV_AVG_WINDOW]; /* micro-volt */
- int intern_res[UG3105_MOV_AVG_WINDOW]; /* milli-ohm */
- int poll_count;
- int ocv_avg_index;
- int ocv_avg; /* micro-volt */
- int intern_res_poll_count;
- int intern_res_avg_index;
- int intern_res_avg; /* milli-ohm */
- int volt; /* micro-volt */
- int curr; /* micro-ampere */
- int total_coulomb_count;
int uv_per_unit;
int ua_per_unit;
- int status;
- int capacity;
- bool supplied;
};
static int ug3105_read_word(struct i2c_client *client, u8 reg)
@@ -101,280 +88,43 @@ static int ug3105_read_word(struct i2c_client *client, u8 reg)
return val;
}
-static int ug3105_get_status(struct ug3105_chip *chip)
-{
- int full = chip->info->constant_charge_voltage_max_uv - UG3105_FULL_BAT_HYST_UV;
-
- if (chip->curr > UG3105_CURR_HYST_UA)
- return POWER_SUPPLY_STATUS_CHARGING;
-
- if (chip->curr < -UG3105_CURR_HYST_UA)
- return POWER_SUPPLY_STATUS_DISCHARGING;
-
- if (chip->supplied && chip->ocv_avg > full)
- return POWER_SUPPLY_STATUS_FULL;
-
- return POWER_SUPPLY_STATUS_NOT_CHARGING;
-}
-
-static int ug3105_get_capacity(struct ug3105_chip *chip)
-{
- /*
- * OCV voltages in uV for 0-110% in 5% increments, the 100-110% is
- * for LiPo HV (High-Voltage) bateries which can go up to 4.35V
- * instead of the usual 4.2V.
- */
- static const int ocv_capacity_tbl[23] = {
- 3350000,
- 3610000,
- 3690000,
- 3710000,
- 3730000,
- 3750000,
- 3770000,
- 3786667,
- 3803333,
- 3820000,
- 3836667,
- 3853333,
- 3870000,
- 3907500,
- 3945000,
- 3982500,
- 4020000,
- 4075000,
- 4110000,
- 4150000,
- 4200000,
- 4250000,
- 4300000,
- };
- int i, ocv_diff, ocv_step;
-
- if (chip->ocv_avg < ocv_capacity_tbl[0])
- return 0;
-
- if (chip->status == POWER_SUPPLY_STATUS_FULL)
- return 100;
-
- for (i = 1; i < ARRAY_SIZE(ocv_capacity_tbl); i++) {
- if (chip->ocv_avg > ocv_capacity_tbl[i])
- continue;
-
- ocv_diff = ocv_capacity_tbl[i] - chip->ocv_avg;
- ocv_step = ocv_capacity_tbl[i] - ocv_capacity_tbl[i - 1];
- /* scale 0-110% down to 0-100% for LiPo HV */
- if (chip->info->constant_charge_voltage_max_uv >= 4300000)
- return (i * 500 - ocv_diff * 500 / ocv_step) / 110;
- else
- return i * 5 - ocv_diff * 5 / ocv_step;
- }
-
- return 100;
-}
-
-static void ug3105_work(struct work_struct *work)
-{
- struct ug3105_chip *chip = container_of(work, struct ug3105_chip,
- work.work);
- int i, val, curr_diff, volt_diff, res, win_size;
- bool prev_supplied = chip->supplied;
- int prev_status = chip->status;
- int prev_volt = chip->volt;
- int prev_curr = chip->curr;
- struct power_supply *psy;
-
- mutex_lock(&chip->lock);
-
- psy = chip->psy;
- if (!psy)
- goto out;
-
- val = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
- if (val < 0)
- goto out;
- chip->volt = val * chip->uv_per_unit;
-
- val = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
- if (val < 0)
- goto out;
- chip->curr = (s16)val * chip->ua_per_unit;
-
- chip->ocv[chip->ocv_avg_index] =
- chip->volt - chip->curr * chip->intern_res_avg / 1000;
- chip->ocv_avg_index = (chip->ocv_avg_index + 1) % UG3105_MOV_AVG_WINDOW;
- chip->poll_count++;
-
- /*
- * See possible improvements comment above.
- *
- * Read + reset coulomb counter every 10 polls (every 300 seconds)
- * if ((chip->poll_count % 10) == 0) {
- * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT);
- * if (val < 0)
- * goto out;
- *
- * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
- * UG3105_CTRL1_RESET_COULOMB_CNT);
- *
- * chip->total_coulomb_count += (s16)val;
- * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n",
- * (s16)val, chip->total_coulomb_count);
- * }
- */
-
- chip->ocv_avg = 0;
- win_size = min(chip->poll_count, UG3105_MOV_AVG_WINDOW);
- for (i = 0; i < win_size; i++)
- chip->ocv_avg += chip->ocv[i];
- chip->ocv_avg /= win_size;
-
- chip->supplied = power_supply_am_i_supplied(psy);
- chip->status = ug3105_get_status(chip);
- chip->capacity = ug3105_get_capacity(chip);
-
- /*
- * Skip internal resistance calc on charger [un]plug and
- * when the battery is almost empty (voltage low).
- */
- if (chip->supplied != prev_supplied ||
- chip->volt < UG3105_LOW_BAT_UV ||
- chip->poll_count < 2)
- goto out;
-
- /*
- * Assuming that the OCV voltage does not change significantly
- * between 2 polls, then we can calculate the internal resistance
- * on a significant current change by attributing all voltage
- * change between the 2 readings to the internal resistance.
- */
- curr_diff = abs(chip->curr - prev_curr);
- if (curr_diff < UG3105_CURR_HYST_UA)
- goto out;
-
- volt_diff = abs(chip->volt - prev_volt);
- res = volt_diff * 1000 / curr_diff;
-
- if ((res < (chip->intern_res_avg * 2 / 3)) ||
- (res > (chip->intern_res_avg * 4 / 3))) {
- dev_dbg(&chip->client->dev, "Ignoring outlier internal resistance %d mOhm\n", res);
- goto out;
- }
-
- dev_dbg(&chip->client->dev, "Internal resistance %d mOhm\n", res);
-
- chip->intern_res[chip->intern_res_avg_index] = res;
- chip->intern_res_avg_index = (chip->intern_res_avg_index + 1) % UG3105_MOV_AVG_WINDOW;
- chip->intern_res_poll_count++;
-
- chip->intern_res_avg = 0;
- win_size = min(chip->intern_res_poll_count, UG3105_MOV_AVG_WINDOW);
- for (i = 0; i < win_size; i++)
- chip->intern_res_avg += chip->intern_res[i];
- chip->intern_res_avg /= win_size;
-
-out:
- mutex_unlock(&chip->lock);
-
- queue_delayed_work(system_wq, &chip->work,
- (chip->poll_count <= UG3105_INIT_POLL_COUNT) ?
- UG3105_INIT_POLL_TIME : UG3105_POLL_TIME);
-
- if (chip->status != prev_status && psy)
- power_supply_changed(psy);
-}
-
-static enum power_supply_property ug3105_battery_props[] = {
- POWER_SUPPLY_PROP_STATUS,
- POWER_SUPPLY_PROP_PRESENT,
- POWER_SUPPLY_PROP_SCOPE,
- POWER_SUPPLY_PROP_VOLTAGE_NOW,
- POWER_SUPPLY_PROP_VOLTAGE_OCV,
- POWER_SUPPLY_PROP_CURRENT_NOW,
- POWER_SUPPLY_PROP_CAPACITY,
-};
-
-static int ug3105_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
+static int ug3105_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr)
{
struct ug3105_chip *chip = power_supply_get_drvdata(psy);
- int ret = 0;
-
- mutex_lock(&chip->lock);
+ int ret;
- if (!chip->psy) {
- ret = -EAGAIN;
- goto out;
- }
+ ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
+ if (ret < 0)
+ return ret;
- switch (psp) {
- case POWER_SUPPLY_PROP_STATUS:
- val->intval = chip->status;
- break;
- case POWER_SUPPLY_PROP_PRESENT:
- val->intval = 1;
- break;
- case POWER_SUPPLY_PROP_SCOPE:
- val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
- break;
- case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
- if (ret < 0)
- break;
- val->intval = ret * chip->uv_per_unit;
- ret = 0;
- break;
- case POWER_SUPPLY_PROP_VOLTAGE_OCV:
- val->intval = chip->ocv_avg;
- break;
- case POWER_SUPPLY_PROP_CURRENT_NOW:
- ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
- if (ret < 0)
- break;
- val->intval = (s16)ret * chip->ua_per_unit;
- ret = 0;
- break;
- case POWER_SUPPLY_PROP_CAPACITY:
- val->intval = chip->capacity;
- break;
- default:
- ret = -EINVAL;
- }
+ *volt = ret * chip->uv_per_unit;
-out:
- mutex_unlock(&chip->lock);
- return ret;
-}
-
-static void ug3105_external_power_changed(struct power_supply *psy)
-{
- struct ug3105_chip *chip = power_supply_get_drvdata(psy);
+ ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
+ if (ret < 0)
+ return ret;
- dev_dbg(&chip->client->dev, "external power changed\n");
- mod_delayed_work(system_wq, &chip->work, UG3105_SETTLE_TIME);
+ *curr = (s16)ret * chip->ua_per_unit;
+ return 0;
}
static const struct power_supply_desc ug3105_psy_desc = {
.name = "ug3105_battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
- .get_property = ug3105_get_property,
- .external_power_changed = ug3105_external_power_changed,
- .properties = ug3105_battery_props,
- .num_properties = ARRAY_SIZE(ug3105_battery_props),
+ .get_property = adc_battery_helper_get_property,
+ .external_power_changed = adc_battery_helper_external_power_changed,
+ .properties = adc_battery_helper_properties,
+ .num_properties = ADC_HELPER_NUM_PROPERTIES,
};
-static void ug3105_init(struct ug3105_chip *chip)
+static void ug3105_start(struct i2c_client *client)
+{
+ i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_RUN);
+ i2c_smbus_write_byte_data(client, UG3105_REG_CTRL1, UG3105_CTRL1_RESET_COULOMB_CNT);
+}
+
+static void ug3105_stop(struct i2c_client *client)
{
- chip->poll_count = 0;
- chip->ocv_avg_index = 0;
- chip->total_coulomb_count = 0;
- i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE,
- UG3105_MODE_RUN);
- i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
- UG3105_CTRL1_RESET_COULOMB_CNT);
- queue_delayed_work(system_wq, &chip->work, 0);
- flush_delayed_work(&chip->work);
+ i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_STANDBY);
}
static int ug3105_probe(struct i2c_client *client)
@@ -382,7 +132,6 @@ static int ug3105_probe(struct i2c_client *client)
struct power_supply_config psy_cfg = {};
struct device *dev = &client->dev;
u32 curr_sense_res_uohm = 10000;
- struct power_supply *psy;
struct ug3105_chip *chip;
int ret;
@@ -391,25 +140,8 @@ static int ug3105_probe(struct i2c_client *client)
return -ENOMEM;
chip->client = client;
- mutex_init(&chip->lock);
- ret = devm_delayed_work_autocancel(dev, &chip->work, ug3105_work);
- if (ret)
- return ret;
-
- psy_cfg.drv_data = chip;
- psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg);
- if (IS_ERR(psy))
- return PTR_ERR(psy);
- ret = power_supply_get_battery_info(psy, &chip->info);
- if (ret)
- return ret;
-
- if (chip->info->factory_internal_resistance_uohm == -EINVAL ||
- chip->info->constant_charge_voltage_max_uv == -EINVAL) {
- dev_err(dev, "error required properties are missing\n");
- return -ENODEV;
- }
+ ug3105_start(client);
device_property_read_u32(dev, "upisemi,rsns-microohm", &curr_sense_res_uohm);
@@ -417,35 +149,36 @@ static int ug3105_probe(struct i2c_client *client)
* DAC maximum is 4.5V divided by 65536 steps + an unknown factor of 10
* coming from somewhere for some reason (verified with a volt-meter).
*/
- chip->uv_per_unit = 45000000/65536;
+ chip->uv_per_unit = 45000000 / 65536;
/* Datasheet says 8.1 uV per unit for the current ADC */
chip->ua_per_unit = 8100000 / curr_sense_res_uohm;
- /* Use provided internal resistance as start point (in milli-ohm) */
- chip->intern_res_avg = chip->info->factory_internal_resistance_uohm / 1000;
- /* Also add it to the internal resistance moving average window */
- chip->intern_res[0] = chip->intern_res_avg;
- chip->intern_res_avg_index = 1;
- chip->intern_res_poll_count = 1;
-
- mutex_lock(&chip->lock);
- chip->psy = psy;
- mutex_unlock(&chip->lock);
+ psy_cfg.drv_data = chip;
+ chip->psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg);
+ if (IS_ERR(chip->psy)) {
+ ret = PTR_ERR(chip->psy);
+ goto stop;
+ }
- ug3105_init(chip);
+ ret = adc_battery_helper_init(&chip->helper, chip->psy,
+ ug3105_get_voltage_and_current_now, NULL);
+ if (ret)
+ goto stop;
i2c_set_clientdata(client, chip);
return 0;
+
+stop:
+ ug3105_stop(client);
+ return ret;
}
static int __maybe_unused ug3105_suspend(struct device *dev)
{
struct ug3105_chip *chip = dev_get_drvdata(dev);
- cancel_delayed_work_sync(&chip->work);
- i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE,
- UG3105_MODE_STANDBY);
-
+ adc_battery_helper_suspend(dev);
+ ug3105_stop(chip->client);
return 0;
}
@@ -453,8 +186,8 @@ static int __maybe_unused ug3105_resume(struct device *dev)
{
struct ug3105_chip *chip = dev_get_drvdata(dev);
- ug3105_init(chip);
-
+ ug3105_start(chip->client);
+ adc_battery_helper_resume(dev);
return 0;
}
@@ -473,10 +206,12 @@ static struct i2c_driver ug3105_i2c_driver = {
.pm = &ug3105_pm_ops,
},
.probe = ug3105_probe,
+ .remove = ug3105_stop,
+ .shutdown = ug3105_stop,
.id_table = ug3105_id,
};
module_i2c_driver(ug3105_i2c_driver);
-MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org");
MODULE_DESCRIPTION("uPI uG3105 battery monitor driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/wm831x_power.c b/drivers/power/supply/wm831x_power.c
index 538055b29dec..78fa0573ef25 100644
--- a/drivers/power/supply/wm831x_power.c
+++ b/drivers/power/supply/wm831x_power.c
@@ -89,7 +89,7 @@ static int wm831x_wall_get_prop(struct power_supply *psy,
return ret;
}
-static enum power_supply_property wm831x_wall_props[] = {
+static const enum power_supply_property wm831x_wall_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
};
@@ -120,7 +120,7 @@ static int wm831x_usb_get_prop(struct power_supply *psy,
return ret;
}
-static enum power_supply_property wm831x_usb_props[] = {
+static const enum power_supply_property wm831x_usb_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
};
@@ -144,6 +144,7 @@ static int wm831x_usb_limit_change(struct notifier_block *nb,
struct wm831x_power,
usb_notify);
unsigned int i, best;
+ int ret;
/* Find the highest supported limit */
best = 0;
@@ -156,8 +157,13 @@ static int wm831x_usb_limit_change(struct notifier_block *nb,
dev_dbg(wm831x_power->wm831x->dev,
"Limiting USB current to %umA", wm831x_usb_limits[best]);
- wm831x_set_bits(wm831x_power->wm831x, WM831X_POWER_STATE,
- WM831X_USB_ILIM_MASK, best);
+ ret = wm831x_set_bits(wm831x_power->wm831x, WM831X_POWER_STATE,
+ WM831X_USB_ILIM_MASK, best);
+ if (ret < 0) {
+ dev_err(wm831x_power->wm831x->dev,
+ "Failed to set USB current limit: %d\n", ret);
+ return ret;
+ }
return 0;
}
@@ -171,21 +177,21 @@ struct chg_map {
int reg_val;
};
-static struct chg_map trickle_ilims[] = {
+static const struct chg_map trickle_ilims[] = {
{ 50, 0 << WM831X_CHG_TRKL_ILIM_SHIFT },
{ 100, 1 << WM831X_CHG_TRKL_ILIM_SHIFT },
{ 150, 2 << WM831X_CHG_TRKL_ILIM_SHIFT },
{ 200, 3 << WM831X_CHG_TRKL_ILIM_SHIFT },
};
-static struct chg_map vsels[] = {
+static const struct chg_map vsels[] = {
{ 4050, 0 << WM831X_CHG_VSEL_SHIFT },
{ 4100, 1 << WM831X_CHG_VSEL_SHIFT },
{ 4150, 2 << WM831X_CHG_VSEL_SHIFT },
{ 4200, 3 << WM831X_CHG_VSEL_SHIFT },
};
-static struct chg_map fast_ilims[] = {
+static const struct chg_map fast_ilims[] = {
{ 0, 0 << WM831X_CHG_FAST_ILIM_SHIFT },
{ 50, 1 << WM831X_CHG_FAST_ILIM_SHIFT },
{ 100, 2 << WM831X_CHG_FAST_ILIM_SHIFT },
@@ -204,7 +210,7 @@ static struct chg_map fast_ilims[] = {
{ 1000, 15 << WM831X_CHG_FAST_ILIM_SHIFT },
};
-static struct chg_map eoc_iterms[] = {
+static const struct chg_map eoc_iterms[] = {
{ 20, 0 << WM831X_CHG_ITERM_SHIFT },
{ 30, 1 << WM831X_CHG_ITERM_SHIFT },
{ 40, 2 << WM831X_CHG_ITERM_SHIFT },
@@ -215,7 +221,7 @@ static struct chg_map eoc_iterms[] = {
{ 90, 7 << WM831X_CHG_ITERM_SHIFT },
};
-static struct chg_map chg_times[] = {
+static const struct chg_map chg_times[] = {
{ 60, 0 << WM831X_CHG_TIME_SHIFT },
{ 90, 1 << WM831X_CHG_TIME_SHIFT },
{ 120, 2 << WM831X_CHG_TIME_SHIFT },
@@ -235,7 +241,7 @@ static struct chg_map chg_times[] = {
};
static void wm831x_battery_apply_config(struct wm831x *wm831x,
- struct chg_map *map, int count, int val,
+ const struct chg_map *map, int count, int val,
int *reg, const char *name,
const char *units)
{
@@ -462,7 +468,7 @@ static int wm831x_bat_get_prop(struct power_supply *psy,
return ret;
}
-static enum power_supply_property wm831x_bat_props[] = {
+static const enum power_supply_property wm831x_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
@@ -470,7 +476,7 @@ static enum power_supply_property wm831x_bat_props[] = {
POWER_SUPPLY_PROP_CHARGE_TYPE,
};
-static const char *wm831x_bat_irqs[] = {
+static const char * const wm831x_bat_irqs[] = {
"BATT HOT",
"BATT COLD",
"BATT FAIL",
diff --git a/drivers/power/supply/wm97xx_battery.c b/drivers/power/supply/wm97xx_battery.c
index b3b0c37a9dd2..a6c55b1e0286 100644
--- a/drivers/power/supply/wm97xx_battery.c
+++ b/drivers/power/supply/wm97xx_battery.c
@@ -178,12 +178,6 @@ static int wm97xx_bat_probe(struct platform_device *dev)
"failed to get charge GPIO\n");
if (charge_gpiod) {
gpiod_set_consumer_name(charge_gpiod, "BATT CHRG");
- ret = request_irq(gpiod_to_irq(charge_gpiod),
- wm97xx_chrg_irq, 0,
- "AC Detect", dev);
- if (ret)
- return dev_err_probe(&dev->dev, ret,
- "failed to request GPIO irq\n");
props++; /* POWER_SUPPLY_PROP_STATUS */
}
@@ -198,11 +192,9 @@ static int wm97xx_bat_probe(struct platform_device *dev)
if (pdata->min_voltage >= 0)
props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */
- prop = kcalloc(props, sizeof(*prop), GFP_KERNEL);
- if (!prop) {
- ret = -ENOMEM;
- goto err3;
- }
+ prop = devm_kcalloc(&dev->dev, props, sizeof(*prop), GFP_KERNEL);
+ if (!prop)
+ return -ENOMEM;
prop[i++] = POWER_SUPPLY_PROP_PRESENT;
if (charge_gpiod)
@@ -231,21 +223,21 @@ static int wm97xx_bat_probe(struct platform_device *dev)
bat_psy_desc.properties = prop;
bat_psy_desc.num_properties = props;
- bat_psy = power_supply_register(&dev->dev, &bat_psy_desc, &cfg);
- if (!IS_ERR(bat_psy)) {
- schedule_work(&bat_work);
- } else {
- ret = PTR_ERR(bat_psy);
- goto err4;
+ bat_psy = devm_power_supply_register(&dev->dev, &bat_psy_desc, &cfg);
+ if (IS_ERR(bat_psy))
+ return PTR_ERR(bat_psy);
+
+ schedule_work(&bat_work);
+
+ if (charge_gpiod) {
+ ret = request_irq(gpiod_to_irq(charge_gpiod), wm97xx_chrg_irq,
+ 0, "AC Detect", dev);
+ if (ret)
+ return dev_err_probe(&dev->dev, ret,
+ "failed to request GPIO irq\n");
}
return 0;
-err4:
- kfree(prop);
-err3:
- if (charge_gpiod)
- free_irq(gpiod_to_irq(charge_gpiod), dev);
- return ret;
}
static void wm97xx_bat_remove(struct platform_device *dev)
@@ -253,8 +245,6 @@ static void wm97xx_bat_remove(struct platform_device *dev)
if (charge_gpiod)
free_irq(gpiod_to_irq(charge_gpiod), dev);
cancel_work_sync(&bat_work);
- power_supply_unregister(bat_psy);
- kfree(prop);
}
static struct platform_driver wm97xx_bat_driver = {