Linux Power Management development
 help / color / mirror / Atom feed
* [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging
@ 2026-09-16  6:49 Linus Walleij
  2026-09-16  6:49 ` [PATCH 01/15] power: supply: ab8500_fg: Accept status from supplied power Linus Walleij
                   ` (16 more replies)
  0 siblings, 17 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij, stable

This series first fix two serious bugs in the AB8500/AB8505 charging
code making the charging report bad figures. (It only affects the
human-visible status, the charging itself is safe.) These two patches
are tagged for stable.

The series then audits the AB8500 and AB8505 battery-charging stack
against the revision 3 hardware manuals.

The audit found incorrect register constants and several independent
logic bugs in the common code. It also found that AB8505 needs dedicated
handling for its five-bit USB link-status table, OTP-programmed VBUS
overvoltage limit, longer VBUS debounce, reserved autopower field, and
lack of an integrated main charger. The final binding and DTS patches
remove the five nonexistent main-charger interrupts from AB8505.

Some further fixes come from auditing the Samsung product trees.

After these long due fixes the AB8500/AB8505 charging is smooth sailing.

Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
Linus Walleij (15):
      power: supply: ab8500_fg: Accept status from supplied power
      power: supply: ab8500_fg: Report sub-percent charge changes
      power: supply: ab8500: Correct register definitions
      power: supply: ab8500_fg: Drop nonexistent AB8505 controls
      power: supply: ab8500_btemp: Fix event temperature reporting
      power: supply: ab8500: Preserve battery termination current
      power: supply: ab8500_charger: Fix AC charger re-enable
      power: supply: ab8500_charger: Decode AB8505 USB status
      power: supply: ab8500_charger: Respect AB8505 register layout
      power: supply: ab8500_charger: Handle detection errors
      power: supply: ab8500_charger: Skip absent AB8505 main charger
      power: supply: ab8500_charger: Retry AB8505 USB detection
      power: supply: ab8500_charger: Recover inactive AB8505 charger
      power: supply: ab8500_fg: Finalize current measurements
      power: supply: ab8500_fg: Avoid reserved AB8505 CCConf bit

 drivers/power/supply/ab8500-bm.h       |   8 +-
 drivers/power/supply/ab8500_bmdata.c   |   3 +-
 drivers/power/supply/ab8500_btemp.c    |  30 +--
 drivers/power/supply/ab8500_chargalg.c |   9 +-
 drivers/power/supply/ab8500_charger.c  | 404 ++++++++++++++++++++++++++++-----
 drivers/power/supply/ab8500_fg.c       | 179 +++++----------
 6 files changed, 421 insertions(+), 212 deletions(-)
---
base-commit: 803733e3585d6fd2d43c452c502173fa153b683d
change-id: 20260915-ab8500-charging-c02f3519a3ab

Best regards,
-- 
Linus Walleij <linusw@kernel.org>


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 01/15] power: supply: ab8500_fg: Accept status from supplied power
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 02/15] power: supply: ab8500_fg: Report sub-percent charge changes Linus Walleij
                   ` (15 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij, stable

Commit dc77721ea4aa ("power: supply: ab8500: Set typing and props")
changed the charging algorithm power supply type from battery to unknown
so userspace would not mistake it for a second battery. The fuel gauge,
however, processes status changes only from battery-type supplies. It
therefore never learns that charging started and keeps running its
discharge state machine.

The power supply relationship already limits the callback to supplies
naming the fuel gauge in supplied_to. Accept status from any such supply
while retaining the battery type checks for battery-specific properties.

The Samsung-GT-I8160-Codina and Samsung-GT-I8160_HD-Codina product trees
keep the charging algorithm typed as a battery. The
Samsung-GT-I8530-Gavini, Samsung-GT-S7710-Skomer,
Samsung-SGH-I407-Kyle and Samsung-SGH-T599-Codina-TMO alternative
fuel-gauge drivers instead pass cable state directly. Both approaches
preserve the charging state handoff that was lost in mainline.

Fixes: dc77721ea4aa ("power: supply: ab8500: Set typing and props")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_fg.c | 60 ++++++++++++++++++----------------------
 1 file changed, 27 insertions(+), 33 deletions(-)

diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index e6b5efdf5dc9..016ed35e90f9 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -2205,42 +2205,36 @@ static int ab8500_fg_get_ext_psy_data(struct power_supply *ext, void *data)
 
 		switch (prop) {
 		case POWER_SUPPLY_PROP_STATUS:
-			switch (ext->desc->type) {
-			case POWER_SUPPLY_TYPE_BATTERY:
-				switch (ret.intval) {
-				case POWER_SUPPLY_STATUS_UNKNOWN:
-				case POWER_SUPPLY_STATUS_DISCHARGING:
-				case POWER_SUPPLY_STATUS_NOT_CHARGING:
-					if (!di->flags.charging)
-						break;
-					di->flags.charging = false;
-					di->flags.fully_charged = false;
-					if (di->bm->capacity_scaling)
-						ab8500_fg_update_cap_scalers(di);
-					queue_work(di->fg_wq, &di->fg_work);
+			switch (ret.intval) {
+			case POWER_SUPPLY_STATUS_UNKNOWN:
+			case POWER_SUPPLY_STATUS_DISCHARGING:
+			case POWER_SUPPLY_STATUS_NOT_CHARGING:
+				if (!di->flags.charging)
 					break;
-				case POWER_SUPPLY_STATUS_FULL:
-					if (di->flags.fully_charged)
-						break;
-					di->flags.fully_charged = true;
-					di->flags.force_full = true;
-					/* Save current capacity as maximum */
-					di->bat_cap.max_mah = di->bat_cap.mah;
-					queue_work(di->fg_wq, &di->fg_work);
-					break;
-				case POWER_SUPPLY_STATUS_CHARGING:
-					if (di->flags.charging &&
-						!di->flags.fully_charged)
-						break;
-					di->flags.charging = true;
-					di->flags.fully_charged = false;
-					if (di->bm->capacity_scaling)
-						ab8500_fg_update_cap_scalers(di);
-					queue_work(di->fg_wq, &di->fg_work);
+				di->flags.charging = false;
+				di->flags.fully_charged = false;
+				if (di->bm->capacity_scaling)
+					ab8500_fg_update_cap_scalers(di);
+				queue_work(di->fg_wq, &di->fg_work);
+				break;
+			case POWER_SUPPLY_STATUS_FULL:
+				if (di->flags.fully_charged)
 					break;
-				}
+				di->flags.fully_charged = true;
+				di->flags.force_full = true;
+				/* Save current capacity as maximum */
+				di->bat_cap.max_mah = di->bat_cap.mah;
+				queue_work(di->fg_wq, &di->fg_work);
 				break;
-			default:
+			case POWER_SUPPLY_STATUS_CHARGING:
+				if (di->flags.charging &&
+				    !di->flags.fully_charged)
+					break;
+				di->flags.charging = true;
+				di->flags.fully_charged = false;
+				if (di->bm->capacity_scaling)
+					ab8500_fg_update_cap_scalers(di);
+				queue_work(di->fg_wq, &di->fg_work);
 				break;
 			}
 			break;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 02/15] power: supply: ab8500_fg: Report sub-percent charge changes
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
  2026-09-16  6:49 ` [PATCH 01/15] power: supply: ab8500_fg: Accept status from supplied power Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 03/15] power: supply: ab8500: Correct register definitions Linus Walleij
                   ` (14 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij, stable

The fuel gauge updates bat_cap.mah from the coulomb counter but exposes
bat_cap.prev_mah through CHARGE_NOW and ENERGY_NOW. It only refreshes
prev_mah when the rounded integer capacity percentage changes, making the
momentary properties appear frozen between percentage steps.

Refresh prev_mah and notify userspace whenever the new charge remains
within the currently accepted percentage. If a discharge estimate tries
to cross upward into a higher percentage, the existing rejection still
leaves both the reported percentage and charge unchanged.

The AB8500 fuel-gauge implementations in the Samsung-GT-I8160-Codina,
Samsung-GT-I8160_HD-Codina, Samsung-GT-I8530-Gavini,
Samsung-GT-S7710-Skomer, Samsung-SGH-I407-Kyle and
Samsung-SGH-T599-Codina-TMO product trees retain the same percentage-gated
prev_mah update and contain no finer-grained reporting workaround.

Fixes: 13151631b5bd ("ab8500-fg: A8500 fuel gauge driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_fg.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index 016ed35e90f9..c5eaa32c4a2d 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -1387,6 +1387,13 @@ static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init)
 		}
 	}
 
+	/* Keep the charge properties current between percentage changes. */
+	if (di->bat_cap.prev_percent == percent &&
+	    di->bat_cap.prev_mah != di->bat_cap.mah) {
+		di->bat_cap.prev_mah = di->bat_cap.mah;
+		changed = true;
+	}
+
 	if (changed) {
 		if (di->bm->capacity_scaling) {
 			di->bat_cap.cap_scale.scaled_cap =

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 03/15] power: supply: ab8500: Correct register definitions
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
  2026-09-16  6:49 ` [PATCH 01/15] power: supply: ab8500_fg: Accept status from supplied power Linus Walleij
  2026-09-16  6:49 ` [PATCH 02/15] power: supply: ab8500_fg: Report sub-percent charge changes Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 04/15] power: supply: ab8500_fg: Drop nonexistent AB8505 controls Linus Walleij
                   ` (13 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

The charger LED PWM register encodes its duty cycle as
(N + 1) / 256. The value for 252/256 is therefore 0xfb, not 0xbf.

Both output-current codes 0xe and 0xf select 1.5 A, while 0x3f
cannot fit in the four-bit field. Also correct the AB8505 BatCtrl
bit 0 current source from 18 uA to 8 uA.

Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500-bm.h      | 5 ++---
 drivers/power/supply/ab8500_btemp.c   | 2 +-
 drivers/power/supply/ab8500_charger.c | 4 ++--
 3 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h
index 180a016b3662..b73fccdc76d2 100644
--- a/drivers/power/supply/ab8500-bm.h
+++ b/drivers/power/supply/ab8500-bm.h
@@ -151,8 +151,7 @@
 #define CH_OP_CUR_LVL_0P9		0x08
 #define CH_OP_CUR_LVL_1P4		0x0D
 #define CH_OP_CUR_LVL_1P5		0x0E
-#define CH_OP_CUR_LVL_1P6		0x0F
-#define CH_OP_CUR_LVL_2P		0x3F
+#define CH_OP_CUR_LVL_1P5_MAX		0x0F
 
 /* BTEMP High thermal limits */
 #define BTEMP_HIGH_TH_57_0		0x00
@@ -233,7 +232,7 @@ enum bup_vch_sel {
 /* BatCtrl Current Source Constants */
 #define BAT_CTRL_7U_ENA			0x01
 #define BAT_CTRL_20U_ENA		0x02
-#define BAT_CTRL_18U_ENA		0x01
+#define BAT_CTRL_8U_ENA			0x01
 #define BAT_CTRL_16U_ENA		0x02
 #define BAT_CTRL_CMP_ENA		0x04
 #define FORCE_BAT_CTRL_CMP_HIGH		0x08
diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c
index 90a61d9db72c..d5c955176dd0 100644
--- a/drivers/power/supply/ab8500_btemp.c
+++ b/drivers/power/supply/ab8500_btemp.c
@@ -42,7 +42,7 @@
 #define BTEMP_BATCTRL_CURR_SRC_20UA	20
 
 #define BTEMP_BATCTRL_CURR_SRC_16UA	16
-#define BTEMP_BATCTRL_CURR_SRC_18UA	18
+#define BTEMP_BATCTRL_CURR_SRC_8UA	8
 
 #define BTEMP_BATCTRL_CURR_SRC_60UA	60
 #define BTEMP_BATCTRL_CURR_SRC_120UA	120
diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index ff38254d06ef..7e6afcf0ac7d 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -66,7 +66,7 @@
 #define LED_INDICATOR_PWM_ENA		0x01
 #define LED_INDICATOR_PWM_DIS		0x00
 #define LED_IND_CUR_5MA			0x04
-#define LED_INDICATOR_PWM_DUTY_252_256	0xBF
+#define LED_INDICATOR_PWM_DUTY_252_256	0xFB
 
 /* HW failure constants */
 #define MAIN_CH_TH_PROT			0x02
@@ -3063,7 +3063,7 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
 
 		ret = abx500_set_register_interruptible(di->dev,
 			AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG,
-			CH_OP_CUR_LVL_1P6);
+			CH_OP_CUR_LVL_1P5_MAX);
 		if (ret) {
 			dev_err(di->dev,
 				"failed to set CH_OPT_CRNTLVL_MAX_REG\n");

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 04/15] power: supply: ab8500_fg: Drop nonexistent AB8505 controls
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (2 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 03/15] power: supply: ab8500: Correct register definitions Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 05/15] power: supply: ab8500_btemp: Fix event temperature reporting Linus Walleij
                   ` (12 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

The AB8505 RTC register map has no register at offset 0x15, but
the fuel-gauge driver initializes and exposes that offset as a
power-cut flag-time control. PcutCtlAndStatus bit 4, exposed as
powercut_flag, is reserved as well.

Remove both nonexistent controls and their internal configuration
field so the driver no longer accesses reserved hardware locations.

Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500-bm.h     |  3 --
 drivers/power/supply/ab8500_bmdata.c |  1 -
 drivers/power/supply/ab8500_fg.c     | 85 ------------------------------------
 3 files changed, 89 deletions(-)

diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h
index b73fccdc76d2..209744c77c3d 100644
--- a/drivers/power/supply/ab8500-bm.h
+++ b/drivers/power/supply/ab8500-bm.h
@@ -245,7 +245,6 @@ enum bup_vch_sel {
 #define AB8505_RTC_PCUT_CTL_STATUS_REG	0x12
 #define AB8505_RTC_PCUT_TIME_REG	0x13
 #define AB8505_RTC_PCUT_MAX_TIME_REG	0x14
-#define AB8505_RTC_PCUT_FLAG_TIME_REG	0x15
 #define AB8505_RTC_PCUT_RESTART_REG	0x16
 #define AB8505_RTC_PCUT_DEBOUNCE_REG	0x17
 
@@ -286,7 +285,6 @@ struct ab8500_fg;
  *				battery full while in maintenance, in per cent
  * @pcut_enable:			Enable power cut feature in ab8505
  * @pcut_max_time:		Max time threshold
- * @pcut_flag_time:		Flagtime threshold
  * @pcut_max_restart:		Max number of restarts
  * @pcut_debounce_time:		Sets battery debounce time
  */
@@ -307,7 +305,6 @@ struct ab8500_fg_parameters {
 	int maint_thres;
 	bool pcut_enable;
 	u8 pcut_max_time;
-	u8 pcut_flag_time;
 	u8 pcut_max_restart;
 	u8 pcut_debounce_time;
 };
diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c
index 19ed52852804..d7fd2d313ff1 100644
--- a/drivers/power/supply/ab8500_bmdata.c
+++ b/drivers/power/supply/ab8500_bmdata.c
@@ -98,7 +98,6 @@ static const struct ab8500_fg_parameters fg = {
 	.user_cap_limit = 15,
 	.pcut_enable = 1,
 	.pcut_max_time = 127,
-	.pcut_flag_time = 112,
 	.pcut_max_restart = 15,
 	.pcut_debounce_time = 2,
 };
diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index c5eaa32c4a2d..70e29c9e8108 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -2355,14 +2355,6 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
 			goto out;
 		}
 
-		ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
-			AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time);
-
-		if (ret) {
-			dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__);
-			goto out;
-		}
-
 		ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
 			AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart);
 
@@ -2569,56 +2561,6 @@ static int ab8500_fg_sysfs_init(struct ab8500_fg *di)
 	return ret;
 }
 
-static ssize_t ab8505_powercut_flagtime_read(struct device *dev,
-			     struct device_attribute *attr,
-			     char *buf)
-{
-	int ret;
-	u8 reg_value;
-	struct power_supply *psy = dev_to_psy(dev);
-	struct ab8500_fg *di = power_supply_get_drvdata(psy);
-
-	ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
-		AB8505_RTC_PCUT_FLAG_TIME_REG, &reg_value);
-
-	if (ret < 0) {
-		dev_err(dev, "Failed to read AB8505_RTC_PCUT_FLAG_TIME_REG\n");
-		goto fail;
-	}
-
-	return sysfs_emit(buf, "%d\n", (reg_value & 0x7F));
-
-fail:
-	return ret;
-}
-
-static ssize_t ab8505_powercut_flagtime_write(struct device *dev,
-				  struct device_attribute *attr,
-				  const char *buf, size_t count)
-{
-	int ret;
-	int reg_value;
-	struct power_supply *psy = dev_to_psy(dev);
-	struct ab8500_fg *di = power_supply_get_drvdata(psy);
-
-	if (kstrtoint(buf, 10, &reg_value))
-		goto fail;
-
-	if (reg_value > 0x7F) {
-		dev_err(dev, "Incorrect parameter, echo 0 (1.98s) - 127 (15.625ms) for flagtime\n");
-		goto fail;
-	}
-
-	ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
-		AB8505_RTC_PCUT_FLAG_TIME_REG, (u8)reg_value);
-
-	if (ret < 0)
-		dev_err(dev, "Failed to set AB8505_RTC_PCUT_FLAG_TIME_REG\n");
-
-fail:
-	return count;
-}
-
 static ssize_t ab8505_powercut_maxtime_read(struct device *dev,
 			     struct device_attribute *attr,
 			     char *buf)
@@ -2815,30 +2757,6 @@ static ssize_t ab8505_powercut_write(struct device *dev,
 	return count;
 }
 
-static ssize_t ab8505_powercut_flag_read(struct device *dev,
-					 struct device_attribute *attr,
-					 char *buf)
-{
-
-	int ret;
-	u8 reg_value;
-	struct power_supply *psy = dev_to_psy(dev);
-	struct ab8500_fg *di = power_supply_get_drvdata(psy);
-
-	ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
-						AB8505_RTC_PCUT_CTL_STATUS_REG,  &reg_value);
-
-	if (ret < 0) {
-		dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n");
-		goto fail;
-	}
-
-	return sysfs_emit(buf, "%d\n", ((reg_value & 0x10) >> 4));
-
-fail:
-	return ret;
-}
-
 static ssize_t ab8505_powercut_debounce_read(struct device *dev,
 					     struct device_attribute *attr,
 					     char *buf)
@@ -2913,8 +2831,6 @@ static ssize_t ab8505_powercut_enable_status_read(struct device *dev,
 }
 
 static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = {
-	__ATTR(powercut_flagtime, (S_IRUGO | S_IWUSR | S_IWGRP),
-		ab8505_powercut_flagtime_read, ab8505_powercut_flagtime_write),
 	__ATTR(powercut_maxtime, (S_IRUGO | S_IWUSR | S_IWGRP),
 		ab8505_powercut_maxtime_read, ab8505_powercut_maxtime_write),
 	__ATTR(powercut_restart_max, (S_IRUGO | S_IWUSR | S_IWGRP),
@@ -2924,7 +2840,6 @@ static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = {
 		ab8505_powercut_restart_counter_read, NULL),
 	__ATTR(powercut_enable, (S_IRUGO | S_IWUSR | S_IWGRP),
 		ab8505_powercut_read, ab8505_powercut_write),
-	__ATTR(powercut_flag, S_IRUGO, ab8505_powercut_flag_read, NULL),
 	__ATTR(powercut_debounce_time, (S_IRUGO | S_IWUSR | S_IWGRP),
 		ab8505_powercut_debounce_read, ab8505_powercut_debounce_write),
 	__ATTR(powercut_enable_status, S_IRUGO,

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 05/15] power: supply: ab8500_btemp: Fix event temperature reporting
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (3 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 04/15] power: supply: ab8500_fg: Drop nonexistent AB8505 controls Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 06/15] power: supply: ab8500: Preserve battery termination current Linus Walleij
                   ` (11 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

The event-aware path initializes its local temperature to zero and
compares that zero against the thermal thresholds. Consequently some
events report the measured temperature after it crosses the event
threshold, while others always report the threshold.

Start with the measured temperature, clamp it according to the active
event, and convert to tenths of a degree only once. This path is used
by AB8505 and later variants.

Fixes: 1f855824757e ("ab8500-btemp: AB8500 battery temperature driver")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_btemp.c | 28 +++++++++-------------------
 1 file changed, 9 insertions(+), 19 deletions(-)

diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c
index d5c955176dd0..d8845fd2f084 100644
--- a/drivers/power/supply/ab8500_btemp.c
+++ b/drivers/power/supply/ab8500_btemp.c
@@ -468,39 +468,29 @@ static void ab8500_btemp_periodic(struct ab8500_btemp *di,
  */
 static int ab8500_btemp_get_temp(struct ab8500_btemp *di)
 {
-	int temp = 0;
+	int temp = di->bat_temp;
 
 	/*
 	 * The BTEMP events are not reliabe on AB8500 cut3.3
 	 * and prior versions
 	 */
-	if (is_ab8500_3p3_or_earlier(di->parent)) {
-		temp = di->bat_temp * 10;
-	} else {
+	if (!is_ab8500_3p3_or_earlier(di->parent)) {
 		if (di->events.btemp_low) {
 			if (temp > di->btemp_ranges.btemp_low_limit)
-				temp = di->btemp_ranges.btemp_low_limit * 10;
-			else
-				temp = di->bat_temp * 10;
+				temp = di->btemp_ranges.btemp_low_limit;
 		} else if (di->events.btemp_high) {
 			if (temp < di->btemp_ranges.btemp_high_limit)
-				temp = di->btemp_ranges.btemp_high_limit * 10;
-			else
-				temp = di->bat_temp * 10;
+				temp = di->btemp_ranges.btemp_high_limit;
 		} else if (di->events.btemp_lowmed) {
 			if (temp > di->btemp_ranges.btemp_med_limit)
-				temp = di->btemp_ranges.btemp_med_limit * 10;
-			else
-				temp = di->bat_temp * 10;
+				temp = di->btemp_ranges.btemp_med_limit;
 		} else if (di->events.btemp_medhigh) {
 			if (temp < di->btemp_ranges.btemp_med_limit)
-				temp = di->btemp_ranges.btemp_med_limit * 10;
-			else
-				temp = di->bat_temp * 10;
-		} else
-			temp = di->bat_temp * 10;
+				temp = di->btemp_ranges.btemp_med_limit;
+		}
 	}
-	return temp;
+
+	return temp * 10;
 }
 
 /**

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 06/15] power: supply: ab8500: Preserve battery termination current
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (4 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 05/15] power: supply: ab8500_btemp: Fix event temperature reporting Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 07/15] power: supply: ab8500_charger: Fix AC charger re-enable Linus Walleij
                   ` (10 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

The battery-info parser uses a negative value for an unspecified
termination current. Testing the value as a boolean instead replaces
every valid positive current from the battery description with 200 mA.

Apply the default only when the property is unspecified so batteries
retain their described charging termination current.

Fixes: 9c20899da46b ("power: supply: ab8500: Standardize termination current")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_bmdata.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c
index d7fd2d313ff1..0f04893d6bf9 100644
--- a/drivers/power/supply/ab8500_bmdata.c
+++ b/drivers/power/supply/ab8500_bmdata.c
@@ -173,7 +173,7 @@ int ab8500_bm_of_probe(struct power_supply *psy,
 	if (bi->constant_charge_voltage_max_uv < 0)
 		bi->constant_charge_voltage_max_uv = 4100000;
 
-	if (bi->charge_term_current_ua)
+	if (bi->charge_term_current_ua < 0)
 		/* Charging stops when we drop below this current */
 		bi->charge_term_current_ua = 200000;
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 07/15] power: supply: ab8500_charger: Fix AC charger re-enable
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (5 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 06/15] power: supply: ab8500: Preserve battery termination current Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 08/15] power: supply: ab8500_charger: Decode AB8505 USB status Linus Walleij
                   ` (9 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

The AC check-enable callback passes the embedded USB charger object to
ab8500_charger_ac_en(). That helper derives the parent with the AC
charger offset, so the wrong object yields an invalid ab8500_charger
pointer.

Pass the AC charger object when re-enabling AC charging.

Fixes: bc6e02871402 ("power: supply: ab8500: Standardize CV voltage")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_charger.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index 7e6afcf0ac7d..19959e48287c 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -1778,7 +1778,7 @@ static int ab8500_charger_usb_check_enable(struct ux500_charger *charger,
 }
 
 /**
- * ab8500_charger_ac_check_enable() - enable usb charging
+ * ab8500_charger_ac_check_enable() - enable AC charging
  * @charger:	pointer to the ux500_charger structure
  * @vset_uv:	charging voltage in microvolt
  * @iset_ua:	charger output current in micrompere
@@ -1817,7 +1817,7 @@ static int ab8500_charger_ac_check_enable(struct ux500_charger *charger,
 			return ret;
 		}
 
-		ret = ab8500_charger_ac_en(&di->usb_chg, true, vset_uv, iset_ua);
+		ret = ab8500_charger_ac_en(&di->ac_chg, true, vset_uv, iset_ua);
 		if (ret < 0) {
 			dev_err(di->dev, "failed to enable AC charger %d\n",
 				__LINE__);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 08/15] power: supply: ab8500_charger: Decode AB8505 USB status
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (6 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 07/15] power: supply: ab8500_charger: Fix AC charger re-enable Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 09/15] power: supply: ab8500_charger: Respect AB8505 register layout Linus Walleij
                   ` (8 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

AB8505 keeps USB link status in the same bit position as AB8500 but
defines a different five-bit value table. Reusing the AB8500 enum
treats several reserved AB8505 values as chargers and misidentifies
the AB8505 charger-fault and DM-high states.

Add an AB8505 decoder, reject reserved and non-charging states, and
retain conservative current limits for supported charger types.
Restrict the invalid-link workaround to AB8500 and stop processing
when the status register cannot be read.

Fixes: d4337660d069 ("ab8500-charger: Add AB8505_USB_LINK_STATUS")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_charger.c | 170 ++++++++++++++++++++++++++++------
 1 file changed, 142 insertions(+), 28 deletions(-)

diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index 19959e48287c..4810dfbe948d 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -136,6 +136,33 @@ enum ab8500_charger_link_status {
 	USB_STAT_ACA_DOCK_CHARGER,
 };
 
+/* UsbLink1Status register values used by AB8505 */
+enum ab8505_charger_link_status {
+	AB8505_USB_STAT_OFF,
+	AB8505_USB_STAT_SDP_NOT_CHARGING,
+	AB8505_USB_STAT_SDP_CHARGING,
+	AB8505_USB_STAT_SDP_SUSPENDED,
+	AB8505_USB_STAT_CDP,
+	AB8505_USB_STAT_RESERVED_5,
+	AB8505_USB_STAT_RESERVED_6,
+	AB8505_USB_STAT_DCP,
+	AB8505_USB_STAT_ACA_RID_A,
+	AB8505_USB_STAT_ACA_RID_B,
+	AB8505_USB_STAT_ACA_RID_C,
+	AB8505_USB_STAT_RESERVED_11,
+	AB8505_USB_STAT_RESERVED_12,
+	AB8505_USB_STAT_UPSTREAM_IDGND,
+	AB8505_USB_STAT_CHARGER_NOT_OK,
+	AB8505_USB_STAT_CHARGER_DM_HIGH,
+	AB8505_USB_STAT_PHY_ENABLED,
+	AB8505_USB_STAT_UPSTREAM_NO_IDGND,
+	AB8505_USB_STAT_UPSTREAM_IDGND_VBUS,
+	AB8505_USB_STAT_CHARGER_SE1,
+	AB8505_USB_STAT_CARKIT_1,
+	AB8505_USB_STAT_CARKIT_2,
+	AB8505_USB_STAT_ACA_DOCK_CHARGER,
+};
+
 enum ab8500_usb_state {
 	AB8500_BM_USB_STATE_RESET_HS,	/* HighSpeed Reset */
 	AB8500_BM_USB_STATE_RESET_FS,	/* FullSpeed/LowSpeed Reset */
@@ -699,20 +726,102 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe)
 }
 
 /**
- * ab8500_charger_max_usb_curr() - get the max curr for the USB type
+ * ab8505_charger_max_usb_curr() - get the max current for the USB type
  * @di:			pointer to the ab8500_charger structure
  * @link_status:	the identified USB type
  *
- * Get the maximum current that is allowed to be drawn from the host
- * based on the USB type.
+ * Decode AB8505-specific UsbLink1Status values and get the maximum current
+ * that is allowed to be drawn based on the USB type.
  * Returns error code in case of failure else 0 on success
  */
+static int ab8505_charger_max_usb_curr(struct ab8500_charger *di,
+				       u8 link_status)
+{
+	int ret = 0;
+
+	di->is_aca_rid = 0;
+
+	switch (link_status) {
+	case AB8505_USB_STAT_SDP_NOT_CHARGING:
+	case AB8505_USB_STAT_SDP_CHARGING:
+	case AB8505_USB_STAT_SDP_SUSPENDED:
+	case AB8505_USB_STAT_CDP:
+	case AB8505_USB_STAT_CHARGER_DM_HIGH:
+	case AB8505_USB_STAT_UPSTREAM_NO_IDGND:
+	case AB8505_USB_STAT_UPSTREAM_IDGND_VBUS:
+	case AB8505_USB_STAT_CHARGER_SE1:
+	case AB8505_USB_STAT_CARKIT_1:
+	case AB8505_USB_STAT_CARKIT_2:
+	case AB8505_USB_STAT_ACA_DOCK_CHARGER:
+		di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P5;
+		break;
+	case AB8505_USB_STAT_DCP:
+		di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_1P5;
+		break;
+	case AB8505_USB_STAT_ACA_RID_A:
+		di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P5;
+		di->is_aca_rid = 1;
+		break;
+	case AB8505_USB_STAT_ACA_RID_B:
+		di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_1P3;
+		di->is_aca_rid = 1;
+		break;
+	case AB8505_USB_STAT_ACA_RID_C:
+		di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_1P5;
+		di->is_aca_rid = 1;
+		break;
+	case AB8505_USB_STAT_OFF:
+		if (di->vbus_detected) {
+			di->usb_device_is_unrecognised = true;
+			di->max_usb_in_curr.usb_type_max_ua =
+				USB_CH_IP_CUR_LVL_1P5;
+			break;
+		}
+		fallthrough;
+	case AB8505_USB_STAT_UPSTREAM_IDGND:
+	case AB8505_USB_STAT_PHY_ENABLED:
+		dev_err(di->dev, "USB Type - Charging not allowed\n");
+		di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P05;
+		ret = -ENXIO;
+		break;
+	case AB8505_USB_STAT_CHARGER_NOT_OK:
+		di->flags.vbus_collapse = true;
+		dev_err(di->dev, "USB Type - VBUS has collapsed\n");
+		di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P05;
+		ret = -ENXIO;
+		break;
+	case AB8505_USB_STAT_RESERVED_5:
+	case AB8505_USB_STAT_RESERVED_6:
+	case AB8505_USB_STAT_RESERVED_11:
+	case AB8505_USB_STAT_RESERVED_12:
+	default:
+		dev_err(di->dev, "USB Type - Unknown\n");
+		di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P05;
+		ret = -ENXIO;
+		break;
+	}
+
+	return ret;
+}
+
+/**
+ * ab8500_charger_max_usb_curr() - get the max current for the USB type
+ * @di:			pointer to the ab8500_charger structure
+ * @link_status:	the identified USB type
+ *
+ * Get the maximum current that is allowed to be drawn based on the USB type.
+ * Returns error code in case of failure else 0 on success.
+ */
 static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
-		enum ab8500_charger_link_status link_status)
+				       u8 link_status)
 {
 	int ret = 0;
 
 	di->usb_device_is_unrecognised = false;
+	if (is_ab8505(di->parent)) {
+		ret = ab8505_charger_max_usb_curr(di, link_status);
+		goto out;
+	}
 
 	/*
 	 * Platform only supports USB 2.0.
@@ -721,7 +830,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
 	 * should set USB_CH_IP_CUR_LVL_0P5.
 	 */
 
-	switch (link_status) {
+	switch ((enum ab8500_charger_link_status)link_status) {
 	case USB_STAT_STD_HOST_NC:
 	case USB_STAT_STD_HOST_C_NS:
 	case USB_STAT_STD_HOST_C_S:
@@ -825,6 +934,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
 		break;
 	}
 
+out:
 	di->max_usb_in_curr.set_max_ua = di->max_usb_in_curr.usb_type_max_ua;
 	dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d",
 		link_status, di->max_usb_in_curr.set_max_ua);
@@ -866,8 +976,7 @@ static int ab8500_charger_read_usb_type(struct ab8500_charger *di)
 		val = (val & AB8500_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT;
 	else
 		val = (val & AB8505_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT;
-	ret = ab8500_charger_max_usb_curr(di,
-		(enum ab8500_charger_link_status) val);
+	ret = ab8500_charger_max_usb_curr(di, val);
 
 	return ret;
 }
@@ -929,8 +1038,7 @@ static int ab8500_charger_detect_usb_type(struct ab8500_charger *di)
 		if (val)
 			break;
 	}
-	ret = ab8500_charger_max_usb_curr(di,
-		(enum ab8500_charger_link_status) val);
+	ret = ab8500_charger_max_usb_curr(di, val);
 
 	return ret;
 }
@@ -2277,6 +2385,7 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work)
 	int ret;
 	u8 val;
 	u8 link_status;
+	u8 link_status_mask;
 
 	struct ab8500_charger *di = container_of(work,
 		struct ab8500_charger, usb_link_status_work);
@@ -2303,20 +2412,22 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work)
 		ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
 					AB8500_USB_LINK1_STAT_REG, &val);
 
-	if (ret >= 0)
-		dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val);
-	else
-		dev_dbg(di->dev, "Error reading USB link status\n");
+	if (ret < 0) {
+		dev_err(di->dev, "Error reading USB link status\n");
+		return;
+	}
+	dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val);
 
 	if (is_ab8500(di->parent))
-		link_status = AB8500_USB_LINK_STATUS;
+		link_status_mask = AB8500_USB_LINK_STATUS;
 	else
-		link_status = AB8505_USB_LINK_STATUS;
+		link_status_mask = AB8505_USB_LINK_STATUS;
+	link_status = (val & link_status_mask) >> USB_LINK_STATUS_SHIFT;
 
 	if (detected_chargers & USB_PW_CONN) {
-		if (((val & link_status) >> USB_LINK_STATUS_SHIFT) ==
-				USB_STAT_NOT_VALID_LINK &&
-				di->invalid_charger_detect_state == 0) {
+		if (is_ab8500(di->parent) &&
+		    link_status == USB_STAT_NOT_VALID_LINK &&
+		    di->invalid_charger_detect_state == 0) {
 			dev_dbg(di->dev,
 					"Invalid charger detected, state= 0\n");
 			/*Enable charger*/
@@ -2339,18 +2450,21 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work)
 			abx500_mask_and_set_register_interruptible(di->dev,
 					AB8500_USB, AB8500_USB_LINE_CTRL2_REG,
 					USB_CH_DET, 0x00);
-			/*Check link status*/
-			if (is_ab8500(di->parent))
-				ret = abx500_get_register_interruptible(di->dev,
-					AB8500_USB, AB8500_USB_LINE_STAT_REG,
-					&val);
-			else
-				ret = abx500_get_register_interruptible(di->dev,
-					AB8500_USB, AB8500_USB_LINK1_STAT_REG,
-					&val);
+			/* Check link status */
+			ret = abx500_get_register_interruptible(di->dev,
+								AB8500_USB,
+								AB8500_USB_LINE_STAT_REG,
+								&val);
+			if (ret < 0) {
+				dev_err(di->dev,
+					"Error reading USB link status\n");
+				return;
+			}
 
+			link_status = (val & link_status_mask) >>
+				USB_LINK_STATUS_SHIFT;
 			dev_dbg(di->dev, "USB link status= 0x%02x\n",
-				(val & link_status) >> USB_LINK_STATUS_SHIFT);
+				link_status);
 			di->invalid_charger_detect_state = 2;
 		}
 	} else {

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 09/15] power: supply: ab8500_charger: Respect AB8505 register layout
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (7 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 08/15] power: supply: ab8500_charger: Decode AB8505 USB status Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 10/15] power: supply: ab8500_charger: Handle detection errors Linus Walleij
                   ` (7 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

Several AB8505 charger fields differ from AB8500. Its long VBUS
falling debounce is 300 ms rather than 100 ms, and its VBUS
overvoltage selector has a different encoding programmed through OTP.
Writing the AB8500 6.3 V value selects 9 V on AB8505.

AB8505 revision 3 also defines SwControlFallback as reserved,
correcting older documentation which led the driver to support
autopower there. Preserve the AB8505 overvoltage setting, wait for its
documented debounce, and reject the unsupported autopower setting.

Fixes: 0ed5107fa860 ("ab8500-charger: Do not touch VBUSOVV bits")
Fixes: b016322293c7 ("ab8500-charger: Add support for autopower on AB8505 and AB9540")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_charger.c | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index 4810dfbe948d..08042ef222be 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -52,7 +52,7 @@
 #define MAIN_CH_DET			0x01
 #define MAIN_CH_CV_ON			0x04
 #define USB_CH_CV_ON			0x08
-#define VBUS_DET_DBNC100		0x02
+#define VBUS_DET_DBNC_LONG		0x02
 #define VBUS_DET_DBNC1			0x01
 #define OTP_ENABLE_WD			0x01
 #define DROP_COUNT_RESET		0x01
@@ -383,6 +383,10 @@ static void ab8500_enable_disable_sw_fallback(struct ab8500_charger *di,
 	u8 bit;
 	int ret;
 
+	/* SwControlFallback is reserved and must remain clear on AB8505. */
+	if (is_ab8505(di->parent))
+		return;
+
 	dev_dbg(di->dev, "SW Fallback: %d\n", fallback);
 
 	if (is_ab8500(di->parent)) {
@@ -703,12 +707,16 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe)
 
 	if (!probe) {
 		/*
-		 * AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC100
+		 * AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC_LONG
 		 * when disconnecting ACA even though no
 		 * charger was connected. Try waiting a little
-		 * longer than the 100 ms of VBUS_DET_DBNC100...
+		 * longer than its 100 ms debounce. AB8505 uses
+		 * a 300 ms falling debounce for the same bit.
 		 */
-		msleep(110);
+		if (is_ab8505(di->parent))
+			msleep(310);
+		else
+			msleep(110);
 	}
 	ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
 		AB8500_CH_USBCH_STAT1_REG, &val);
@@ -719,7 +727,7 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe)
 	dev_dbg(di->dev,
 		"%s AB8500_CH_USBCH_STAT1_REG %x\n", __func__,
 		val);
-	if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100))
+	if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC_LONG))
 		result |= USB_PW_CONN;
 
 	return result;
@@ -3185,7 +3193,7 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
 		}
 	}
 
-	if (is_ab8505_2p0(di->parent))
+	if (is_ab8505(di->parent))
 		ret = abx500_mask_and_set_register_interruptible(di->dev,
 			AB8500_CHARGER,
 			AB8500_USBCH_CTRL2_REG,
@@ -3534,11 +3542,14 @@ static int ab8500_charger_probe(struct platform_device *pdev)
 
 	di->bm = &ab8500_bm_data;
 
-	di->autopower_cfg = of_property_read_bool(np, "autopower_cfg");
-
 	/* get parent data */
 	di->dev = dev;
 	di->parent = dev_get_drvdata(pdev->dev.parent);
+	di->autopower_cfg = of_property_read_bool(np, "autopower_cfg");
+	if (di->autopower_cfg && is_ab8505(di->parent)) {
+		dev_warn(dev, "autopower is not supported on AB8505\n");
+		di->autopower_cfg = false;
+	}
 
 	/* Get ADC channels */
 	if (!is_ab8505(di->parent)) {

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 10/15] power: supply: ab8500_charger: Handle detection errors
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (8 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 09/15] power: supply: ab8500_charger: Respect AB8505 register layout Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 11/15] power: supply: ab8500_charger: Skip absent AB8505 main charger Linus Walleij
                   ` (6 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

ab8500_charger_detect_chargers() returns either a charger bitmask or
a negative register-access error. The probe and component-bind paths
use a negative return value as a bitmask, making failed reads appear
as both AC and USB connections.

Propagate detection errors and release the bind-time workqueue on
failure.

Fixes: 84edbeeab67c ("ab8500-charger: AB8500 charger driver")
Fixes: 1c1f13a006ed ("power: supply: ab8500: Move to componentized binding")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_charger.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index 08042ef222be..938c3923afde 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -3462,6 +3462,10 @@ static int ab8500_charger_bind(struct device *dev)
 	}
 
 	ch_stat = ab8500_charger_detect_chargers(di, false);
+	if (ch_stat < 0) {
+		destroy_workqueue(di->charger_wq);
+		return ch_stat;
+	}
 
 	if (ch_stat & AC_PW_CONN) {
 		if (is_ab8500(di->parent))
@@ -3747,6 +3751,10 @@ static int ab8500_charger_probe(struct platform_device *pdev)
 
 	/* Identify the connected charger types during startup */
 	charger_status = ab8500_charger_detect_chargers(di, true);
+	if (charger_status < 0) {
+		ret = charger_status;
+		goto remove_ab8500_bm;
+	}
 	if (charger_status & AC_PW_CONN) {
 		di->ac.charger_connected = 1;
 		di->ac_conn = true;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 11/15] power: supply: ab8500_charger: Skip absent AB8505 main charger
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (9 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 10/15] power: supply: ab8500_charger: Handle detection errors Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 12/15] power: supply: ab8500_charger: Retry AB8505 USB detection Linus Walleij
                   ` (5 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

AB8505 contains only the USB charger. It has no integrated main
charger, main-charger status register, or main-charger interrupts.

Avoid reading the missing status register, requesting the five missing
interrupts, or disabling an AC charger which was never enabled. This
lets the driver bind using only the AB8505 USB charger resources.

Fixes: d4337660d069 ("ab8500-charger: Add AB8505_USB_LINK_STATUS")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_charger.c | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)

diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index 938c3923afde..c7225135af57 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -692,16 +692,19 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe)
 	int ret;
 	u8 val;
 
-	/* Check for AC charger */
-	ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
-		AB8500_CH_STATUS1_REG, &val);
-	if (ret < 0) {
-		dev_err(di->dev, "%s ab8500 read failed\n", __func__);
-		return ret;
-	}
+	/* AB8505 has no integrated main charger. */
+	if (!is_ab8505(di->parent)) {
+		ret = abx500_get_register_interruptible(di->dev,
+							AB8500_CHARGER,
+							AB8500_CH_STATUS1_REG, &val);
+		if (ret < 0) {
+			dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+			return ret;
+		}
 
-	if (val & MAIN_CH_DET)
-		result = AC_PW_CONN;
+		if (val & MAIN_CH_DET)
+			result = AC_PW_CONN;
+	}
 
 	/* Check for USB charger */
 
@@ -3500,7 +3503,8 @@ static void ab8500_charger_unbind(struct device *dev)
 	int ret;
 
 	/* Disable AC charging */
-	ab8500_charger_ac_en(&di->ac_chg, false, 0, 0);
+	if (di->ac_chg.enabled)
+		ab8500_charger_ac_en(&di->ac_chg, false, 0, 0);
 
 	/* Disable USB charging */
 	ab8500_charger_usb_en(&di->usb_chg, false, 0, 0);
@@ -3724,7 +3728,13 @@ static int ab8500_charger_probe(struct platform_device *pdev)
 	}
 
 	/* Request interrupts */
-	for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
+	/* The first five interrupts belong to the AB8500 main charger. */
+	if (is_ab8505(di->parent))
+		i = 5;
+	else
+		i = 0;
+
+	for (; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
 		irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
 		if (irq < 0)
 			return irq;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 12/15] power: supply: ab8500_charger: Retry AB8505 USB detection
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (10 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 11/15] power: supply: ab8500_charger: Skip absent AB8505 main charger Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 13/15] power: supply: ab8500_charger: Recover inactive AB8505 charger Linus Walleij
                   ` (4 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

AB8505 reports UsbLink1Status state 14 when the USB charger port is
not OK. The manual says that software can return the port detector to
the detection state by setting the self-clearing
ChargerCtrl.DropCountReset bit.

The Samsung-GT-S7710-Skomer, Samsung-SGH-I407-Kyle, and
Samsung-SGH-T599-Codina-TMO product trees are reference points for a
workaround that cycles the USB charger and retries detection three times
before rejecting VBUS. Only the Kyle tree writes 1 to DropCountReset;
the other two write 0 and therefore do not request the documented reset.

Add the same bounded recovery for AB8505, preserving the configured USB
charger control bits across each cycle. Keep the charger connected while
recovering, then report the fault and disconnect it if all three attempts
fail. Leave AB8500 behavior unchanged.

Fixes: d4337660d069 ("ab8500-charger: Add AB8505_USB_LINK_STATUS")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_charger.c | 114 ++++++++++++++++++++++++++++++++--
 1 file changed, 109 insertions(+), 5 deletions(-)

diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index c7225135af57..8430bc247df7 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -99,6 +99,8 @@
 
 #define CHARGER_STATUS_POLL 10 /* in ms */
 
+#define AB8505_USB_CHARGER_NOT_OK_RETRIES	3
+
 #define CHG_WD_INTERVAL			(60 * HZ)
 
 #define AB8500_SW_CONTROL_FALLBACK	0x03
@@ -261,6 +263,8 @@ struct ab8500_charger_max_usb_in_curr {
  * @is_aca_rid:		Incicate if accessory is ACA type
  * @current_stepping_sessions:
  *			Counter for current stepping sessions
+ * @usb_charger_not_ok_retries:
+ *			Number of AB8505 USB charger recovery attempts
  * @parent:		Pointer to the struct ab8500
  * @adc_main_charger_v	ADC channel for main charger voltage
  * @adc_main_charger_c	ADC channel for main charger current
@@ -314,6 +318,7 @@ struct ab8500_charger {
 	int invalid_charger_detect_state;
 	int is_aca_rid;
 	atomic_t current_stepping_sessions;
+	unsigned int usb_charger_not_ok_retries;
 	struct ab8500 *parent;
 	struct iio_channel *adc_main_charger_v;
 	struct iio_channel *adc_main_charger_c;
@@ -473,6 +478,9 @@ static void ab8500_power_supply_changed(struct ab8500_charger *di,
 static void ab8500_charger_set_usb_connected(struct ab8500_charger *di,
 	bool connected)
 {
+	if (!connected && !di->vbus_detected)
+		di->usb_charger_not_ok_retries = 0;
+
 	if (connected != di->usb.charger_connected) {
 		dev_dbg(di->dev, "USB connected:%i\n", connected);
 		di->usb.charger_connected = connected;
@@ -796,10 +804,19 @@ static int ab8505_charger_max_usb_curr(struct ab8500_charger *di,
 		ret = -ENXIO;
 		break;
 	case AB8505_USB_STAT_CHARGER_NOT_OK:
-		di->flags.vbus_collapse = true;
-		dev_err(di->dev, "USB Type - VBUS has collapsed\n");
 		di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P05;
-		ret = -ENXIO;
+		if (di->usb_charger_not_ok_retries <
+		    AB8505_USB_CHARGER_NOT_OK_RETRIES) {
+			dev_warn(di->dev,
+				 "USB Type - VBUS has collapsed, retrying\n");
+			queue_delayed_work(di->charger_wq,
+					   &di->check_usbchgnotok_work, 0);
+			ret = -EAGAIN;
+		} else {
+			dev_err(di->dev,
+				"USB Type - VBUS recovery failed\n");
+			ret = -ENXIO;
+		}
 		break;
 	case AB8505_USB_STAT_RESERVED_5:
 	case AB8505_USB_STAT_RESERVED_6:
@@ -2587,6 +2604,48 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
 	}
 }
 
+static int ab8505_charger_retry_usb_detection(struct ab8500_charger *di)
+{
+	u8 usbch_ctrl1;
+	bool usb_enabled;
+	int restore_ret;
+	int ret;
+
+	ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+						AB8500_USBCH_CTRL1_REG,
+						&usbch_ctrl1);
+	if (ret < 0)
+		return ret;
+
+	usb_enabled = usbch_ctrl1 & USB_CH_ENA;
+	if (usb_enabled) {
+		ret = abx500_mask_and_set_register_interruptible(di->dev,
+								 AB8500_CHARGER,
+								 AB8500_USBCH_CTRL1_REG,
+								 USB_CH_ENA, 0);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = abx500_mask_and_set_register_interruptible(di->dev,
+							 AB8500_CHARGER,
+							 AB8500_CHARGER_CTRL,
+							 DROP_COUNT_RESET,
+							 DROP_COUNT_RESET);
+
+	if (usb_enabled) {
+		restore_ret = abx500_mask_and_set_register_interruptible(di->dev,
+									 AB8500_CHARGER,
+									 AB8500_USBCH_CTRL1_REG,
+									 USB_CH_ENA,
+									 USB_CH_ENA);
+		if (restore_ret < 0)
+			return restore_ret;
+	}
+
+	return ret;
+}
+
 /**
  * ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status
  * @work:	pointer to the work_struct structure
@@ -2596,7 +2655,10 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
 static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work)
 {
 	int ret;
+	u8 link_status = 0;
 	u8 reg_value;
+	bool charger_not_ok;
+	bool prev_collapse;
 	bool prev_status;
 
 	struct ab8500_charger *di = container_of(work,
@@ -2609,19 +2671,61 @@ static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work)
 		dev_err(di->dev, "%s ab8500 read failed\n", __func__);
 		return;
 	}
+	charger_not_ok = reg_value & VBUS_CH_NOK;
+	if (is_ab8505(di->parent)) {
+		ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+							AB8500_USB_LINK1_STAT_REG,
+							&link_status);
+		if (ret < 0) {
+			dev_err(di->dev, "%s USB link status read failed\n",
+				__func__);
+			return;
+		}
+
+		link_status = (link_status & AB8505_USB_LINK_STATUS) >>
+			USB_LINK_STATUS_SHIFT;
+		charger_not_ok |=
+			link_status == AB8505_USB_STAT_CHARGER_NOT_OK;
+	}
+
 	prev_status = di->flags.usbchargernotok;
+	prev_collapse = di->flags.vbus_collapse;
+
+	if (is_ab8505(di->parent) &&
+	    link_status == AB8505_USB_STAT_CHARGER_NOT_OK &&
+	    di->usb_charger_not_ok_retries <
+	    AB8505_USB_CHARGER_NOT_OK_RETRIES) {
+		di->usb_charger_not_ok_retries++;
+		di->flags.usbchargernotok = false;
+		di->flags.vbus_collapse = true;
+		dev_warn(di->dev, "Retrying USB charger detection (%u/%u)\n",
+			 di->usb_charger_not_ok_retries,
+			 AB8505_USB_CHARGER_NOT_OK_RETRIES);
 
-	if (reg_value & VBUS_CH_NOK) {
+		ret = ab8505_charger_retry_usb_detection(di);
+		if (ret < 0)
+			dev_err(di->dev, "USB charger retry failed: %d\n", ret);
+
+		queue_delayed_work(di->charger_wq,
+				   &di->check_usbchgnotok_work, HZ);
+	} else if (charger_not_ok) {
 		di->flags.usbchargernotok = true;
+		if (is_ab8505(di->parent) &&
+		    link_status == AB8505_USB_STAT_CHARGER_NOT_OK) {
+			di->flags.vbus_collapse = false;
+			ab8500_charger_set_usb_connected(di, false);
+		}
 		/* Check again in 1sec */
 		queue_delayed_work(di->charger_wq,
 			&di->check_usbchgnotok_work, HZ);
 	} else {
 		di->flags.usbchargernotok = false;
 		di->flags.vbus_collapse = false;
+		di->usb_charger_not_ok_retries = 0;
 	}
 
-	if (prev_status != di->flags.usbchargernotok)
+	if (prev_status != di->flags.usbchargernotok ||
+	    prev_collapse != di->flags.vbus_collapse)
 		ab8500_power_supply_changed(di, di->usb_chg.psy);
 }
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 13/15] power: supply: ab8500_charger: Recover inactive AB8505 charger
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (11 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 12/15] power: supply: ab8500_charger: Retry AB8505 USB detection Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 14/15] power: supply: ab8500_fg: Finalize current measurements Linus Walleij
                   ` (3 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

UsbChCtrl1.UsbChEna can remain set while UsbChStatus1.UsbChOn is
clear. The existing recovery only checks the control bit, so it considers
the charger enabled and leaves charging stopped.

The Samsung-GT-S7710-Skomer, Samsung-SGH-I407-Kyle, and
Samsung-SGH-T599-Codina-TMO product trees are reference points for
checking both bits and cycling the charger when they disagree.

Extend the USB charger check to detect this AB8505 state and enable the
check in the charging algorithm for AB8505. Only cycle the charger when
VBUS is debounced and no charger-not-OK, thermal, overvoltage, or
watchdog fault accounts for the inactive hardware.

Fixes: 4dcdf57773fd ("ab8500-bm: Quick re-attach charging behaviour")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_chargalg.c |  9 ++----
 drivers/power/supply/ab8500_charger.c  | 51 +++++++++++++++++++++++++++++++++-
 2 files changed, 53 insertions(+), 7 deletions(-)

diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c
index d2b9880b0770..71d82c6c193e 100644
--- a/drivers/power/supply/ab8500_chargalg.c
+++ b/drivers/power/supply/ab8500_chargalg.c
@@ -1241,12 +1241,9 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
 
 	charger_status = ab8500_chargalg_check_charger_connection(di);
 
-	if (is_ab8500(di->parent)) {
-		ret = ab8500_chargalg_check_charger_enable(di);
-		if (ret < 0)
-			dev_err(di->dev, "Checking charger is enabled error"
-					": Returned Value %d\n", ret);
-	}
+	ret = ab8500_chargalg_check_charger_enable(di);
+	if (ret < 0)
+		dev_err(di->dev, "Checking charger enable failed: %d\n", ret);
 
 	/*
 	 * First check if we have a charger connected.
diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index 8430bc247df7..19cc56624ee4 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -52,6 +52,7 @@
 #define MAIN_CH_DET			0x01
 #define MAIN_CH_CV_ON			0x04
 #define USB_CH_CV_ON			0x08
+#define USB_CH_ON			0x04
 #define VBUS_DET_DBNC_LONG		0x02
 #define VBUS_DET_DBNC1			0x01
 #define OTP_ENABLE_WD			0x01
@@ -1877,6 +1878,9 @@ static int ab8500_charger_usb_check_enable(struct ux500_charger *charger,
 	int vset_uv, int iset_ua)
 {
 	u8 usbch_ctrl1 = 0;
+	u8 usbch_status1;
+	u8 usbch_status2;
+	bool reenable;
 	int ret = 0;
 
 	struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger);
@@ -1892,7 +1896,52 @@ static int ab8500_charger_usb_check_enable(struct ux500_charger *charger,
 	}
 	dev_dbg(di->dev, "USB charger ctrl: 0x%02x\n", usbch_ctrl1);
 
-	if (!(usbch_ctrl1 & USB_CH_ENA)) {
+	reenable = !(usbch_ctrl1 & USB_CH_ENA);
+	if (is_ab8505(di->parent)) {
+		ret = abx500_get_register_interruptible(di->dev,
+							AB8500_CHARGER,
+							AB8500_CH_USBCH_STAT1_REG,
+							&usbch_status1);
+		if (ret < 0) {
+			dev_err(di->dev, "USB charger status read failed\n");
+			return ret;
+		}
+
+		if (!reenable && (usbch_status1 & USB_CH_ON))
+			return 0;
+
+		if (!(usbch_status1 & VBUS_DET_DBNC_LONG))
+			return 0;
+
+		ret = abx500_get_register_interruptible(di->dev,
+							AB8500_CHARGER,
+							AB8500_CH_USBCH_STAT2_REG,
+							&usbch_status2);
+		if (ret < 0) {
+			dev_err(di->dev, "USB charger fault status read failed\n");
+			return ret;
+		}
+
+		if ((usbch_status2 &
+		     (VBUS_CH_NOK | USB_CH_TH_PROT | VBUS_OVV_TH)) ||
+		    di->flags.usbchargernotok || di->flags.usb_thermal_prot ||
+		    di->flags.vbus_ovv || di->usb.wd_expired)
+			return 0;
+
+		if (!reenable) {
+			dev_info(di->dev,
+				 "USB charger enabled but inactive, cycling it\n");
+			ret = abx500_mask_and_set_register_interruptible(di->dev,
+									 AB8500_CHARGER,
+									 AB8500_USBCH_CTRL1_REG,
+									 USB_CH_ENA, 0);
+			if (ret < 0)
+				return ret;
+			reenable = true;
+		}
+	}
+
+	if (reenable) {
 		dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n");
 
 		ret = abx500_mask_and_set_register_interruptible(di->dev,

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 14/15] power: supply: ab8500_fg: Finalize current measurements
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (12 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 13/15] power: supply: ab8500_charger: Recover inactive AB8505 charger Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-09-16  6:49 ` [PATCH 15/15] power: supply: ab8500_fg: Avoid reserved AB8505 CCConf bit Linus Walleij
                   ` (2 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

ab8500_fg_inst_curr_start() returns with cc_lock held and the CCEOC
IRQ enabled. The bounded sampling loop in the load-compensated voltage
path returns without calling ab8500_fg_inst_curr_finalize() when it
expires, leaving both resources active. The next fuel-gauge work item
then blocks on cc_lock and all cached power-supply values stop updating.

The Samsung-GT-I8530-Gavini, Samsung-GT-S7710-Skomer,
Samsung-SGH-I407-Kyle and Samsung-SGH-T599-Codina-TMO product trees use
a bounded sampling loop in drivers/battery/abb_fuelgauge.c but still
call the finalizer. Follow that pattern so the normal completion timeout
also performs the required cleanup.

Check the start result as well and return an uncompensated voltage if
the instantaneous current transaction cannot be started or completed.

Fixes: edc400e1632f ("power: supply: ab8500_fg: Break loop for measurement")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_fg.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index 70e29c9e8108..b3f29a43745d 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -934,9 +934,15 @@ static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di, bool always)
 	int i = 0;
 	int vbat_uv = 0;
 	int rcomp;
+	int ret;
 
 	/* Average the instant current to get a stable current measurement */
-	ab8500_fg_inst_curr_start(di);
+	ret = ab8500_fg_inst_curr_start(di);
+	if (ret) {
+		dev_err(di->dev, "failed to start instantaneous current measurement\n");
+		di->vbat_uv = ab8500_fg_bat_voltage(di);
+		return di->vbat_uv;
+	}
 
 	do {
 		vbat_uv += ab8500_fg_bat_voltage(di);
@@ -945,15 +951,14 @@ static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di, bool always)
 	} while (!ab8500_fg_inst_curr_done(di) &&
 		 i <= WAIT_FOR_INST_CURRENT_MAX);
 
-	if (i > WAIT_FOR_INST_CURRENT_MAX) {
+	ret = ab8500_fg_inst_curr_finalize(di, &di->inst_curr_ua);
+	if (ret) {
 		dev_err(di->dev,
 			"TIMEOUT: return uncompensated measurement of VBAT\n");
 		di->vbat_uv = vbat_uv / i;
 		return di->vbat_uv;
 	}
 
-	ab8500_fg_inst_curr_finalize(di, &di->inst_curr_ua);
-
 	/*
 	 * If there is too high current dissipation, the compensation cannot be
 	 * trusted so return an error unless we must return something here, as

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 15/15] power: supply: ab8500_fg: Avoid reserved AB8505 CCConf bit
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (13 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 14/15] power: supply: ab8500_fg: Finalize current measurements Linus Walleij
@ 2026-09-16  6:49 ` Linus Walleij
  2026-10-02 22:07 ` [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
  2026-10-04 19:33 ` Sebastian Reichel
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-09-16  6:49 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, Linus Walleij

AB8500 defines CCConf bit 1 as CCDeepSleepEna, but AB8505 reserves
bits 7:1 and only defines bit 0, CCPwrUpEna. The fuel-gauge driver
currently writes both bits whenever it starts the coulomb counter.

The Samsung-GT-S7710-Skomer and related Samsung product trees use the
same unconditional value in their fuel-gauge drivers, so they provide
no AB8505 workaround to carry over. Select the value from the detected
PMIC variant instead, retaining the deep-sleep enable bit on all
existing non-AB8505 paths and avoiding the reserved bit on AB8505.

Fixes: a982362c1723 ("mfd: Support for the AB8500 AB8505 variant")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/power/supply/ab8500_fg.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index b3f29a43745d..069e7fee1d16 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -451,6 +451,16 @@ static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample)
 	avg->avg = sample;
 }
 
+static u8 ab8500_fg_cc_config(struct ab8500_fg *di)
+{
+	u8 config = CC_PWR_UP_ENA;
+
+	if (!is_ab8505(di->parent))
+		config |= CC_DEEP_SLEEP_ENA;
+
+	return config;
+}
+
 /**
  * ab8500_fg_coulomb_counter() - enable coulomb counter
  * @di:		pointer to the ab8500_fg structure
@@ -481,7 +491,7 @@ static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable)
 		/* Start the CC */
 		ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
 			AB8500_RTC_CC_CONF_REG,
-			(CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
+			ab8500_fg_cc_config(di));
 		if (ret)
 			goto cc_err;
 
@@ -555,7 +565,7 @@ int ab8500_fg_inst_curr_start(struct ab8500_fg *di)
 		/* Start the CC */
 		ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
 			AB8500_RTC_CC_CONF_REG,
-			(CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
+			ab8500_fg_cc_config(di));
 		if (ret)
 			goto fail;
 	} else {

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (14 preceding siblings ...)
  2026-09-16  6:49 ` [PATCH 15/15] power: supply: ab8500_fg: Avoid reserved AB8505 CCConf bit Linus Walleij
@ 2026-10-02 22:07 ` Linus Walleij
  2026-10-04 19:33 ` Sebastian Reichel
  16 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-10-02 22:07 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: linux-pm, stable

Hi Sebastian,

On Wed, Sep 16, 2026 at 8:49 AM Linus Walleij <linusw@kernel.org> wrote:

> This series first fix two serious bugs in the AB8500/AB8505 charging
> code making the charging report bad figures. (It only affects the
> human-visible status, the charging itself is safe.) These two patches
> are tagged for stable.

Sorry for hammering, but can these be applied?

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging
  2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
                   ` (15 preceding siblings ...)
  2026-10-02 22:07 ` [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
@ 2026-10-04 19:33 ` Sebastian Reichel
  16 siblings, 0 replies; 18+ messages in thread
From: Sebastian Reichel @ 2026-10-04 19:33 UTC (permalink / raw)
  To: Sebastian Reichel, Linus Walleij; +Cc: linux-pm, stable


On Wed, 16 Sep 2026 08:49:22 +0200, Linus Walleij wrote:
> This series first fix two serious bugs in the AB8500/AB8505 charging
> code making the charging report bad figures. (It only affects the
> human-visible status, the charging itself is safe.) These two patches
> are tagged for stable.
> 
> The series then audits the AB8500 and AB8505 battery-charging stack
> against the revision 3 hardware manuals.
> 
> [...]

Applied, thanks!

[01/15] power: supply: ab8500_fg: Accept status from supplied power
        commit: 16f506a13b58a55308449bbcbec89c817d619ca2
[02/15] power: supply: ab8500_fg: Report sub-percent charge changes
        commit: 4d40dba1815d46c6ef9019ed80e3b998b0fc443d
[03/15] power: supply: ab8500: Correct register definitions
        commit: b4d3dc743df09cf467271e1490963ed9699ddfa1
[04/15] power: supply: ab8500_fg: Drop nonexistent AB8505 controls
        commit: 0bc9adf42698de962e845a9355b53bdbee5b5895
[05/15] power: supply: ab8500_btemp: Fix event temperature reporting
        commit: 95990cda8e42dda4bd3f1475d578323981bd8579
[06/15] power: supply: ab8500: Preserve battery termination current
        commit: 3b33eda4067494658d51db0792377fef98787e3f
[07/15] power: supply: ab8500_charger: Fix AC charger re-enable
        commit: 3e1d2517347f6bb72f74e63ec77d1634385fbf21
[08/15] power: supply: ab8500_charger: Decode AB8505 USB status
        commit: 2b697df2b6394613a3bb168adfa8a845ad7c83d9
[09/15] power: supply: ab8500_charger: Respect AB8505 register layout
        commit: dcecf527632297011153f088573689a1ad1de790
[10/15] power: supply: ab8500_charger: Handle detection errors
        commit: 1098c619ce7595a77a8c2e366a703e9fc5f29bf7
[11/15] power: supply: ab8500_charger: Skip absent AB8505 main charger
        commit: 52c8cdcc1146d86fae524a589d4512c8d59ea8b4
[12/15] power: supply: ab8500_charger: Retry AB8505 USB detection
        commit: 95fba2e3055df99ed19e7d6399101bac42b3e7d5
[13/15] power: supply: ab8500_charger: Recover inactive AB8505 charger
        commit: 76bdf9d78245bb068207c23e9bf43d4b9bb201c3
[14/15] power: supply: ab8500_fg: Finalize current measurements
        commit: 9a0461fb5e7cc64ed303bee3661452879ee139a1
[15/15] power: supply: ab8500_fg: Avoid reserved AB8505 CCConf bit
        commit: edafb2e4642b72b0f86ce5990d5c29c116bb642b

Best regards,
-- 
Sebastian Reichel <sebastian.reichel@collabora.com>


^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2026-10-04 19:33 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-16  6:49 [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
2026-09-16  6:49 ` [PATCH 01/15] power: supply: ab8500_fg: Accept status from supplied power Linus Walleij
2026-09-16  6:49 ` [PATCH 02/15] power: supply: ab8500_fg: Report sub-percent charge changes Linus Walleij
2026-09-16  6:49 ` [PATCH 03/15] power: supply: ab8500: Correct register definitions Linus Walleij
2026-09-16  6:49 ` [PATCH 04/15] power: supply: ab8500_fg: Drop nonexistent AB8505 controls Linus Walleij
2026-09-16  6:49 ` [PATCH 05/15] power: supply: ab8500_btemp: Fix event temperature reporting Linus Walleij
2026-09-16  6:49 ` [PATCH 06/15] power: supply: ab8500: Preserve battery termination current Linus Walleij
2026-09-16  6:49 ` [PATCH 07/15] power: supply: ab8500_charger: Fix AC charger re-enable Linus Walleij
2026-09-16  6:49 ` [PATCH 08/15] power: supply: ab8500_charger: Decode AB8505 USB status Linus Walleij
2026-09-16  6:49 ` [PATCH 09/15] power: supply: ab8500_charger: Respect AB8505 register layout Linus Walleij
2026-09-16  6:49 ` [PATCH 10/15] power: supply: ab8500_charger: Handle detection errors Linus Walleij
2026-09-16  6:49 ` [PATCH 11/15] power: supply: ab8500_charger: Skip absent AB8505 main charger Linus Walleij
2026-09-16  6:49 ` [PATCH 12/15] power: supply: ab8500_charger: Retry AB8505 USB detection Linus Walleij
2026-09-16  6:49 ` [PATCH 13/15] power: supply: ab8500_charger: Recover inactive AB8505 charger Linus Walleij
2026-09-16  6:49 ` [PATCH 14/15] power: supply: ab8500_fg: Finalize current measurements Linus Walleij
2026-09-16  6:49 ` [PATCH 15/15] power: supply: ab8500_fg: Avoid reserved AB8505 CCConf bit Linus Walleij
2026-10-02 22:07 ` [PATCH 00/15] power: supply: Fix AB8500 and AB8505 charging Linus Walleij
2026-10-04 19:33 ` Sebastian Reichel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox