Linux Power Management development
 help / color / mirror / Atom feed
From: Linus Walleij <linusw@kernel.org>
To: Sebastian Reichel <sre@kernel.org>
Cc: linux-pm@vger.kernel.org, Linus Walleij <linusw@kernel.org>
Subject: [PATCH 12/15] power: supply: ab8500_charger: Retry AB8505 USB detection
Date: Wed, 16 Sep 2026 08:49:34 +0200	[thread overview]
Message-ID: <20260916-ab8500-charging-v1-12-e986ed321cc6@kernel.org> (raw)
In-Reply-To: <20260916-ab8500-charging-v1-0-e986ed321cc6@kernel.org>

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


  parent reply	other threads:[~2026-09-16  6:49 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Linus Walleij [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260916-ab8500-charging-v1-12-e986ed321cc6@kernel.org \
    --to=linusw@kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=sre@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox