diff options
| author | Rong Zhang <i@rong.moe> | 2025-11-06 02:28:27 +0800 |
|---|---|---|
| committer | Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> | 2025-11-10 11:55:43 +0200 |
| commit | 90430ea98f5585270bc185cee548154d239af8d7 (patch) | |
| tree | f5049dd58e922f63d3deaec4d17e1aa1eae3ae89 | |
| parent | 5c54ece0476638f7c5fc655c655e721286a26bf1 (diff) | |
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: 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>
| -rw-r--r-- | drivers/platform/x86/lenovo/ideapad-laptop.c | 105 |
1 files changed, 89 insertions, 16 deletions
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c index af89063108be..5171a077f62c 100644 --- a/drivers/platform/x86/lenovo/ideapad-laptop.c +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c @@ -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")) |
