Unverified Commit 90430ea9 authored by Rong Zhang's avatar Rong Zhang Committed by Ilpo Järvinen
Browse files

platform/x86: ideapad-laptop: Add charge_types:Fast (Rapid Charge)



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: default avatarRong Zhang <i@rong.moe>
Acked-by: default avatarIke Panhc <ikepanhc@gmail.com>
Reviewed-by: default avatarMark Pearson <mpearson-lenovo@squebb.ca>
Tested-By: default avatarJelle van der Waa <jelle@vdwaa.nl>
Link: https://patch.msgid.link/20251105182832.104946-5-i@rong.moe


Reviewed-by: default avatarIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: default avatarIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
parent 5c54ece0
Loading
Loading
Loading
Loading
+89 −16
Original line number Diff line number Diff line
@@ -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,18 +2190,26 @@ 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")) {
		/* 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 = &ideapad_battery_ext_v1;
			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);
			err = devm_battery_hook_register(&priv->platform_device->dev,
							 &priv->battery_hook);
			if (err)
				return err;
		}
	}

	if (acpi_has_method(handle, "DYTC"))
		priv->features.dytc = true;