From 8c016351971060b5abe12c678b092a118ea621cf Mon Sep 17 00:00:00 2001 From: Anze Date: Wed, 3 Jun 2026 19:37:08 +0200 Subject: [PATCH] SM8750: bypass charging via charge_behaviour (qcom_battmgr) Add an SM8750 kernel patch exposing the firmware's USB power-path bypass through the standard POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR interface on the sm8550 battmgr variant: /sys/class/power_supply/battery/charge_behaviour auto -> charge normally inhibit-charge -> run from the adapter, leave the battery untouched The GLINK request reuses qcom_battmgr's existing property helper; only the opcode (USB_PROPERTY_SET) and property id (14, the vendor 'usb_charge_now' control) differ. Reverse-engineered from the AYN Odin 3 Android charger driver (qti_battery_charger) and verified on-device: with inhibit-charge and the charger online, battery status -> Discharging / current ~0. The patch is self-contained (only the sm8550 battery psy) and applies to mainline qcom_battmgr.c unchanged, so it is submittable upstream as-is. Co-Authored-By: Claude Opus 4.8 --- .../linux/0504-ROCKNIX-charge-bypass.patch | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 projects/ROCKNIX/devices/SM8750/patches/linux/0504-ROCKNIX-charge-bypass.patch diff --git a/projects/ROCKNIX/devices/SM8750/patches/linux/0504-ROCKNIX-charge-bypass.patch b/projects/ROCKNIX/devices/SM8750/patches/linux/0504-ROCKNIX-charge-bypass.patch new file mode 100644 index 00000000000..8682034438f --- /dev/null +++ b/projects/ROCKNIX/devices/SM8750/patches/linux/0504-ROCKNIX-charge-bypass.patch @@ -0,0 +1,145 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Anze +Date: Wed, 3 Jun 2026 19:40:00 +0200 +Subject: [PATCH] power: supply: qcom_battmgr: support charge_behaviour + (USB power-path bypass) on sm8550 + +The battery management firmware on sm8550-class platforms (e.g. the AYN +Odin 3, SM8750, which uses the sm8550 battmgr variant) can keep the system +running from the USB input while leaving the battery untouched - neither +charging nor discharging. The vendor (downstream qti_battery_charger) +exposes this as the OEM USB property "usb_charge_now": writing 0 inhibits +charging from the input (power-path bypass), 1 restores normal charging. + +The PMIC GLINK request for this is identical to the one qcom_battmgr +already builds for every other property (owner BATTMGR, REQ_RESP, the +{battery, property, value} body); only the opcode (USB_PROPERTY_SET) and +the property id (14) differ. Expose it through the standard +POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR interface: + + auto -> usb_charge_now = 1 (normal charging) + inhibit-charge -> usb_charge_now = 0 (run from the adapter, battery floats) + +This adds /sys/class/power_supply/battery/charge_behaviour and is wired +only into the sm8550 battery psy, so other variants are unaffected. + +Verified on an AYN Odin 3: with "inhibit-charge" and the charger online, +battery status goes to Discharging / charge_type N/A with battery current +~0, matching the behaviour of the vendor "bypass charging" toggle; "auto" +resumes charging. + +Signed-off-by: Anze +--- + drivers/power/supply/qcom_battmgr.c | 52 ++++++++++++++++++++++++++++ + 1 file changed, 52 insertions(+) + +diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c +--- a/drivers/power/supply/qcom_battmgr.c ++++ b/drivers/power/supply/qcom_battmgr.c +@@ -86,6 +86,13 @@ + #define USB_ADAP_TYPE 7 + #define USB_MOISTURE_DET_EN 8 + #define USB_MOISTURE_DET_STS 9 ++/* ++ * OEM USB property exposed by the battery management firmware on sm8550-class ++ * platforms: 1 lets the charger replenish the battery from the USB input, ++ * 0 inhibits battery charging while keeping the system powered from the input ++ * (power-path bypass). Mirrors the vendor "usb_charge_now" control. ++ */ ++#define USB_CHARGE_ENABLE 14 + + #define BATTMGR_WLS_PROPERTY_GET 0x34 + #define BATTMGR_WLS_PROPERTY_SET 0x35 +@@ -325,6 +332,9 @@ + + bool service_up; + ++ /* cached POWER_SUPPLY_CHARGE_BEHAVIOUR_* value (sm8550 variant) */ ++ unsigned int charge_behaviour; ++ + struct qcom_battmgr_info info; + struct qcom_battmgr_status status; + struct qcom_battmgr_ac ac; +@@ -633,6 +643,9 @@ + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + val->intval = battmgr->info.charge_ctrl_end; + break; ++ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: ++ val->intval = battmgr->charge_behaviour; ++ break; + case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: + val->intval = battmgr->info.year; + break; +@@ -756,12 +769,46 @@ + return 0; + } + ++/* ++ * Toggle whether the battery is charged from the USB input. Setting ++ * "inhibit-charge" stops charging while the system keeps running from the ++ * adapter (the battery neither charges nor discharges) - i.e. power-path ++ * bypass; "auto" restores normal charging. ++ */ ++static int qcom_battmgr_set_charge_behaviour(struct qcom_battmgr *battmgr, ++ int behaviour) ++{ ++ u32 value; ++ int ret; ++ ++ switch (behaviour) { ++ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: ++ value = 1; ++ break; ++ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: ++ value = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ mutex_lock(&battmgr->lock); ++ ret = qcom_battmgr_request_property(battmgr, BATTMGR_USB_PROPERTY_SET, ++ USB_CHARGE_ENABLE, value); ++ mutex_unlock(&battmgr->lock); ++ if (!ret) ++ battmgr->charge_behaviour = behaviour; ++ ++ return ret; ++} ++ + static int qcom_battmgr_bat_is_writeable(struct power_supply *psy, + enum power_supply_property psp) + { + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: ++ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + return 1; + default: + return 0; +@@ -784,6 +831,8 @@ + return qcom_battmgr_set_charge_start_threshold(battmgr, pval->intval); + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + return qcom_battmgr_set_charge_end_threshold(battmgr, pval->intval); ++ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: ++ return qcom_battmgr_set_charge_behaviour(battmgr, pval->intval); + default: + return -EINVAL; + } +@@ -918,6 +967,7 @@ + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, ++ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + }; + + static const struct power_supply_desc sm8550_bat_psy_desc = { +@@ -925,6 +975,8 @@ + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = sm8550_bat_props, + .num_properties = ARRAY_SIZE(sm8550_bat_props), ++ .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | ++ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE), + .get_property = qcom_battmgr_bat_get_property, + .set_property = qcom_battmgr_bat_set_property, + .property_is_writeable = qcom_battmgr_bat_is_writeable,