summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-08-21 14:07:25 +0100
committerMark Brown <broonie@kernel.org>2026-08-21 14:07:25 +0100
commitb5b59fef531867ccf61c426ad8761ed033b8f2c8 (patch)
treede95ff16fb53f49717b059a3099235bc71eae9fe /drivers
parent5415235ff1aa2c5a2a3792aabcb6fb070537f3e6 (diff)
parent2da28b059e0ddcd2e1956eeae383246207965573 (diff)
downloadlinux-next-b5b59fef531867ccf61c426ad8761ed033b8f2c8.tar.gz
linux-next-b5b59fef531867ccf61c426ad8761ed033b8f2c8.zip
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git
Diffstat (limited to 'drivers')
-rw-r--r--drivers/power/reset/Kconfig12
-rw-r--r--drivers/power/reset/Makefile1
-rw-r--r--drivers/power/reset/mcf-rcm-reset.c126
-rw-r--r--drivers/power/reset/piix4-poweroff.c1
-rw-r--r--drivers/power/reset/pwr-mlxbf.c2
-rw-r--r--drivers/power/reset/reboot-mode.c30
-rw-r--r--drivers/power/reset/syscon-reboot.c4
-rw-r--r--drivers/power/supply/88pm860x_battery.c10
-rw-r--r--drivers/power/supply/88pm860x_charger.c5
-rw-r--r--drivers/power/supply/Kconfig16
-rw-r--r--drivers/power/supply/Makefile2
-rw-r--r--drivers/power/supply/ab8500_btemp.c5
-rw-r--r--drivers/power/supply/ab8500_charger.c5
-rw-r--r--drivers/power/supply/ab8500_fg.c32
-rw-r--r--drivers/power/supply/act8945a_charger.c4
-rw-r--r--drivers/power/supply/axp20x_ac_power.c5
-rw-r--r--drivers/power/supply/axp20x_usb_power.c5
-rw-r--r--drivers/power/supply/axp288_charger.c3
-rw-r--r--drivers/power/supply/axp288_fuel_gauge.c2
-rw-r--r--drivers/power/supply/bd71828-power.c20
-rw-r--r--drivers/power/supply/bd99954-charger.h23
-rw-r--r--drivers/power/supply/bq24190_charger.c18
-rw-r--r--drivers/power/supply/bq24257_charger.c20
-rw-r--r--drivers/power/supply/bq24735-charger.c6
-rw-r--r--drivers/power/supply/bq25630_charger.c1075
-rw-r--r--drivers/power/supply/bq256xx_charger.c41
-rw-r--r--drivers/power/supply/bq257xx_charger.c530
-rw-r--r--drivers/power/supply/bq25890_charger.c12
-rw-r--r--drivers/power/supply/bq27xxx_battery.c6
-rw-r--r--drivers/power/supply/charger-manager.c54
-rw-r--r--drivers/power/supply/cpcap-battery.c6
-rw-r--r--drivers/power/supply/cpcap-charger.c6
-rw-r--r--drivers/power/supply/cros_peripheral_charger.c13
-rw-r--r--drivers/power/supply/cros_usbpd-charger.c17
-rw-r--r--drivers/power/supply/cw2015_battery.c1
-rw-r--r--drivers/power/supply/da9150-fg.c4
-rw-r--r--drivers/power/supply/generic-adc-battery.c2
-rw-r--r--drivers/power/supply/isp1704_charger.c1
-rw-r--r--drivers/power/supply/lp8727_charger.c4
-rw-r--r--drivers/power/supply/lp8788-charger.c2
-rw-r--r--drivers/power/supply/ltc2941-battery-gauge.c2
-rw-r--r--drivers/power/supply/max14656_charger_detector.c4
-rw-r--r--drivers/power/supply/max17040_battery.c32
-rw-r--r--drivers/power/supply/max17042_battery.c373
-rw-r--r--drivers/power/supply/max77759_charger.c4
-rw-r--r--drivers/power/supply/max8903_charger.c430
-rw-r--r--drivers/power/supply/max8971_charger.c2
-rw-r--r--drivers/power/supply/mp2629_charger.c4
-rw-r--r--drivers/power/supply/mt6360_charger.c3
-rw-r--r--drivers/power/supply/mt6370-charger.c4
-rw-r--r--drivers/power/supply/pf1550-charger.c5
-rw-r--r--drivers/power/supply/pm8916_lbc.c6
-rw-r--r--drivers/power/supply/power_supply_core.c169
-rw-r--r--drivers/power/supply/power_supply_leds.c88
-rw-r--r--drivers/power/supply/power_supply_sysfs.c4
-rw-r--r--drivers/power/supply/qcom_battmgr.c17
-rw-r--r--drivers/power/supply/qcom_smbb.c5
-rw-r--r--drivers/power/supply/qcom_smbx.c3
-rw-r--r--drivers/power/supply/rk817_charger.c12
-rw-r--r--drivers/power/supply/rn5t618_power.c5
-rw-r--r--drivers/power/supply/rt9455_charger.c25
-rw-r--r--drivers/power/supply/rt9467-charger.c3
-rw-r--r--drivers/power/supply/rt9471.c52
-rw-r--r--drivers/power/supply/sbs-battery.c15
-rw-r--r--drivers/power/supply/sbs-charger.c2
-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/sgm41542_charger.c1127
-rw-r--r--drivers/power/supply/surface-rt-ec.c2
-rw-r--r--drivers/power/supply/tps65090-charger.c6
-rw-r--r--drivers/power/supply/tps65217_charger.c6
-rw-r--r--drivers/power/supply/twl4030_charger.c22
-rw-r--r--drivers/power/supply/twl6030_charger.c4
-rw-r--r--drivers/power/supply/ucs1002_power.c16
74 files changed, 4165 insertions, 428 deletions
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 124afb99febe..bce996bbef28 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -128,6 +128,18 @@ config POWER_RESET_LINKSTATION
Say Y here if you have a Buffalo LinkStation LS421D/E.
+config POWER_RESET_MCF_RCM
+ tristate "Freescale ColdFire RCM power-on reason driver"
+ depends on M5441x || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ This driver exposes the cause of the last reset on Freescale
+ ColdFire 5441x SoCs through the standard power_on_reason sysfs
+ ABI. It reads the Reset Status Register (RSR) of the Reset
+ Controller Module once at probe time and reports a normalised
+ string (regular power-up, reset button action, software reset
+ or unknown reason).
+
config POWER_RESET_MACSMC
tristate "Apple SMC reset/power-off driver"
depends on MFD_MACSMC
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index d7ae97241a83..e31cab4ba78e 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_MCF_RCM) += mcf-rcm-reset.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
diff --git a/drivers/power/reset/mcf-rcm-reset.c b/drivers/power/reset/mcf-rcm-reset.c
new file mode 100644
index 000000000000..18f11c05a868
--- /dev/null
+++ b/drivers/power/reset/mcf-rcm-reset.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Freescale ColdFire MCF5441x RCM power-on reason driver
+ *
+ * Copyright (C) 2026 Jean-Michel Hautbois <jeanmichel.hautbois@yoseli.org>
+ */
+
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power/power_on_reason.h>
+
+/*
+ * Reset Status Register (RSR) layout, MCF54418 Reference Manual chapter
+ * 12.3.2. The register is 8-bit read-only and latches the cause (or
+ * causes) of the most recent reset until the next one occurs.
+ */
+#define MCF_RSR_SOFT 0x20 /* Last reset caused by software */
+#define MCF_RSR_LOC 0x10 /* Last reset caused by PLL loss of clock */
+#define MCF_RSR_POR 0x08 /* Last reset caused by power-on */
+#define MCF_RSR_EXT 0x04 /* Last reset caused by external pin */
+#define MCF_RSR_WDRCORE 0x02 /* Last reset caused by core watchdog */
+#define MCF_RSR_LOL 0x01 /* Last reset caused by PLL loss of lock */
+
+#define MCF_RSR_KNOWN_CAUSES (MCF_RSR_POR | MCF_RSR_EXT | MCF_RSR_WDRCORE | \
+ MCF_RSR_LOC | MCF_RSR_LOL | MCF_RSR_SOFT)
+
+struct mcf_rcm {
+ const char *reason;
+};
+
+/*
+ * Decode RSR into a power_on_reason string.
+ *
+ * The MCF5441x Reset Status Register can latch several cause bits at the
+ * same time (Reference Manual chapter 12.3.2: "one or more status bits
+ * may be set at the same time"). A power-on, for example, also resets
+ * the PLL and may co-flag LOC and LOL during the boot sequence. The
+ * power_on_reason ABI carries a single string, so this routine picks
+ * one cause; the chosen priority surfaces the most explanatory one for
+ * diagnostics:
+ *
+ * POR cold boot dominates any spurious co-flagged cause
+ * EXT operator action via the RESET pin
+ * WDRCORE core watchdog timeout, a fault to investigate
+ * LOC, LOL PLL clock or lock failure, hardware fault
+ * SOFT explicit software-requested reset
+ */
+static const char *mcf_rcm_decode(u8 rsr)
+{
+ if (rsr & MCF_RSR_POR)
+ return POWER_ON_REASON_REGULAR;
+ if (rsr & MCF_RSR_EXT)
+ return POWER_ON_REASON_RST_BTN;
+ if (rsr & MCF_RSR_WDRCORE)
+ return POWER_ON_REASON_WATCHDOG;
+ if (rsr & (MCF_RSR_LOC | MCF_RSR_LOL))
+ return POWER_ON_REASON_CPU_CLK_FAIL;
+ if (rsr & MCF_RSR_SOFT)
+ return POWER_ON_REASON_SOFTWARE;
+ return POWER_ON_REASON_UNKNOWN;
+}
+
+static ssize_t power_on_reason_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mcf_rcm *rcm = platform_get_drvdata(to_platform_device(dev));
+
+ return sysfs_emit(buf, "%s\n", rcm->reason);
+}
+static DEVICE_ATTR_RO(power_on_reason);
+
+static struct attribute *mcf_rcm_attrs[] = {
+ &dev_attr_power_on_reason.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(mcf_rcm);
+
+static int mcf_rcm_probe(struct platform_device *pdev)
+{
+ struct mcf_rcm *rcm;
+ void __iomem *base;
+ u8 rsr;
+
+ rcm = devm_kzalloc(&pdev->dev, sizeof(*rcm), GFP_KERNEL);
+ if (!rcm)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ rsr = readb_relaxed(base);
+ rcm->reason = mcf_rcm_decode(rsr);
+
+ platform_set_drvdata(pdev, rcm);
+
+ if (!(rsr & MCF_RSR_KNOWN_CAUSES))
+ dev_warn(&pdev->dev, "Unknown reset cause (RSR=0x%02x)\n", rsr);
+ else
+ dev_info(&pdev->dev, "Starting after %s (RSR=0x%02x)\n",
+ rcm->reason, rsr);
+
+ return 0;
+}
+
+static const struct platform_device_id mcf_rcm_id[] = {
+ { "mcf-rcm-reset" },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, mcf_rcm_id);
+
+static struct platform_driver mcf_rcm_driver = {
+ .probe = mcf_rcm_probe,
+ .id_table = mcf_rcm_id,
+ .driver = {
+ .name = "mcf-rcm-reset",
+ .dev_groups = mcf_rcm_groups,
+ },
+};
+module_platform_driver(mcf_rcm_driver);
+
+MODULE_AUTHOR("Jean-Michel Hautbois <jeanmichel.hautbois@yoseli.org>");
+MODULE_DESCRIPTION("Freescale ColdFire RCM power-on reason driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/reset/piix4-poweroff.c b/drivers/power/reset/piix4-poweroff.c
index e6822c021000..1a6f3d22d297 100644
--- a/drivers/power/reset/piix4-poweroff.c
+++ b/drivers/power/reset/piix4-poweroff.c
@@ -96,6 +96,7 @@ static const struct pci_device_id piix4_poweroff_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3) },
{ 0 },
};
+MODULE_DEVICE_TABLE(pci, piix4_poweroff_ids);
static struct pci_driver piix4_poweroff_driver = {
.name = "piix4-poweroff",
diff --git a/drivers/power/reset/pwr-mlxbf.c b/drivers/power/reset/pwr-mlxbf.c
index 15d92c9f64dd..459c2b7f3a21 100644
--- a/drivers/power/reset/pwr-mlxbf.c
+++ b/drivers/power/reset/pwr-mlxbf.c
@@ -68,8 +68,6 @@ static int pwr_mlxbf_probe(struct platform_device *pdev)
return err;
err = devm_request_irq(dev, irq, pwr_mlxbf_irq, 0, hid, priv);
- if (err)
- dev_err(dev, "Failed request of %s irq\n", priv->hid);
return err;
}
diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c
index d20e44db0532..af00c00eceee 100644
--- a/drivers/power/reset/reboot-mode.c
+++ b/drivers/power/reset/reboot-mode.c
@@ -10,6 +10,7 @@
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/property.h>
#include <linux/reboot.h>
#include <linux/reboot-mode.h>
#include <linux/slab.h>
@@ -168,10 +169,11 @@ error:
*/
int reboot_mode_register(struct reboot_mode_driver *reboot)
{
- struct mode_info *info;
+ struct mode_info *info = NULL;
struct property *prop;
struct device_node *np = reboot->dev->of_node;
size_t len = strlen(PREFIX);
+ u32 magic;
int ret;
INIT_LIST_HEAD(&reboot->head);
@@ -180,22 +182,22 @@ int reboot_mode_register(struct reboot_mode_driver *reboot)
if (strncmp(prop->name, PREFIX, len))
continue;
- info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL);
+ if (device_property_read_u32(reboot->dev, prop->name, &magic)) {
+ dev_dbg(reboot->dev, "reboot mode %s without magic number\n",
+ prop->name);
+ continue;
+ }
+
+ info = kzalloc_obj(*info, GFP_KERNEL);
if (!info) {
ret = -ENOMEM;
goto error;
}
- if (of_property_read_u32(np, prop->name, &info->magic)) {
- dev_err(reboot->dev, "reboot mode %s without magic number\n",
- info->mode);
- devm_kfree(reboot->dev, info);
- continue;
- }
-
+ info->magic = magic;
info->mode = kstrdup_const(prop->name + len, GFP_KERNEL);
if (!info->mode) {
- ret = -ENOMEM;
+ ret = -ENOMEM;
goto error;
} else if (info->mode[0] == '\0') {
kfree_const(info->mode);
@@ -206,6 +208,7 @@ int reboot_mode_register(struct reboot_mode_driver *reboot)
}
list_add_tail(&info->list, &reboot->head);
+ info = NULL;
}
reboot->reboot_notifier.notifier_call = reboot_mode_notify;
@@ -218,6 +221,7 @@ int reboot_mode_register(struct reboot_mode_driver *reboot)
return 0;
error:
+ kfree(info);
reboot_mode_unregister(reboot);
return ret;
}
@@ -261,12 +265,16 @@ static inline void reboot_mode_unregister_device(struct reboot_mode_driver *rebo
int reboot_mode_unregister(struct reboot_mode_driver *reboot)
{
struct mode_info *info;
+ struct mode_info *next;
unregister_reboot_notifier(&reboot->reboot_notifier);
reboot_mode_unregister_device(reboot);
- list_for_each_entry(info, &reboot->head, list)
+ list_for_each_entry_safe(info, next, &reboot->head, list) {
+ list_del(&info->list);
kfree_const(info->mode);
+ kfree(info);
+ }
return 0;
}
diff --git a/drivers/power/reset/syscon-reboot.c b/drivers/power/reset/syscon-reboot.c
index 2e2cf5f62d73..60e855cae68e 100644
--- a/drivers/power/reset/syscon-reboot.c
+++ b/drivers/power/reset/syscon-reboot.c
@@ -67,7 +67,7 @@ static int syscon_reboot_probe(struct platform_device *pdev)
{
struct syscon_reboot_context *ctx;
struct device *dev = &pdev->dev;
- int priority;
+ u32 priority;
int err;
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
@@ -81,7 +81,7 @@ static int syscon_reboot_probe(struct platform_device *pdev)
return PTR_ERR(ctx->map);
}
- if (of_property_read_s32(pdev->dev.of_node, "priority", &priority))
+ if (of_property_read_u32(pdev->dev.of_node, "priority", &priority))
priority = 192;
ctx->rd = of_device_get_match_data(dev);
diff --git a/drivers/power/supply/88pm860x_battery.c b/drivers/power/supply/88pm860x_battery.c
index edae1e843c51..8d93c7a6a5f0 100644
--- a/drivers/power/supply/88pm860x_battery.c
+++ b/drivers/power/supply/88pm860x_battery.c
@@ -959,20 +959,14 @@ static int pm860x_battery_probe(struct platform_device *pdev)
ret = devm_request_threaded_irq(chip->dev, info->irq_cc, NULL,
pm860x_coulomb_handler, IRQF_ONESHOT,
"coulomb", info);
- if (ret < 0) {
- dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
- info->irq_cc, ret);
+ if (ret < 0)
return ret;
- }
ret = devm_request_threaded_irq(chip->dev, info->irq_batt, NULL,
pm860x_batt_handler,
IRQF_ONESHOT, "battery", info);
- if (ret < 0) {
- dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
- info->irq_batt, ret);
+ if (ret < 0)
return ret;
- }
return 0;
diff --git a/drivers/power/supply/88pm860x_charger.c b/drivers/power/supply/88pm860x_charger.c
index 8d99c6ff72ed..47f95f31e472 100644
--- a/drivers/power/supply/88pm860x_charger.c
+++ b/drivers/power/supply/88pm860x_charger.c
@@ -716,11 +716,8 @@ static int pm860x_charger_probe(struct platform_device *pdev)
pm860x_irq_descs[i].handler,
IRQF_ONESHOT,
pm860x_irq_descs[i].name, info);
- if (ret < 0) {
- dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
- info->irq[i], ret);
+ if (ret < 0)
return ret;
- }
}
return 0;
}
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index f0ede1cecb6a..b89ef40df7f8 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -839,6 +839,13 @@ config CHARGER_BQ25980
Say Y to enable support for the TI BQ25980, BQ25975 and BQ25960
series of fast battery chargers.
+config CHARGER_BQ25630
+ tristate "TI BQ25630 battery charger driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the TI BQ25630 battery charger.
+
config CHARGER_BQ256XX
tristate "TI BQ256XX battery charger driver"
depends on I2C
@@ -866,6 +873,15 @@ config CHARGER_S2M
devices provide USB power supply information and also required
for USB OTG role switching.
+config CHARGER_SGM41542
+ tristate "SGM41542 charger driver"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ depends on REGULATOR
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the SGM41542 battery charger.
+
config CHARGER_SMB347
tristate "Summit Microelectronics SMB3XX Battery Charger"
depends on I2C
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 31fe4a145929..f3c0d1006c1a 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -105,9 +105,11 @@ 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_BQ25630) += bq25630_charger.o
obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o
obj-$(CONFIG_CHARGER_RK817) += rk817_charger.o
obj-$(CONFIG_CHARGER_S2M) += s2m-charger.o
+obj-$(CONFIG_CHARGER_SGM41542) += sgm41542_charger.o
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c
index e5202a7b6209..032140bd6682 100644
--- a/drivers/power/supply/ab8500_btemp.c
+++ b/drivers/power/supply/ab8500_btemp.c
@@ -787,11 +787,8 @@ static int ab8500_btemp_probe(struct platform_device *pdev)
IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT,
ab8500_btemp_irq[i].name, di);
- if (ret) {
- dev_err(dev, "failed to request %s IRQ %d: %d\n"
- , ab8500_btemp_irq[i].name, irq, ret);
+ if (ret)
return ret;
- }
dev_dbg(dev, "Requested %s IRQ %d: %d\n",
ab8500_btemp_irq[i].name, irq, ret);
}
diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index 1813fbdfa1c1..bc6c070a564d 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -3605,11 +3605,8 @@ static int ab8500_charger_probe(struct platform_device *pdev)
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);
+ if (ret)
return ret;
- }
dev_dbg(dev, "Requested %s IRQ %d: %d\n",
ab8500_charger_irq[i].name, irq, ret);
}
diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index eb5c1ae68e44..cfcc05dfe28d 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -3054,6 +3054,20 @@ static void ab8500_fg_unbind(struct device *dev, struct device *master,
flush_workqueue(di->fg_wq);
}
+/* Disable, not cancel: works stay disabled so nothing can re-arm them. */
+static void ab8500_fg_destroy_workqueue(void *data)
+{
+ struct ab8500_fg *di = data;
+
+ disable_work_sync(&di->fg_acc_cur_work);
+ disable_work_sync(&di->fg_work);
+ disable_delayed_work_sync(&di->fg_reinit_work);
+ disable_delayed_work_sync(&di->fg_low_bat_work);
+ disable_delayed_work_sync(&di->fg_check_hw_failure_work);
+ disable_delayed_work_sync(&di->fg_periodic_work);
+ destroy_workqueue(di->fg_wq);
+}
+
static const struct component_ops ab8500_fg_component_ops = {
.bind = ab8500_fg_bind,
.unbind = ab8500_fg_unbind,
@@ -3155,6 +3169,11 @@ static int ab8500_fg_probe(struct platform_device *pdev)
return PTR_ERR(di->fg_psy);
}
+ /* Registered after fg_psy, before the IRQs: devm frees IRQ -> workqueue -> fg_psy. */
+ ret = devm_add_action_or_reset(dev, ab8500_fg_destroy_workqueue, di);
+ if (ret)
+ return ret;
+
di->fg_samples = SEC_TO_SAMPLE(di->bm->fg_params->init_timer);
/*
@@ -3167,22 +3186,16 @@ static int ab8500_fg_probe(struct platform_device *pdev)
/* Register primary interrupt handlers */
for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq); i++) {
irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name);
- if (irq < 0) {
- destroy_workqueue(di->fg_wq);
+ if (irq < 0)
return irq;
- }
ret = devm_request_threaded_irq(dev, irq, NULL,
ab8500_fg_irq[i].isr,
IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT,
ab8500_fg_irq[i].name, di);
- if (ret != 0) {
- dev_err(dev, "failed to request %s IRQ %d: %d\n",
- ab8500_fg_irq[i].name, irq, ret);
- destroy_workqueue(di->fg_wq);
+ if (ret != 0)
return ret;
- }
dev_dbg(dev, "Requested %s IRQ %d: %d\n",
ab8500_fg_irq[i].name, irq, ret);
}
@@ -3196,7 +3209,6 @@ static int ab8500_fg_probe(struct platform_device *pdev)
ret = ab8500_fg_sysfs_init(di);
if (ret) {
dev_err(dev, "failed to create sysfs entry\n");
- destroy_workqueue(di->fg_wq);
return ret;
}
@@ -3204,7 +3216,6 @@ static int ab8500_fg_probe(struct platform_device *pdev)
if (ret) {
dev_err(dev, "failed to create FG psy\n");
ab8500_fg_sysfs_exit(di);
- destroy_workqueue(di->fg_wq);
return ret;
}
@@ -3224,7 +3235,6 @@ static void ab8500_fg_remove(struct platform_device *pdev)
{
struct ab8500_fg *di = platform_get_drvdata(pdev);
- destroy_workqueue(di->fg_wq);
component_del(&pdev->dev, &ab8500_fg_component_ops);
list_del(&di->node);
ab8500_fg_sysfs_exit(di);
diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c
index 9dec4486b143..7e4b087f7679 100644
--- a/drivers/power/supply/act8945a_charger.c
+++ b/drivers/power/supply/act8945a_charger.c
@@ -620,10 +620,8 @@ static int act8945a_charger_probe(struct platform_device *pdev)
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");
+ if (ret)
return ret;
- }
platform_set_drvdata(pdev, charger);
diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c
index 5f6ea416fa30..474661ba7cf2 100644
--- a/drivers/power/supply/axp20x_ac_power.c
+++ b/drivers/power/supply/axp20x_ac_power.c
@@ -383,11 +383,8 @@ static int axp20x_ac_power_probe(struct platform_device *pdev)
ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i],
axp20x_ac_power_irq, 0,
DRVNAME, power);
- if (ret < 0) {
- dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n",
- axp_data->irq_names[i], ret);
+ if (ret < 0)
return ret;
- }
}
return 0;
diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c
index e75d1e377ac1..29f105f9212d 100644
--- a/drivers/power/supply/axp20x_usb_power.c
+++ b/drivers/power/supply/axp20x_usb_power.c
@@ -1030,11 +1030,8 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i],
axp20x_usb_power_irq, 0,
DRVNAME, power);
- if (ret < 0) {
- dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n",
- axp_data->irq_names[i], ret);
+ if (ret < 0)
return ret;
- }
}
if (axp20x_usb_vbus_needs_polling(power))
diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c
index 24a8b1ee2fec..8f2090356017 100644
--- a/drivers/power/supply/axp288_charger.c
+++ b/drivers/power/supply/axp288_charger.c
@@ -946,8 +946,7 @@ static int axp288_charger_probe(struct platform_device *pdev)
NULL, axp288_charger_irq_thread_handler,
IRQF_ONESHOT, info->pdev->name, info);
if (ret)
- return dev_err_probe(dev, ret, "failed to request interrupt=%d\n",
- info->irq[i]);
+ return ret;
}
return 0;
diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c
index 5af334c0a980..aa04c54ff97e 100644
--- a/drivers/power/supply/axp288_fuel_gauge.c
+++ b/drivers/power/supply/axp288_fuel_gauge.c
@@ -791,7 +791,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
fuel_gauge_thread_handler,
IRQF_ONESHOT, DEV_NAME, info);
if (ret)
- return dev_err_probe(dev, ret, "requesting IRQ %d\n", info->irq[i]);
+ return ret;
}
return 0;
diff --git a/drivers/power/supply/bd71828-power.c b/drivers/power/supply/bd71828-power.c
index 19f24f859666..3be7a75a7d6c 100644
--- a/drivers/power/supply/bd71828-power.c
+++ b/drivers/power/supply/bd71828-power.c
@@ -191,12 +191,13 @@ static int bd71828_get_current_ds_adc(struct bd71828_power *pwr, int *curr, int
{
__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++) {
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ int dir = 1;
+
ret = regmap_bulk_read(pwr->regmap, regs[i], &tmp_curr,
sizeof(tmp_curr));
if (ret)
@@ -224,13 +225,13 @@ static int bd71815_get_temp(struct bd71828_power *pwr, int *temp)
if (ret)
return ret;
- t = 200 - t;
-
if (t > 200) {
dev_err(pwr->dev, "Failed to read battery temperature\n");
return -ENODATA;
}
+ *temp = 200 - t;
+
return 0;
}
@@ -454,8 +455,10 @@ static int bd71828_charger_get_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = get_chg_online(pwr, &online);
- if (!ret)
- val->intval = online;
+ if (ret)
+ return ret;
+
+ val->intval = online;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = bd7182x_read16_himask(pwr, pwr->regs->vdcin,
@@ -638,7 +641,6 @@ static const enum power_supply_property bd71828_charger_props[] = {
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,
@@ -1217,7 +1219,9 @@ static int bd71828_power_probe(struct platform_device *pdev)
return dev_err_probe(&pdev->dev, ret, "sense resistor missing\n");
dev_set_drvdata(&pdev->dev, pwr);
- bd71828_init_hardware(pwr);
+ ret = bd71828_init_hardware(pwr);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "hardware init failed\n");
bat_cfg.drv_data = pwr;
bat_cfg.fwnode = dev_fwnode(&pdev->dev);
diff --git a/drivers/power/supply/bd99954-charger.h b/drivers/power/supply/bd99954-charger.h
index f58897925383..d053ac901274 100644
--- a/drivers/power/supply/bd99954-charger.h
+++ b/drivers/power/supply/bd99954-charger.h
@@ -279,17 +279,7 @@ enum bd9995x_fields {
F_VBUS_EXTID,
F_VBUS_IDRDET,
F_VBUS_INDO,
- F_VBUS_UCDSWEN,
- F_VBUS_RREF_EN,
- F_VBUS_DPPU_EN,
- F_VBUS_DPREF_EN,
- F_VBUS_DMREF_EN,
- F_VBUS_DPDET_EN,
- F_VBUS_DMDET_EN,
- F_VBUS_DPSINK_EN,
- F_VBUS_DMSINK_EN,
- F_VBUS_DP_BUFF_EN,
- F_VBUS_DM_BUFF_EN,
+
F_VBUS_EXTCLKENBL,
F_VBUS_PLSTESTEN,
F_VBUS_UCDSWEN_TSTENB,
@@ -626,17 +616,6 @@ static const struct reg_field bd9995x_reg_fields[] = {
[F_VBUS_EXTID] = REG_FIELD(VBUS_IDD_STATUS, 5, 5),
[F_VBUS_IDRDET] = REG_FIELD(VBUS_IDD_STATUS, 4, 4),
[F_VBUS_INDO] = REG_FIELD(VBUS_IDD_STATUS, 0, 3),
- [F_VBUS_UCDSWEN] = REG_FIELD(VCC_UCD_FCTRL_SET, 10, 10),
- [F_VBUS_RREF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 9, 9),
- [F_VBUS_DPPU_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 8, 8),
- [F_VBUS_DPREF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 7, 7),
- [F_VBUS_DMREF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 6, 6),
- [F_VBUS_DPDET_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 5, 5),
- [F_VBUS_DMDET_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 4, 4),
- [F_VBUS_DPSINK_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 3, 3),
- [F_VBUS_DMSINK_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 2, 2),
- [F_VBUS_DP_BUFF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 1, 1),
- [F_VBUS_DM_BUFF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 0, 0),
[F_VBUS_EXTCLKENBL] = REG_FIELD(VBUS_UCD_FCTRL_EN, 15, 15),
[F_VBUS_PLSTESTEN] = REG_FIELD(VBUS_UCD_FCTRL_EN, 14, 14),
diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c
index 8e28f86ae09f..4bea6fd83c36 100644
--- a/drivers/power/supply/bq24190_charger.c
+++ b/drivers/power/supply/bq24190_charger.c
@@ -242,7 +242,6 @@ struct bq24190_dev_info {
struct mutex f_reg_lock;
u8 f_reg;
u8 ss_reg;
- u8 watchdog;
const struct bq24190_chip_info *info;
};
@@ -751,13 +750,6 @@ static int bq24190_set_config(struct bq24190_dev_info *bdi)
int ret;
u8 v;
- ret = bq24190_read(bdi, BQ24190_REG_CTTC, &v);
- if (ret < 0)
- return ret;
-
- bdi->watchdog = ((v & BQ24190_REG_CTTC_WATCHDOG_MASK) >>
- BQ24190_REG_CTTC_WATCHDOG_SHIFT);
-
/*
* According to the "Host Mode and default Mode" section of the
* manual, a write to any register causes the bq24190 to switch
@@ -766,9 +758,9 @@ static int bq24190_set_config(struct bq24190_dev_info *bdi)
* So, by simply turning off the WDT, we accomplish both with the
* same write.
*/
- v &= ~BQ24190_REG_CTTC_WATCHDOG_MASK;
-
- ret = bq24190_write(bdi, BQ24190_REG_CTTC, v);
+ ret = bq24190_write_mask(bdi, BQ24190_REG_CTTC,
+ BQ24190_REG_CTTC_WATCHDOG_MASK,
+ BQ24190_REG_CTTC_WATCHDOG_SHIFT, 0);
if (ret < 0)
return ret;
@@ -2169,10 +2161,8 @@ static int bq24190_probe(struct i2c_client *client)
bq24190_irq_handler_thread,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"bq24190-charger", bdi);
- if (ret < 0) {
- dev_err(dev, "Can't set up irq handler\n");
+ if (ret < 0)
goto out_charger;
- }
ret = bq24190_register_vbus_regulator(bdi);
if (ret < 0)
diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c
index 72f1bfea8d54..944a7aa6f187 100644
--- a/drivers/power/supply/bq24257_charger.c
+++ b/drivers/power/supply/bq24257_charger.c
@@ -18,6 +18,7 @@
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
+#include <linux/devm-helpers.h>
#include <linux/acpi.h>
#include <linux/of.h>
@@ -1003,10 +1004,6 @@ static int bq24257_probe(struct i2c_client *client)
if (bq->info->chip == BQ24250)
bq->iilimit_autoset_enable = false;
- if (bq->iilimit_autoset_enable)
- INIT_DELAYED_WORK(&bq->iilimit_setup_work,
- bq24257_iilimit_setup_work);
-
/*
* The BQ24250 doesn't have a dedicated Power Good (PG) pin so let's
* not probe for it and instead use a SW-based approach to determine
@@ -1047,15 +1044,21 @@ static int bq24257_probe(struct i2c_client *client)
return ret;
}
+ if (bq->iilimit_autoset_enable) {
+ ret = devm_delayed_work_autocancel(dev,
+ &bq->iilimit_setup_work,
+ bq24257_iilimit_setup_work);
+ if (ret)
+ return ret;
+ }
+
ret = devm_request_threaded_irq(dev, client->irq, NULL,
bq24257_irq_handler_thread,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
bq->info->name, bq);
- if (ret) {
- dev_err(dev, "Failed to request IRQ #%d\n", client->irq);
+ if (ret)
return ret;
- }
return 0;
}
@@ -1064,9 +1067,6 @@ static void bq24257_remove(struct i2c_client *client)
{
struct bq24257_device *bq = i2c_get_clientdata(client);
- if (bq->iilimit_autoset_enable)
- cancel_delayed_work_sync(&bq->iilimit_setup_work);
-
bq24257_field_write(bq, F_RESET, 1); /* reset to defaults */
}
diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c
index 99abbaf0470f..22c2b86c3b30 100644
--- a/drivers/power/supply/bq24735-charger.c
+++ b/drivers/power/supply/bq24735-charger.c
@@ -462,12 +462,8 @@ static int bq24735_charger_probe(struct i2c_client *client)
IRQF_ONESHOT,
supply_desc->name,
charger->charger);
- if (ret) {
- dev_err(&client->dev,
- "Unable to register IRQ %d err %d\n",
- client->irq, ret);
+ if (ret)
return ret;
- }
} else {
ret = device_property_read_u32(&client->dev, "poll-interval",
&charger->poll_interval);
diff --git a/drivers/power/supply/bq25630_charger.c b/drivers/power/supply/bq25630_charger.c
new file mode 100644
index 000000000000..200f74f8eab6
--- /dev/null
+++ b/drivers/power/supply/bq25630_charger.c
@@ -0,0 +1,1075 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for TI BQ25630 charger.
+ *
+ * Copyright (C) 2026 Axis Communications AB
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include <linux/power_supply.h>
+
+#define BQ25630_DRV_NAME "bq25630-charger"
+
+/* Registers. */
+#define BQ25630_REG_CHARGE_CURRENT_LIMIT 0x02
+#define BQ25630_REG_CHARGE_VOLTAGE_LIMIT 0x04
+#define BQ25630_REG_INPUT_CURRENT_LIMIT 0x06
+#define BQ25630_REG_INPUT_VOLTAGE_LIMIT 0x08
+#define BQ25630_REG_IOTG_REGULATION 0x0a
+#define BQ25630_REG_VOTG_REGULATION 0x0c
+#define BQ25630_REG_MINIMAL_SYSTEM_VOLTAGE 0x0e
+#define BQ25630_REG_PRECHARGE_CONTROL 0x10
+#define BQ25630_REG_TERMINATION_CONTROL 0x12
+#define BQ25630_REG_CHARGE_TIMER_CONTROL 0x14
+#define BQ25630_REG_CHARGER_CONTROL_0 0x15
+#define BQ25630_REG_CHARGER_CONTROL_1 0x16
+#define BQ25630_REG_CHARGER_CONTROL_2 0x17
+#define BQ25630_REG_CHARGER_CONTROL_3 0x18
+#define BQ25630_REG_CHARGER_CONTROL_4 0x19
+#define BQ25630_REG_CHARGER_CONTROL_5 0x1a
+#define BQ25630_REG_NTC_CONTROL_0 0x1b
+#define BQ25630_REG_NTC_CONTROL_1 0x1c
+#define BQ25630_REG_NTC_CONTROL_2 0x1d
+#define BQ25630_REG_NTC_CONTROL_3 0x1e
+#define BQ25630_REG_CHARGER_STATUS_0 0x1f
+#define BQ25630_REG_CHARGER_STATUS_1 0x20
+#define BQ25630_REG_CHARGER_STATUS_2 0x21
+#define BQ25630_REG_FAULT_STATUS 0x22
+#define BQ25630_REG_CHARGER_FLAG_0 0x23
+#define BQ25630_REG_CHARGER_FLAG_1 0x24
+#define BQ25630_REG_FAULT_FLAG 0x25
+#define BQ25630_REG_CHARGER_MASK_0 0x26
+#define BQ25630_REG_CHARGER_MASK_1 0x27
+#define BQ25630_REG_FAULT_MASK 0x28
+#define BQ25630_REG_ICO_CURRENT_LIMIT 0x29
+#define BQ25630_REG_ADC_CONTROL 0x2b
+#define BQ25630_REG_ADC_CHANNEL_DISABLE_1 0x2c
+#define BQ25630_REG_IBUS_ADC 0x32
+#define BQ25630_REG_IBAT_ADC 0x34
+#define BQ25630_REG_VBUS_ADC 0x36
+#define BQ25630_REG_VPMID_ADC 0x38
+#define BQ25630_REG_VBAT_ADC 0x3a
+#define BQ25630_REG_VSYS_ADC 0x3c
+#define BQ25630_REG_TS_ADC 0x3e
+#define BQ25630_REG_TDIE_ADC 0x40
+#define BQ25630_REG_USB_C_CONTROL_0 0x44
+#define BQ25630_REG_USB_C_CONTROL_1 0x45
+#define BQ25630_REG_LIQUID_CONTROL_0 0x46
+#define BQ25630_REG_LIQUID_CONTROL_1 0x47
+#define BQ25630_REG_USB_C_INFORMATION_0 0x48
+#define BQ25630_REG_USB_C_INFORMATION_1 0x49
+#define BQ25630_REG_USB_DAC_CONTROL_0 0x4a
+#define BQ25630_REG_USB_DAC_CONTROL_1 0x4b
+#define BQ25630_REG_PART_INFORMATION 0x4d
+
+#define BQ25630_NR_STAT_REGS \
+ (BQ25630_REG_FAULT_FLAG - BQ25630_REG_CHARGER_STATUS_0 + 1)
+
+/* Charge current limits. */
+#define BQ25630_ICHG_MIN_REGVAL 0x04
+#define BQ25630_ICHG_MIN 80000
+#define BQ25630_ICHG_MAX 5040000
+#define BQ25630_ICHG_STEP 20000
+
+/* Charge voltage limits. */
+#define BQ25630_VREG_MIN_REGVAL 0x15e
+#define BQ25630_VREG_MIN 3500000
+#define BQ25630_VREG_MAX 4800000
+#define BQ25630_VREG_STEP 10000
+
+/* Input current limits. */
+#define BQ25630_IINDPM_MIN_REGVAL 0x0a
+#define BQ25630_IINDPM_MIN 100000
+#define BQ25630_IINDPM_MAX 3200000
+#define BQ25630_IINDPM_STEP 10000
+
+/* Input voltage limits. */
+#define BQ25630_VINDPM_MIN_REGVAL 0x5f
+#define BQ25630_VINDPM_MIN 3800000
+#define BQ25630_VINDPM_MAX 16800000
+#define BQ25630_VINDPM_STEP 40000
+
+/* Minimal system voltage limits. */
+#define BQ25630_VSYSMIN_MIN_REGVAL 0x20
+#define BQ25630_VSYSMIN_MIN 2560000
+#define BQ25630_VSYSMIN_MAX 4000000
+#define BQ25630_VSYSMIN_STEP 80000
+
+/* Pre-charge current limits. */
+#define BQ25630_IPRECHG_MIN_REGVAL 0x02
+#define BQ25630_IPRECHG_MIN 40000
+#define BQ25630_IPRECHG_MAX 1000000
+#define BQ25630_IPRECHG_STEP 20000
+
+/* Termination current limits. */
+#define BQ25630_ITERM_MIN_REGVAL 0x03
+#define BQ25630_ITERM_MIN 30000
+#define BQ25630_ITERM_MAX 1000000
+#define BQ25630_ITERM_STEP 10000
+
+/* Charge types. */
+#define BQ25630_CHG_STAT_NOT_CHARGING 0x00
+#define BQ25630_CHG_STAT_TRICKLE_CHARGE 0x01
+#define BQ25630_CHG_STAT_PRE_CHARGE 0x02
+#define BQ25630_CHG_STAT_FAST_CHARGE 0x03
+#define BQ25630_CHG_STAT_TAPER_CHARGE 0x04
+#define BQ25630_CHG_STAT_TERMINATION 0x07
+
+/* USB types (VBUS). */
+#define BQ25630_VBUS_STAT_NONE 0x00
+#define BQ25630_VBUS_STAT_SDP 0x01
+#define BQ25630_VBUS_STAT_CDP 0x02
+#define BQ25630_VBUS_STAT_DCP 0x03
+#define BQ25630_VBUS_STAT_HVDCP 0x06
+#define BQ25630_VBUS_STAT_BOOST_OTG 0x07
+#define BQ25630_VBUS_STAT_USB_C_DEFAULT 0x08
+#define BQ25630_VBUS_STAT_USB_C_MEDIUM 0x09
+#define BQ25630_VBUS_STAT_USB_C_HIGH 0x0a
+
+/* Temperature status. */
+#define BQ25630_TS_STAT_NORMAL 0x00
+#define BQ25630_TS_STAT_COLD 0x01
+#define BQ25630_TS_STAT_HOT 0x02
+#define BQ25630_TS_STAT_COOL 0x03
+#define BQ25630_TS_STAT_WARM 0x04
+#define BQ25630_TS_STAT_PRECOOL 0x05
+#define BQ25630_TS_STAT_PREWARM 0x06
+
+/* Register fields. */
+enum bq25630_regfield {
+ /* Charge current limit. */
+ BQ25630_REGF_ICHG,
+ /* Charge voltage limit. */
+ BQ25630_REGF_VREG,
+ /* Input current limit. */
+ BQ25630_REGF_IINDPM,
+ /* Input voltage limit. */
+ BQ25630_REGF_VINDPM,
+ /* Minimal system voltage. */
+ BQ25630_REGF_VSYSMIN,
+ /* Pre-charge current limit. */
+ BQ25630_REGF_IPRECHG,
+ /* Termination current threshold. */
+ BQ25630_REGF_ITERM,
+
+ /* IBUS ADC reading. */
+ BQ25630_REGF_IBUS_ADC,
+ /* VBUS ADC reading. */
+ BQ25630_REGF_VBUS_ADC,
+
+ /* Watchdog timer. */
+ BQ25630_REGF_WATCHDOG,
+ /* Enable charger. */
+ BQ25630_REGF_EN_CHG,
+ /* Register reset. */
+ BQ25630_REGF_REG_RST,
+ /* BATFET control. */
+ BQ25630_REGF_BATFET_CTRL,
+ /* Power good indicator. */
+ BQ25630_REGF_PG_STAT,
+ /* Charge status. */
+ BQ25630_REGF_CHG_STAT,
+ /* VBUS status. */
+ BQ25630_REGF_VBUS_STAT,
+
+ /* Temperature zone. */
+ BQ25630_REGF_TS_STAT,
+ /* Temperature shutdwon. */
+ BQ25630_REGF_TSHUT_STAT,
+ /* OTG fault. */
+ BQ25630_REGF_OTG_FAULT_STAT,
+ /* System voltage fault. */
+ BQ25630_REGF_VSYS_FAULT_STAT,
+ /* Battery fault. */
+ BQ25630_REGF_BAT_FAULT_STAT,
+ /* VBUS fault. */
+ BQ25630_REGF_VBUS_FAULT_STAT,
+
+ /* Sentinel value. */
+ BQ25630_REGF_MAX
+};
+
+static const struct reg_field bq25630_regfields[] = {
+ [BQ25630_REGF_ICHG] =
+ REG_FIELD(BQ25630_REG_CHARGE_CURRENT_LIMIT, 4, 11),
+ [BQ25630_REGF_VREG] =
+ REG_FIELD(BQ25630_REG_CHARGE_VOLTAGE_LIMIT, 3, 11),
+ [BQ25630_REGF_IINDPM] =
+ REG_FIELD(BQ25630_REG_INPUT_CURRENT_LIMIT, 3, 11),
+ [BQ25630_REGF_VINDPM] =
+ REG_FIELD(BQ25630_REG_INPUT_VOLTAGE_LIMIT, 5, 13),
+
+ [BQ25630_REGF_VSYSMIN] =
+ REG_FIELD(BQ25630_REG_MINIMAL_SYSTEM_VOLTAGE, 6, 11),
+
+ [BQ25630_REGF_IPRECHG] =
+ REG_FIELD(BQ25630_REG_PRECHARGE_CONTROL, 4, 9),
+ [BQ25630_REGF_ITERM] =
+ REG_FIELD(BQ25630_REG_TERMINATION_CONTROL, 3, 9),
+
+ [BQ25630_REGF_IBUS_ADC] = REG_FIELD(BQ25630_REG_IBUS_ADC, 1, 15),
+ [BQ25630_REGF_VBUS_ADC] = REG_FIELD(BQ25630_REG_VBUS_ADC, 2, 14),
+
+ [BQ25630_REGF_WATCHDOG] =
+ REG_FIELD(BQ25630_REG_CHARGER_CONTROL_1, 0, 1),
+ [BQ25630_REGF_EN_CHG] =
+ REG_FIELD(BQ25630_REG_CHARGER_CONTROL_1, 5, 5),
+ [BQ25630_REGF_REG_RST] =
+ REG_FIELD(BQ25630_REG_CHARGER_CONTROL_2, 7, 7),
+ [BQ25630_REGF_BATFET_CTRL] =
+ REG_FIELD(BQ25630_REG_CHARGER_CONTROL_3, 0, 1),
+ [BQ25630_REGF_PG_STAT] =
+ REG_FIELD(BQ25630_REG_CHARGER_STATUS_0, 7, 7),
+ [BQ25630_REGF_CHG_STAT] =
+ REG_FIELD(BQ25630_REG_CHARGER_STATUS_1, 3, 5),
+ [BQ25630_REGF_VBUS_STAT] =
+ REG_FIELD(BQ25630_REG_CHARGER_STATUS_2, 4, 7),
+
+ [BQ25630_REGF_TS_STAT] =
+ REG_FIELD(BQ25630_REG_FAULT_STATUS, 0, 2),
+ [BQ25630_REGF_TSHUT_STAT] =
+ REG_FIELD(BQ25630_REG_FAULT_STATUS, 3, 3),
+ [BQ25630_REGF_OTG_FAULT_STAT] =
+ REG_FIELD(BQ25630_REG_FAULT_STATUS, 4, 4),
+ [BQ25630_REGF_VSYS_FAULT_STAT] =
+ REG_FIELD(BQ25630_REG_FAULT_STATUS, 5, 5),
+ [BQ25630_REGF_BAT_FAULT_STAT] =
+ REG_FIELD(BQ25630_REG_FAULT_STATUS, 6, 6),
+ [BQ25630_REGF_VBUS_FAULT_STAT] =
+ REG_FIELD(BQ25630_REG_FAULT_STATUS, 7, 7),
+};
+
+/* 8-bit value regmap. */
+static const struct regmap_range bq25630_read_reg_range8[] = {
+ regmap_reg_range(BQ25630_REG_CHARGE_TIMER_CONTROL,
+ BQ25630_REG_FAULT_MASK),
+ regmap_reg_range(BQ25630_REG_ADC_CONTROL,
+ BQ25630_REG_ADC_CHANNEL_DISABLE_1),
+ regmap_reg_range(BQ25630_REG_USB_C_CONTROL_0,
+ BQ25630_REG_PART_INFORMATION),
+};
+
+static const struct regmap_range bq25630_write_reg_range8[] = {
+ regmap_reg_range(BQ25630_REG_CHARGE_TIMER_CONTROL,
+ BQ25630_REG_NTC_CONTROL_3),
+ regmap_reg_range(BQ25630_REG_CHARGER_MASK_0,
+ BQ25630_REG_FAULT_MASK),
+ regmap_reg_range(BQ25630_REG_ADC_CONTROL,
+ BQ25630_REG_ADC_CHANNEL_DISABLE_1),
+ regmap_reg_range(BQ25630_REG_USB_C_CONTROL_0,
+ BQ25630_REG_LIQUID_CONTROL_1),
+ regmap_reg_range(BQ25630_REG_USB_DAC_CONTROL_0,
+ BQ25630_REG_USB_DAC_CONTROL_1),
+};
+
+static const struct regmap_access_table bq25630_read_reg_access_table8 = {
+ .yes_ranges = bq25630_read_reg_range8,
+ .n_yes_ranges = ARRAY_SIZE(bq25630_read_reg_range8),
+};
+
+static const struct regmap_access_table bq25630_write_reg_access_table8 = {
+ .yes_ranges = bq25630_write_reg_range8,
+ .n_yes_ranges = ARRAY_SIZE(bq25630_write_reg_range8),
+};
+
+static const struct regmap_config bq25630_regmap_config8 = {
+ .name = BQ25630_DRV_NAME "-8bit",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = BQ25630_REG_PART_INFORMATION,
+ .rd_table = &bq25630_read_reg_access_table8,
+ .wr_table = &bq25630_write_reg_access_table8,
+};
+
+/* 16-bit little-endian value regmap. */
+static const struct regmap_range bq25630_read_reg_range16le[] = {
+ regmap_reg_range(BQ25630_REG_CHARGE_CURRENT_LIMIT,
+ BQ25630_REG_TERMINATION_CONTROL),
+ regmap_reg_range(BQ25630_REG_ICO_CURRENT_LIMIT,
+ BQ25630_REG_ICO_CURRENT_LIMIT),
+};
+
+static const struct regmap_range bq25630_write_reg_range16le[] = {
+ regmap_reg_range(BQ25630_REG_CHARGE_CURRENT_LIMIT,
+ BQ25630_REG_TERMINATION_CONTROL),
+ regmap_reg_range(BQ25630_REG_ICO_CURRENT_LIMIT,
+ BQ25630_REG_ICO_CURRENT_LIMIT),
+};
+
+static const struct regmap_access_table bq25630_read_reg_access_table16le = {
+ .yes_ranges = bq25630_read_reg_range16le,
+ .n_yes_ranges = ARRAY_SIZE(bq25630_read_reg_range16le),
+};
+
+static const struct regmap_access_table bq25630_write_reg_access_table16le = {
+ .yes_ranges = bq25630_write_reg_range16le,
+ .n_yes_ranges = ARRAY_SIZE(bq25630_write_reg_range16le),
+};
+
+static const struct regmap_config bq25630_regmap_config16le = {
+ .name = BQ25630_DRV_NAME "-16bit-le",
+ .reg_bits = 8,
+ .val_bits = 16,
+ .reg_stride = 2,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+ /*
+ * Datasheet doesn't mention that this register is little-endian, but it
+ * looks like it?
+ */
+ .max_register = BQ25630_REG_TERMINATION_CONTROL,
+ .rd_table = &bq25630_read_reg_access_table16le,
+ .wr_table = &bq25630_write_reg_access_table16le,
+};
+
+/* 16-bit big-endian value regmap. */
+static const struct regmap_range bq25630_read_reg_range16be[] = {
+ regmap_reg_range(BQ25630_REG_IBUS_ADC,
+ BQ25630_REG_TDIE_ADC),
+};
+
+static const struct regmap_access_table bq25630_read_reg_access_table16be = {
+ .yes_ranges = bq25630_read_reg_range16be,
+ .n_yes_ranges = ARRAY_SIZE(bq25630_read_reg_range16be),
+};
+
+static const struct regmap_config bq25630_regmap_config16be = {
+ .name = BQ25630_DRV_NAME "-16bit-be",
+ .reg_bits = 8,
+ .val_bits = 16,
+ .reg_stride = 2,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .max_register = BQ25630_REG_TDIE_ADC,
+ .rd_table = &bq25630_read_reg_access_table16be,
+ .wr_table = NULL,
+};
+
+struct bq25630_data {
+ struct device *dev;
+ struct regmap *regmap8;
+ struct regmap *regmap16le;
+ struct regmap *regmap16be;
+ struct regmap_field *regfields[BQ25630_REGF_MAX];
+
+ struct power_supply *psy;
+
+ /* State status from IRQs. */
+ u8 statregs[BQ25630_NR_STAT_REGS];
+};
+
+static int bq25630_alloc_regfield_range(struct bq25630_data *data,
+ const enum bq25630_regfield from,
+ const enum bq25630_regfield to,
+ struct regmap *regmap)
+{
+ int i;
+
+ for (i = from; i <= to; ++i) {
+ data->regfields[i] = devm_regmap_field_alloc(
+ data->dev, regmap, bq25630_regfields[i]);
+ if (IS_ERR(data->regfields[i]))
+ return dev_err_probe(
+ data->dev, PTR_ERR(data->regfields[i]),
+ "Could not allocate register field %d\n", i);
+ }
+
+ return 0;
+}
+
+static irqreturn_t bq25630_irq_thread(int irq, void *dev_id)
+{
+ struct bq25630_data *data = dev_id;
+ u8 regbuf[BQ25630_NR_STAT_REGS];
+ int ret;
+
+ BUILD_BUG_ON(ARRAY_SIZE(regbuf) != ARRAY_SIZE(data->statregs));
+
+ ret = regmap_bulk_read(data->regmap8, BQ25630_REG_CHARGER_STATUS_0,
+ regbuf, ARRAY_SIZE(regbuf));
+ if (ret) {
+ dev_err(data->dev, "Could not bulk read IRQ registers (%d)\n",
+ ret);
+ goto out;
+ }
+
+ if (memcmp(data->statregs, regbuf, ARRAY_SIZE(data->statregs))) {
+ power_supply_changed(data->psy);
+ memcpy(data->statregs, regbuf, ARRAY_SIZE(data->statregs));
+ }
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int bq25630_read_limit(struct bq25630_data *data,
+ const enum bq25630_regfield regfield,
+ const int minval, const int step,
+ const int minregval, int *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_field_read(data->regfields[regfield], &regval);
+ if (ret) {
+ dev_err(data->dev, "Could not read limit (%d)\n", ret);
+ return ret;
+ }
+
+ *val = minval + step * (regval - minregval);
+
+ return 0;
+}
+
+static int bq25630_write_limit(struct bq25630_data *data,
+ const enum bq25630_regfield regfield,
+ const int minval, const int maxval,
+ const int step, const int minregval, int val)
+{
+ unsigned int regval;
+ int ret;
+
+ val = clamp(val, minval, maxval);
+ regval = minregval + ((val - minval) / step);
+ ret = regmap_field_write(data->regfields[regfield], regval);
+ if (ret) {
+ dev_err(data->dev, "Could not write limit (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bq25630_read_charge_type(struct bq25630_data *data, int *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_field_read(data->regfields[BQ25630_REGF_CHG_STAT],
+ &regval);
+ if (ret) {
+ dev_err(data->dev, "Could not read charge type (%d)\n", ret);
+ return ret;
+ }
+
+ switch (regval) {
+ case BQ25630_CHG_STAT_NOT_CHARGING:
+ *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ case BQ25630_CHG_STAT_TRICKLE_CHARGE:
+ case BQ25630_CHG_STAT_PRE_CHARGE:
+ *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case BQ25630_CHG_STAT_FAST_CHARGE:
+ *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case BQ25630_CHG_STAT_TAPER_CHARGE:
+ *val = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
+ break;
+ case BQ25630_CHG_STAT_TERMINATION:
+ *val = POWER_SUPPLY_CHARGE_TYPE_BYPASS;
+ break;
+ default:
+ *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static int bq25630_read_health(struct bq25630_data *data, int *val)
+{
+ unsigned int regval;
+ int ret;
+ u8 temp;
+
+ ret = regmap_read(data->regmap8, BQ25630_REG_FAULT_STATUS, &regval);
+ if (ret) {
+ dev_err(data->dev, "Could not read fault status (%d)\n", ret);
+ return ret;
+ }
+
+ temp = regval & GENMASK(bq25630_regfields[BQ25630_REGF_TS_STAT].msb,
+ bq25630_regfields[BQ25630_REGF_TS_STAT].lsb);
+ if (regval &
+ GENMASK(bq25630_regfields[BQ25630_REGF_VBUS_FAULT_STAT].msb,
+ bq25630_regfields[BQ25630_REGF_VBUS_FAULT_STAT].lsb)) {
+ *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ } else if (regval &
+ GENMASK(bq25630_regfields[BQ25630_REGF_BAT_FAULT_STAT].msb,
+ bq25630_regfields[BQ25630_REGF_BAT_FAULT_STAT].lsb)) {
+ /*
+ * We can't differentiate between dead, under voltage or over
+ * voltage.
+ */
+ *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ } else if (regval &
+ GENMASK(bq25630_regfields[BQ25630_REGF_VSYS_FAULT_STAT].msb,
+ bq25630_regfields[BQ25630_REGF_VSYS_FAULT_STAT].lsb)) {
+ /*
+ * We can't differentiate between under voltage or over voltage.
+ */
+ *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ } else if (regval &
+ GENMASK(bq25630_regfields[BQ25630_REGF_OTG_FAULT_STAT].msb,
+ bq25630_regfields[BQ25630_REGF_OTG_FAULT_STAT].lsb)) {
+ /*
+ * We can't differentiate between under voltage or over voltage.
+ */
+ *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ } else if (regval &
+ GENMASK(bq25630_regfields[BQ25630_REGF_TSHUT_STAT].msb,
+ bq25630_regfields[BQ25630_REGF_TSHUT_STAT].lsb)) {
+ /* Temperature shutdown is always due to hot temperatures. */
+ *val = POWER_SUPPLY_HEALTH_HOT;
+ } else if (temp) {
+ switch (temp) {
+ case BQ25630_TS_STAT_COLD:
+ *val = POWER_SUPPLY_HEALTH_COLD;
+ break;
+ case BQ25630_TS_STAT_COOL:
+ *val = POWER_SUPPLY_HEALTH_COOL;
+ break;
+ case BQ25630_TS_STAT_WARM:
+ *val = POWER_SUPPLY_HEALTH_WARM;
+ break;
+ case BQ25630_TS_STAT_HOT:
+ *val = POWER_SUPPLY_HEALTH_HOT;
+ break;
+ default:
+ /* Interpret PRECOOL and PREWARM as NORMAL. */
+ *val = POWER_SUPPLY_HEALTH_GOOD;
+ }
+ } else {
+ *val = POWER_SUPPLY_HEALTH_GOOD;
+ }
+
+ return 0;
+}
+
+static int bq25630_read_vbus(struct bq25630_data *data, int *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_field_read(data->regfields[BQ25630_REGF_VBUS_STAT],
+ &regval);
+ if (ret) {
+ dev_err(data->dev, "Could not read VBUS (%d)\n", ret);
+ return ret;
+ }
+
+ switch (regval) {
+ case BQ25630_VBUS_STAT_NONE:
+ *val = -1;
+ break;
+ case BQ25630_VBUS_STAT_SDP:
+ *val = POWER_SUPPLY_USB_TYPE_SDP;
+ break;
+ case BQ25630_VBUS_STAT_CDP:
+ *val = POWER_SUPPLY_USB_TYPE_CDP;
+ break;
+ case BQ25630_VBUS_STAT_DCP:
+ case BQ25630_VBUS_STAT_HVDCP:
+ *val = POWER_SUPPLY_USB_TYPE_DCP;
+ break;
+ case BQ25630_VBUS_STAT_USB_C_DEFAULT:
+ case BQ25630_VBUS_STAT_USB_C_MEDIUM:
+ case BQ25630_VBUS_STAT_USB_C_HIGH:
+ *val = POWER_SUPPLY_USB_TYPE_C;
+ break;
+ default:
+ *val = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static int bq25630_get_status(struct bq25630_data *data, int *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_field_read(data->regfields[BQ25630_REGF_PG_STAT], &regval);
+ if (ret) {
+ dev_err(data->dev, "Could not read PG status (%d)", ret);
+ return ret;
+ }
+
+ if (!regval) {
+ /* There is not enough power, battery must be discharging. */
+ *val = POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ }
+
+ ret = regmap_field_read(data->regfields[BQ25630_REGF_EN_CHG], &regval);
+ if (ret) {
+ dev_err(data->dev, "Could not read charge status (%d)", ret);
+ return ret;
+ }
+
+ if (!regval) {
+ /* Charging is not enabled, battery must be discharging. */
+ *val = POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ }
+
+ ret = bq25630_read_charge_type(data, val);
+ if (ret)
+ return ret;
+
+ switch (*val) {
+ case POWER_SUPPLY_CHARGE_TYPE_NONE:
+ *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case POWER_SUPPLY_CHARGE_TYPE_BYPASS:
+ /* Corresponds to BQ25630_CHG_STAT_TERMINATION. */
+ *val = POWER_SUPPLY_STATUS_FULL;
+ break;
+ default:
+ *val = POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ return 0;
+}
+
+static int bq25630_reset(struct bq25630_data *data)
+{
+ unsigned int regval = 1;
+ int ret;
+
+ ret = regmap_field_force_write(data->regfields[BQ25630_REGF_REG_RST],
+ regval);
+ if (ret) {
+ dev_err(data->dev,
+ "Could not force write reset register field (%d)\n",
+ ret);
+ return ret;
+ }
+
+ /*
+ * After a successful register reset, the device signals by resetting
+ * this register field to 0. Try reading it for some interrupt cycles.
+ */
+ ret = regmap_field_read_poll_timeout(
+ data->regfields[BQ25630_REGF_REG_RST], regval, regval == 0, 256,
+ 100000);
+ if (ret) {
+ dev_err(data->dev, "Could not read reset register field (%d)\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bq25630_setup(struct power_supply *psy)
+{
+ struct bq25630_data *data = power_supply_get_drvdata(psy);
+ struct power_supply_battery_info *batinfo;
+ int ret;
+
+ ret = bq25630_reset(data);
+ if (ret) {
+ dev_err(data->dev, "Could not reset device (%d)\n", ret);
+ return ret;
+ }
+
+ /* Disable the watchdog. */
+ ret = regmap_field_write(data->regfields[BQ25630_REGF_WATCHDOG], 0);
+ if (ret) {
+ dev_err(data->dev, "Could not write watchdog timer (%d)\n",
+ ret);
+ return ret;
+ }
+
+ ret = power_supply_get_battery_info(psy, &batinfo);
+ if (ret) {
+ dev_err(data->dev, "Could not get battery info (%d)\n", ret);
+ return ret;
+ }
+
+ /*
+ * Set values according to battery info. Warn on missing "dangerous"
+ * properties.
+ */
+ if (batinfo->voltage_min_design_uv >= 0) {
+ ret = bq25630_write_limit(data, BQ25630_REGF_VSYSMIN,
+ BQ25630_VSYSMIN_MIN,
+ BQ25630_VSYSMIN_MAX,
+ BQ25630_VSYSMIN_STEP,
+ BQ25630_VSYSMIN_MIN_REGVAL,
+ batinfo->voltage_min_design_uv);
+ if (ret)
+ goto out_put_batinfo;
+ } else
+ dev_warn(data->dev,
+ "Using default value for minimum voltage\n");
+
+ if (batinfo->constant_charge_voltage_max_uv >= 0) {
+ ret = bq25630_write_limit(
+ data, BQ25630_REGF_VREG, BQ25630_VREG_MIN,
+ BQ25630_VREG_MAX, BQ25630_VREG_STEP,
+ BQ25630_VREG_MIN_REGVAL,
+ batinfo->constant_charge_voltage_max_uv);
+ if (ret)
+ goto out_put_batinfo;
+ } else
+ dev_warn(data->dev,
+ "Using default value for maximum constant charge voltage\n");
+
+ if (batinfo->constant_charge_current_max_ua >= 0) {
+ ret = bq25630_write_limit(
+ data, BQ25630_REGF_ICHG, BQ25630_ICHG_MIN,
+ BQ25630_ICHG_MAX, BQ25630_ICHG_STEP,
+ BQ25630_ICHG_MIN_REGVAL,
+ batinfo->constant_charge_current_max_ua);
+ if (ret)
+ goto out_put_batinfo;
+ } else
+ dev_warn(data->dev,
+ "Using default value for maximum constant charge current\n");
+
+ if (batinfo->charge_term_current_ua >= 0) {
+ ret = bq25630_write_limit(
+ data, BQ25630_REGF_ITERM, BQ25630_ITERM_MIN,
+ BQ25630_ITERM_MAX, BQ25630_ITERM_STEP,
+ BQ25630_ITERM_MIN_REGVAL,
+ batinfo->charge_term_current_ua);
+ if (ret)
+ goto out_put_batinfo;
+ }
+
+ if (batinfo->precharge_current_ua >= 0) {
+ ret = bq25630_write_limit(data, BQ25630_REGF_IPRECHG,
+ BQ25630_IPRECHG_MIN,
+ BQ25630_IPRECHG_MAX,
+ BQ25630_IPRECHG_STEP,
+ BQ25630_IPRECHG_MIN_REGVAL,
+ batinfo->precharge_current_ua);
+ if (ret)
+ goto out_put_batinfo;
+ }
+
+out_put_batinfo:
+ power_supply_put_battery_info(psy, batinfo);
+
+ return ret;
+}
+
+static int bq25630_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bq25630_data *data = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = bq25630_get_status(data, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ case POWER_SUPPLY_PROP_CHARGE_TYPES:
+ ret = bq25630_read_charge_type(data, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = bq25630_read_health(data, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = regmap_field_read(data->regfields[BQ25630_REGF_EN_CHG],
+ &val->intval);
+ if (ret || !val->intval) {
+ /* Charging is not even enabled. */
+ break;
+ }
+
+ ret = bq25630_read_vbus(data, &val->intval);
+ val->intval = val->intval >= 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ ret = bq25630_read_limit(data, BQ25630_REGF_VSYSMIN,
+ BQ25630_VSYSMIN_MIN,
+ BQ25630_VSYSMIN_STEP,
+ BQ25630_VSYSMIN_MIN_REGVAL,
+ &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = bq25630_read_limit(data, BQ25630_REGF_ICHG,
+ BQ25630_ICHG_MIN, BQ25630_ICHG_STEP,
+ BQ25630_ICHG_MIN_REGVAL, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = BQ25630_ICHG_MAX;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = bq25630_read_limit(data, BQ25630_REGF_VREG,
+ BQ25630_VREG_MIN, BQ25630_VREG_STEP,
+ BQ25630_VREG_MIN_REGVAL, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ val->intval = BQ25630_VREG_MAX;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ ret = bq25630_read_limit(data, BQ25630_REGF_IINDPM,
+ BQ25630_IINDPM_MIN,
+ BQ25630_IINDPM_STEP,
+ BQ25630_IINDPM_MIN_REGVAL,
+ &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
+ ret = bq25630_read_limit(data, BQ25630_REGF_VINDPM,
+ BQ25630_VINDPM_MIN,
+ BQ25630_VINDPM_STEP,
+ BQ25630_VINDPM_MIN_REGVAL,
+ &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ ret = bq25630_read_vbus(data, &val->intval);
+ if (!ret && val->intval < 0) {
+ /* Nothing connected. */
+ val->intval = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+ }
+ break;
+ case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
+ ret = bq25630_read_limit(data, BQ25630_REGF_IPRECHG,
+ BQ25630_IPRECHG_MIN,
+ BQ25630_IPRECHG_STEP,
+ BQ25630_IPRECHG_MIN_REGVAL,
+ &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ ret = bq25630_read_limit(data, BQ25630_REGF_ITERM,
+ BQ25630_ITERM_MIN, BQ25630_ITERM_STEP,
+ BQ25630_ITERM_MIN_REGVAL,
+ &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = "BQ25630";
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "Texas Instruments";
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int bq25630_charger_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct bq25630_data *data = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = regmap_field_write(data->regfields[BQ25630_REGF_EN_CHG],
+ !!val->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ ret = bq25630_write_limit(
+ data, BQ25630_REGF_VSYSMIN, BQ25630_VSYSMIN_MIN,
+ BQ25630_VSYSMIN_MAX, BQ25630_VSYSMIN_STEP,
+ BQ25630_VSYSMIN_MIN_REGVAL, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = bq25630_write_limit(data, BQ25630_REGF_ICHG,
+ BQ25630_ICHG_MIN, BQ25630_ICHG_MAX,
+ BQ25630_ICHG_STEP,
+ BQ25630_ICHG_MIN_REGVAL, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = bq25630_write_limit(data, BQ25630_REGF_VREG,
+ BQ25630_VREG_MIN, BQ25630_VREG_MAX,
+ BQ25630_VREG_STEP,
+ BQ25630_VREG_MIN_REGVAL, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ ret = bq25630_write_limit(
+ data, BQ25630_REGF_IINDPM, BQ25630_IINDPM_MIN,
+ BQ25630_IINDPM_MAX, BQ25630_IINDPM_STEP,
+ BQ25630_IINDPM_MIN_REGVAL, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
+ ret = bq25630_write_limit(
+ data, BQ25630_REGF_VINDPM, BQ25630_VINDPM_MIN,
+ BQ25630_VINDPM_MAX, BQ25630_VINDPM_STEP,
+ BQ25630_VINDPM_MIN_REGVAL, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
+ ret = bq25630_write_limit(
+ data, BQ25630_REGF_IPRECHG, BQ25630_IPRECHG_MIN,
+ BQ25630_IPRECHG_MAX, BQ25630_IPRECHG_STEP,
+ BQ25630_IPRECHG_MIN_REGVAL, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ ret = bq25630_write_limit(data, BQ25630_REGF_ITERM,
+ BQ25630_ITERM_MIN, BQ25630_ITERM_MAX,
+ BQ25630_ITERM_STEP,
+ BQ25630_ITERM_MIN_REGVAL,
+ val->intval);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int bq25630_charger_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
+ case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const enum power_supply_property bq25630_charger_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CHARGE_TYPES,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
+ POWER_SUPPLY_PROP_USB_TYPE,
+ POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static const struct power_supply_desc bq25630_charger_psy_desc = {
+ .name = BQ25630_DRV_NAME,
+ .type = POWER_SUPPLY_TYPE_USB,
+ .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_NONE) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_TRICKLE) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_BYPASS) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_UNKNOWN),
+ .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) |
+ BIT(POWER_SUPPLY_USB_TYPE_SDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_DCP) |
+ BIT(POWER_SUPPLY_USB_TYPE_CDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_C),
+ .properties = bq25630_charger_properties,
+ .num_properties = ARRAY_SIZE(bq25630_charger_properties),
+ .get_property = bq25630_charger_get_property,
+ .set_property = bq25630_charger_set_property,
+ .property_is_writeable = bq25630_charger_property_is_writeable,
+ .init = bq25630_setup,
+};
+
+static int bq25630_probe(struct i2c_client *client)
+{
+ struct power_supply_config psy_cfg = {};
+ struct bq25630_data *data;
+ int ret;
+
+ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = &client->dev;
+
+ data->regmap8 = devm_regmap_init_i2c(client, &bq25630_regmap_config8);
+ if (IS_ERR(data->regmap8))
+ return dev_err_probe(data->dev, PTR_ERR(data->regmap8),
+ "Could not initialize regmap8\n");
+
+ ret = bq25630_alloc_regfield_range(data, BQ25630_REGF_WATCHDOG,
+ BQ25630_REGF_TS_STAT,
+ data->regmap8);
+ if (ret)
+ return ret;
+
+ data->regmap16le =
+ devm_regmap_init_i2c(client, &bq25630_regmap_config16le);
+ if (IS_ERR(data->regmap16le))
+ return dev_err_probe(data->dev, PTR_ERR(data->regmap16le),
+ "Could not initialize regmap16le\n");
+
+ ret = bq25630_alloc_regfield_range(data, BQ25630_REGF_ICHG,
+ BQ25630_REGF_ITERM,
+ data->regmap16le);
+ if (ret)
+ return ret;
+
+ data->regmap16be =
+ devm_regmap_init_i2c(client, &bq25630_regmap_config16be);
+ if (IS_ERR(data->regmap16be))
+ return dev_err_probe(data->dev, PTR_ERR(data->regmap16be),
+ "Could not initialize regmap16be\n");
+
+ ret = bq25630_alloc_regfield_range(data, BQ25630_REGF_IBUS_ADC,
+ BQ25630_REGF_VBUS_ADC,
+ data->regmap16be);
+ if (ret)
+ return ret;
+
+ psy_cfg.drv_data = data;
+ psy_cfg.fwnode = dev_fwnode(data->dev);
+ data->psy = devm_power_supply_register(
+ data->dev, &bq25630_charger_psy_desc, &psy_cfg);
+ if (IS_ERR(data->psy))
+ return dev_err_probe(data->dev, PTR_ERR(data->psy),
+ "Could not register power supply\n");
+
+ /*
+ * Device sends active low 256 µs pulse to report status and fault.
+ *
+ * Note that we need to request this *after* registering the power
+ * supply so devm destructs it correctly in the reverse order. Otherwise
+ * spurious interrupts could call power_supply_changed() wrongly with a
+ * uninitialized/deallocated power supply.
+ */
+ ret = devm_request_threaded_irq(data->dev, client->irq, NULL,
+ bq25630_irq_thread,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ NULL, data);
+ if (ret)
+ return dev_err_probe(data->dev, ret, "Could not request IRQ\n");
+
+ return 0;
+}
+
+static const struct of_device_id bq25630_of_match[] = {
+ {
+ .compatible = "ti,bq25630",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, bq25630_of_match);
+
+static struct i2c_driver bq25630_driver = {
+ .driver = {
+ .name = BQ25630_DRV_NAME,
+ .of_match_table = bq25630_of_match,
+ },
+ .probe = bq25630_probe,
+};
+module_i2c_driver(bq25630_driver);
+
+MODULE_AUTHOR("Waqar Hameed <waqar.hameed@axis.com>");
+MODULE_DESCRIPTION("TI BQ25630 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c
index 5a6634fde837..4b1f81b1ed86 100644
--- a/drivers/power/supply/bq256xx_charger.c
+++ b/drivers/power/supply/bq256xx_charger.c
@@ -347,12 +347,8 @@ static int bq256xx_array_parse(int array_size, int val, const int array[])
if (val == array[i])
return i;
- if (val > array[i - 1] && val < array[i]) {
- if (val < array[i])
- return i - 1;
- else
- return i;
- }
+ if (val > array[i - 1] && val < array[i])
+ return i - 1;
}
return -EINVAL;
}
@@ -896,6 +892,8 @@ static void bq256xx_charger_reset(void *data)
if (!IS_ERR_OR_NULL(bq->usb3_phy))
usb_unregister_notifier(bq->usb3_phy, &bq->usb_nb);
+
+ cancel_work_sync(&bq->usb_work);
}
static int bq256xx_set_charger_property(struct power_supply *psy,
@@ -1721,24 +1719,12 @@ static int bq256xx_probe(struct i2c_client *client)
return ret;
}
- ret = devm_add_action_or_reset(dev, bq256xx_charger_reset, bq);
- if (ret)
- return ret;
+ INIT_WORK(&bq->usb_work, bq256xx_usb_work);
+ bq->usb_nb.notifier_call = bq256xx_usb_notifier;
/* OTG reporting */
bq->usb2_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
- if (!IS_ERR_OR_NULL(bq->usb2_phy)) {
- INIT_WORK(&bq->usb_work, bq256xx_usb_work);
- bq->usb_nb.notifier_call = bq256xx_usb_notifier;
- usb_register_notifier(bq->usb2_phy, &bq->usb_nb);
- }
-
bq->usb3_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB3);
- if (!IS_ERR_OR_NULL(bq->usb3_phy)) {
- INIT_WORK(&bq->usb_work, bq256xx_usb_work);
- bq->usb_nb.notifier_call = bq256xx_usb_notifier;
- usb_register_notifier(bq->usb3_phy, &bq->usb_nb);
- }
ret = bq256xx_power_supply_init(bq, &psy_cfg, dev);
if (ret) {
@@ -1746,16 +1732,25 @@ static int bq256xx_probe(struct i2c_client *client)
return ret;
}
+ /* Register after the power supplies so devm runs it first. */
+ ret = devm_add_action_or_reset(dev, bq256xx_charger_reset, bq);
+ if (ret)
+ return ret;
+
+ if (!IS_ERR_OR_NULL(bq->usb2_phy))
+ usb_register_notifier(bq->usb2_phy, &bq->usb_nb);
+
+ if (!IS_ERR_OR_NULL(bq->usb3_phy))
+ usb_register_notifier(bq->usb3_phy, &bq->usb_nb);
+
if (client->irq) {
ret = devm_request_threaded_irq(dev, client->irq, NULL,
bq256xx_irq_handler_thread,
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
dev_name(&client->dev), bq);
- if (ret < 0) {
- dev_err(dev, "get irq fail: %d\n", ret);
+ if (ret < 0)
return ret;
- }
}
ret = bq256xx_hw_init(bq);
diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c
index 9c082865e745..b735bb02a8e0 100644
--- a/drivers/power/supply/bq257xx_charger.c
+++ b/drivers/power/supply/bq257xx_charger.c
@@ -5,6 +5,7 @@
*/
#include <linux/bitfield.h>
+#include <linux/byteorder/generic.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/mfd/bq257xx.h>
@@ -89,6 +90,53 @@ struct bq257xx_chg {
};
/**
+ * bq25792_read16() - Read a 16-bit value from device register
+ * @pdata: driver platform data
+ * @reg: register address to read from
+ * @val: pointer to store the register value
+ *
+ * Read a 16-bit big-endian value from the BQ25792 device via regmap
+ * and convert to CPU byte order.
+ *
+ * Return: Returns 0 on success or error on failure to read.
+ */
+static int bq25792_read16(struct bq257xx_chg *pdata, unsigned int reg, u16 *val)
+{
+ __be16 regval;
+ int ret;
+
+ ret = regmap_raw_read(pdata->bq->regmap, reg, &regval, sizeof(regval));
+ if (ret)
+ return ret;
+
+ *val = be16_to_cpu(regval);
+ return 0;
+}
+
+/**
+ * bq25792_write16() - Write a 16-bit value to device register
+ * @pdata: driver platform data
+ * @reg: register address to write to
+ * @val: 16-bit value to write in CPU byte order
+ *
+ * Convert the value to big-endian and write a 16-bit value to the
+ * BQ25792 device via regmap.
+ *
+ * Return: Returns 0 on success or error on failure to write.
+ */
+static int bq25792_write16(struct bq257xx_chg *pdata, unsigned int reg, u16 val)
+{
+ __be16 regval = cpu_to_be16(val);
+ int ret;
+
+ ret = regmap_raw_write(pdata->bq->regmap, reg, &regval, sizeof(regval));
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/**
* bq25703_get_state() - Get the current state of the device
* @pdata: driver platform data
*
@@ -120,6 +168,43 @@ static int bq25703_get_state(struct bq257xx_chg *pdata)
}
/**
+ * bq25792_get_state() - Get the current state of the device
+ * @pdata: driver platform data
+ *
+ * Get the current state of the BQ25792 charger by reading status
+ * registers. Updates the online, charging, overvoltage, and fault
+ * status fields in the driver data structure.
+ *
+ * Return: Returns 0 on success or error on failure to read device.
+ */
+static int bq25792_get_state(struct bq257xx_chg *pdata)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25792_REG1B_CHARGER_STATUS_0, &reg);
+ if (ret)
+ return ret;
+
+ pdata->online = reg & BQ25792_REG1B_PG_STAT;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25792_REG1C_CHARGER_STATUS_1, &reg);
+ if (ret)
+ return ret;
+
+ pdata->charging = reg & BQ25792_REG1C_CHG_STAT_MASK;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25792_REG20_FAULT_STATUS_0, &reg);
+ if (ret)
+ return ret;
+
+ pdata->overvoltage = reg & BQ25792_REG20_OVERVOLTAGE_MASK;
+ pdata->oc_fault = reg & BQ25792_REG20_OVERCURRENT_MASK;
+
+ return 0;
+}
+
+/**
* bq25703_get_min_vsys() - Get the minimum system voltage
* @pdata: driver platform data
* @intval: value for minimum voltage
@@ -143,6 +228,31 @@ static int bq25703_get_min_vsys(struct bq257xx_chg *pdata, int *intval)
}
/**
+ * bq25792_get_min_vsys() - Get the minimum system voltage
+ * @pdata: driver platform data
+ * @intval: pointer to store the minimum voltage value
+ *
+ * Read the current minimum system voltage setting from the device
+ * and return it in microvolts.
+ *
+ * Return: Returns 0 on success or error on failure to read.
+ */
+static int bq25792_get_min_vsys(struct bq257xx_chg *pdata, int *intval)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25792_REG00_MIN_SYS_VOLTAGE, &reg);
+ if (ret)
+ return ret;
+
+ reg = FIELD_GET(BQ25792_REG00_VSYSMIN_MASK, reg);
+ *intval = (reg * BQ25792_MINVSYS_STEP_UV) + BQ25792_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.
@@ -167,6 +277,29 @@ static int bq25703_set_min_vsys(struct bq257xx_chg *pdata, int vsys)
}
/**
+ * bq25792_set_min_vsys() - Set the minimum system voltage
+ * @pdata: driver platform data
+ * @vsys: voltage value to set in uV
+ *
+ * Set the minimum system voltage by clamping the requested value
+ * between device limits and writing to the appropriate register.
+ *
+ * Return: Returns 0 on success or error on failure to write.
+ */
+static int bq25792_set_min_vsys(struct bq257xx_chg *pdata, int vsys)
+{
+ unsigned int reg;
+ int vsys_min = pdata->vsys_min;
+
+ vsys = clamp(vsys, vsys_min, BQ25792_MINVSYS_MAX_UV);
+ reg = ((vsys - BQ25792_MINVSYS_MIN_UV) / BQ25792_MINVSYS_STEP_UV);
+ reg = FIELD_PREP(BQ25792_REG00_VSYSMIN_MASK, reg);
+
+ return regmap_write(pdata->bq->regmap,
+ BQ25792_REG00_MIN_SYS_VOLTAGE, reg);
+}
+
+/**
* bq25703_get_cur() - Get the reported current from the battery
* @pdata: driver platform data
* @intval: value of reported battery current
@@ -196,6 +329,30 @@ static int bq25703_get_cur(struct bq257xx_chg *pdata, int *intval)
}
/**
+ * bq25792_get_cur() - Get the reported current from the battery
+ * @pdata: driver platform data
+ * @intval: pointer to store the battery current value
+ *
+ * Read the current ADC value from the device representing the battery
+ * charge or discharge current and return it in microamps.
+ *
+ * Return: Returns 0 on success or error on failure to read.
+ */
+static int bq25792_get_cur(struct bq257xx_chg *pdata, int *intval)
+{
+ u16 reg;
+ int ret;
+
+ ret = bq25792_read16(pdata, BQ25792_REG33_IBAT_ADC, &reg);
+ if (ret < 0)
+ return ret;
+
+ *intval = (s16)reg * BQ25792_ADCIBAT_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
@@ -219,6 +376,30 @@ static int bq25703_get_ichg_cur(struct bq257xx_chg *pdata, int *intval)
}
/**
+ * bq25792_get_ichg_cur() - Get the maximum reported charge current
+ * @pdata: driver platform data
+ * @intval: pointer to store the maximum charge current value
+ *
+ * Read the programmed maximum charge current limit from the device.
+ *
+ * Return: Returns 0 on success or error on failure to read value.
+ */
+static int bq25792_get_ichg_cur(struct bq257xx_chg *pdata, int *intval)
+{
+ u16 reg;
+ int ret;
+
+ ret = bq25792_read16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, &reg);
+ if (ret)
+ return ret;
+
+ *intval = FIELD_GET(BQ25792_REG03_ICHG_MASK, reg) *
+ BQ25792_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.
@@ -243,6 +424,28 @@ static int bq25703_set_ichg_cur(struct bq257xx_chg *pdata, int ichg)
}
/**
+ * bq25792_set_ichg_cur() - Set the maximum charge current
+ * @pdata: driver platform data
+ * @ichg: current value to set in uA
+ *
+ * Set the maximum charge current by clamping the requested value
+ * between device limits and writing to the appropriate register.
+ *
+ * Return: Returns 0 on success or error on failure to write.
+ */
+static int bq25792_set_ichg_cur(struct bq257xx_chg *pdata, int ichg)
+{
+ int ichg_max = pdata->ichg_max;
+ u16 reg;
+
+ ichg = clamp(ichg, BQ25792_ICHG_MIN_UA, ichg_max);
+ reg = FIELD_PREP(BQ25792_REG03_ICHG_MASK,
+ (ichg / BQ25792_ICHG_STEP_UA));
+
+ return bq25792_write16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, reg);
+}
+
+/**
* bq25703_get_chrg_volt() - Get the maximum set charge voltage
* @pdata: driver platform data
* @intval: maximum charge voltage value
@@ -266,6 +469,30 @@ static int bq25703_get_chrg_volt(struct bq257xx_chg *pdata, int *intval)
}
/**
+ * bq25792_get_chrg_volt() - Get the maximum set charge voltage
+ * @pdata: driver platform data
+ * @intval: pointer to store the maximum charge voltage value
+ *
+ * Read the current charge voltage limit from the device.
+ *
+ * Return: Returns 0 on success or error on failure to read value.
+ */
+static int bq25792_get_chrg_volt(struct bq257xx_chg *pdata, int *intval)
+{
+ u16 reg;
+ int ret;
+
+ ret = bq25792_read16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, &reg);
+ if (ret)
+ return ret;
+
+ *intval = FIELD_GET(BQ25792_REG01_VREG_MASK, reg) *
+ BQ25792_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.
@@ -292,6 +519,29 @@ static int bq25703_set_chrg_volt(struct bq257xx_chg *pdata, int vbat)
}
/**
+ * bq25792_set_chrg_volt() - Set the maximum charge voltage
+ * @pdata: driver platform data
+ * @vbat: voltage value to set in uV
+ *
+ * Set the maximum charge voltage by clamping the requested value
+ * between device limits and writing to the appropriate register.
+ *
+ * Return: Returns 0 on success or error on failure to write.
+ */
+static int bq25792_set_chrg_volt(struct bq257xx_chg *pdata, int vbat)
+{
+ int vbat_max = pdata->vbat_max;
+ u16 reg;
+
+ vbat = clamp(vbat, BQ25792_VBATREG_MIN_UV, vbat_max);
+
+ reg = FIELD_PREP(BQ25792_REG01_VREG_MASK,
+ (vbat / BQ25792_VBATREG_STEP_UV));
+
+ return bq25792_write16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, reg);
+}
+
+/**
* bq25703_get_iindpm() - Get the maximum set input current
* @pdata: driver platform data
* @intval: maximum input current value
@@ -320,6 +570,30 @@ static int bq25703_get_iindpm(struct bq257xx_chg *pdata, int *intval)
}
/**
+ * bq25792_get_iindpm() - Get the maximum set input current
+ * @pdata: driver platform data
+ * @intval: pointer to store the maximum input current value
+ *
+ * Read the current input current limit from the device.
+ *
+ * Return: Returns 0 on success or error on failure to read value.
+ */
+static int bq25792_get_iindpm(struct bq257xx_chg *pdata, int *intval)
+{
+ u16 reg;
+ int ret;
+
+ ret = bq25792_read16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT, &reg);
+ if (ret)
+ return ret;
+
+ reg = FIELD_GET(BQ25792_REG06_IINDPM_MASK, reg);
+ *intval = reg * BQ25792_IINDPM_STEP_UA;
+
+ return ret;
+}
+
+/**
* bq25703_set_iindpm() - Set the maximum input current
* @pdata: driver platform data
* @iindpm: current value in uA.
@@ -345,6 +619,29 @@ static int bq25703_set_iindpm(struct bq257xx_chg *pdata, int iindpm)
}
/**
+ * bq25792_set_iindpm() - Set the maximum input current
+ * @pdata: driver platform data
+ * @iindpm: current value in uA
+ *
+ * Set the maximum input current by clamping the requested value
+ * between device limits and writing to the appropriate register.
+ *
+ * Return: Returns 0 on success or error on failure to write.
+ */
+static int bq25792_set_iindpm(struct bq257xx_chg *pdata, int iindpm)
+{
+ u16 reg;
+ int iindpm_max = pdata->iindpm_max;
+
+ iindpm = clamp(iindpm, BQ25792_IINDPM_MIN_UA, iindpm_max);
+
+ reg = iindpm / BQ25792_IINDPM_STEP_UA;
+
+ return bq25792_write16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT,
+ FIELD_PREP(BQ25792_REG06_IINDPM_MASK, reg));
+}
+
+/**
* bq25703_get_vbat() - Get the reported voltage from the battery
* @pdata: driver platform data
* @intval: value of reported battery voltage
@@ -369,6 +666,30 @@ static int bq25703_get_vbat(struct bq257xx_chg *pdata, int *intval)
}
/**
+ * bq25792_get_vbat() - Get the reported voltage from the battery
+ * @pdata: driver platform data
+ * @intval: pointer to store the battery voltage value
+ *
+ * Read the current ADC value representing the battery voltage
+ * and return it in microvolts.
+ *
+ * Return: Returns 0 on success or error on failure to read value.
+ */
+static int bq25792_get_vbat(struct bq257xx_chg *pdata, int *intval)
+{
+ u16 reg;
+ int ret;
+
+ ret = bq25792_read16(pdata, BQ25792_REG3B_VBAT_ADC, &reg);
+ if (ret)
+ return ret;
+
+ *intval = reg * BQ25792_ADCVSYSVBAT_STEP_UV;
+
+ return ret;
+}
+
+/**
* bq25703_hw_init() - Set all the required registers to init the charger
* @pdata: driver platform data
*
@@ -435,6 +756,108 @@ static int bq25703_hw_init(struct bq257xx_chg *pdata)
}
/**
+ * bq25792_hw_init() - Initialize BQ25792 hardware
+ * @pdata: driver platform data
+ *
+ * Initialize the BQ25792 by disabling the watchdog, enabling discharge
+ * current sensing with 5A limit, and configuring input current regulation.
+ * Set the charge current, charge voltage, minimum system voltage, and
+ * input current limit from platform data. Enable and configure the ADC
+ * to measure all available channels.
+ *
+ * Return: Returns 0 on success or error code on error.
+ */
+static int bq25792_hw_init(struct bq257xx_chg *pdata)
+{
+ struct regmap *regmap = pdata->bq->regmap;
+ int ret = 0;
+ u8 reg;
+
+ /* Disable watchdog (TODO: make it work instead) */
+ ret = regmap_write(regmap, BQ25792_REG10_CHARGER_CONTROL_1, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * Enable battery discharge current sensing, 5A discharge current
+ * limit, input current regulation and ship FET functions
+ */
+ ret = regmap_write(regmap, BQ25792_REG14_CHARGER_CONTROL_5,
+ BQ25792_REG14_SFET_PRESENT |
+ BQ25792_REG14_EN_IBAT |
+ BQ25792_IBAT_5A |
+ BQ25792_REG14_EN_IINDPM);
+ if (ret)
+ return ret;
+
+ if (pdata->vbat_max < 5000000) {
+ /* 1S batteries */
+ reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_1S);
+ } else if (pdata->vbat_max < 10000000) {
+ /* 2S batteries */
+ reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_2S);
+ } else if (pdata->vbat_max < 14000000) {
+ /* 3S batteries */
+ reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_3S);
+ } else {
+ /* 4S batteries */
+ reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_4S);
+ }
+
+ /* Recharge voltage detection deglitch time (default 1024ms) */
+ reg |= FIELD_PREP(BQ25792_REG0A_TRECHG_MASK, BQ25792_TRECHG_1024MS);
+
+ /* Recharge voltage offset: 5% of the set charge voltage */
+ reg |= FIELD_PREP(BQ25792_REG0A_VRECHG_MASK,
+ (pdata->vbat_max / 20 - BQ25792_VRECHG_MIN_UV) / BQ25792_VRECHG_STEP_UV);
+
+ ret = regmap_write(regmap, BQ25792_REG0A_RECHARGE_CONTROL, reg);
+ if (ret)
+ return ret;
+
+ 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 = bq25792_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;
+
+ /* Enable the Input Current Optimizer (the rest is at POR value) */
+ ret = regmap_write(regmap, BQ25792_REG0F_CHARGER_CONTROL_0,
+ BQ25792_REG0F_EN_AUTO_IBATDIS |
+ BQ25792_REG0F_EN_CHG |
+ BQ25792_REG0F_EN_ICO |
+ BQ25792_REG0F_EN_TERM);
+ if (ret)
+ return ret;
+
+ /* Enable the ADC. */
+ ret = regmap_write(regmap, BQ25792_REG2E_ADC_CONTROL, BQ25792_REG2E_ADC_EN);
+ if (ret)
+ return ret;
+
+ /* Clear per-channel ADC disable bits - enable all channels */
+ ret = regmap_write(regmap, BQ25792_REG2F_ADC_FUNCTION_DISABLE_0, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(regmap, BQ25792_REG30_ADC_FUNCTION_DISABLE_1, 0);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+/**
* bq25703_hw_shutdown() - Set registers for shutdown
* @pdata: driver platform data
*
@@ -446,6 +869,30 @@ static void bq25703_hw_shutdown(struct bq257xx_chg *pdata)
BQ25703_EN_LWPWR, BQ25703_EN_LWPWR);
}
+/**
+ * bq25792_hw_shutdown() - Shutdown BQ25792 hardware
+ * @pdata: driver platform data
+ *
+ * Perform hardware shutdown for the BQ25792. Currently a no-op
+ * as the device does not require special shutdown configuration.
+ */
+static void bq25792_hw_shutdown(struct bq257xx_chg *pdata)
+{
+ /* Nothing to do here */
+}
+
+/**
+ * bq257xx_set_charger_property() - Set a power supply property
+ * @psy: power supply device
+ * @prop: power supply property to set
+ * @val: value to set for the property
+ *
+ * Handle requests to set power supply properties such as input current
+ * limit, constant charge voltage, and constant charge current. Routes
+ * the request to the chip-specific implementation.
+ *
+ * Return: Returns 0 on success or -EINVAL if property is not supported.
+ */
static int bq257xx_set_charger_property(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
@@ -469,6 +916,19 @@ static int bq257xx_set_charger_property(struct power_supply *psy,
return -EINVAL;
}
+/**
+ * bq257xx_get_charger_property() - Get a power supply property
+ * @psy: power supply device
+ * @psp: power supply property to get
+ * @val: pointer to store the property value
+ *
+ * Handle requests to get power supply properties, including status,
+ * health, manufacturer, online state, and various voltage/current
+ * measurements. Reads current device state and routes chip-specific
+ * property requests to appropriate handlers.
+ *
+ * Return: Returns 0 on success or -EINVAL if property is not supported.
+ */
static int bq257xx_get_charger_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -550,6 +1010,17 @@ static enum power_supply_property bq257xx_power_supply_props[] = {
POWER_SUPPLY_PROP_USB_TYPE,
};
+/**
+ * bq257xx_property_is_writeable() - Check if a property is writeable
+ * @psy: power supply device
+ * @prop: power supply property to check
+ *
+ * Determines which power supply properties can be written to. Only
+ * charge current limit, charge voltage limit, and input current
+ * limit are writeable.
+ *
+ * Return: Returns 1 if property is writeable, 0 otherwise.
+ */
static int bq257xx_property_is_writeable(struct power_supply *psy,
enum power_supply_property prop)
{
@@ -622,6 +1093,17 @@ out:
power_supply_changed(psy);
}
+/**
+ * bq257xx_irq_handler_thread() - Handle charger interrupt
+ * @irq: interrupt number
+ * @private: pointer to driver private data
+ *
+ * Thread handler for charger interrupts. Triggers re-evaluation of
+ * external power status and updates power supply state in response
+ * to charger events.
+ *
+ * Return: Returns IRQ_HANDLED if interrupt was processed.
+ */
static irqreturn_t bq257xx_irq_handler_thread(int irq, void *private)
{
struct bq257xx_chg *pdata = private;
@@ -662,6 +1144,22 @@ static const struct bq257xx_chip_info bq25703_chip_info = {
.bq257xx_get_min_vsys = &bq25703_get_min_vsys,
};
+static const struct bq257xx_chip_info bq25792_chip_info = {
+ .default_iindpm_uA = BQ25792_IINDPM_DEFAULT_UA,
+ .bq257xx_hw_init = &bq25792_hw_init,
+ .bq257xx_hw_shutdown = &bq25792_hw_shutdown,
+ .bq257xx_get_state = &bq25792_get_state,
+ .bq257xx_get_ichg = &bq25792_get_ichg_cur,
+ .bq257xx_set_ichg = &bq25792_set_ichg_cur,
+ .bq257xx_get_vbatreg = &bq25792_get_chrg_volt,
+ .bq257xx_set_vbatreg = &bq25792_set_chrg_volt,
+ .bq257xx_get_iindpm = &bq25792_get_iindpm,
+ .bq257xx_set_iindpm = &bq25792_set_iindpm,
+ .bq257xx_get_cur = &bq25792_get_cur,
+ .bq257xx_get_vbat = &bq25792_get_vbat,
+ .bq257xx_get_min_vsys = &bq25792_get_min_vsys,
+};
+
/**
* bq257xx_parse_dt() - Parse the device tree for required properties
* @pdata: driver platform data
@@ -707,6 +1205,17 @@ static int bq257xx_parse_dt(struct bq257xx_chg *pdata,
return 0;
}
+/**
+ * bq257xx_charger_probe() - Probe routine for charger platform device
+ * @pdev: platform device
+ *
+ * Probe the charger device, allocate driver data structure, select the
+ * appropriate chip-specific function pointers, register the power supply,
+ * parse device tree properties for battery limits, initialize hardware,
+ * and set up the interrupt handler if available.
+ *
+ * Return: Returns 0 on success or error code on failure.
+ */
static int bq257xx_charger_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -722,7 +1231,17 @@ static int bq257xx_charger_probe(struct platform_device *pdev)
return -ENOMEM;
pdata->bq = bq;
- pdata->chip = &bq25703_chip_info;
+
+ switch (bq->type) {
+ case BQ25703A:
+ pdata->chip = &bq25703_chip_info;
+ break;
+ case BQ25792:
+ pdata->chip = &bq25792_chip_info;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL, "Unknown chip type\n");
+ }
platform_set_drvdata(pdev, pdata);
@@ -753,13 +1272,18 @@ static int bq257xx_charger_probe(struct platform_device *pdev)
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;
}
+/**
+ * bq257xx_charger_shutdown() - Shutdown routine for charger platform device
+ * @pdev: platform device
+ *
+ * Called during system shutdown to perform charger cleanup, including
+ * disabling watchdog timers or other chip-specific shutdown procedures.
+ */
static void bq257xx_charger_shutdown(struct platform_device *pdev)
{
struct bq257xx_chg *pdata = platform_get_drvdata(pdev);
diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c
index 180bc137a863..741a45cc5f35 100644
--- a/drivers/power/supply/bq25890_charger.c
+++ b/drivers/power/supply/bq25890_charger.c
@@ -1389,6 +1389,14 @@ static int bq25890_fw_read_u32_props(struct bq25890_device *bq)
return 0;
}
+static void bq25890_release_secondary_chrg(void *data)
+{
+ struct bq25890_device *bq = data;
+
+ power_supply_put(bq->secondary_chrg);
+ bq->secondary_chrg = NULL;
+}
+
static int bq25890_fw_probe(struct bq25890_device *bq)
{
int ret;
@@ -1401,6 +1409,10 @@ static int bq25890_fw_probe(struct bq25890_device *bq)
bq->secondary_chrg = power_supply_get_by_name(str);
if (!bq->secondary_chrg)
return -EPROBE_DEFER;
+
+ ret = devm_add_action_or_reset(bq->dev, bq25890_release_secondary_chrg, bq);
+ if (ret)
+ return ret;
}
/* Optional, left at 0 if property is not present */
diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
index 45f0e39b8c2d..01c9ec1116fa 100644
--- a/drivers/power/supply/bq27xxx_battery.c
+++ b/drivers/power/supply/bq27xxx_battery.c
@@ -330,7 +330,7 @@ static u8
[BQ27XXX_REG_FLAGS] = 0x0a,
[BQ27XXX_REG_TTE] = 0x16,
[BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
- [BQ27XXX_REG_TTES] = 0x1c,
+ [BQ27XXX_REG_TTES] = 0x1a,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = 0x10,
@@ -495,7 +495,7 @@ static u8
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
- [BQ27XXX_REG_AE] = 0x22,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x22,
@@ -516,7 +516,7 @@ static u8
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
- [BQ27XXX_REG_AE] = 0x22,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x22,
diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c
index 71b6e49a835f..fd4cd9cd2ce2 100644
--- a/drivers/power/supply/charger-manager.c
+++ b/drivers/power/supply/charger-manager.c
@@ -1014,6 +1014,29 @@ static int charger_extcon_init(struct charger_manager *cm,
return 0;
}
+static int charger_manager_get_regulators(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+ struct charger_regulator *charger;
+ int i, ret;
+
+ for (i = 0; i < desc->num_charger_regulators; i++) {
+ charger = &desc->charger_regulators[i];
+ charger->consumer = regulator_get(cm->dev,
+ charger->regulator_name);
+ if (IS_ERR(charger->consumer)) {
+ dev_err(cm->dev, "Cannot find charger(%s)\n",
+ charger->regulator_name);
+ ret = PTR_ERR(charger->consumer);
+ while (i-- > 0)
+ regulator_put(desc->charger_regulators[i].consumer);
+ return ret;
+ }
+ charger->cm = cm;
+ }
+ return 0;
+}
+
/**
* charger_manager_register_extcon - Register extcon device to receive state
* of charger cable.
@@ -1036,15 +1059,6 @@ static int charger_manager_register_extcon(struct charger_manager *cm)
for (i = 0; i < desc->num_charger_regulators; i++) {
charger = &desc->charger_regulators[i];
- charger->consumer = regulator_get(cm->dev,
- charger->regulator_name);
- if (IS_ERR(charger->consumer)) {
- dev_err(cm->dev, "Cannot find charger(%s)\n",
- charger->regulator_name);
- return PTR_ERR(charger->consumer);
- }
- charger->cm = cm;
-
for (j = 0; j < charger->num_cables; j++) {
struct charger_cable *cable = &charger->cables[j];
@@ -1580,13 +1594,23 @@ static int charger_manager_probe(struct platform_device *pdev)
}
psy_cfg.attr_grp = desc->sysfs_groups;
+ /*
+ * Acquire charger regulators before exposing the sysfs entries, so
+ * userspace cannot reach externally_control before the regulators
+ * (and charger->cm) are available. Mirrors the order in remove().
+ */
+ ret = charger_manager_get_regulators(cm);
+ if (ret < 0)
+ return ret;
+
cm->charger_psy = power_supply_register(&pdev->dev,
&cm->charger_psy_desc,
&psy_cfg);
if (IS_ERR(cm->charger_psy)) {
dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n",
cm->charger_psy_desc.name);
- return PTR_ERR(cm->charger_psy);
+ ret = PTR_ERR(cm->charger_psy);
+ goto err_regulator;
}
/* Register extcon device for charger cable */
@@ -1620,11 +1644,11 @@ static int charger_manager_probe(struct platform_device *pdev)
return 0;
err_reg_extcon:
+ power_supply_unregister(cm->charger_psy);
+err_regulator:
for (i = 0; i < desc->num_charger_regulators; i++)
regulator_put(desc->charger_regulators[i].consumer);
- power_supply_unregister(cm->charger_psy);
-
return ret;
}
@@ -1642,12 +1666,12 @@ static void charger_manager_remove(struct platform_device *pdev)
cancel_work_sync(&setup_polling);
cancel_delayed_work_sync(&cm_monitor_work);
- for (i = 0 ; i < desc->num_charger_regulators ; i++)
- regulator_put(desc->charger_regulators[i].consumer);
+ try_charger_enable(cm, false);
power_supply_unregister(cm->charger_psy);
- try_charger_enable(cm, false);
+ for (i = 0 ; i < desc->num_charger_regulators ; i++)
+ regulator_put(desc->charger_regulators[i].consumer);
}
static const struct platform_device_id charger_manager_id[] = {
diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c
index 59c741993ef8..1d2b8bb08564 100644
--- a/drivers/power/supply/cpcap-battery.c
+++ b/drivers/power/supply/cpcap-battery.c
@@ -957,12 +957,8 @@ static int cpcap_battery_init_irq(struct platform_device *pdev,
cpcap_battery_irq_thread,
IRQF_SHARED | IRQF_ONESHOT,
name, ddata);
- if (error) {
- dev_err(ddata->dev, "could not get irq %s: %i\n",
- name, error);
-
+ if (error)
return error;
- }
d = devm_kzalloc(ddata->dev, sizeof(*d), GFP_KERNEL);
if (!d)
diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c
index ec8d2a9245d9..76e2d2e3f5f5 100644
--- a/drivers/power/supply/cpcap-charger.c
+++ b/drivers/power/supply/cpcap-charger.c
@@ -761,12 +761,8 @@ static int cpcap_usb_init_irq(struct platform_device *pdev,
cpcap_charger_irq_thread,
IRQF_SHARED | IRQF_ONESHOT,
name, ddata);
- if (error) {
- dev_err(ddata->dev, "could not get irq %s: %i\n",
- name, error);
-
+ if (error)
return error;
- }
d = devm_kzalloc(ddata->dev, sizeof(*d), GFP_KERNEL);
if (!d)
diff --git a/drivers/power/supply/cros_peripheral_charger.c b/drivers/power/supply/cros_peripheral_charger.c
index 612bc7badac0..b640930366d4 100644
--- a/drivers/power/supply/cros_peripheral_charger.c
+++ b/drivers/power/supply/cros_peripheral_charger.c
@@ -258,6 +258,14 @@ static int cros_ec_notify(struct notifier_block *nb,
return cros_pchg_event(charger);
}
+static void cros_pchg_unregister_notifier(void *data)
+{
+ struct charger_data *charger = data;
+
+ blocking_notifier_chain_unregister(&charger->ec_device->event_notifier,
+ &charger->notifier);
+}
+
static int cros_pchg_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -345,9 +353,10 @@ static int cros_pchg_probe(struct platform_device *pdev)
ret = blocking_notifier_chain_register(&ec_dev->ec_dev->event_notifier,
nb);
if (ret < 0)
- dev_err(dev, "Failed to register notifier (err:%d)\n", ret);
+ return dev_err_probe(dev, ret, "Failed to register notifier\n");
- return 0;
+ return devm_add_action_or_reset(dev, cros_pchg_unregister_notifier,
+ charger);
}
#ifdef CONFIG_PM_SLEEP
diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c
index c1cbe6e5476e..69d227ceb5c9 100644
--- a/drivers/power/supply/cros_usbpd-charger.c
+++ b/drivers/power/supply/cros_usbpd-charger.c
@@ -124,6 +124,11 @@ static int cros_usbpd_charger_get_num_ports(struct charger_data *charger)
if (ret < 0)
return ret;
+ if (resp.port_count > EC_USB_PD_MAX_PORTS) {
+ dev_warn(charger->dev, "Charge port count out of bounds\n");
+ return EC_USB_PD_MAX_PORTS;
+ }
+
return resp.port_count;
}
@@ -137,6 +142,11 @@ static int cros_usbpd_charger_get_usbpd_num_ports(struct charger_data *charger)
if (ret < 0)
return ret;
+ if (resp.num_ports > EC_USB_PD_MAX_PORTS) {
+ dev_warn(charger->dev, "USB PD port count out of bounds\n");
+ return EC_USB_PD_MAX_PORTS;
+ }
+
return resp.num_ports;
}
@@ -588,10 +598,13 @@ static int cros_usbpd_charger_probe(struct platform_device *pd)
/*
* Sanity checks on the number of ports:
- * there should be at most 1 dedicated port
+ * there should be at most 1 dedicated port, and the count must
+ * not exceed the maximum number of supported ports
+ * (EC_USB_PD_MAX_PORTS).
*/
if (charger->num_charger_ports < charger->num_usbpd_ports ||
- charger->num_charger_ports > (charger->num_usbpd_ports + 1)) {
+ charger->num_charger_ports > (charger->num_usbpd_ports + 1) ||
+ charger->num_charger_ports > EC_USB_PD_MAX_PORTS) {
dev_err(dev, "Unexpected number of charge port count\n");
ret = -EPROTO;
goto fail_nowarn;
diff --git a/drivers/power/supply/cw2015_battery.c b/drivers/power/supply/cw2015_battery.c
index 7496295d2948..1bcb60cf763d 100644
--- a/drivers/power/supply/cw2015_battery.c
+++ b/drivers/power/supply/cw2015_battery.c
@@ -736,6 +736,7 @@ static const struct i2c_device_id cw_bat_id_table[] = {
{ .name = "cw2015" },
{ }
};
+MODULE_DEVICE_TABLE(i2c, cw_bat_id_table);
static const struct of_device_id cw2015_of_match[] = {
{ .compatible = "cellwise,cw2015" },
diff --git a/drivers/power/supply/da9150-fg.c b/drivers/power/supply/da9150-fg.c
index 4f28ef1bba1a..7b2fa723acd9 100644
--- a/drivers/power/supply/da9150-fg.c
+++ b/drivers/power/supply/da9150-fg.c
@@ -524,10 +524,8 @@ static int da9150_fg_probe(struct platform_device *pdev)
ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq,
IRQF_ONESHOT, "FG", fg);
- if (ret) {
- dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret);
+ if (ret)
return ret;
- }
return 0;
}
diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c
index f5f2566b3a32..6913179ebb84 100644
--- a/drivers/power/supply/generic-adc-battery.c
+++ b/drivers/power/supply/generic-adc-battery.c
@@ -246,7 +246,7 @@ static int gab_probe(struct platform_device *pdev)
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"battery charged", adc_bat);
if (ret < 0)
- return dev_err_probe(&pdev->dev, ret, "Failed to register irq\n");
+ return ret;
}
platform_set_drvdata(pdev, adc_bat);
diff --git a/drivers/power/supply/isp1704_charger.c b/drivers/power/supply/isp1704_charger.c
index 237912a92272..e329321d06db 100644
--- a/drivers/power/supply/isp1704_charger.c
+++ b/drivers/power/supply/isp1704_charger.c
@@ -482,6 +482,7 @@ static void isp1704_charger_remove(struct platform_device *pdev)
struct isp1704_charger *isp = platform_get_drvdata(pdev);
usb_unregister_notifier(isp->phy, &isp->nb);
+ cancel_work_sync(&isp->work);
power_supply_unregister(isp->psy);
isp1704_charger_set_power(isp, 0);
}
diff --git a/drivers/power/supply/lp8727_charger.c b/drivers/power/supply/lp8727_charger.c
index 641e0445a410..4537b7b413dd 100644
--- a/drivers/power/supply/lp8727_charger.c
+++ b/drivers/power/supply/lp8727_charger.c
@@ -280,10 +280,10 @@ static int lp8727_setup_irq(struct lp8727_chg *pchg)
static void lp8727_release_irq(struct lp8727_chg *pchg)
{
- cancel_delayed_work_sync(&pchg->work);
-
if (pchg->irq)
free_irq(pchg->irq, pchg);
+
+ cancel_delayed_work_sync(&pchg->work);
}
static enum power_supply_property lp8727_charger_prop[] = {
diff --git a/drivers/power/supply/lp8788-charger.c b/drivers/power/supply/lp8788-charger.c
index f0a680c155c4..1bc2c150fb91 100644
--- a/drivers/power/supply/lp8788-charger.c
+++ b/drivers/power/supply/lp8788-charger.c
@@ -710,8 +710,8 @@ static void lp8788_charger_remove(struct platform_device *pdev)
{
struct lp8788_charger *pchg = platform_get_drvdata(pdev);
- flush_work(&pchg->charger_work);
lp8788_irq_unregister(pdev, pchg);
+ cancel_work_sync(&pchg->charger_work);
}
static struct platform_driver lp8788_charger_driver = {
diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c
index 5b6760722f38..d1be314f36a0 100644
--- a/drivers/power/supply/ltc2941-battery-gauge.c
+++ b/drivers/power/supply/ltc2941-battery-gauge.c
@@ -463,7 +463,7 @@ static int ltc294x_i2c_probe(struct i2c_client *client)
/* r_sense can be negative, when sense+ is connected to the battery
* instead of the sense-. This results in reversed measurements. */
- ret = of_property_read_u32(np, "lltc,resistor-sense", &r_sense);
+ ret = of_property_read_s32(np, "lltc,resistor-sense", &r_sense);
if (ret < 0)
return dev_err_probe(&client->dev, ret,
"Could not find lltc,resistor-sense in devicetree\n");
diff --git a/drivers/power/supply/max14656_charger_detector.c b/drivers/power/supply/max14656_charger_detector.c
index 81798f22a037..f78d0341d0ef 100644
--- a/drivers/power/supply/max14656_charger_detector.c
+++ b/drivers/power/supply/max14656_charger_detector.c
@@ -287,10 +287,8 @@ static int max14656_probe(struct i2c_client *client)
ret = devm_request_irq(dev, chip->irq, max14656_irq,
IRQF_TRIGGER_FALLING,
MAX14656_NAME, chip);
- if (ret) {
- dev_err(dev, "request_irq %d failed\n", chip->irq);
+ if (ret)
return -EINVAL;
- }
enable_irq_wake(chip->irq);
schedule_delayed_work(&chip->irq_work, msecs_to_jiffies(2000));
diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c
index 03ac569e185c..41744191a363 100644
--- a/drivers/power/supply/max17040_battery.c
+++ b/drivers/power/supply/max17040_battery.c
@@ -192,8 +192,11 @@ static int max17040_raw_vcell_to_uvolts(struct max17040_chip *chip, u16 vcell)
static int max17040_get_vcell(struct max17040_chip *chip)
{
u32 vcell;
+ int ret;
- regmap_read(chip->regmap, MAX17040_VCELL, &vcell);
+ ret = regmap_read(chip->regmap, MAX17040_VCELL, &vcell);
+ if (ret)
+ return ret;
return max17040_raw_vcell_to_uvolts(chip, vcell);
}
@@ -201,8 +204,11 @@ static int max17040_get_vcell(struct max17040_chip *chip)
static int max17040_get_soc(struct max17040_chip *chip)
{
u32 soc;
+ int ret;
- regmap_read(chip->regmap, MAX17040_SOC, &soc);
+ ret = regmap_read(chip->regmap, MAX17040_SOC, &soc);
+ if (ret)
+ return ret;
return soc >> (chip->quirk_double_soc ? 9 : 8);
}
@@ -261,7 +267,11 @@ static int max17040_get_of_data(struct max17040_chip *chip)
static void max17040_check_changes(struct max17040_chip *chip)
{
- chip->soc = max17040_get_soc(chip);
+ int soc;
+
+ soc = max17040_get_soc(chip);
+ if (soc >= 0)
+ chip->soc = soc;
}
static void max17040_queue_work(struct max17040_chip *chip)
@@ -396,10 +406,16 @@ static int max17040_get_property(struct power_supply *psy,
val->intval = max17040_get_online(chip);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- val->intval = max17040_get_vcell(chip);
+ ret = max17040_get_vcell(chip);
+ if (ret < 0)
+ return ret;
+ val->intval = ret;
break;
case POWER_SUPPLY_PROP_CAPACITY:
- val->intval = max17040_get_soc(chip);
+ ret = max17040_get_soc(chip);
+ if (ret < 0)
+ return ret;
+ val->intval = ret;
break;
case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
val->intval = chip->low_soc_alert;
@@ -458,16 +474,12 @@ static const struct power_supply_desc max17040_battery_desc = {
static int max17040_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
- struct i2c_adapter *adapter = client->adapter;
struct power_supply_config psy_cfg = {};
struct max17040_chip *chip;
enum chip_id chip_id;
bool enable_irq = false;
int ret;
- if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
- return -EIO;
-
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
@@ -568,7 +580,7 @@ static int max17040_suspend(struct device *dev)
// disable soc alert to prevent wakeup
max17040_set_soc_alert(chip, 0);
else
- cancel_delayed_work(&chip->work);
+ cancel_delayed_work_sync(&chip->work);
if (client->irq && device_may_wakeup(dev))
enable_irq_wake(client->irq);
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index d409d2f0d383..cbf3ac9c3e14 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -62,18 +62,32 @@
#define MAX17042_RESISTANCE_LSB 1 / 4096 /* Ω */
#define MAX17042_TEMPERATURE_LSB 1 / 256 /* °C */
+#define MAX17055_DQACC_DIV 32
+#define MAX17055_DPACC_FACTOR 44138
+#define MAX17055_DPACC_VCHG_FACTOR 51200
+#define MAX17055_FSTAT_DNR_BIT BIT(0)
+#define MAX17055_VCHG_THRESHOLD_UV 4275000
+#define MAX17055_DNR_POLL_US 10000
+#define MAX17055_DNR_TIMEOUT_US 2000000
+#define MAX17055_INIT_RETRY_DELAY_MS 10000
+#define MAX17055_REFRESH_POLL_US 10000
+#define MAX17055_REFRESH_TIMEOUT_US 1000000
+
struct max17042_chip {
struct device *dev;
struct regmap *regmap;
struct power_supply *battery;
enum max170xx_chip_type chip_type;
struct max17042_config_data *config_data;
- struct work_struct work;
- int init_complete;
+ struct delayed_work work;
int irq;
int task_period;
bool enable_current_sense;
bool enable_por_init;
+ bool enable_vchg_override;
+ bool init_complete;
+ bool hib_restore_pending;
+ u16 hib_cfg;
unsigned int r_sns;
int vmin; /* in millivolts */
int vmax; /* in millivolts */
@@ -256,7 +270,7 @@ static int max17042_get_property(struct power_supply *psy,
u32 data;
u64 data64;
- if (!chip->init_complete)
+ if (!READ_ONCE(chip->init_complete))
return -EAGAIN;
switch (psp) {
@@ -566,6 +580,24 @@ static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value)
return ret;
}
+static int max17055_write_verify_reg(struct regmap *map, u8 reg, u32 value)
+{
+ u32 read_value;
+ int ret;
+
+ ret = regmap_write(map, reg, value);
+ if (ret)
+ return ret;
+
+ usleep_range(1000, 2000);
+
+ ret = regmap_read(map, reg, &read_value);
+ if (ret)
+ return ret;
+
+ return read_value == value ? 0 : -EIO;
+}
+
static inline void max17042_override_por(struct regmap *map,
u8 reg, u16 value)
{
@@ -801,8 +833,12 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17042_CONFIG, config->config);
max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer);
- max17042_override_por(map, MAX17042_DesignCap, config->design_cap);
- max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term);
+ if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) {
+ max17042_override_por(map, MAX17042_DesignCap,
+ config->design_cap);
+ max17042_override_por(map, MAX17042_ICHGTerm,
+ config->ichgt_term);
+ }
max17042_override_por(map, MAX17042_AtRate, config->at_rate);
max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg);
@@ -812,8 +848,10 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17042_FullCAP, config->fullcap);
max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom);
- max17042_override_por(map, MAX17042_dQacc, config->dqacc);
- max17042_override_por(map, MAX17042_dPacc, config->dpacc);
+ if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) {
+ max17042_override_por(map, MAX17042_dQacc, config->dqacc);
+ max17042_override_por(map, MAX17042_dPacc, config->dpacc);
+ }
max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0);
max17042_override_por(map, MAX17042_TempCo, config->tcompc0);
@@ -843,30 +881,183 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17047_V_empty, config->vempty);
}
- if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 &&
+ !chip->enable_vchg_override)
max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg);
}
-static int max17042_init_chip(struct max17042_chip *chip)
+static int max17055_override_battery_values(struct max17042_chip *chip)
{
+ struct max17042_config_data *config = chip->config_data;
struct regmap *map = chip->regmap;
+ unsigned int design_cap;
+ unsigned int model_cfg;
+ unsigned int dqacc;
+ u64 dpacc;
int ret;
+ if (config->design_cap) {
+ ret = max17055_write_verify_reg(map, MAX17042_DesignCap,
+ config->design_cap);
+ if (ret)
+ return ret;
+ }
+
+ if (config->dqacc) {
+ ret = max17055_write_verify_reg(map, MAX17042_dQacc,
+ config->dqacc);
+ if (ret)
+ return ret;
+ }
+
+ if (config->ichgt_term) {
+ ret = max17055_write_verify_reg(map, MAX17042_ICHGTerm,
+ config->ichgt_term);
+ if (ret)
+ return ret;
+ }
+
+ if (chip->enable_vchg_override) {
+ ret = regmap_update_bits(map, MAX17055_ModelCfg,
+ MAX17055_MODELCFG_VCHG_BIT,
+ config->model_cfg &
+ MAX17055_MODELCFG_VCHG_BIT);
+ if (ret)
+ return ret;
+
+ usleep_range(1000, 2000);
+ }
+
+ if (!config->design_cap && !config->dqacc &&
+ !chip->enable_vchg_override)
+ return 0;
+
+ ret = regmap_read(map, MAX17042_DesignCap, &design_cap);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, MAX17042_dQacc, &dqacc);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, MAX17055_ModelCfg, &model_cfg);
+ if (ret)
+ return ret;
+ if (chip->enable_vchg_override &&
+ (model_cfg & MAX17055_MODELCFG_VCHG_BIT) !=
+ (config->model_cfg & MAX17055_MODELCFG_VCHG_BIT))
+ return -EIO;
+
+ if (!design_cap || !dqacc)
+ return -ERANGE;
+
+ dpacc = (u64)dqacc *
+ (model_cfg & MAX17055_MODELCFG_VCHG_BIT ?
+ MAX17055_DPACC_VCHG_FACTOR : MAX17055_DPACC_FACTOR);
+ do_div(dpacc, design_cap);
+ if (dpacc > U16_MAX)
+ return -ERANGE;
+
+ return max17055_write_verify_reg(map, MAX17042_dPacc, (u16)dpacc);
+}
+
+static int max17055_restore_hibernate(struct max17042_chip *chip)
+{
+ int restore_hib_ret;
+ int soft_wakeup_ret;
+
+ soft_wakeup_ret = regmap_write(chip->regmap, MAX17055_SoftWakeup, 0);
+ restore_hib_ret = max17055_write_verify_reg(chip->regmap,
+ MAX17055_HibCfg,
+ chip->hib_cfg);
+ if (!soft_wakeup_ret && !restore_hib_ret)
+ chip->hib_restore_pending = false;
+
+ return soft_wakeup_ret ?: restore_hib_ret;
+}
+
+static int max17055_init_chip(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+ unsigned int hib_cfg;
+ unsigned int model_cfg;
+ unsigned int fstat;
+ int restore_ret;
+ int ret;
+
+ if (chip->hib_restore_pending) {
+ ret = max17055_restore_hibernate(chip);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_read_poll_timeout(map, MAX17042_FSTAT, fstat,
+ !(fstat & MAX17055_FSTAT_DNR_BIT),
+ MAX17055_DNR_POLL_US,
+ MAX17055_DNR_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, MAX17055_HibCfg, &hib_cfg);
+ if (ret)
+ return ret;
+
+ chip->hib_cfg = hib_cfg;
+ chip->hib_restore_pending = true;
+
+ ret = regmap_write(map, MAX17055_SoftWakeup, 0x0090);
+ if (ret)
+ goto restore_hibernate;
+
+ ret = max17055_write_verify_reg(map, MAX17055_HibCfg, 0);
+ if (ret)
+ goto restore_hibernate;
+
+ ret = regmap_write(map, MAX17055_SoftWakeup, 0);
+ if (ret)
+ goto restore_hibernate;
+
max17042_override_por_values(chip);
+ ret = max17055_override_battery_values(chip);
+ if (ret)
+ goto restore_hibernate;
+
+ ret = regmap_write_bits(map, MAX17055_ModelCfg,
+ MAX17055_MODELCFG_REFRESH_BIT,
+ MAX17055_MODELCFG_REFRESH_BIT);
+ if (ret)
+ goto restore_hibernate;
+
+ ret = regmap_read_poll_timeout(map, MAX17055_ModelCfg, model_cfg,
+ !(model_cfg &
+ MAX17055_MODELCFG_REFRESH_BIT),
+ MAX17055_REFRESH_POLL_US,
+ MAX17055_REFRESH_TIMEOUT_US);
+
+restore_hibernate:
+ restore_ret = max17055_restore_hibernate(chip);
+ if (restore_ret)
+ return restore_ret;
+
+ return ret;
+}
+
+static int max17042_init_chip(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+ int ret;
+
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
- regmap_write_bits(map, MAX17055_ModelCfg,
- MAX17055_MODELCFG_REFRESH_BIT,
- MAX17055_MODELCFG_REFRESH_BIT);
- }
+ ret = max17055_init_chip(chip);
+ if (ret)
+ return ret;
+ } else {
+ max17042_override_por_values(chip);
- /* After Power up, the MAX17042 requires 500mS in order
- * to perform signal debouncing and initial SOC reporting
- */
- msleep(500);
+ /* Allow signal debouncing and initial SOC reporting. */
+ msleep(500);
- if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) {
- /* Initialize configuration */
max17042_write_config_regs(chip);
/* write cell characterization data */
@@ -902,8 +1093,7 @@ static int max17042_init_chip(struct max17042_chip *chip)
}
/* Init complete, Clear the POR bit */
- regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0);
- return 0;
+ return regmap_clear_bits(map, MAX17042_STATUS, STATUS_POR_BIT);
}
static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off)
@@ -989,18 +1179,28 @@ static irqreturn_t max17042_thread_handler(int id, void *dev)
static void max17042_init_worker(struct work_struct *work)
{
- struct max17042_chip *chip = container_of(work,
+ struct max17042_chip *chip = container_of(to_delayed_work(work),
struct max17042_chip, work);
- int ret;
+ int ret = 0;
/* Initialize registers according to values from config_data */
- if (chip->enable_por_init && chip->config_data) {
+ if (chip->enable_por_init && chip->config_data)
ret = max17042_init_chip(chip);
- if (ret)
- return;
+
+ if (ret) {
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
+ dev_warn_ratelimited(chip->dev,
+ "initialization failed: %d, retrying\n", ret);
+ schedule_delayed_work(&chip->work,
+ msecs_to_jiffies(MAX17055_INIT_RETRY_DELAY_MS));
+ } else {
+ dev_err(chip->dev, "initialization failed: %d\n", ret);
+ }
+ return;
}
- chip->init_complete = 1;
+ WRITE_ONCE(chip->init_complete, true);
+ power_supply_changed(chip->battery);
}
#ifdef CONFIG_OF
@@ -1026,10 +1226,14 @@ static int max17042_parse_dt(struct max17042_chip *chip)
chip->temp_min = INT_MIN;
if (of_property_read_s32(np, "maxim,over-heat-temp", &chip->temp_max))
chip->temp_max = INT_MAX;
- if (of_property_read_s32(np, "maxim,dead-volt", &chip->vmin))
+ if (of_property_read_u32(np, "maxim,dead-volt", &prop))
chip->vmin = INT_MIN;
- if (of_property_read_s32(np, "maxim,over-volt", &chip->vmax))
+ else
+ chip->vmin = prop;
+ if (of_property_read_u32(np, "maxim,over-volt", &prop))
chip->vmax = INT_MAX;
+ else
+ chip->vmin = prop;
return 0;
}
@@ -1060,6 +1264,102 @@ static int max17042_init_defaults(struct max17042_chip *chip)
return 0;
}
+static int max17042_apply_battery_properties(struct max17042_chip *chip,
+ struct power_supply_battery_info *info)
+{
+ struct max17042_config_data *config;
+ struct device *dev = chip->dev;
+ bool have_design_cap;
+ bool have_ichgt_term;
+ bool have_vchg;
+ u16 design_cap = 0;
+ u16 ichgt_term = 0;
+ u16 dqacc = 0;
+ u16 model_cfg = 0;
+ u64 data64;
+
+ if (!info || chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055)
+ return 0;
+
+ have_design_cap = chip->enable_current_sense &&
+ info->charge_full_design_uah > 0;
+ have_ichgt_term = chip->enable_current_sense &&
+ info->charge_term_current_ua > 0;
+ have_vchg = info->voltage_max_design_uv >= 0;
+ if (!have_design_cap && !have_ichgt_term && !have_vchg)
+ return 0;
+
+ if (have_design_cap) {
+ data64 = (u64)info->charge_full_design_uah * chip->r_sns;
+ do_div(data64, MAX17042_CAPACITY_LSB);
+ if (!data64)
+ return dev_err_probe(dev, -ERANGE,
+ "battery design capacity is too small for sense resistor\n");
+ if (data64 > U16_MAX)
+ return dev_err_probe(dev, -ERANGE,
+ "battery design capacity exceeds register range\n");
+
+ design_cap = (u16)data64;
+ dqacc = design_cap / MAX17055_DQACC_DIV;
+ if (!dqacc)
+ return dev_err_probe(dev, -ERANGE,
+ "battery design capacity is too small for EZ config\n");
+ }
+
+ if (have_ichgt_term) {
+ data64 = (u64)info->charge_term_current_ua * chip->r_sns;
+ do_div(data64, MAX17042_CURRENT_LSB);
+ if (!data64)
+ return dev_err_probe(dev, -ERANGE,
+ "charge termination current is too small for sense resistor\n");
+ if (data64 > S16_MAX)
+ return dev_err_probe(dev, -ERANGE,
+ "charge termination current exceeds positive register range\n");
+
+ ichgt_term = (u16)data64;
+ }
+
+ if (have_vchg) {
+ if (!info->voltage_max_design_uv)
+ return dev_err_probe(dev, -EINVAL,
+ "battery design voltage must be positive\n");
+
+ if (info->voltage_max_design_uv > MAX17055_VCHG_THRESHOLD_UV)
+ model_cfg = MAX17055_MODELCFG_VCHG_BIT;
+ }
+
+ config = chip->config_data;
+ if (!config) {
+ config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL);
+ if (!config)
+ return -ENOMEM;
+ }
+
+ if (have_design_cap) {
+ config->design_cap = design_cap;
+ config->dqacc = dqacc;
+ }
+ if (have_ichgt_term)
+ config->ichgt_term = ichgt_term;
+ if (have_vchg) {
+ config->model_cfg &= ~MAX17055_MODELCFG_VCHG_BIT;
+ config->model_cfg |= model_cfg;
+ chip->enable_vchg_override = true;
+ }
+
+ chip->config_data = config;
+ chip->enable_por_init = true;
+
+ return 0;
+}
+
+static int max17042_init_battery(struct power_supply *psy)
+{
+ struct max17042_chip *chip = power_supply_get_drvdata(psy);
+
+ return max17042_apply_battery_properties(chip, psy->battery_info);
+}
+
static const struct regmap_config max17042_regmap_config = {
.name = "max17042",
.reg_bits = 8,
@@ -1113,6 +1413,7 @@ static const struct power_supply_desc max17042_psy_desc = {
.set_property = max17042_set_property,
.property_is_writeable = max17042_property_is_writeable,
.external_power_changed = power_supply_changed,
+ .init = max17042_init_battery,
.properties = max17042_battery_props,
.num_properties = ARRAY_SIZE(max17042_battery_props),
};
@@ -1123,6 +1424,7 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = {
.get_property = max17042_get_property,
.set_property = max17042_set_property,
.property_is_writeable = max17042_property_is_writeable,
+ .init = max17042_init_battery,
.properties = max17042_battery_props,
.num_properties = ARRAY_SIZE(max17042_battery_props) - 2,
};
@@ -1242,15 +1544,18 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
chip->irq = irq;
- regmap_read(chip->regmap, MAX17042_STATUS, &val);
+ ret = regmap_read(chip->regmap, MAX17042_STATUS, &val);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to read status\n");
+
if (val & STATUS_POR_BIT) {
- ret = devm_work_autocancel(dev, &chip->work,
- max17042_init_worker);
+ ret = devm_delayed_work_autocancel(dev, &chip->work,
+ max17042_init_worker);
if (ret)
return ret;
- schedule_work(&chip->work);
+ schedule_delayed_work(&chip->work, 0);
} else {
- chip->init_complete = 1;
+ WRITE_ONCE(chip->init_complete, true);
}
return 0;
diff --git a/drivers/power/supply/max77759_charger.c b/drivers/power/supply/max77759_charger.c
index 41d810bb6744..689fc733fa3f 100644
--- a/drivers/power/supply/max77759_charger.c
+++ b/drivers/power/supply/max77759_charger.c
@@ -532,9 +532,7 @@ static int max77759_init_irqhandler(struct max77759_charger *chg)
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 ret;
}
return 0;
diff --git a/drivers/power/supply/max8903_charger.c b/drivers/power/supply/max8903_charger.c
index 45fbaad6c647..d5267ba19969 100644
--- a/drivers/power/supply/max8903_charger.c
+++ b/drivers/power/supply/max8903_charger.c
@@ -9,11 +9,29 @@
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/of.h>
+#include <linux/property.h>
#include <linux/slab.h>
#include <linux/power_supply.h>
#include <linux/platform_device.h>
+/*
+ * IUSB pin: hardcoded by silicon to 100 mA (low) / 500 mA (high).
+ * MAX8903A/B/C/D/E/F/G/H/I datasheet, "Pin Description" table:
+ * "USB Current-Limit Set Input. Drive IUSB logic-low to set the
+ * USB current limit to 100mA. Drive IUSB logic-high to set the
+ * USB current limit to 500mA."
+ * Not a board parameter - never DT-configurable.
+ */
+#define MAX8903_USB_CURRENT_LIMIT_LOW_UA 100000
+#define MAX8903_USB_CURRENT_LIMIT_HIGH_UA 500000
+
+struct max8903_current_limit_mapping {
+ u32 limit_ua; /* Current limit in microamps */
+ u32 gpio_value; /* GPIO bit pattern */
+};
+
struct max8903_data {
struct device *dev;
struct power_supply *psy;
@@ -31,6 +49,25 @@ struct max8903_data {
struct gpio_desc *flt; /* Fault output */
struct gpio_desc *dcm; /* Current-Limit Mode input (1: DC, 2: USB) */
struct gpio_desc *usus; /* USB Suspend Input (1: suspended) */
+
+ /* DC current limit control (ISET pins) */
+ struct gpio_descs *dc_current_limit_gpios;
+ struct max8903_current_limit_mapping *dc_current_limit_map;
+ u32 dc_current_limit_map_size;
+ u32 dc_current_limit_ua; /* Current setting in uA */
+
+ /* USB current limit control (IUSB pin) */
+ struct gpio_desc *usb_current_limit_gpio;
+ u32 usb_current_limit_ua; /* Current setting in uA */
+
+ /*
+ * Serialises ta_in / usb_in updates against
+ * max8903_set_property() which steers the current-limit write to
+ * the DC or USB path based on which source is currently online.
+ * The IRQ handlers are requested with IRQF_ONESHOT (threaded), so
+ * a sleepable mutex is the right primitive in both contexts.
+ */
+ struct mutex source_lock;
bool fault;
bool usb_in;
bool ta_in;
@@ -40,6 +77,7 @@ static enum power_supply_property max8903_charger_props[] = {
POWER_SUPPLY_PROP_STATUS, /* Charger status output */
POWER_SUPPLY_PROP_ONLINE, /* External power source */
POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, /* Input current limit */
};
static int max8903_get_property(struct power_supply *psy,
@@ -47,6 +85,24 @@ static int max8903_get_property(struct power_supply *psy,
union power_supply_propval *val)
{
struct max8903_data *data = power_supply_get_drvdata(psy);
+ bool ta_in, usb_in;
+ u32 dc_limit, usb_limit;
+
+ /*
+ * Snapshot the source flags and current-limit settings under the
+ * source_lock that the IRQs (max8903_dcin / max8903_usbin) and
+ * max8903_set_property() take when updating them, so we never
+ * observe a torn pair of (source-online flag, current-limit ua).
+ * The gpiod_get_value() reads further down deliberately stay
+ * outside the lock — they hit the GPIO controller, not driver
+ * state, and the IRQs do not touch them under the lock either.
+ */
+ mutex_lock(&data->source_lock);
+ ta_in = data->ta_in;
+ usb_in = data->usb_in;
+ dc_limit = data->dc_current_limit_ua;
+ usb_limit = data->usb_current_limit_ua;
+ mutex_unlock(&data->source_lock);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
@@ -55,21 +111,42 @@ static int max8903_get_property(struct power_supply *psy,
if (gpiod_get_value(data->chg))
/* CHG asserted */
val->intval = POWER_SUPPLY_STATUS_CHARGING;
- else if (data->usb_in || data->ta_in)
+ else if (usb_in || ta_in)
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
}
break;
case POWER_SUPPLY_PROP_ONLINE:
- val->intval = 0;
- if (data->usb_in || data->ta_in)
- val->intval = 1;
+ val->intval = (ta_in || usb_in) ? 1 : 0;
break;
case POWER_SUPPLY_PROP_HEALTH:
- val->intval = POWER_SUPPLY_HEALTH_GOOD;
- if (data->fault)
- val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ /*
+ * data->fault is a single bool toggled from one IRQ
+ * handler, so a torn read is not possible; no need to
+ * extend source_lock coverage here.
+ */
+ val->intval = data->fault ? POWER_SUPPLY_HEALTH_UNSPEC_FAILURE
+ : POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ /*
+ * Hardware prioritises DC over USB - when ta_in is asserted
+ * the part draws from the DC input regardless of USB state.
+ * So always report the DC-side limit when DC is online, and
+ * refuse rather than silently fall back to the USB cap if
+ * the DC GPIOs are not configured - that would mis-describe
+ * the active source. Same policy applies in the set path.
+ */
+ if (ta_in) {
+ if (!data->dc_current_limit_gpios)
+ return -ENODATA;
+ val->intval = dc_limit;
+ } else if (usb_in && data->usb_current_limit_gpio) {
+ val->intval = usb_limit;
+ } else {
+ return -ENODATA;
+ }
break;
default:
return -EINVAL;
@@ -78,6 +155,144 @@ static int max8903_get_property(struct power_supply *psy,
return 0;
}
+static int max8903_set_dc_current_limit(struct max8903_data *data, u32 limit_ua)
+{
+ int i, best_idx = -1;
+ /*
+ * The mapping's gpio_value fits in the lowest ndescs bits of one
+ * unsigned long (parse_dc_current_limit enforces ndescs < 32 and
+ * gpio_value < BIT(ndescs)); a single-word bitmap is sufficient
+ * on both 32- and 64-bit builds. Don't use bitmap_from_arr32() -
+ * that macro reinterprets its source pointer as unsigned long on
+ * 64-bit and would read past the on-stack u32.
+ */
+ DECLARE_BITMAP(values, BITS_PER_TYPE(u32));
+
+ if (!data->dc_current_limit_gpios)
+ return -EOPNOTSUPP;
+
+ /*
+ * Find the highest supported current <= requested. Use a -1
+ * "not found" sentinel rather than tracking best_limit > 0 so
+ * that a 0 uA entry (used to disable charging) can be selected
+ * by a 0 uA request.
+ */
+ for (i = 0; i < data->dc_current_limit_map_size; i++) {
+ if (data->dc_current_limit_map[i].limit_ua > limit_ua)
+ continue;
+ if (best_idx < 0 ||
+ data->dc_current_limit_map[i].limit_ua >
+ data->dc_current_limit_map[best_idx].limit_ua)
+ best_idx = i;
+ }
+
+ if (best_idx < 0)
+ return -EINVAL;
+
+ bitmap_zero(values, BITS_PER_TYPE(u32));
+ values[0] = data->dc_current_limit_map[best_idx].gpio_value;
+ gpiod_set_array_value_cansleep(data->dc_current_limit_gpios->ndescs,
+ data->dc_current_limit_gpios->desc,
+ data->dc_current_limit_gpios->info,
+ values);
+
+ data->dc_current_limit_ua = data->dc_current_limit_map[best_idx].limit_ua;
+ dev_dbg(data->dev, "DC current limit set to %u uA\n",
+ data->dc_current_limit_ua);
+
+ return 0;
+}
+
+static int max8903_set_usb_current_limit(struct max8903_data *data, u32 limit_ua)
+{
+ u32 selected;
+ int gpio_val;
+
+ if (!data->usb_current_limit_gpio)
+ return -EOPNOTSUPP;
+
+ /*
+ * IUSB is a single-bit input with two silicon-fixed settings;
+ * pick HIGH (500 mA) iff the caller's cap can absorb it, else
+ * LOW (100 mA), else refuse rather than program a higher current
+ * than the request allows.
+ */
+ if (limit_ua >= MAX8903_USB_CURRENT_LIMIT_HIGH_UA) {
+ selected = MAX8903_USB_CURRENT_LIMIT_HIGH_UA;
+ gpio_val = 1;
+ } else if (limit_ua >= MAX8903_USB_CURRENT_LIMIT_LOW_UA) {
+ selected = MAX8903_USB_CURRENT_LIMIT_LOW_UA;
+ gpio_val = 0;
+ } else {
+ return -EINVAL;
+ }
+
+ gpiod_set_value_cansleep(data->usb_current_limit_gpio, gpio_val);
+ data->usb_current_limit_ua = selected;
+
+ dev_dbg(data->dev, "USB current limit set to %u uA\n",
+ data->usb_current_limit_ua);
+
+ return 0;
+}
+
+static int max8903_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max8903_data *data = power_supply_get_drvdata(psy);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ /*
+ * val->intval is signed; the set_*_current_limit() helpers
+ * take a u32. Reject negatives explicitly so a negative
+ * request cannot widen into a huge unsigned value, bypass
+ * the "limit <= cap" bounds check inside the helper, and
+ * silently program the maximum permitted current.
+ */
+ if (val->intval < 0)
+ return -EINVAL;
+ /*
+ * Hold source_lock across the source check and the
+ * resulting hardware write so the IRQ handler cannot
+ * flip ta_in/usb_in between them and have us program the
+ * limit for a source that has just gone offline. Mirror
+ * the DC-priority policy of the get path: if DC is online
+ * route to the DC helper (refuse if DC GPIOs aren't
+ * configured) rather than fall through to USB.
+ */
+ mutex_lock(&data->source_lock);
+ if (data->ta_in)
+ ret = data->dc_current_limit_gpios ?
+ max8903_set_dc_current_limit(data, val->intval) :
+ -ENODEV;
+ else if (data->usb_in && data->usb_current_limit_gpio)
+ ret = max8903_set_usb_current_limit(data, val->intval);
+ else
+ ret = -EINVAL;
+ mutex_unlock(&data->source_lock);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int max8903_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ struct max8903_data *data = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return data->dc_current_limit_gpios ||
+ data->usb_current_limit_gpio;
+ default:
+ return 0;
+ }
+}
+
static irqreturn_t max8903_dcin(int irq, void *_data)
{
struct max8903_data *data = _data;
@@ -91,10 +306,21 @@ static irqreturn_t max8903_dcin(int irq, void *_data)
* library as the line should be flagged GPIO_ACTIVE_LOW in the device
* tree.
*/
+ /*
+ * Hold source_lock across the full read-modify-evaluate block:
+ * - so a concurrent max8903_set_property() sees a consistent
+ * state (lock release would otherwise expose a window where
+ * data->ta_in is updated but the cen/dcm writes still pend);
+ * - so the cen enable calculation reads a stable data->usb_in
+ * rather than racing with max8903_usbin() and writing the
+ * wrong enable state.
+ */
+ mutex_lock(&data->source_lock);
ta_in = gpiod_get_value(data->dok);
-
- if (ta_in == data->ta_in)
+ if (ta_in == data->ta_in) {
+ mutex_unlock(&data->source_lock);
return IRQ_HANDLED;
+ }
data->ta_in = ta_in;
@@ -119,9 +345,6 @@ static irqreturn_t max8903_dcin(int irq, void *_data)
gpiod_set_value(data->cen, val);
}
- dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ?
- "Connected" : "Disconnected");
-
old_type = data->psy_desc.type;
if (data->ta_in)
@@ -130,6 +353,10 @@ static irqreturn_t max8903_dcin(int irq, void *_data)
data->psy_desc.type = POWER_SUPPLY_TYPE_USB;
else
data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ mutex_unlock(&data->source_lock);
+
+ dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ?
+ "Connected" : "Disconnected");
if (old_type != data->psy_desc.type)
power_supply_changed(data->psy);
@@ -150,10 +377,13 @@ static irqreturn_t max8903_usbin(int irq, void *_data)
* library as the line should be flagged GPIO_ACTIVE_LOW in the device
* tree.
*/
+ /* See max8903_dcin(): hold the lock across the full update. */
+ mutex_lock(&data->source_lock);
usb_in = gpiod_get_value(data->uok);
-
- if (usb_in == data->usb_in)
+ if (usb_in == data->usb_in) {
+ mutex_unlock(&data->source_lock);
return IRQ_HANDLED;
+ }
data->usb_in = usb_in;
@@ -176,9 +406,6 @@ static irqreturn_t max8903_usbin(int irq, void *_data)
gpiod_set_value(data->cen, val);
}
- dev_dbg(data->dev, "USB Charger %s.\n", usb_in ?
- "Connected" : "Disconnected");
-
old_type = data->psy_desc.type;
if (data->ta_in)
@@ -187,6 +414,10 @@ static irqreturn_t max8903_usbin(int irq, void *_data)
data->psy_desc.type = POWER_SUPPLY_TYPE_USB;
else
data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ mutex_unlock(&data->source_lock);
+
+ dev_dbg(data->dev, "USB Charger %s.\n", usb_in ?
+ "Connected" : "Disconnected");
if (old_type != data->psy_desc.type)
power_supply_changed(data->psy);
@@ -221,6 +452,145 @@ static irqreturn_t max8903_fault(int irq, void *_data)
return IRQ_HANDLED;
}
+static int max8903_parse_dc_current_limit(struct platform_device *pdev,
+ struct max8903_data *data)
+{
+ struct device *dev = &pdev->dev;
+ int ret, i, map_size;
+ u32 *map;
+
+ data->dc_current_limit_gpios = devm_gpiod_get_array_optional(dev,
+ "dc-current-limit", GPIOD_OUT_LOW);
+ if (IS_ERR(data->dc_current_limit_gpios))
+ return dev_err_probe(dev, PTR_ERR(data->dc_current_limit_gpios),
+ "failed to get DC current limit GPIOs");
+
+ if (!data->dc_current_limit_gpios)
+ return 0; /* Optional feature not present */
+
+ /*
+ * gpio_value entries below are bit patterns indexed into the
+ * dc-current-limit GPIO array. The driver represents them in
+ * a single unsigned long for gpiod_set_array_value_cansleep(),
+ * and BIT(ndescs) further down assumes ndescs fits in a u32
+ * shift; reject pathological DTs at parse time instead of
+ * relying on undefined-behaviour-free dtschema. The binding
+ * already caps maxItems at 4 so this is purely defensive.
+ */
+ if (data->dc_current_limit_gpios->ndescs >= BITS_PER_TYPE(u32)) {
+ dev_err(dev, "dc-current-limit-gpios: %u GPIOs exceeds %u-bit cap\n",
+ data->dc_current_limit_gpios->ndescs,
+ (unsigned int)BITS_PER_TYPE(u32));
+ return -EINVAL;
+ }
+
+ /* Parse mapping: pairs of (current_ua, gpio_value) */
+ map_size = device_property_count_u32(dev, "dc-current-limit-mapping");
+ if (map_size <= 0 || map_size % 2) {
+ dev_err(dev, "invalid dc-current-limit-mapping\n");
+ return -EINVAL;
+ }
+
+ /*
+ * map[] is a scratch buffer used only inside this function to
+ * read the property and unpack it into data->dc_current_limit_map.
+ * Use a plain kmalloc + kfree rather than devm_*: there is no
+ * reason to keep the raw mirror around for the lifetime of the
+ * device.
+ */
+ map = kmalloc_array(map_size, sizeof(*map), GFP_KERNEL);
+ if (!map)
+ return -ENOMEM;
+
+ ret = device_property_read_u32_array(dev, "dc-current-limit-mapping",
+ map, map_size);
+ if (ret) {
+ dev_err(dev, "failed to read dc-current-limit-mapping\n");
+ kfree(map);
+ return ret;
+ }
+
+ data->dc_current_limit_map_size = map_size / 2;
+ data->dc_current_limit_map = devm_kcalloc(dev,
+ data->dc_current_limit_map_size,
+ sizeof(*data->dc_current_limit_map),
+ GFP_KERNEL);
+ if (!data->dc_current_limit_map) {
+ kfree(map);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < data->dc_current_limit_map_size; i++) {
+ u32 gpio_value = map[i * 2 + 1];
+
+ /*
+ * gpio_value is the bitmap programmed across the
+ * dc-current-limit GPIOs, so it cannot represent more
+ * bits than the GPIO array width. A larger value would
+ * be silently truncated by gpiod_set_array_value() and
+ * select the wrong limit; reject it at parse time so
+ * the bogus DT is visible to the integrator.
+ */
+ if (gpio_value >= BIT(data->dc_current_limit_gpios->ndescs)) {
+ dev_err(dev,
+ "dc-current-limit-mapping entry %d: gpio_value 0x%x exceeds %u-GPIO range\n",
+ i, gpio_value,
+ data->dc_current_limit_gpios->ndescs);
+ kfree(map);
+ return -EINVAL;
+ }
+ data->dc_current_limit_map[i].limit_ua = map[i * 2];
+ data->dc_current_limit_map[i].gpio_value = gpio_value;
+ }
+
+ kfree(map);
+
+ /*
+ * devm_gpiod_get_array_optional() above asked for GPIOD_OUT_LOW,
+ * so the hardware mux starts at gpio_value 0. Require the DT
+ * mapping to include a gpio_value=0 entry so the software
+ * current-limit state has a definite initial value matching the
+ * hardware. Without this entry we would have to guess and the
+ * reported INPUT_CURRENT_LIMIT could disagree with what the
+ * mux is actually wired to until a set_property write picks a
+ * real value.
+ */
+ for (i = 0; i < data->dc_current_limit_map_size; i++)
+ if (data->dc_current_limit_map[i].gpio_value == 0)
+ break;
+ if (i == data->dc_current_limit_map_size) {
+ dev_err(dev,
+ "dc-current-limit-mapping must include a gpio_value=0 entry to describe the boot-time mux state\n");
+ return -EINVAL;
+ }
+ data->dc_current_limit_ua = data->dc_current_limit_map[i].limit_ua;
+
+ dev_dbg(dev, "DC current limit control: %d levels available, initial %u uA\n",
+ data->dc_current_limit_map_size, data->dc_current_limit_ua);
+
+ return 0;
+}
+
+static int max8903_parse_usb_current_limit(struct platform_device *pdev,
+ struct max8903_data *data)
+{
+ struct device *dev = &pdev->dev;
+
+ data->usb_current_limit_gpio = devm_gpiod_get_optional(dev,
+ "usb-current-limit", GPIOD_OUT_LOW);
+ if (IS_ERR(data->usb_current_limit_gpio))
+ return dev_err_probe(dev, PTR_ERR(data->usb_current_limit_gpio),
+ "failed to get USB current limit GPIO");
+
+ if (!data->usb_current_limit_gpio)
+ return 0; /* Optional feature not present */
+
+ /* Start at low current (IUSB low = 100 mA) for safety */
+ data->usb_current_limit_ua = MAX8903_USB_CURRENT_LIMIT_LOW_UA;
+
+ return 0;
+}
+
static int max8903_setup_gpios(struct platform_device *pdev)
{
struct max8903_data *data = platform_get_drvdata(pdev);
@@ -335,17 +705,28 @@ static int max8903_probe(struct platform_device *pdev)
return -ENOMEM;
data->dev = dev;
+ mutex_init(&data->source_lock);
platform_set_drvdata(pdev, data);
ret = max8903_setup_gpios(pdev);
if (ret)
return ret;
+ ret = max8903_parse_dc_current_limit(pdev, data);
+ if (ret)
+ return ret;
+
+ ret = max8903_parse_usb_current_limit(pdev, data);
+ if (ret)
+ return ret;
+
data->psy_desc.name = "max8903_charger";
data->psy_desc.type = (data->ta_in) ? POWER_SUPPLY_TYPE_MAINS :
((data->usb_in) ? POWER_SUPPLY_TYPE_USB :
POWER_SUPPLY_TYPE_BATTERY);
data->psy_desc.get_property = max8903_get_property;
+ data->psy_desc.set_property = max8903_set_property;
+ data->psy_desc.property_is_writeable = max8903_property_is_writeable;
data->psy_desc.properties = max8903_charger_props;
data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props);
@@ -364,11 +745,8 @@ static int max8903_probe(struct platform_device *pdev)
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"MAX8903 DC IN", data);
- if (ret) {
- dev_err(dev, "Cannot request irq %d for DC (%d)\n",
- gpiod_to_irq(data->dok), ret);
+ if (ret)
return ret;
- }
}
if (data->uok) {
@@ -377,11 +755,8 @@ static int max8903_probe(struct platform_device *pdev)
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"MAX8903 USB IN", data);
- if (ret) {
- dev_err(dev, "Cannot request irq %d for USB (%d)\n",
- gpiod_to_irq(data->uok), ret);
+ if (ret)
return ret;
- }
}
if (data->flt) {
@@ -390,11 +765,8 @@ static int max8903_probe(struct platform_device *pdev)
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"MAX8903 Fault", data);
- if (ret) {
- dev_err(dev, "Cannot request irq %d for Fault (%d)\n",
- gpiod_to_irq(data->flt), ret);
+ if (ret)
return ret;
- }
}
return 0;
diff --git a/drivers/power/supply/max8971_charger.c b/drivers/power/supply/max8971_charger.c
index 82f20b89b6b4..0d69e9f44c05 100644
--- a/drivers/power/supply/max8971_charger.c
+++ b/drivers/power/supply/max8971_charger.c
@@ -683,7 +683,7 @@ static int max8971_probe(struct i2c_client *client)
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);
+ return err;
extcon = of_graph_get_remote_node(dev->of_node, -1, -1);
if (!extcon)
diff --git a/drivers/power/supply/mp2629_charger.c b/drivers/power/supply/mp2629_charger.c
index f758d6a7bc8c..04294359ed7d 100644
--- a/drivers/power/supply/mp2629_charger.c
+++ b/drivers/power/supply/mp2629_charger.c
@@ -630,10 +630,8 @@ static int mp2629_charger_probe(struct platform_device *pdev)
ret = devm_request_threaded_irq(dev, irq, NULL, mp2629_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_RISING,
"mp2629-charger", charger);
- if (ret) {
- dev_err(dev, "failed to request gpio IRQ\n");
+ if (ret)
return ret;
- }
regmap_update_bits(charger->regmap, MP2629_REG_INTERRUPT,
GENMASK(6, 5), BIT(6) | BIT(5));
diff --git a/drivers/power/supply/mt6360_charger.c b/drivers/power/supply/mt6360_charger.c
index d3b0731f6562..b4d462343151 100644
--- a/drivers/power/supply/mt6360_charger.c
+++ b/drivers/power/supply/mt6360_charger.c
@@ -721,8 +721,7 @@ static int mt6360_chg_irq_register(struct platform_device *pdev)
irq_descs[i].name,
platform_get_drvdata(pdev));
if (ret < 0)
- return dev_err_probe(&pdev->dev, ret, "Failed to request %s irq\n",
- irq_descs[i].name);
+ return ret;
}
return 0;
diff --git a/drivers/power/supply/mt6370-charger.c b/drivers/power/supply/mt6370-charger.c
index 916556baa854..9d388bc53098 100644
--- a/drivers/power/supply/mt6370-charger.c
+++ b/drivers/power/supply/mt6370-charger.c
@@ -837,9 +837,7 @@ static int mt6370_chg_init_irq(struct mt6370_priv *priv)
IRQF_TRIGGER_FALLING,
dev_name(priv->dev), priv);
if (ret)
- return dev_err_probe(priv->dev, ret,
- "Failed to request irq %s\n",
- mt6370_chg_irqs[i].name);
+ return ret;
}
return 0;
diff --git a/drivers/power/supply/pf1550-charger.c b/drivers/power/supply/pf1550-charger.c
index 41036f4cb64a..2ead1df60e65 100644
--- a/drivers/power/supply/pf1550-charger.c
+++ b/drivers/power/supply/pf1550-charger.c
@@ -514,7 +514,7 @@ static int pf1550_reg_init(struct pf1550_charger *chg)
* 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)) {
+ if (!power_supply_get_battery_info(chg->charger, &info)) {
ret = regmap_write(chg->pf1550->regmap,
PF1550_CHARG_REG_CHG_OPER,
PF1550_CHG_BAT_ON);
@@ -612,8 +612,7 @@ static int pf1550_charger_probe(struct platform_device *pdev)
IRQF_NO_SUSPEND,
"pf1550-charger", chg);
if (ret)
- return dev_err_probe(&pdev->dev, ret,
- "failed irq request\n");
+ return ret;
}
pf1550_dt_parse_dev_info(chg);
diff --git a/drivers/power/supply/pm8916_lbc.c b/drivers/power/supply/pm8916_lbc.c
index cdc4d78c4219..6910b8c24343 100644
--- a/drivers/power/supply/pm8916_lbc.c
+++ b/drivers/power/supply/pm8916_lbc.c
@@ -231,11 +231,7 @@ static int pm8916_lbc_charger_probe_dt(struct pm8916_lbc_charger *chg)
return ret;
/* Disable charger timeout. */
- ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, 0x00);
- if (ret)
- return ret;
-
- return ret;
+ return regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, 0x00);
}
static const struct power_supply_desc pm8916_lbc_charger_psy_desc = {
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 2532e221b2e1..00d8bc98d588 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -477,6 +477,159 @@ struct power_supply *power_supply_get_by_name(const char *name)
}
EXPORT_SYMBOL_GPL(power_supply_get_by_name);
+static bool power_supply_is_system_battery(struct power_supply *psy)
+{
+ union power_supply_propval val;
+
+ if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY)
+ return false;
+
+ if (!power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_SCOPE,
+ &val))
+ if (val.intval == POWER_SUPPLY_SCOPE_DEVICE)
+ return false;
+
+ return true;
+}
+
+static int __power_supply_get_num_system_batteries(struct power_supply *epsy,
+ void *data)
+{
+ int *count = data;
+
+ if (power_supply_is_system_battery(epsy))
+ (*count)++;
+
+ return 0;
+}
+
+static int power_supply_get_num_system_batteries(struct device *dev)
+{
+ int ret, count = 0;
+
+ ret = power_supply_for_each_psy(&count,
+ __power_supply_get_num_system_batteries);
+
+ dev_dbg(dev, "%s: count: %d ret %d\n", __func__, count, ret);
+
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+struct psy_get_supplies_data {
+ int cnt;
+ int size;
+ struct power_supply **psys;
+};
+
+static int
+__power_supply_populate_system_batteries_array(struct power_supply *epsy,
+ void *_data)
+{
+ struct psy_get_supplies_data *data = _data;
+
+ if (power_supply_is_system_battery(epsy)) {
+ if (data->size <= data->cnt)
+ return -EOVERFLOW;
+
+ get_device(&epsy->dev);
+ data->psys[data->cnt] = epsy;
+ atomic_inc(&epsy->use_cnt);
+ data->cnt++;
+ }
+
+ return 0;
+}
+
+static int
+power_supply_populate_system_batteries_array(struct device *dev, int size,
+ struct power_supply **batteries)
+{
+ int ret;
+
+ struct psy_get_supplies_data data = {
+ .cnt = 0,
+ .size = size,
+ .psys = batteries,
+ };
+
+ ret = power_supply_for_each_psy(&data,
+ __power_supply_populate_system_batteries_array);
+
+ dev_dbg(dev, "%s Found %d batteries with array size %d ret %d\n",
+ __func__, data.cnt, data.size, ret);
+
+ if (ret < 0 || !data.cnt) {
+ power_supply_put_system_batteries(batteries, data.cnt);
+ return ret;
+ }
+
+ return data.cnt;
+}
+
+/**
+ * power_supply_get_system_batteries() - Fetches references to battery type
+ * power supplies in the system.
+ * @dev: Pointer to device requesting the power supply refs.
+ * @psys: Pointer to an array of power supply refs.
+ *
+ * Helper function to get handles to battery type power supplies in the system.
+ * If acquiring a ref to a power supply fails, then the search for battery type
+ * power supplies will abort and the acquired power supply references will be
+ * released.
+ *
+ * Return: Indicates the number of battery type power supplies returned on
+ * success or a negative error code on failure.
+ *
+ * Call power_supply_put_system_batteries() after use to cleanup resources.
+ */
+int __must_check power_supply_get_system_batteries(struct device *dev,
+ struct power_supply ***psys)
+{
+ int ret;
+
+ if (!psys)
+ return -EINVAL;
+
+ ret = power_supply_get_num_system_batteries(dev);
+ if (ret <= 0) {
+ *psys = NULL;
+ return ret;
+ }
+
+ *psys = kzalloc_objs(**psys, ret);
+ if (!*psys)
+ return -ENOMEM;
+
+ ret = power_supply_populate_system_batteries_array(dev, ret, *psys);
+ if (ret <= 0)
+ *psys = NULL;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(power_supply_get_system_batteries);
+
+/**
+ * power_supply_put_system_batteries() - Cleanup resources allocated by
+ * power_supply_get_system_batteries()
+ * @psys: Array of power supply references to release and free.
+ * @count: Number of elements in the array.
+ */
+void power_supply_put_system_batteries(struct power_supply **psys, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (psys[i])
+ power_supply_put(psys[i]);
+ }
+
+ kfree(psys);
+}
+EXPORT_SYMBOL_GPL(power_supply_put_system_batteries);
+
/**
* power_supply_put() - Drop reference obtained with power_supply_get_by_name
* @psy: Reference to put
@@ -1624,6 +1777,14 @@ __power_supply_register(struct device *parent,
init_rwsem(&psy->extensions_sem);
INIT_LIST_HEAD(&psy->extensions);
+ if (desc->init) {
+ rc = desc->init(psy);
+ if (WARN_ON_ONCE(rc > 0))
+ rc = -EINVAL;
+ if (rc)
+ goto check_supplies_failed;
+ }
+
rc = device_add(dev);
if (rc)
goto device_add_failed;
@@ -1636,11 +1797,11 @@ __power_supply_register(struct device *parent,
if (rc)
goto register_thermal_failed;
- rc = power_supply_create_triggers(psy);
- if (rc)
- goto create_triggers_failed;
-
scoped_guard(rwsem_read, &psy->extensions_sem) {
+ rc = power_supply_create_triggers(psy);
+ if (rc)
+ goto create_triggers_failed;
+
rc = power_supply_add_hwmon_sysfs(psy);
if (rc)
goto add_hwmon_sysfs_failed;
diff --git a/drivers/power/supply/power_supply_leds.c b/drivers/power/supply/power_supply_leds.c
index 1548aaba3362..538cc0440e53 100644
--- a/drivers/power/supply/power_supply_leds.c
+++ b/drivers/power/supply/power_supply_leds.c
@@ -17,8 +17,6 @@
#include "power_supply.h"
-/* Battery specific LEDs triggers. */
-
struct power_supply_led_trigger {
struct led_trigger trig;
struct power_supply *psy;
@@ -89,7 +87,7 @@ static void power_supply_unregister_led_trigger(struct led_trigger *trig)
kfree(psy_trig);
}
-static void power_supply_update_bat_leds(struct power_supply *psy)
+static void power_supply_update_status_leds(struct power_supply *psy)
{
union power_supply_propval status;
unsigned int intensity_green[3] = { 0, 255, 0 };
@@ -102,7 +100,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy)
switch (status.intval) {
case POWER_SUPPLY_STATUS_FULL:
- led_trigger_event(psy->trig, LED_FULL);
+ led_trigger_event(psy->charging_or_full_trig, LED_FULL);
led_trigger_event(psy->charging_trig, LED_OFF);
led_trigger_event(psy->full_trig, LED_FULL);
/* Going from blink to LED on requires a LED_OFF event to stop blink */
@@ -114,7 +112,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy)
LED_FULL);
break;
case POWER_SUPPLY_STATUS_CHARGING:
- led_trigger_event(psy->trig, LED_FULL);
+ led_trigger_event(psy->charging_or_full_trig, LED_FULL);
led_trigger_event(psy->charging_trig, LED_FULL);
led_trigger_event(psy->full_trig, LED_OFF);
led_trigger_blink(psy->charging_blink_full_solid_trig, 0, 0);
@@ -124,7 +122,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy)
LED_FULL);
break;
default:
- led_trigger_event(psy->trig, LED_OFF);
+ led_trigger_event(psy->charging_or_full_trig, LED_OFF);
led_trigger_event(psy->charging_trig, LED_OFF);
led_trigger_event(psy->full_trig, LED_OFF);
led_trigger_event(psy->charging_blink_full_solid_trig,
@@ -135,21 +133,15 @@ static void power_supply_update_bat_leds(struct power_supply *psy)
}
}
-static void power_supply_remove_bat_triggers(struct power_supply *psy)
-{
- power_supply_unregister_led_trigger(psy->trig);
- power_supply_unregister_led_trigger(psy->charging_trig);
- power_supply_unregister_led_trigger(psy->full_trig);
- power_supply_unregister_led_trigger(psy->charging_blink_full_solid_trig);
- power_supply_unregister_led_trigger(psy->charging_orange_full_green_trig);
-}
-
-static int power_supply_create_bat_triggers(struct power_supply *psy)
+static int power_supply_create_status_triggers(struct power_supply *psy)
{
int err = 0;
+ if (!power_supply_has_property(psy, POWER_SUPPLY_PROP_STATUS))
+ return 0;
+
power_supply_register_led_trigger(psy, "%s-charging-or-full",
- &psy->trig, &err);
+ &psy->charging_or_full_trig, &err);
power_supply_register_led_trigger(psy, "%s-charging",
&psy->charging_trig, &err);
power_supply_register_led_trigger(psy, "%s-full",
@@ -158,15 +150,11 @@ static int power_supply_create_bat_triggers(struct power_supply *psy)
&psy->charging_blink_full_solid_trig, &err);
power_supply_register_led_trigger(psy, "%s-charging-orange-full-green",
&psy->charging_orange_full_green_trig, &err);
- if (err)
- power_supply_remove_bat_triggers(psy);
return err;
}
-/* Generated power specific LEDs triggers. */
-
-static void power_supply_update_gen_leds(struct power_supply *psy)
+static void power_supply_update_online_leds(struct power_supply *psy)
{
union power_supply_propval online;
@@ -175,43 +163,53 @@ static void power_supply_update_gen_leds(struct power_supply *psy)
dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval);
- if (online.intval)
- led_trigger_event(psy->trig, LED_FULL);
- else
- led_trigger_event(psy->trig, LED_OFF);
+ led_trigger_event(psy->online_trig, online.intval ? LED_FULL : LED_OFF);
}
-static int power_supply_create_gen_triggers(struct power_supply *psy)
+static int power_supply_create_online_trigger(struct power_supply *psy)
{
- return power_supply_register_led_trigger(psy, "%s-online", &psy->trig, NULL);
-}
+ int err = 0;
-static void power_supply_remove_gen_triggers(struct power_supply *psy)
-{
- power_supply_unregister_led_trigger(psy->trig);
-}
+ if (!power_supply_has_property(psy, POWER_SUPPLY_PROP_ONLINE))
+ return 0;
-/* Choice what triggers to create&update. */
+ power_supply_register_led_trigger(psy, "%s-online", &psy->online_trig,
+ &err);
+
+ return err;
+}
void power_supply_update_leds(struct power_supply *psy)
{
- if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY)
- power_supply_update_bat_leds(psy);
- else
- power_supply_update_gen_leds(psy);
+ power_supply_update_online_leds(psy);
+ power_supply_update_status_leds(psy);
}
int power_supply_create_triggers(struct power_supply *psy)
{
- if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY)
- return power_supply_create_bat_triggers(psy);
- return power_supply_create_gen_triggers(psy);
+ int err;
+
+ err = power_supply_create_online_trigger(psy);
+ if (err)
+ goto err_remove;
+
+ err = power_supply_create_status_triggers(psy);
+ if (err)
+ goto err_remove;
+
+ return 0;
+
+err_remove:
+ power_supply_remove_triggers(psy);
+ return err;
}
void power_supply_remove_triggers(struct power_supply *psy)
{
- if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY)
- power_supply_remove_bat_triggers(psy);
- else
- power_supply_remove_gen_triggers(psy);
+ power_supply_unregister_led_trigger(psy->online_trig);
+ power_supply_unregister_led_trigger(psy->charging_or_full_trig);
+ power_supply_unregister_led_trigger(psy->charging_trig);
+ power_supply_unregister_led_trigger(psy->full_trig);
+ power_supply_unregister_led_trigger(psy->charging_blink_full_solid_trig);
+ power_supply_unregister_led_trigger(psy->charging_orange_full_green_trig);
}
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index f30a7b9ccd5e..9d6b24856c8b 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -124,6 +124,10 @@ static const char * const POWER_SUPPLY_TECHNOLOGY_TEXT[] = {
[POWER_SUPPLY_TECHNOLOGY_LiFe] = "LiFe",
[POWER_SUPPLY_TECHNOLOGY_NiCd] = "NiCd",
[POWER_SUPPLY_TECHNOLOGY_LiMn] = "LiMn",
+ [POWER_SUPPLY_TECHNOLOGY_PbAc] = "PbAc",
+ [POWER_SUPPLY_TECHNOLOGY_NiZn] = "NiZn",
+ [POWER_SUPPLY_TECHNOLOGY_RAM] = "RAM",
+ [POWER_SUPPLY_TECHNOLOGY_ZnAr] = "ZnAr",
};
static const char * const POWER_SUPPLY_CAPACITY_LEVEL_TEXT[] = {
diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c
index 490137a23d00..7716eb9e1aff 100644
--- a/drivers/power/supply/qcom_battmgr.c
+++ b/drivers/power/supply/qcom_battmgr.c
@@ -5,6 +5,7 @@
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/auxiliary_bus.h>
+#include <linux/devm-helpers.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/nvmem-consumer.h>
@@ -1231,17 +1232,17 @@ static void qcom_battmgr_sc8280xp_strcpy(char *dest, const char *src)
memcpy(dest, src + 1, len);
dest[len] = '\0';
} else {
- memcpy(dest, src, BATTMGR_STRING_LEN);
+ strscpy(dest, src, BATTMGR_STRING_LEN);
}
}
static unsigned int qcom_battmgr_sc8280xp_parse_technology(const char *chemistry)
{
- if ((!strncmp(chemistry, "LIO", BATTMGR_CHEMISTRY_LEN)) ||
- (!strncmp(chemistry, "OOI", BATTMGR_CHEMISTRY_LEN)))
+ if ((!strncmp(chemistry, "LIO", 3)) ||
+ (!strncmp(chemistry, "OOI", 3)))
return POWER_SUPPLY_TECHNOLOGY_LION;
- if (!strncmp(chemistry, "LIP", BATTMGR_CHEMISTRY_LEN) ||
- !strncmp(chemistry, "LiP", BATTMGR_CHEMISTRY_LEN))
+ if (!strncmp(chemistry, "LIP", 3) ||
+ !strncmp(chemistry, "LiP", 3))
return POWER_SUPPLY_TECHNOLOGY_LIPO;
pr_err("Unknown battery technology '%s'\n", chemistry);
@@ -1648,7 +1649,6 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev,
psy_cfg_supply.supplied_to = qcom_battmgr_battery;
psy_cfg_supply.num_supplicants = 1;
- INIT_WORK(&battmgr->enable_work, qcom_battmgr_enable_worker);
mutex_init(&battmgr->lock);
init_completion(&battmgr->ack);
@@ -1711,6 +1711,11 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev,
"failed to register wireless charing power supply\n");
}
+ ret = devm_work_autocancel(dev, &battmgr->enable_work,
+ qcom_battmgr_enable_worker);
+ if (ret)
+ return ret;
+
battmgr->client = devm_pmic_glink_client_alloc(dev, PMIC_GLINK_OWNER_BATTMGR,
qcom_battmgr_callback,
qcom_battmgr_pdr_notify,
diff --git a/drivers/power/supply/qcom_smbb.c b/drivers/power/supply/qcom_smbb.c
index 28afe758a2da..f33aab9619c4 100644
--- a/drivers/power/supply/qcom_smbb.c
+++ b/drivers/power/supply/qcom_smbb.c
@@ -937,11 +937,8 @@ static int smbb_charger_probe(struct platform_device *pdev)
rc = devm_request_threaded_irq(&pdev->dev, irq, NULL,
smbb_charger_irqs[i].handler, IRQF_ONESHOT,
smbb_charger_irqs[i].name, chg);
- if (rc) {
- dev_err(&pdev->dev, "failed to request irq '%s'\n",
- smbb_charger_irqs[i].name);
+ if (rc)
return rc;
- }
}
/*
diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c
index bf2e2ccc454a..7bda5cd77a75 100644
--- a/drivers/power/supply/qcom_smbx.c
+++ b/drivers/power/supply/qcom_smbx.c
@@ -922,8 +922,7 @@ static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name,
rc = devm_request_threaded_irq(chip->dev, irqnum, NULL, handler,
IRQF_ONESHOT, name, chip);
if (rc < 0)
- return dev_err_probe(chip->dev, rc, "Couldn't request irq %s\n",
- name);
+ return rc;
if (irq)
*irq = irqnum;
diff --git a/drivers/power/supply/rk817_charger.c b/drivers/power/supply/rk817_charger.c
index 9436c6bbf51f..5558b182a1a6 100644
--- a/drivers/power/supply/rk817_charger.c
+++ b/drivers/power/supply/rk817_charger.c
@@ -1186,19 +1186,15 @@ static int rk817_charger_probe(struct platform_device *pdev)
rk817_plug_in_isr,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"rk817_plug_in", charger);
- if (ret) {
- return dev_err_probe(&pdev->dev, ret,
- "plug_in_irq request failed!\n");
- }
+ if (ret)
+ return ret;
ret = devm_request_threaded_irq(charger->dev, plugout_irq, NULL,
rk817_plug_out_isr,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"rk817_plug_out", charger);
- if (ret) {
- return dev_err_probe(&pdev->dev, ret,
- "plug_out_irq request failed!\n");
- }
+ if (ret)
+ return ret;
ret = devm_delayed_work_autocancel(&pdev->dev, &charger->work,
rk817_charging_monitor);
diff --git a/drivers/power/supply/rn5t618_power.c b/drivers/power/supply/rn5t618_power.c
index 40dec55a9f73..5963a55341ee 100644
--- a/drivers/power/supply/rn5t618_power.c
+++ b/drivers/power/supply/rn5t618_power.c
@@ -800,11 +800,8 @@ static int rn5t618_power_probe(struct platform_device *pdev)
"rn5t618_power",
&pdev->dev);
- if (ret < 0) {
- dev_err(&pdev->dev, "request IRQ:%d fail\n",
- info->irq);
+ if (ret < 0)
info->irq = -1;
- }
}
return 0;
diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c
index 7045d2908148..313a7371211e 100644
--- a/drivers/power/supply/rt9455_charger.c
+++ b/drivers/power/supply/rt9455_charger.c
@@ -1582,6 +1582,19 @@ static const struct regmap_config rt9455_regmap_config = {
.cache_type = REGCACHE_MAPLE,
};
+static void rt9455_cancel_all_delayed_works(void *data)
+{
+ struct rt9455_info *info = data;
+
+ /*
+ * Both pwr_rdy_work and batt_presence_work can queue
+ * max_charging_time_work, so cancel them first.
+ */
+ cancel_delayed_work_sync(&info->pwr_rdy_work);
+ cancel_delayed_work_sync(&info->batt_presence_work);
+ cancel_delayed_work_sync(&info->max_charging_time_work);
+}
+
static int rt9455_probe(struct i2c_client *client)
{
struct i2c_adapter *adapter = client->adapter;
@@ -1672,14 +1685,16 @@ static int rt9455_probe(struct i2c_client *client)
goto put_usb_notifier;
}
+ ret = devm_add_action_or_reset(dev, rt9455_cancel_all_delayed_works, info);
+ if (ret)
+ goto put_usb_notifier;
+
ret = devm_request_threaded_irq(dev, client->irq, NULL,
rt9455_irq_handler_thread,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
RT9455_DRIVER_NAME, info);
- if (ret) {
- dev_err(dev, "Failed to register IRQ handler\n");
+ if (ret)
goto put_usb_notifier;
- }
ret = rt9455_hw_init(info, ichrg, ieoc_percentage, mivr, iaicr);
if (ret) {
@@ -1712,10 +1727,6 @@ static void rt9455_remove(struct i2c_client *client)
if (info->nb.notifier_call)
usb_unregister_notifier(info->usb_phy, &info->nb);
#endif
-
- cancel_delayed_work_sync(&info->pwr_rdy_work);
- cancel_delayed_work_sync(&info->max_charging_time_work);
- cancel_delayed_work_sync(&info->batt_presence_work);
}
static const struct i2c_device_id rt9455_i2c_id_table[] = {
diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c
index de0471e54978..a9a83b263e18 100644
--- a/drivers/power/supply/rt9467-charger.c
+++ b/drivers/power/supply/rt9467-charger.c
@@ -1030,8 +1030,7 @@ static int rt9467_request_interrupt(struct rt9467_chg_data *data)
ret = devm_request_threaded_irq(dev, virq, NULL, chg_irqs[i].handler,
IRQF_ONESHOT, chg_irqs[i].name, data);
if (ret)
- return dev_err_probe(dev, ret, "Failed to request (%s) irq\n",
- chg_irqs[i].name);
+ return ret;
}
return 0;
diff --git a/drivers/power/supply/rt9471.c b/drivers/power/supply/rt9471.c
index ca7d426849a7..b67f5abaa993 100644
--- a/drivers/power/supply/rt9471.c
+++ b/drivers/power/supply/rt9471.c
@@ -369,23 +369,28 @@ static int rt9471_charger_set_property(struct power_supply *psy,
const union power_supply_propval *val)
{
struct rt9471_chip *chip = power_supply_get_drvdata(psy);
- int value = val->intval;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
- return regmap_field_write(chip->rm_fields[F_CHG_EN], !!value);
+ return regmap_field_write(chip->rm_fields[F_CHG_EN],
+ !!val->intval);
case POWER_SUPPLY_PROP_ONLINE:
- return regmap_field_write(chip->rm_fields[F_HZ], !value);
+ return regmap_field_write(chip->rm_fields[F_HZ], !val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
- return rt9471_set_value_by_field_range(chip, F_ICHG_REG, RT9471_RANGE_ICHG, value);
+ return rt9471_set_value_by_field_range(
+ chip, F_ICHG_REG, RT9471_RANGE_ICHG, val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
- return rt9471_set_value_by_field_range(chip, F_VBAT_REG, RT9471_RANGE_VCHG, value);
+ return rt9471_set_value_by_field_range(
+ chip, F_VBAT_REG, RT9471_RANGE_VCHG, val->intval);
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
- return rt9471_set_value_by_field_range(chip, F_AICR, RT9471_RANGE_AICR, value);
+ return rt9471_set_value_by_field_range(
+ chip, F_AICR, RT9471_RANGE_AICR, val->intval);
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
- return rt9471_set_value_by_field_range(chip, F_MIVR, RT9471_RANGE_MIVR, value);
+ return rt9471_set_value_by_field_range(
+ chip, F_MIVR, RT9471_RANGE_MIVR, val->intval);
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
- return rt9471_set_value_by_field_range(chip, F_IPRE_CHG, RT9471_RANGE_IPRE, value);
+ return rt9471_set_value_by_field_range(
+ chip, F_IPRE_CHG, RT9471_RANGE_IPRE, val->intval);
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return rt9471_set_ieoc(chip, val->intval);
default:
@@ -401,35 +406,39 @@ static int rt9471_charger_get_property(struct power_supply *psy,
union power_supply_propval *val)
{
struct rt9471_chip *chip = power_supply_get_drvdata(psy);
- int *pvalue = &val->intval;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
- return rt9471_get_status(chip, pvalue);
+ return rt9471_get_status(chip, &val->intval);
case POWER_SUPPLY_PROP_ONLINE:
- return rt9471_get_vbus_good(chip, pvalue);
+ return rt9471_get_vbus_good(chip, &val->intval);
case POWER_SUPPLY_PROP_CURRENT_MAX:
- return rt9471_get_usb_type_current(chip, pvalue);
+ return rt9471_get_usb_type_current(chip, &val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
- return rt9471_get_value_by_field_range(chip, F_ICHG_REG, RT9471_RANGE_ICHG, pvalue);
+ return rt9471_get_value_by_field_range(
+ chip, F_ICHG_REG, RT9471_RANGE_ICHG, &val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
- *pvalue = RT9471_ICHG_MAXUA;
+ val->intval = RT9471_ICHG_MAXUA;
return 0;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
- return rt9471_get_value_by_field_range(chip, F_VBAT_REG, RT9471_RANGE_VCHG, pvalue);
+ return rt9471_get_value_by_field_range(
+ chip, F_VBAT_REG, RT9471_RANGE_VCHG, &val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
val->intval = RT9471_VCHG_MAXUV;
return 0;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
- return rt9471_get_value_by_field_range(chip, F_AICR, RT9471_RANGE_AICR, pvalue);
+ return rt9471_get_value_by_field_range(
+ chip, F_AICR, RT9471_RANGE_AICR, &val->intval);
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
- return rt9471_get_value_by_field_range(chip, F_MIVR, RT9471_RANGE_MIVR, pvalue);
+ return rt9471_get_value_by_field_range(
+ chip, F_MIVR, RT9471_RANGE_MIVR, &val->intval);
case POWER_SUPPLY_PROP_USB_TYPE:
- return rt9471_get_usb_type(chip, pvalue);
+ return rt9471_get_usb_type(chip, &val->intval);
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
- return rt9471_get_value_by_field_range(chip, F_IPRE_CHG, RT9471_RANGE_IPRE, pvalue);
+ return rt9471_get_value_by_field_range(
+ chip, F_IPRE_CHG, RT9471_RANGE_IPRE, &val->intval);
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
- return rt9471_get_ieoc(chip, pvalue);
+ return rt9471_get_ieoc(chip, &val->intval);
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = rt9471_model;
return 0;
@@ -583,8 +592,7 @@ static int rt9471_register_interrupts(struct rt9471_chip *chip)
ret = devm_request_threaded_irq(dev, virq, NULL, curr->handler,
IRQF_ONESHOT, curr->name, chip);
if (ret)
- return dev_err_probe(dev, ret, "Failed to register IRQ (%s)\n",
- curr->name);
+ return ret;
}
return 0;
diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c
index 017ec06be766..501c8e069755 100644
--- a/drivers/power/supply/sbs-battery.c
+++ b/drivers/power/supply/sbs-battery.c
@@ -217,6 +217,7 @@ struct sbs_info {
u32 flags;
int technology;
char strings[NR_STRING_BUFFERS][I2C_SMBUS_BLOCK_MAX + 1];
+ char serial[5];
};
static char *sbs_get_string_buf(struct sbs_info *chip,
@@ -821,18 +822,18 @@ static int sbs_get_battery_capacity(struct i2c_client *client,
return 0;
}
-static char sbs_serial[5];
static int sbs_get_battery_serial_number(struct i2c_client *client,
union power_supply_propval *val)
{
+ struct sbs_info *chip = i2c_get_clientdata(client);
int ret;
ret = sbs_read_word_data(client, sbs_data[REG_SERIAL_NUMBER].addr);
if (ret < 0)
return ret;
- sprintf(sbs_serial, "%04x", ret);
- val->strval = sbs_serial;
+ sprintf(chip->serial, "%04x", (u16)ret);
+ val->strval = chip->serial;
return 0;
}
@@ -860,6 +861,14 @@ static int sbs_get_chemistry(struct sbs_info *chip,
chip->technology = POWER_SUPPLY_TECHNOLOGY_NiCd;
else if (!strncasecmp(chemistry, "NiMH", 4))
chip->technology = POWER_SUPPLY_TECHNOLOGY_NiMH;
+ else if (!strncasecmp(chemistry, "PbAc", 4))
+ chip->technology = POWER_SUPPLY_TECHNOLOGY_PbAc;
+ else if (!strncasecmp(chemistry, "NiZn", 4))
+ chip->technology = POWER_SUPPLY_TECHNOLOGY_NiZn;
+ else if (!strncasecmp(chemistry, "RAM", 3))
+ chip->technology = POWER_SUPPLY_TECHNOLOGY_RAM;
+ else if (!strncasecmp(chemistry, "ZnAr", 4))
+ chip->technology = POWER_SUPPLY_TECHNOLOGY_ZnAr;
else
chip->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c
index a00c710601e8..e7b4e10dcfd5 100644
--- a/drivers/power/supply/sbs-charger.c
+++ b/drivers/power/supply/sbs-charger.c
@@ -217,7 +217,7 @@ static int sbs_probe(struct i2c_client *client)
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
dev_name(&client->dev), chip);
if (ret)
- return dev_err_probe(&client->dev, ret, "Failed to request irq\n");
+ return ret;
} else {
ret = devm_delayed_work_autocancel(&client->dev, &chip->work,
sbs_delayed_work);
diff --git a/drivers/power/supply/sc2731_charger.c b/drivers/power/supply/sc2731_charger.c
index 58b86fd78771..2b25e44da797 100644
--- a/drivers/power/supply/sc2731_charger.c
+++ b/drivers/power/supply/sc2731_charger.c
@@ -466,6 +466,7 @@ static int sc2731_charger_probe(struct platform_device *pdev)
mutex_init(&info->lock);
info->dev = &pdev->dev;
INIT_WORK(&info->work, sc2731_charger_work);
+ platform_set_drvdata(pdev, info);
info->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!info->regmap) {
@@ -516,6 +517,7 @@ static void sc2731_charger_remove(struct platform_device *pdev)
struct sc2731_charger_info *info = platform_get_drvdata(pdev);
usb_unregister_notifier(info->usb_phy, &info->usb_notify);
+ cancel_work_sync(&info->work);
}
static const struct of_device_id sc2731_charger_of_match[] = {
diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c
index a7ed9de8a289..9af0017fa88e 100644
--- a/drivers/power/supply/sc27xx_fuel_gauge.c
+++ b/drivers/power/supply/sc27xx_fuel_gauge.c
@@ -1231,10 +1231,8 @@ static int sc27xx_fgu_probe(struct platform_device *pdev)
sc27xx_fgu_interrupt,
IRQF_NO_SUSPEND | IRQF_ONESHOT,
pdev->name, data);
- if (ret) {
- dev_err(data->dev, "failed to request fgu IRQ\n");
+ if (ret)
return ret;
- }
irq = gpiod_to_irq(data->gpiod);
if (irq < 0) {
@@ -1247,10 +1245,8 @@ static int sc27xx_fgu_probe(struct platform_device *pdev)
IRQF_ONESHOT | IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING,
pdev->name, data);
- if (ret) {
- dev_err(dev, "failed to request IRQ\n");
+ if (ret)
return ret;
- }
return 0;
}
diff --git a/drivers/power/supply/sgm41542_charger.c b/drivers/power/supply/sgm41542_charger.c
new file mode 100644
index 000000000000..8880962528d6
--- /dev/null
+++ b/drivers/power/supply/sgm41542_charger.c
@@ -0,0 +1,1127 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Charger driver for SGM4154x
+ *
+ * Copyright (c) 2026 Rockchip Electronics Co., Ltd.
+ *
+ * Author: Xu Shengfei <xsf@rock-chips.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/devm-helpers.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/types.h>
+
+#define SGM4154X_MANUFACTURER "SGMICRO"
+#define SGM4154X_NAME "sgm41542"
+
+#define SGM4154X_CHRG_CTRL_0 0x00
+#define SGM4154X_HIZ_EN BIT(7)
+#define SGM4154X_IINDPM_I_MASK GENMASK(4, 0)
+#define SGM4154X_IINDPM_I_MIN_UA 100000
+#define SGM4154X_IINDPM_I_MAX_UA 3800000
+#define SGM4154X_IINDPM_STEP_UA 100000
+#define SGM4154X_IINDPM_DEF_UA 2400000
+
+#define SGM4154X_CHRG_CTRL_1 0x01
+#define SGM4154X_WDT_RST BIT(6)
+#define SGM4154X_OTG_EN BIT(5)
+#define SGM4154X_CHRG_EN BIT(4)
+
+#define SGM4154X_CHRG_CTRL_2 0x02
+#define SGM4154X_BOOST_LIM BIT(7)
+#define SGM4154X_ICHRG_CUR_MASK GENMASK(5, 0)
+#define SGM4154X_ICHRG_I_STEP_UA 60000
+#define SGM4154X_ICHRG_I_MIN_UA 0
+#define SGM4154X_ICHRG_I_MAX_UA 3780000
+#define SGM4154X_ICHRG_I_DEF_UA 2040000
+
+#define SGM4154X_CHRG_CTRL_3 0x03
+#define SGM4154X_PRECHRG_CUR_MASK GENMASK(7, 4)
+#define SGM4154X_PRECHRG_CURRENT_STEP_UA 60000
+#define SGM4154X_PRECHRG_I_MIN_UA 60000
+#define SGM4154X_PRECHRG_I_MAX_UA 780000
+#define SGM4154X_PRECHRG_I_DEF_UA 180000
+#define SGM4154X_TERMCHRG_CUR_MASK GENMASK(3, 0)
+#define SGM4154X_TERMCHRG_CURRENT_STEP_UA 60000
+#define SGM4154X_TERMCHRG_I_MIN_UA 60000
+#define SGM4154X_TERMCHRG_I_MAX_UA 960000
+#define SGM4154X_TERMCHRG_I_DEF_UA 180000
+
+#define SGM4154X_CHRG_CTRL_4 0x04
+#define SGM4154X_VREG_V_MASK GENMASK(7, 3)
+#define SGM4154X_VREG_V_MAX_UV 4624000
+#define SGM4154X_VREG_V_MIN_UV 3856000
+#define SGM4154X_VREG_V_DEF_UV 4208000
+#define SGM4154X_VREG_V_STEP_UV 32000
+#define SGM4154X_VRECHARGE BIT(0)
+#define SGM4154X_VRECHRG_STEP_UV 100000
+#define SGM4154X_VRECHRG_OFFSET_UV 100000
+
+#define SGM4154X_CHRG_CTRL_5 0x05
+#define SGM4154X_TERM_EN BIT(7)
+#define SGM4154X_WDT_TIMER_MASK GENMASK(5, 4)
+
+#define SGM4154X_CHRG_CTRL_6 0x06
+#define SGM4154X_VAC_OVP_MASK GENMASK(7, 6)
+#define SGM4154X_OVP_14V (BIT(7) | BIT(6))
+#define SGM4154X_OVP_10_5V BIT(7)
+#define SGM4154X_OVP_6_5V BIT(6)
+#define SGM4154X_OVP_5_5V 0
+#define SGM4154X_OVP_DEFAULT SGM4154X_OVP_14V
+#define SGM4154X_BOOSTV GENMASK(5, 4)
+#define SGM4154X_VINDPM_V_MASK GENMASK(3, 0)
+#define SGM4154X_VINDPM_V_MIN_UV 3900000
+#define SGM4154X_VINDPM_V_MAX_UV 12000000
+#define SGM4154X_VINDPM_STEP_UV 100000
+#define SGM4154X_VINDPM_DEF_UV 4500000
+
+#define SGM4154X_CHRG_CTRL_7 0x07
+
+#define SGM4154X_CHRG_STAT 0x08
+#define SGM4154X_VBUS_STAT_MASK GENMASK(7, 5)
+#define SGM4154X_OTG_MODE (BIT(7) | BIT(6) | BIT(5))
+#define SGM4154X_NON_STANDARD (BIT(7) | BIT(6))
+#define SGM4154X_UNKNOWN (BIT(7) | BIT(5))
+#define SGM4154X_USB_DCP (BIT(6) | BIT(5))
+#define SGM4154X_USB_CDP BIT(6)
+#define SGM4154X_USB_SDP BIT(5)
+#define SGM4154X_NOT_CHRGING 0
+#define SGM4154X_CHG_STAT_MASK GENMASK(4, 3)
+#define SGM4154X_TERM_CHRG (BIT(4) | BIT(3))
+#define SGM4154X_FAST_CHRG BIT(4)
+#define SGM4154X_PRECHRG BIT(3)
+#define SGM4154X_PG_STAT BIT(2)
+#define SGM4154X_THERM_STAT BIT(1)
+#define SGM4154X_VSYS_STAT BIT(0)
+
+#define SGM4154X_CHRG_FAULT 0x09
+#define SGM4154X_TEMP_MASK GENMASK(2, 0)
+#define SGM4154X_TEMP_HOT (BIT(2) | BIT(1))
+#define SGM4154X_TEMP_COLD (BIT(2) | BIT(0))
+#define SGM4154X_TEMP_COOL (BIT(1) | BIT(0))
+#define SGM4154X_TEMP_WARM BIT(1)
+#define SGM4154X_TEMP_NORMAL BIT(0)
+
+#define SGM4154X_CHRG_CTRL_A 0x0a
+#define SGM4154X_VBUS_GOOD BIT(7)
+#define SGM4154X_VINDPM_INT_MASK BIT(1)
+#define SGM4154X_IINDPM_INT_MASK BIT(0)
+
+#define SGM4154X_CHRG_CTRL_B 0x0b
+#define SGM4154X_PN_ID (BIT(6) | BIT(5) | BIT(3))
+#define SGM4154X_PN_MASK GENMASK(6, 3)
+
+#define SGM4154X_CHRG_CTRL_C 0x0c
+
+#define SGM4154X_CHRG_CTRL_D 0x0d
+#define SGM4154X_JEITA_EN BIT(0)
+
+#define SGM4154X_INPUT_DET 0x0e
+#define SGM4154X_DPDM_ONGOING BIT(7)
+
+#define SGM4154X_CHRG_CTRL_F 0x0f
+#define SGM4154X_VINDPM_OS_MASK GENMASK(1, 0)
+
+#define SGM4154X_DEFAULT_INPUT_CUR (500 * 1000)
+
+struct sgm4154x_init_data {
+ int ilim; /* input current limit */
+ int vlim; /* minimum system voltage limit */
+ int iterm; /* termination current */
+ int iprechg; /* precharge current */
+ int max_ichg; /* maximum charge current */
+ int max_vreg; /* maximum charge voltage */
+};
+
+struct sgm4154x_state {
+ bool vsys_stat;
+ bool therm_stat;
+ bool online;
+ u8 chrg_stat;
+ bool chrg_en;
+ bool vbus_gd;
+ u8 chrg_type;
+ u8 health;
+ u8 chrg_fault;
+ u8 ntc_fault;
+};
+
+struct sgm4154x_device {
+ struct device *dev;
+ struct power_supply *charger;
+ struct regmap *regmap;
+ struct sgm4154x_init_data init_data;
+ struct sgm4154x_state state;
+ struct regulator_dev *otg_rdev;
+ struct mutex lock;
+ bool watchdog_enable;
+ struct workqueue_struct *sgm_monitor_wq;
+ struct delayed_work sgm_delay_work;
+};
+
+enum SGM4154X_VINDPM_OS {
+ VINDPM_OS_3900MV,
+ VINDPM_OS_5900MV,
+ VINDPM_OS_7500MV,
+ VINDPM_OS_10500MV,
+};
+
+enum sgm4154x_wdt_values {
+ SGM4154X_WDT_TIMER_DISABLE = 0,
+ SGM4154X_WDT_TIMER_40S = 1,
+ SGM4154X_WDT_TIMER_80S = 2,
+ SGM4154X_WDT_TIMER_160S = 3,
+};
+
+static int sgm4154x_set_term_curr(struct sgm4154x_device *sgm, int cur_ua)
+{
+ int reg_val;
+ int ret;
+
+ cur_ua = clamp(cur_ua, SGM4154X_TERMCHRG_I_MIN_UA, SGM4154X_TERMCHRG_I_MAX_UA);
+ reg_val = (cur_ua - SGM4154X_TERMCHRG_I_MIN_UA) / SGM4154X_TERMCHRG_CURRENT_STEP_UA;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_3,
+ SGM4154X_TERMCHRG_CUR_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set term current error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_prechrg_curr(struct sgm4154x_device *sgm, int cur_ua)
+{
+ int reg_val;
+ int ret;
+
+ cur_ua = clamp(cur_ua, SGM4154X_PRECHRG_I_MIN_UA, SGM4154X_PRECHRG_I_MAX_UA);
+ reg_val = (cur_ua - SGM4154X_PRECHRG_I_MIN_UA) / SGM4154X_PRECHRG_CURRENT_STEP_UA;
+
+ reg_val = FIELD_PREP(SGM4154X_PRECHRG_CUR_MASK, reg_val);
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_3,
+ SGM4154X_PRECHRG_CUR_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set precharge current error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_ichrg_curr(struct sgm4154x_device *sgm, int cur_ua)
+{
+ int reg_val;
+ int ret;
+
+ cur_ua = clamp(cur_ua, SGM4154X_ICHRG_I_MIN_UA, sgm->init_data.max_ichg);
+ reg_val = cur_ua / SGM4154X_ICHRG_I_STEP_UA;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_2,
+ SGM4154X_ICHRG_CUR_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set icharge current error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_get_ichrg_curr(struct sgm4154x_device *sgm)
+{
+ u32 reg;
+ int ret, val;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_2, &reg);
+ if (ret) {
+ dev_err(sgm->dev, "get charge current error!\n");
+ return ret;
+ }
+
+ val = FIELD_GET(SGM4154X_ICHRG_CUR_MASK, reg);
+
+ return val * SGM4154X_ICHRG_I_STEP_UA;
+}
+
+static int sgm4154x_set_chrg_volt(struct sgm4154x_device *sgm, int chrg_volt)
+{
+ int reg_val;
+ int ret;
+
+ /*
+ * Note that the value of 0x01111 represents a "special value"
+ * corresponding to 4352000uV instead of the expected 4336000uV,
+ * per the datasheet. All other values are as expected. So not
+ * only do we need to clamp between max and min values, but
+ * also clamp anything below 4352000uv to 4304000uv to prevent
+ * overcharging.
+ */
+ chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, sgm->init_data.max_vreg);
+ if (chrg_volt < 4352000)
+ chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, 4304000);
+ reg_val = (chrg_volt - SGM4154X_VREG_V_MIN_UV) / SGM4154X_VREG_V_STEP_UV;
+ reg_val = FIELD_PREP(SGM4154X_VREG_V_MASK, reg_val);
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_4,
+ SGM4154X_VREG_V_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set charge voltage error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_get_chrg_volt(struct sgm4154x_device *sgm)
+{
+ u32 reg;
+ int ret, val;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_4, &reg);
+ if (ret) {
+ dev_err(sgm->dev, "get charge voltage error!\n");
+ return ret;
+ }
+
+ val = FIELD_GET(SGM4154X_VREG_V_MASK, reg);
+
+ /*
+ * 0x01111 is a special value meaning 4352000uV, all other
+ * values are as expected based on the offset and step values.
+ */
+ if (val == 0x0f)
+ return 4352000;
+
+ return val * SGM4154X_VREG_V_STEP_UV + SGM4154X_VREG_V_MIN_UV;
+}
+
+static int sgm4154x_set_input_volt_lim(struct sgm4154x_device *sgm,
+ unsigned int vindpm)
+{
+ enum SGM4154X_VINDPM_OS os_val;
+ unsigned int offset;
+ u8 reg_val;
+ int ret;
+
+
+ if (vindpm < SGM4154X_VINDPM_V_MIN_UV ||
+ vindpm > SGM4154X_VINDPM_V_MAX_UV) {
+ dev_err(sgm->dev, "input voltage limit %u outside range\n", vindpm);
+ return -EINVAL;
+ }
+
+ /*
+ * Supported ranges per the datasheet are as follows:
+ * 3.9v - 5.4v with a 3.9v offset
+ * 5.9v - 7.4v with a 5.9v offset
+ * 7.5v - 9.0v with a 7.5v offset
+ * 10.5v - 12.0v with a 10.5v offset
+ * Step size is a constant 100mv
+ */
+ if (vindpm < 5900000) {
+ offset = 3900000;
+ vindpm = clamp(vindpm, offset, 5400000);
+ os_val = VINDPM_OS_3900MV;
+ } else if (vindpm < 7500000) {
+ offset = 5900000;
+ vindpm = clamp(vindpm, offset, 7400000);
+ os_val = VINDPM_OS_5900MV;
+ } else if (vindpm < 10500000) {
+ offset = 7500000;
+ vindpm = clamp(vindpm, offset, 9000000);
+ os_val = VINDPM_OS_7500MV;
+ } else {
+ offset = 10500000;
+ vindpm = clamp(vindpm, offset, 12000000);
+ os_val = VINDPM_OS_10500MV;
+ }
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_F,
+ SGM4154X_VINDPM_OS_MASK,
+ os_val);
+ if (ret) {
+ dev_err(sgm->dev, "set vin dpm error!\n");
+ return ret;
+ }
+
+ reg_val = (vindpm - offset) / SGM4154X_VINDPM_STEP_UV;
+
+ ret = regmap_update_bits(sgm->regmap, SGM4154X_CHRG_CTRL_6,
+ SGM4154X_VINDPM_V_MASK, reg_val);
+ if (ret)
+ dev_err(sgm->dev, "input voltage error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_input_curr_lim(struct sgm4154x_device *sgm, int iindpm)
+{
+ int reg_val;
+ int ret;
+
+ if (iindpm < SGM4154X_IINDPM_I_MIN_UA)
+ return -EINVAL;
+
+ /*
+ * Protect against a timing-issue edge case if a sysfs write occurs in the middle
+ * of a probe (very unlikely).
+ */
+ if (sgm->init_data.ilim < SGM4154X_IINDPM_I_MIN_UA)
+ return -EINVAL;
+
+ /*
+ * Per the datasheet, values between 100000uA and 3100000uA work
+ * as expected with the register defined as having a step of
+ * 100000 and a min/max of 100000 (0x00) through 3100000 (0x1e).
+ * The register value of 0x1f however corresponds to 3800000uA not
+ * 3200000uA as one would expect.
+ */
+ if ((iindpm > SGM4154X_IINDPM_I_MAX_UA) || (iindpm > sgm->init_data.ilim))
+ iindpm = min(SGM4154X_IINDPM_I_MAX_UA, sgm->init_data.ilim);
+
+ if (iindpm > 3100000 && iindpm < SGM4154X_IINDPM_I_MAX_UA)
+ iindpm = 3100000;
+
+ if (iindpm == SGM4154X_IINDPM_I_MAX_UA)
+ reg_val = 0x1f;
+ else
+ reg_val = (iindpm - SGM4154X_IINDPM_I_MIN_UA) / SGM4154X_IINDPM_STEP_UA;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_0,
+ SGM4154X_IINDPM_I_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set input current limit error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_get_input_curr_lim(struct sgm4154x_device *sgm)
+{
+ int ret;
+ int ilim;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_0, &ilim);
+ if (ret) {
+ dev_err(sgm->dev, "get input current limit error!\n");
+ return ret;
+ }
+
+ ilim &= SGM4154X_IINDPM_I_MASK;
+
+ /* Max value is not 3200000uA as expected but is 3800000uA */
+ if (ilim == SGM4154X_IINDPM_I_MASK)
+ return SGM4154X_IINDPM_I_MAX_UA;
+
+ ilim = ilim * SGM4154X_IINDPM_STEP_UA + SGM4154X_IINDPM_I_MIN_UA;
+
+ return ilim;
+}
+
+static int sgm4154x_watchdog_timer_reset(struct sgm4154x_device *sgm)
+{
+ int ret;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_1,
+ SGM4154X_WDT_RST,
+ SGM4154X_WDT_RST);
+
+ if (ret)
+ dev_err(sgm->dev, "set watchdog timer error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_watchdog_timer(struct sgm4154x_device *sgm, u8 time)
+{
+ int ret;
+
+ if (time > SGM4154X_WDT_TIMER_160S)
+ return -EINVAL;
+
+ time = FIELD_PREP(SGM4154X_WDT_TIMER_MASK, time);
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_5,
+ SGM4154X_WDT_TIMER_MASK,
+ time);
+
+ if (ret) {
+ dev_err(sgm->dev, "set watchdog timer error!\n");
+ return ret;
+ }
+
+ if (time) {
+ if (!sgm->watchdog_enable)
+ queue_delayed_work(sgm->sgm_monitor_wq,
+ &sgm->sgm_delay_work,
+ msecs_to_jiffies(1000 * 5));
+ sgm->watchdog_enable = true;
+ } else {
+ sgm->watchdog_enable = false;
+ sgm4154x_watchdog_timer_reset(sgm);
+ }
+
+ return ret;
+}
+
+static int sgm4154x_enable_charger(struct sgm4154x_device *sgm)
+{
+ int ret;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_1,
+ SGM4154X_CHRG_EN,
+ SGM4154X_CHRG_EN);
+ if (ret)
+ dev_err(sgm->dev, "enable charger error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_disable_charger(struct sgm4154x_device *sgm)
+{
+ int ret;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_1,
+ SGM4154X_CHRG_EN,
+ 0);
+ if (ret)
+ dev_err(sgm->dev, "disable charger error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_vac_ovp(struct sgm4154x_device *sgm)
+{
+ int reg_val;
+ int ret;
+
+ reg_val = SGM4154X_OVP_DEFAULT & SGM4154X_VAC_OVP_MASK;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_6,
+ SGM4154X_VAC_OVP_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set vac ovp error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_recharge_volt_ua(struct sgm4154x_device *sgm, int recharge_volt)
+{
+ int reg_val;
+ int ret;
+
+ reg_val = (recharge_volt - SGM4154X_VRECHRG_OFFSET_UV) / SGM4154X_VRECHRG_STEP_UV;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_4,
+ SGM4154X_VRECHARGE,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set recharger error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_get_state(struct sgm4154x_device *sgm,
+ struct sgm4154x_state *state)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_STAT, &reg);
+ if (ret) {
+ dev_err(sgm->dev, "read SGM4154X_CHRG_STAT fail\n");
+ return ret;
+ }
+ state->chrg_type = reg & SGM4154X_VBUS_STAT_MASK;
+ state->chrg_stat = reg & SGM4154X_CHG_STAT_MASK;
+ state->online = !!(reg & SGM4154X_PG_STAT);
+ state->therm_stat = !!(reg & SGM4154X_THERM_STAT);
+ state->vsys_stat = !!(reg & SGM4154X_VSYS_STAT);
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_FAULT, &reg);
+ if (ret) {
+ dev_err(sgm->dev, "read SGM4154X_CHRG_FAULT fail\n");
+ return ret;
+ }
+ state->chrg_fault = reg;
+ state->ntc_fault = reg & SGM4154X_TEMP_MASK;
+ state->health = state->ntc_fault;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_A, &reg);
+ if (ret) {
+ dev_err(sgm->dev, "read SGM4154X_CHRG_CTRL_A fail\n");
+ return ret;
+ }
+ state->vbus_gd = !!(reg & SGM4154X_VBUS_GOOD);
+
+ return ret;
+}
+
+static int sgm4154x_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ case POWER_SUPPLY_PROP_ONLINE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int sgm4154x_charger_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct sgm4154x_device *sgm = power_supply_get_drvdata(psy);
+ int ret = -EINVAL;
+
+ guard(mutex)(&sgm->lock);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (val->intval) {
+ ret = sgm4154x_enable_charger(sgm);
+ sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_40S);
+ } else {
+ sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE);
+ ret = sgm4154x_disable_charger(sgm);
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ ret = sgm4154x_set_input_curr_lim(sgm, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = sgm4154x_set_ichrg_curr(sgm, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = sgm4154x_set_chrg_volt(sgm, val->intval);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int sgm4154x_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sgm4154x_device *sgm = power_supply_get_drvdata(psy);
+ struct sgm4154x_state state;
+ int ret;
+
+ scoped_guard(mutex, &sgm->lock) {
+ ret = sgm4154x_get_state(sgm, &state);
+ if (ret) {
+ dev_err(sgm->dev, "get state error!\n");
+ return ret;
+ }
+ sgm->state = state;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!state.chrg_type || (state.chrg_type == SGM4154X_OTG_MODE))
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (!state.chrg_stat)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (state.chrg_stat == SGM4154X_TERM_CHRG)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ switch (state.chrg_stat) {
+ case SGM4154X_PRECHRG:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case SGM4154X_FAST_CHRG:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case SGM4154X_TERM_CHRG:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case SGM4154X_NOT_CHRGING:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ }
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = SGM4154X_MANUFACTURER;
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = SGM4154X_NAME;
+ break;
+
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = state.online;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = state.vbus_gd;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ val->intval = sgm4154x_get_chrg_volt(sgm);
+ if (val->intval < 0)
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ val->intval = sgm4154x_get_ichrg_curr(sgm);
+ if (val->intval < 0)
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
+ val->intval = sgm->init_data.vlim;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ val->intval = sgm4154x_get_input_curr_lim(sgm);
+ if (val->intval < 0)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static irqreturn_t sgm4154x_irq_handler_thread(int irq, void *private)
+{
+ struct sgm4154x_device *sgm = private;
+ struct sgm4154x_state oldstate, state;
+ int ret;
+
+ guard(mutex)(&sgm->lock);
+
+ oldstate = sgm->state;
+
+ ret = sgm4154x_get_state(sgm, &state);
+ if (ret) {
+ dev_err(sgm->dev, "get state error!\n");
+ return IRQ_NONE;
+ }
+
+ sgm->state = state;
+
+ if (state.vbus_gd && !oldstate.vbus_gd) {
+ if (sgm->init_data.ilim >= SGM4154X_DEFAULT_INPUT_CUR) {
+ ret = sgm4154x_set_input_curr_lim(sgm, sgm->init_data.ilim);
+ if (ret) {
+ dev_err(sgm->dev, "set input current error!\n");
+ /*
+ * Reading IRQ clears interrupt, so return handled
+ * even if error.
+ */
+ }
+ }
+ }
+
+ power_supply_changed(sgm->charger);
+
+ return IRQ_HANDLED;
+}
+
+static int sgm4154x_hw_init(struct power_supply *psy)
+{
+ struct sgm4154x_device *sgm = power_supply_get_drvdata(psy);
+ struct power_supply_battery_info *bat_info;
+ int ret;
+ u32 val;
+
+ /*
+ * If unable to read devicetree info, use default/reset
+ * values from hardware. If the devicetree info has data out
+ * of range for any of the values, use the default value.
+ */
+ sgm->init_data.iprechg = SGM4154X_PRECHRG_I_DEF_UA;
+ sgm->init_data.iterm = SGM4154X_TERMCHRG_I_DEF_UA;
+ sgm->init_data.max_ichg = SGM4154X_ICHRG_I_DEF_UA;
+ sgm->init_data.max_vreg = SGM4154X_VREG_V_DEF_UV;
+ sgm->init_data.vlim = SGM4154X_VINDPM_DEF_UV;
+ sgm->init_data.ilim = SGM4154X_IINDPM_DEF_UA;
+
+ ret = power_supply_get_battery_info(psy, &bat_info);
+ if (ret)
+ dev_warn(sgm->dev, "sgm4154x: cannot read battery info\n");
+ else {
+ if ((bat_info->constant_charge_current_max_ua >= SGM4154X_ICHRG_I_MIN_UA) &&
+ (bat_info->constant_charge_current_max_ua <= SGM4154X_ICHRG_I_MAX_UA))
+ sgm->init_data.max_ichg = bat_info->constant_charge_current_max_ua;
+ if ((bat_info->constant_charge_voltage_max_uv >= SGM4154X_VREG_V_MIN_UV) &&
+ (bat_info->constant_charge_voltage_max_uv <= SGM4154X_VREG_V_MAX_UV))
+ sgm->init_data.max_vreg = bat_info->constant_charge_voltage_max_uv;
+ if ((bat_info->charge_term_current_ua >= SGM4154X_TERMCHRG_I_MIN_UA) &&
+ (bat_info->charge_term_current_ua <= SGM4154X_TERMCHRG_I_MAX_UA))
+ sgm->init_data.iterm = bat_info->charge_term_current_ua;
+ if ((bat_info->precharge_current_ua >= SGM4154X_PRECHRG_I_MIN_UA) &&
+ (bat_info->precharge_current_ua <= SGM4154X_PRECHRG_I_MAX_UA))
+ sgm->init_data.iprechg = bat_info->precharge_current_ua;
+
+ power_supply_put_battery_info(psy, bat_info);
+ }
+
+ ret = device_property_read_u32(sgm->dev,
+ "input-voltage-limit-microvolt",
+ &val);
+ if (!ret)
+ sgm->init_data.vlim = clamp(val, SGM4154X_VINDPM_V_MIN_UV,
+ SGM4154X_VINDPM_V_MAX_UV);
+
+ ret = device_property_read_u32(sgm->dev,
+ "input-current-limit-microamp",
+ &val);
+ if (!ret)
+ sgm->init_data.ilim = clamp(val, SGM4154X_IINDPM_I_MIN_UA,
+ SGM4154X_IINDPM_I_MAX_UA);
+
+ ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_prechrg_curr(sgm, sgm->init_data.iprechg);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_chrg_volt(sgm, sgm->init_data.max_vreg);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_term_curr(sgm, sgm->init_data.iterm);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_ichrg_curr(sgm, sgm->init_data.max_ichg);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_input_volt_lim(sgm, sgm->init_data.vlim);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_input_curr_lim(sgm, sgm->init_data.ilim);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_vac_ovp(sgm);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_D,
+ SGM4154X_JEITA_EN,
+ 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_A,
+ SGM4154X_IINDPM_INT_MASK,
+ SGM4154X_IINDPM_INT_MASK);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_A,
+ SGM4154X_VINDPM_INT_MASK,
+ SGM4154X_VINDPM_INT_MASK);
+ if (ret)
+ return ret;
+
+ /*
+ * Recharge microvolt set to 200000 by BSP driver instead of hardware
+ * default value of 100000.
+ */
+ ret = sgm4154x_set_recharge_volt_ua(sgm, 200000);
+
+ return ret;
+}
+
+static enum power_supply_property sgm4154x_power_supply_props[] = {
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_PRESENT
+};
+
+static struct power_supply_desc sgm4154x_power_supply_desc = {
+ .name = "sgm4154x-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .init = sgm4154x_hw_init,
+ .properties = sgm4154x_power_supply_props,
+ .num_properties = ARRAY_SIZE(sgm4154x_power_supply_props),
+ .get_property = sgm4154x_charger_get_property,
+ .set_property = sgm4154x_charger_set_property,
+ .property_is_writeable = sgm4154x_property_is_writeable,
+};
+
+static const struct regmap_config sgm4154x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = SGM4154X_CHRG_CTRL_F,
+ .cache_type = REGCACHE_NONE,
+};
+
+static const u32 sgm4154x_chg_otg_cur_ua[] = {
+ 1200000, 2000000,
+};
+
+static const struct regulator_ops sgm4154x_vbus_ops = {
+ .list_voltage = regulator_list_voltage_linear,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_current_limit = regulator_set_current_limit_regmap,
+ .get_current_limit = regulator_get_current_limit_regmap,
+};
+
+static const struct regulator_desc sgm4154x_otg_rdesc = {
+ .of_match = "otg-vbus",
+ .name = "otg-vbus",
+ .regulators_node = of_match_ptr("regulators"),
+ .ops = &sgm4154x_vbus_ops,
+ .owner = THIS_MODULE,
+ .type = REGULATOR_VOLTAGE,
+ .min_uV = 4850000,
+ .uV_step = 150000,
+ .n_voltages = 4,
+ .vsel_reg = SGM4154X_CHRG_CTRL_6,
+ .vsel_mask = SGM4154X_BOOSTV,
+ .enable_reg = SGM4154X_CHRG_CTRL_1,
+ .enable_mask = SGM4154X_OTG_EN,
+ .curr_table = sgm4154x_chg_otg_cur_ua,
+ .n_current_limits = ARRAY_SIZE(sgm4154x_chg_otg_cur_ua),
+ .csel_reg = SGM4154X_CHRG_CTRL_2,
+ .csel_mask = SGM4154X_BOOST_LIM,
+};
+
+static int sgm4154x_vbus_regulator_register(struct sgm4154x_device *sgm)
+{
+ struct regulator_config config = {
+ .dev = sgm->dev,
+ .regmap = sgm->regmap,
+ .driver_data = sgm,
+ };
+
+ sgm->otg_rdev = devm_regulator_register(sgm->dev,
+ &sgm4154x_otg_rdesc,
+ &config);
+
+ return PTR_ERR_OR_ZERO(sgm->otg_rdev);
+}
+
+static int sgm4154x_hw_chipid_detect(struct sgm4154x_device *sgm)
+{
+ int ret;
+ int val;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_B, &val);
+ if (ret)
+ return ret;
+
+ if ((val & SGM4154X_PN_MASK) != SGM4154X_PN_ID)
+ dev_warn(sgm->dev, "sgm4154x device ID mismatch\n");
+
+ return 0;
+}
+
+static void sgm_charger_work(struct work_struct *work)
+{
+ struct sgm4154x_device *sgm =
+ container_of(work,
+ struct sgm4154x_device,
+ sgm_delay_work.work);
+
+ sgm4154x_watchdog_timer_reset(sgm);
+ if (sgm->watchdog_enable)
+ queue_delayed_work(sgm->sgm_monitor_wq,
+ &sgm->sgm_delay_work,
+ msecs_to_jiffies(1000 * 5));
+}
+
+static void sgm4154x_disable_irq_wake(void *data)
+{
+ struct sgm4154x_device *sgm = data;
+ struct i2c_client *client = to_i2c_client(sgm->dev);
+
+ disable_irq_wake(client->irq);
+}
+
+static int sgm4154x_probe(struct i2c_client *client)
+{
+ struct power_supply_config psy_cfg = {};
+ struct device *dev = &client->dev;
+ struct sgm4154x_device *sgm;
+ int ret;
+
+ sgm = devm_kzalloc(dev, sizeof(*sgm), GFP_KERNEL);
+ if (!sgm)
+ return -ENOMEM;
+
+ sgm->dev = dev;
+
+ sgm->regmap = devm_regmap_init_i2c(client, &sgm4154x_regmap_config);
+ if (IS_ERR(sgm->regmap))
+ return dev_err_probe(dev, PTR_ERR(sgm->regmap),
+ "Failed to allocate register map\n");
+
+ i2c_set_clientdata(client, sgm);
+
+ ret = devm_mutex_init(dev, &sgm->lock);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_hw_chipid_detect(sgm);
+ if (ret)
+ return dev_err_probe(dev, ret, "Unable to read HW ID\n");
+
+ devm_device_init_wakeup(dev);
+
+ sgm->sgm_monitor_wq = devm_alloc_ordered_workqueue(dev, "sgm-monitor-wq",
+ WQ_MEM_RECLAIM | WQ_FREEZABLE);
+ if (!sgm->sgm_monitor_wq)
+ return -EINVAL;
+
+ ret = devm_delayed_work_autocancel(dev, &sgm->sgm_delay_work,
+ sgm_charger_work);
+ if (ret)
+ return dev_err_probe(dev, ret, "Unable to register delayed work\n");
+
+ psy_cfg.drv_data = sgm;
+ psy_cfg.fwnode = dev_fwnode(dev);
+
+ sgm->charger = devm_power_supply_register(sgm->dev,
+ &sgm4154x_power_supply_desc,
+ &psy_cfg);
+ if (IS_ERR(sgm->charger))
+ return dev_err_probe(dev, PTR_ERR(sgm->charger),
+ "Failed to register power supply\n");
+
+ if (client->irq) {
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ sgm4154x_irq_handler_thread,
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "sgm41542-irq", sgm);
+ if (ret)
+ return ret;
+
+ ret = enable_irq_wake(client->irq);
+ if (!ret) {
+ ret = devm_add_action_or_reset(dev, sgm4154x_disable_irq_wake, sgm);
+ if (ret)
+ return ret;
+ }
+ }
+
+ ret = sgm4154x_vbus_regulator_register(sgm);
+ if (ret) {
+ return dev_err_probe(dev, ret,
+ "Unable to register VBUS regulator\n");
+ }
+
+ return 0;
+}
+
+static int __maybe_unused sgm4154x_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct sgm4154x_device *sgm = i2c_get_clientdata(client);
+ bool watchdog = sgm->watchdog_enable;
+ int ret;
+
+ /*
+ * Disable watchdog during suspend and stop delayed work. When
+ * delayed work is restarted after resume watchdog will be
+ * re-enabled if it was previously enabled. Retain the current
+ * state of the watchdog timer though, so it can be re-enabled
+ * if necessary by the work queue once resume is triggered.
+ */
+ ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE);
+ if (ret)
+ return ret;
+ cancel_delayed_work_sync(&sgm->sgm_delay_work);
+ sgm->watchdog_enable = watchdog;
+ return 0;
+}
+
+static int __maybe_unused sgm4154x_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct sgm4154x_device *sgm = i2c_get_clientdata(client);
+ int ret;
+
+ if (sgm->watchdog_enable) {
+ ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_40S);
+ if (ret)
+ return ret;
+ }
+
+ queue_delayed_work(sgm->sgm_monitor_wq,
+ &sgm->sgm_delay_work, 0);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(sgm4154x_pm_ops, sgm4154x_suspend, sgm4154x_resume);
+
+static const struct i2c_device_id sgm4154x_i2c_ids[] = {
+ { "sgm41542" },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, sgm4154x_i2c_ids);
+
+static const struct of_device_id sgm4154x_of_match[] = {
+ { .compatible = "sgmicro,sgm41542", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sgm4154x_of_match);
+
+static struct i2c_driver sgm4154x_driver = {
+ .driver = {
+ .name = "sgm4154x-charger",
+ .of_match_table = sgm4154x_of_match,
+ .pm = &sgm4154x_pm_ops,
+ },
+ .probe = sgm4154x_probe,
+ .id_table = sgm4154x_i2c_ids,
+};
+
+module_i2c_driver(sgm4154x_driver);
+
+MODULE_AUTHOR("Xu Shengfei <xsf@rock-chips.com>");
+MODULE_DESCRIPTION("sgm4154x charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/surface-rt-ec.c b/drivers/power/supply/surface-rt-ec.c
index a728ae0db858..da57b0a1a495 100644
--- a/drivers/power/supply/surface-rt-ec.c
+++ b/drivers/power/supply/surface-rt-ec.c
@@ -337,7 +337,7 @@ static int srt_ec_probe(struct i2c_client *client)
ret = devm_request_threaded_irq(dev, client->irq, NULL, srt_psy_detect_irq,
IRQF_ONESHOT, client->name, srt);
if (ret < 0)
- return dev_err_probe(dev, ret, "failed to request interrupt\n");
+ return ret;
ret = devm_delayed_work_autocancel(dev, &srt->poll_work, srt_bat_poll_work);
if (ret < 0)
diff --git a/drivers/power/supply/tps65090-charger.c b/drivers/power/supply/tps65090-charger.c
index d010f013af8c..36340c56d272 100644
--- a/drivers/power/supply/tps65090-charger.c
+++ b/drivers/power/supply/tps65090-charger.c
@@ -302,12 +302,8 @@ static int tps65090_charger_probe(struct platform_device *pdev)
if (irq != -ENXIO) {
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
tps65090_charger_isr, IRQF_ONESHOT, "tps65090-charger", cdata);
- if (ret) {
- dev_err(cdata->dev,
- "Unable to register irq %d err %d\n", irq,
- ret);
+ if (ret)
return ret;
- }
} else {
cdata->poll_task = kthread_run(tps65090_charger_poll_task,
cdata, "ktps65090charger");
diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c
index 6af17ce0b204..cd00a3f372b3 100644
--- a/drivers/power/supply/tps65217_charger.c
+++ b/drivers/power/supply/tps65217_charger.c
@@ -239,12 +239,8 @@ static int tps65217_charger_probe(struct platform_device *pdev)
tps65217_charger_irq,
IRQF_SHARED, "tps65217-charger",
charger);
- if (ret) {
- dev_err(charger->dev,
- "Unable to register irq %d err %d\n", irq[i],
- ret);
+ if (ret)
return ret;
- }
/* Check current state */
tps65217_charger_irq(-1, charger);
diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c
index 04216b2bfb6c..c46b22ab8d99 100644
--- a/drivers/power/supply/twl4030_charger.c
+++ b/drivers/power/supply/twl4030_charger.c
@@ -14,6 +14,7 @@
#include <linux/err.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/devm-helpers.h>
#include <linux/interrupt.h>
#include <linux/mfd/twl.h>
#include <linux/power_supply.h>
@@ -1002,8 +1003,15 @@ static int twl4030_bci_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, bci);
- INIT_WORK(&bci->work, twl4030_bci_usb_work);
- INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker);
+ ret = devm_delayed_work_autocancel(&pdev->dev, &bci->current_worker,
+ twl4030_current_worker);
+ if (ret)
+ return ret;
+
+ ret = devm_work_autocancel(&pdev->dev, &bci->work,
+ twl4030_bci_usb_work);
+ if (ret)
+ return ret;
bci->channel_vac = devm_iio_channel_get(&pdev->dev, "vac");
if (IS_ERR(bci->channel_vac)) {
@@ -1055,19 +1063,13 @@ static int twl4030_bci_probe(struct platform_device *pdev)
ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL,
twl4030_charger_interrupt, IRQF_ONESHOT, pdev->name,
bci);
- if (ret < 0) {
- dev_err(&pdev->dev, "could not request irq %d, status %d\n",
- bci->irq_chg, ret);
+ if (ret < 0)
return ret;
- }
ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL,
twl4030_bci_interrupt, IRQF_ONESHOT, pdev->name, bci);
- if (ret < 0) {
- dev_err(&pdev->dev, "could not request irq %d, status %d\n",
- bci->irq_bci, ret);
+ if (ret < 0)
return ret;
- }
/* Enable interrupts now. */
reg = ~(u32)(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 |
diff --git a/drivers/power/supply/twl6030_charger.c b/drivers/power/supply/twl6030_charger.c
index b4ec26ff257c..020512d0e306 100644
--- a/drivers/power/supply/twl6030_charger.c
+++ b/drivers/power/supply/twl6030_charger.c
@@ -542,9 +542,7 @@ static int twl6030_charger_probe(struct platform_device *pdev)
IRQF_ONESHOT, pdev->name,
charger);
if (ret < 0)
- return dev_err_probe(&pdev->dev, ret,
- "could not request irq %d\n",
- charger->irq_chg);
+ return ret;
/* turing to charging to configure things */
twl6030_charger_write(CONTROLLER_CTRL1, 0);
diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c
index 3f44cc902f84..bf13e958982e 100644
--- a/drivers/power/supply/ucs1002_power.c
+++ b/drivers/power/supply/ucs1002_power.c
@@ -11,6 +11,7 @@
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/device.h>
+#include <linux/devm-helpers.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
@@ -640,28 +641,25 @@ static int ucs1002_probe(struct i2c_client *client)
}
info->health = POWER_SUPPLY_HEALTH_GOOD;
- INIT_DELAYED_WORK(&info->health_poll, ucs1002_health_poll);
+ ret = devm_delayed_work_autocancel(dev, &info->health_poll,
+ ucs1002_health_poll);
+ if (ret)
+ return ret;
if (irq_a_det > 0) {
ret = devm_request_threaded_irq(dev, irq_a_det, NULL,
ucs1002_charger_irq,
IRQF_ONESHOT,
"ucs1002-a_det", info);
- if (ret) {
- dev_err(dev, "Failed to request A_DET threaded irq: %d\n",
- ret);
+ if (ret)
return ret;
- }
}
if (irq_alert > 0) {
ret = devm_request_irq(dev, irq_alert, ucs1002_alert_irq,
0,"ucs1002-alert", info);
- if (ret) {
- dev_err(dev, "Failed to request ALERT threaded irq: %d\n",
- ret);
+ if (ret)
return ret;
- }
}
return 0;