The GBMD/SBMC interface supports Rapid Charge mode (charge_types: Fast)
in addition to Conservation Mode (charge_types: Long_Life).
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>
---
drivers/platform/x86/lenovo/ideapad-laptop.c | 61 ++++++++++++++++++--
1 file changed, 56 insertions(+), 5 deletions(-)
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 9f956f51ec8db..d2bfaa532020a 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -62,13 +62,26 @@ 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,
};
enum {
SBMC_CONSERVATION_ON = 3,
SBMC_CONSERVATION_OFF = 5,
+ SBMC_RAPID_CHARGE_ON = 7,
+ SBMC_RAPID_CHARGE_OFF = 8,
};
enum {
@@ -632,6 +645,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));
}
@@ -651,6 +668,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 (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;
@@ -2015,14 +2042,21 @@ 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:
+ 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;
@@ -2030,7 +2064,11 @@ static int ideapad_psy_ext_set_prop(struct power_supply *psy,
guard(mutex)(&priv->gbmd_sbmc_mutex);
- return exec_sbmc(priv->adev->handle, op);
+ 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 +2080,7 @@ static int ideapad_psy_ext_get_prop(struct power_supply *psy,
struct ideapad_private *priv = ext_data;
unsigned long result;
int err;
+ bool is_rapid_charge, is_conservation;
scoped_guard(mutex, &priv->gbmd_sbmc_mutex) {
err = eval_gbmd(priv->adev->handle, &result);
@@ -2049,7 +2088,18 @@ static int ideapad_psy_ext_get_prop(struct power_supply *psy,
return err;
}
- if (test_bit(GBMD_CONSERVATION_STATE_BIT, &result))
+ is_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;
@@ -2074,6 +2124,7 @@ static const struct power_supply_ext ideapad_battery_ext = {
.properties = ideapad_power_supply_props,
.num_properties = ARRAY_SIZE(ideapad_power_supply_props),
.charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
.get_property = ideapad_psy_ext_get_prop,
.set_property = ideapad_psy_ext_set_prop,
--
2.51.0
Hi Rong
On Mon, Oct 20, 2025, at 9:24 PM, Rong Zhang wrote:
> The GBMD/SBMC interface supports Rapid Charge mode (charge_types: Fast)
> in addition to Conservation Mode (charge_types: Long_Life).
>
> 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>
> ---
> drivers/platform/x86/lenovo/ideapad-laptop.c | 61 ++++++++++++++++++--
> 1 file changed, 56 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c
> b/drivers/platform/x86/lenovo/ideapad-laptop.c
> index 9f956f51ec8db..d2bfaa532020a 100644
> --- a/drivers/platform/x86/lenovo/ideapad-laptop.c
> +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
> @@ -62,13 +62,26 @@ 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,
> };
>
> enum {
> SBMC_CONSERVATION_ON = 3,
> SBMC_CONSERVATION_OFF = 5,
> + SBMC_RAPID_CHARGE_ON = 7,
> + SBMC_RAPID_CHARGE_OFF = 8,
> };
>
> enum {
> @@ -632,6 +645,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));
> }
>
> @@ -651,6 +668,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 (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;
> @@ -2015,14 +2042,21 @@ 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:
> + 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;
> @@ -2030,7 +2064,11 @@ static int ideapad_psy_ext_set_prop(struct
> power_supply *psy,
>
> guard(mutex)(&priv->gbmd_sbmc_mutex);
>
> - return exec_sbmc(priv->adev->handle, op);
> + 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 +2080,7 @@ static int ideapad_psy_ext_get_prop(struct
> power_supply *psy,
> struct ideapad_private *priv = ext_data;
> unsigned long result;
> int err;
> + bool is_rapid_charge, is_conservation;
>
> scoped_guard(mutex, &priv->gbmd_sbmc_mutex) {
> err = eval_gbmd(priv->adev->handle, &result);
> @@ -2049,7 +2088,18 @@ static int ideapad_psy_ext_get_prop(struct
> power_supply *psy,
> return err;
> }
>
> - if (test_bit(GBMD_CONSERVATION_STATE_BIT, &result))
> + is_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;
> @@ -2074,6 +2124,7 @@ static const struct power_supply_ext
> ideapad_battery_ext = {
> .properties = ideapad_power_supply_props,
> .num_properties = ARRAY_SIZE(ideapad_power_supply_props),
> .charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
> + BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
> BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
> .get_property = ideapad_psy_ext_get_prop,
> .set_property = ideapad_psy_ext_set_prop,
> --
> 2.51.0
I can't comment on the functionality as I don't have hooks into the Ideapad team to confirm - but the implementation looks good to me.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
© 2016 - 2026 Red Hat, Inc.