[PATCH 2/2] platform/x86: ideapad-laptop: Add charge_types:Fast (Rapid Charge)

Rong Zhang posted 2 patches 3 months, 3 weeks ago
There is a newer version of this series
[PATCH 2/2] platform/x86: ideapad-laptop: Add charge_types:Fast (Rapid Charge)
Posted by Rong Zhang 3 months, 3 weeks ago
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
Re: [PATCH 2/2] platform/x86: ideapad-laptop: Add charge_types:Fast (Rapid Charge)
Posted by Mark Pearson 3 months, 2 weeks ago
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>