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 08/15] power: supply: ab8500_charger: Decode AB8505 USB status
Date: Wed, 16 Sep 2026 08:49:30 +0200 [thread overview]
Message-ID: <20260916-ab8500-charging-v1-8-e986ed321cc6@kernel.org> (raw)
In-Reply-To: <20260916-ab8500-charging-v1-0-e986ed321cc6@kernel.org>
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
next prev 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 ` Linus Walleij [this message]
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
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-8-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