[PATCH] power: supply: qcom_battmgr: Add multi-port USB-C power supply support

From: Kamal Wadhwa

Date: Sat Aug 01 2026 - 03:53:40 EST


Extend the qcom_battmgr driver to report up to MAX_USB_PORTS (3)
USB-C power supply ports on the X1E80100 & Glymur platform, which
exposes more than one charger port to firmware.

At firmware-enable time, query USB_NUM_PORTS over the existing
BATTMGR_USB_PROPERTY_GET opcode to discover how many ports the
firmware actually reports, and register the additional
"qcom-battmgr-usb2"/"qcom-battmgr-usb3" power supplies only when the
firmware confirms a second/third port. Each additional port is polled
independently via new BATTMGR_USB2_PROPERTY_GET/SET (0xC0/0xC1) and
BATTMGR_USB3_PROPERTY_GET/SET (0xC2/0xC3) opcodes so its properties
are not aliased to the primary port's state.

Also add the POWER_SUPPLY_PROP_CAPACITY entry to x1e80100_bat_props[].

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Kamal Wadhwa <kamal.wadhwa@xxxxxxxxxxxxxxxx>
---
drivers/power/supply/qcom_battmgr.c | 353 +++++++++++++++++++++++++++++++++++-
1 file changed, 351 insertions(+), 2 deletions(-)

diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c
index 490137a23d00..17675a9975d6 100644
--- a/drivers/power/supply/qcom_battmgr.c
+++ b/drivers/power/supply/qcom_battmgr.c
@@ -26,6 +26,8 @@ enum qcom_battmgr_variant {
QCOM_BATTMGR_X1E80100,
};

+#define MAX_USB_PORTS 3
+
#define BATTMGR_BAT_STATUS 0x1

#define BATTMGR_REQUEST_NOTIFICATION 0x4
@@ -76,6 +78,10 @@ enum qcom_battmgr_variant {

#define BATTMGR_USB_PROPERTY_GET 0x32
#define BATTMGR_USB_PROPERTY_SET 0x33
+#define BATTMGR_USB2_PROPERTY_GET 0xC0
+#define BATTMGR_USB2_PROPERTY_SET 0xC1
+#define BATTMGR_USB3_PROPERTY_GET 0xC2
+#define BATTMGR_USB3_PROPERTY_SET 0xC3
#define USB_ONLINE 0
#define USB_VOLT_NOW 1
#define USB_VOLT_MAX 2
@@ -86,6 +92,13 @@ enum qcom_battmgr_variant {
#define USB_ADAP_TYPE 7
#define USB_MOISTURE_DET_EN 8
#define USB_MOISTURE_DET_STS 9
+#define USB_CONNECTOR_TEMP 10
+#define USB_REAL_TYPE 11
+#define USB_TYPEC_COMPLIANT 12
+#define USB_SCOPE 13
+#define USB_CONNECTOR_TYPE 14
+#define USB_F_ACTIVE 15
+#define USB_NUM_PORTS 16

#define BATTMGR_WLS_PROPERTY_GET 0x34
#define BATTMGR_WLS_PROPERTY_SET 0x35
@@ -297,6 +310,7 @@ struct qcom_battmgr_usb {
unsigned int current_max;
unsigned int current_limit;
unsigned int usb_type;
+ unsigned int num_ports;
};

struct qcom_battmgr_wireless {
@@ -316,6 +330,8 @@ struct qcom_battmgr {
struct power_supply *ac_psy;
struct power_supply *bat_psy;
struct power_supply *usb_psy;
+ struct power_supply *usb2_psy;
+ struct power_supply *usb3_psy;
struct power_supply *wls_psy;

enum qcom_battmgr_unit unit;
@@ -329,8 +345,12 @@ struct qcom_battmgr {
struct qcom_battmgr_status status;
struct qcom_battmgr_ac ac;
struct qcom_battmgr_usb usb;
+ struct qcom_battmgr_usb usb2;
+ struct qcom_battmgr_usb usb3;
struct qcom_battmgr_wireless wireless;

+ struct power_supply_config usb_psy_cfg;
+
struct work_struct enable_work;

/*
@@ -829,6 +849,7 @@ static const enum power_supply_property x1e80100_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
@@ -1042,8 +1063,136 @@ static int qcom_battmgr_usb_get_property(struct power_supply *psy,
return 0;
}

+static int qcom_battmgr_usb2_x1e80100_update(struct qcom_battmgr *battmgr,
+ enum power_supply_property psp)
+{
+ unsigned int prop;
+ int ret;
+
+ if (psp >= ARRAY_SIZE(sm8350_usb_prop_map))
+ return -EINVAL;
+
+ prop = sm8350_usb_prop_map[psp];
+
+ mutex_lock(&battmgr->lock);
+ ret = qcom_battmgr_request_property(battmgr, BATTMGR_USB2_PROPERTY_GET, prop, 0);
+ mutex_unlock(&battmgr->lock);
+
+ return ret;
+}
+
+static int qcom_battmgr_usb3_x1e80100_update(struct qcom_battmgr *battmgr,
+ enum power_supply_property psp)
+{
+ unsigned int prop;
+ int ret;
+
+ if (psp >= ARRAY_SIZE(sm8350_usb_prop_map))
+ return -EINVAL;
+
+ prop = sm8350_usb_prop_map[psp];
+
+ mutex_lock(&battmgr->lock);
+ ret = qcom_battmgr_request_property(battmgr, BATTMGR_USB3_PROPERTY_GET, prop, 0);
+ mutex_unlock(&battmgr->lock);
+
+ return ret;
+}
+
+static int qcom_battmgr_usb2_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy);
+ int ret;
+
+ if (!battmgr->service_up)
+ return -EAGAIN;
+
+ ret = qcom_battmgr_usb2_x1e80100_update(battmgr, psp);
+ if (ret)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = battmgr->usb2.online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = battmgr->usb2.voltage_now;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = battmgr->usb2.voltage_max;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = battmgr->usb2.current_now;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = battmgr->usb2.current_max;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ val->intval = battmgr->usb2.current_limit;
+ break;
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = battmgr->usb2.usb_type;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qcom_battmgr_usb3_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy);
+ int ret;
+
+ if (!battmgr->service_up)
+ return -EAGAIN;
+
+ ret = qcom_battmgr_usb3_x1e80100_update(battmgr, psp);
+ if (ret)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = battmgr->usb3.online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = battmgr->usb3.voltage_now;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = battmgr->usb3.voltage_max;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = battmgr->usb3.current_now;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = battmgr->usb3.current_max;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ val->intval = battmgr->usb3.current_limit;
+ break;
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = battmgr->usb3.usb_type;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static const enum power_supply_property sc8280xp_usb_props[] = {
POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_USB_TYPE,
};

static const struct power_supply_desc sc8280xp_usb_psy_desc = {
@@ -1064,6 +1213,42 @@ static const struct power_supply_desc sc8280xp_usb_psy_desc = {
BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID),
};

+static const struct power_supply_desc x1e80100_usb2_psy_desc = {
+ .name = "qcom-battmgr-usb2",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = sc8280xp_usb_props,
+ .num_properties = ARRAY_SIZE(sc8280xp_usb_props),
+ .get_property = qcom_battmgr_usb2_get_property,
+ .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) |
+ BIT(POWER_SUPPLY_USB_TYPE_SDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_DCP) |
+ BIT(POWER_SUPPLY_USB_TYPE_CDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_ACA) |
+ BIT(POWER_SUPPLY_USB_TYPE_C) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD_DRP) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD_PPS) |
+ BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID),
+};
+
+static const struct power_supply_desc x1e80100_usb3_psy_desc = {
+ .name = "qcom-battmgr-usb3",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = sc8280xp_usb_props,
+ .num_properties = ARRAY_SIZE(sc8280xp_usb_props),
+ .get_property = qcom_battmgr_usb3_get_property,
+ .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) |
+ BIT(POWER_SUPPLY_USB_TYPE_SDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_DCP) |
+ BIT(POWER_SUPPLY_USB_TYPE_CDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_ACA) |
+ BIT(POWER_SUPPLY_USB_TYPE_C) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD_DRP) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD_PPS) |
+ BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID),
+};
+
static const enum power_supply_property sm8350_usb_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
@@ -1212,6 +1397,10 @@ static void qcom_battmgr_notification(struct qcom_battmgr *battmgr,
break;
case NOTIF_USB_PROPERTY:
power_supply_changed(battmgr->usb_psy);
+ if (battmgr->usb2_psy)
+ power_supply_changed(battmgr->usb2_psy);
+ if (battmgr->usb3_psy)
+ power_supply_changed(battmgr->usb3_psy);
break;
case NOTIF_WLS_PROPERTY:
power_supply_changed(battmgr->wls_psy);
@@ -1260,6 +1449,7 @@ static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr,
unsigned int opcode = le32_to_cpu(resp->hdr.opcode);
unsigned int source;
unsigned int state;
+ unsigned int property;
size_t payload_len = len - sizeof(struct pmic_glink_hdr);

if (payload_len < sizeof(__le32)) {
@@ -1355,6 +1545,121 @@ static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr,
case BATTMGR_CHG_CTRL_LIMIT_EN:
battmgr->error = 0;
break;
+ case BATTMGR_USB_PROPERTY_GET:
+ property = le32_to_cpu(resp->intval.property);
+ if (payload_len != sizeof(resp->intval)) {
+ dev_warn(battmgr->dev,
+ "invalid payload length for %#x request: %zd\n",
+ property, payload_len);
+ battmgr->error = -ENODATA;
+ return;
+ }
+
+ switch (property) {
+ case USB_ONLINE:
+ battmgr->usb.online = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_VOLT_NOW:
+ battmgr->usb.voltage_now = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_VOLT_MAX:
+ battmgr->usb.voltage_max = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_CURR_NOW:
+ battmgr->usb.current_now = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_CURR_MAX:
+ battmgr->usb.current_max = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_INPUT_CURR_LIMIT:
+ battmgr->usb.current_limit = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_TYPE:
+ battmgr->usb.usb_type = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_NUM_PORTS:
+ battmgr->usb.num_ports = le32_to_cpu(resp->intval.value);
+ break;
+ default:
+ dev_warn(battmgr->dev, "unknown property %#x\n", property);
+ break;
+ }
+ break;
+
+ case BATTMGR_USB2_PROPERTY_GET:
+ property = le32_to_cpu(resp->intval.property);
+ if (payload_len != sizeof(resp->intval)) {
+ dev_warn(battmgr->dev,
+ "invalid payload length for %#x request: %zd\n",
+ property, payload_len);
+ battmgr->error = -ENODATA;
+ return;
+ }
+
+ switch (property) {
+ case USB_ONLINE:
+ battmgr->usb2.online = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_VOLT_NOW:
+ battmgr->usb2.voltage_now = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_VOLT_MAX:
+ battmgr->usb2.voltage_max = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_CURR_NOW:
+ battmgr->usb2.current_now = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_CURR_MAX:
+ battmgr->usb2.current_max = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_INPUT_CURR_LIMIT:
+ battmgr->usb2.current_limit = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_TYPE:
+ battmgr->usb2.usb_type = le32_to_cpu(resp->intval.value);
+ break;
+ default:
+ dev_warn(battmgr->dev, "unknown property %#x\n", property);
+ break;
+ }
+ break;
+ case BATTMGR_USB3_PROPERTY_GET:
+ property = le32_to_cpu(resp->intval.property);
+ if (payload_len != sizeof(resp->intval)) {
+ dev_warn(battmgr->dev,
+ "invalid payload length for %#x request: %zd\n",
+ property, payload_len);
+ battmgr->error = -ENODATA;
+ return;
+ }
+
+ switch (property) {
+ case USB_ONLINE:
+ battmgr->usb3.online = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_VOLT_NOW:
+ battmgr->usb3.voltage_now = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_VOLT_MAX:
+ battmgr->usb3.voltage_max = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_CURR_NOW:
+ battmgr->usb3.current_now = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_CURR_MAX:
+ battmgr->usb3.current_max = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_INPUT_CURR_LIMIT:
+ battmgr->usb3.current_limit = le32_to_cpu(resp->intval.value);
+ break;
+ case USB_TYPE:
+ battmgr->usb3.usb_type = le32_to_cpu(resp->intval.value);
+ break;
+ default:
+ dev_warn(battmgr->dev, "unknown property %#x\n", property);
+ break;
+ }
+ break;
default:
dev_warn(battmgr->dev, "unknown message %#x\n", opcode);
break;
@@ -1583,6 +1888,8 @@ static void qcom_battmgr_callback(const void *data, size_t len, void *priv)
qcom_battmgr_sm8350_callback(battmgr, data, len);
}

+static char *qcom_battmgr_battery[] = { "battery" };
+
static void qcom_battmgr_enable_worker(struct work_struct *work)
{
struct qcom_battmgr *battmgr = container_of(work, struct qcom_battmgr, enable_work);
@@ -1591,11 +1898,53 @@ static void qcom_battmgr_enable_worker(struct work_struct *work)
.hdr.type = cpu_to_le32(PMIC_GLINK_NOTIFY),
.hdr.opcode = cpu_to_le32(BATTMGR_REQUEST_NOTIFICATION),
};
+ struct power_supply *psy;
int ret;
+ int num_ports_fw = 0;

ret = qcom_battmgr_request(battmgr, &req, sizeof(req));
if (ret)
dev_err(battmgr->dev, "failed to request power notifications\n");
+
+ if (battmgr->variant == QCOM_BATTMGR_X1E80100) {
+ mutex_lock(&battmgr->lock);
+ ret = qcom_battmgr_request_property(battmgr, BATTMGR_USB_PROPERTY_GET,
+ USB_NUM_PORTS, 0);
+ mutex_unlock(&battmgr->lock);
+ if (ret < 0) {
+ dev_dbg(battmgr->dev, "Failed to read USB_NUM_PORTS from SoCCP, rc=%d\n",
+ ret);
+ } else {
+ num_ports_fw = battmgr->usb.num_ports;
+ if (num_ports_fw > MAX_USB_PORTS) {
+ dev_err(battmgr->dev, "USB ports reported by SoCCP: %d exceeds max %d\n",
+ num_ports_fw, MAX_USB_PORTS);
+ num_ports_fw = MAX_USB_PORTS;
+ }
+ }
+
+ if (num_ports_fw >= 2 && !battmgr->usb2_psy) {
+ psy = devm_power_supply_register(battmgr->dev, &x1e80100_usb2_psy_desc,
+ &battmgr->usb_psy_cfg);
+ if (IS_ERR(psy)) {
+ dev_err(battmgr->dev, "failed to register USB port-1 power supply: %ld\n",
+ PTR_ERR(psy));
+ } else {
+ battmgr->usb2_psy = psy;
+ }
+ }
+
+ if (num_ports_fw >= 3 && !battmgr->usb3_psy) {
+ psy = devm_power_supply_register(battmgr->dev, &x1e80100_usb3_psy_desc,
+ &battmgr->usb_psy_cfg);
+ if (IS_ERR(psy)) {
+ dev_err(battmgr->dev, "failed to register USB port-2 power supply: %ld\n",
+ PTR_ERR(psy));
+ } else {
+ battmgr->usb3_psy = psy;
+ }
+ }
+ }
}

static void qcom_battmgr_pdr_notify(void *priv, int state)
@@ -1621,8 +1970,6 @@ static const struct of_device_id qcom_battmgr_of_variants[] = {
{}
};

-static char *qcom_battmgr_battery[] = { "battery" };
-
static int qcom_battmgr_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
@@ -1648,6 +1995,8 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev,
psy_cfg_supply.supplied_to = qcom_battmgr_battery;
psy_cfg_supply.num_supplicants = 1;

+ battmgr->usb_psy_cfg = psy_cfg_supply;
+
INIT_WORK(&battmgr->enable_work, qcom_battmgr_enable_worker);
mutex_init(&battmgr->lock);
init_completion(&battmgr->ack);

---
base-commit: 5c73cd9f0819c1c44e373e3dabb68318b1de1a12
change-id: 20260801-b4-battmgr-multiport-usb-46eb0798668f

Best regards,
--
Kamal Wadhwa <kamal.wadhwa@xxxxxxxxxxxxxxxx>