]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
platform/x86: ideapad-laptop: Add charge_types:Fast (Rapid Charge)
authorRong Zhang <i@rong.moe>
Wed, 5 Nov 2025 18:28:27 +0000 (02:28 +0800)
committerIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Mon, 10 Nov 2025 09:55:43 +0000 (11:55 +0200)
The GBMD/SBMC interface on recent devices supports Rapid Charge mode
(charge_types: Fast) in addition to Conservation Mode (charge_types:
Long_Life).

Query the GBMD interface on probe to determine if a device supports
Rapid Charge. If so, expose these two modes while carefully maintaining
their mutually exclusive state, which aligns with the behavior of
manufacturer utilities on Windows.

Signed-off-by: Rong Zhang <i@rong.moe>
Acked-by: Ike Panhc <ikepanhc@gmail.com>
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Tested-By: Jelle van der Waa <jelle@vdwaa.nl>
Link: https://patch.msgid.link/20251105182832.104946-5-i@rong.moe
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
drivers/platform/x86/lenovo/ideapad-laptop.c

index af89063108bea64d7ac81956d95461ab9c4d28f1..5171a077f62c3e8dd4eb84bbd8112a4fb5ca482e 100644 (file)
@@ -63,13 +63,27 @@ enum {
        CFG_OSD_CAM_BIT      = 31,
 };
 
+/*
+ * There are two charge modes supported by the GBMD/SBMC interface:
+ * - "Rapid Charge": increase power to speed up charging
+ * - "Conservation Mode": stop charging at 60-80% (depends on model)
+ *
+ * The interface doesn't prohibit enabling both modes at the same time.
+ * However, doing so is essentially meaningless, and the manufacturer utilities
+ * on Windows always make them mutually exclusive.
+ */
+
 enum {
+       GBMD_RAPID_CHARGE_STATE_BIT = 2,
        GBMD_CONSERVATION_STATE_BIT = 5,
+       GBMD_RAPID_CHARGE_SUPPORTED_BIT = 17,
 };
 
 enum {
        SBMC_CONSERVATION_ON  = 3,
        SBMC_CONSERVATION_OFF = 5,
+       SBMC_RAPID_CHARGE_ON  = 7,
+       SBMC_RAPID_CHARGE_OFF = 8,
 };
 
 enum {
@@ -172,6 +186,7 @@ struct ideapad_private {
        unsigned long cfg;
        unsigned long r_touchpad_val;
        struct {
+               bool rapid_charge         : 1;
                bool conservation_mode    : 1;
                bool dytc                 : 1;
                bool fan_mode             : 1;
@@ -634,6 +649,10 @@ static ssize_t conservation_mode_show(struct device *dev,
                        return err;
        }
 
+       /*
+        * For backward compatibility, ignore Rapid Charge while reporting the
+        * state of Conservation Mode.
+        */
        return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result));
 }
 
@@ -653,6 +672,16 @@ static ssize_t conservation_mode_store(struct device *dev,
 
        guard(mutex)(&priv->gbmd_sbmc_mutex);
 
+       /*
+        * Prevent mutually exclusive modes from being set at the same time,
+        * but do not disable Rapid Charge while disabling Conservation Mode.
+        */
+       if (priv->features.rapid_charge && state) {
+               err = exec_sbmc(priv->adev->handle, SBMC_RAPID_CHARGE_OFF);
+               if (err)
+                       return err;
+       }
+
        err = exec_sbmc(priv->adev->handle, state ? SBMC_CONSERVATION_ON : SBMC_CONSERVATION_OFF);
        if (err)
                return err;
@@ -2017,14 +2046,24 @@ static int ideapad_psy_ext_set_prop(struct power_supply *psy,
                                    const union power_supply_propval *val)
 {
        struct ideapad_private *priv = ext_data;
-       unsigned long op;
+       unsigned long op1, op2;
+       int err;
 
        switch (val->intval) {
+       case POWER_SUPPLY_CHARGE_TYPE_FAST:
+               if (WARN_ON(!priv->features.rapid_charge))
+                       return -EINVAL;
+
+               op1 = SBMC_CONSERVATION_OFF;
+               op2 = SBMC_RAPID_CHARGE_ON;
+               break;
        case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
-               op = SBMC_CONSERVATION_ON;
+               op1 = SBMC_RAPID_CHARGE_OFF;
+               op2 = SBMC_CONSERVATION_ON;
                break;
        case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
-               op = SBMC_CONSERVATION_OFF;
+               op1 = SBMC_RAPID_CHARGE_OFF;
+               op2 = SBMC_CONSERVATION_OFF;
                break;
        default:
                return -EINVAL;
@@ -2032,7 +2071,14 @@ static int ideapad_psy_ext_set_prop(struct power_supply *psy,
 
        guard(mutex)(&priv->gbmd_sbmc_mutex);
 
-       return exec_sbmc(priv->adev->handle, op);
+       /* If !rapid_charge, op1 must be SBMC_RAPID_CHARGE_OFF. Skip it. */
+       if (priv->features.rapid_charge) {
+               err = exec_sbmc(priv->adev->handle, op1);
+               if (err)
+                       return err;
+       }
+
+       return exec_sbmc(priv->adev->handle, op2);
 }
 
 static int ideapad_psy_ext_get_prop(struct power_supply *psy,
@@ -2042,6 +2088,7 @@ static int ideapad_psy_ext_get_prop(struct power_supply *psy,
                                    union power_supply_propval *val)
 {
        struct ideapad_private *priv = ext_data;
+       bool is_rapid_charge, is_conservation;
        unsigned long result;
        int err;
 
@@ -2051,7 +2098,19 @@ static int ideapad_psy_ext_get_prop(struct power_supply *psy,
                        return err;
        }
 
-       if (test_bit(GBMD_CONSERVATION_STATE_BIT, &result))
+       is_rapid_charge = (priv->features.rapid_charge &&
+                          test_bit(GBMD_RAPID_CHARGE_STATE_BIT, &result));
+       is_conservation = test_bit(GBMD_CONSERVATION_STATE_BIT, &result);
+
+       if (unlikely(is_rapid_charge && is_conservation)) {
+               dev_err(&priv->platform_device->dev,
+                       "unexpected charge_types: both [Fast] and [Long_Life] are enabled\n");
+               return -EINVAL;
+       }
+
+       if (is_rapid_charge)
+               val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+       else if (is_conservation)
                val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
        else
                val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
@@ -2087,6 +2146,12 @@ DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v1,
         BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
 );
 
+DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v2,
+       (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+        BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
+        BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
+);
+
 static int ideapad_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
 {
        struct ideapad_private *priv = container_of(hook, struct ideapad_private, battery_hook);
@@ -2125,17 +2190,25 @@ static int ideapad_check_features(struct ideapad_private *priv)
                priv->features.fan_mode = true;
 
        if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
-               priv->features.conservation_mode = true;
-
-               priv->battery_ext = &ideapad_battery_ext_v1;
-
-               priv->battery_hook.add_battery = ideapad_battery_add;
-               priv->battery_hook.remove_battery = ideapad_battery_remove;
-               priv->battery_hook.name = "Ideapad Battery Extension";
-
-               err = devm_battery_hook_register(&priv->platform_device->dev, &priv->battery_hook);
-               if (err)
-                       return err;
+               /* Not acquiring gbmd_sbmc_mutex as race condition is impossible on init */
+               if (!eval_gbmd(handle, &val)) {
+                       priv->features.conservation_mode = true;
+                       priv->features.rapid_charge = test_bit(GBMD_RAPID_CHARGE_SUPPORTED_BIT,
+                                                              &val);
+
+                       priv->battery_ext = priv->features.rapid_charge
+                                           ? &ideapad_battery_ext_v2
+                                           : &ideapad_battery_ext_v1;
+
+                       priv->battery_hook.add_battery = ideapad_battery_add;
+                       priv->battery_hook.remove_battery = ideapad_battery_remove;
+                       priv->battery_hook.name = "Ideapad Battery Extension";
+
+                       err = devm_battery_hook_register(&priv->platform_device->dev,
+                                                        &priv->battery_hook);
+                       if (err)
+                               return err;
+               }
        }
 
        if (acpi_has_method(handle, "DYTC"))