In the following changes, charge_types will be checked before registering the battery hook. Factor out charge_types get/set methods for internal use. Fixes: 90430ea98f55 ("platform/x86: ideapad-laptop: Add charge_types:Fast (Rapid Charge)") Cc: stable@vger.kernel.org Signed-off-by: Rong Zhang --- drivers/platform/x86/lenovo/ideapad-laptop.c | 57 +++++++++++++++++----------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c index 8213524504ee..40dab4d321a4 100644 --- a/drivers/platform/x86/lenovo/ideapad-laptop.c +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c @@ -2032,17 +2032,13 @@ static const struct dmi_system_id ctrl_ps2_aux_port_list[] = { {} }; -static int ideapad_psy_ext_set_prop(struct power_supply *psy, - const struct power_supply_ext *ext, - void *ext_data, - enum power_supply_property psp, - const union power_supply_propval *val) +static int ideapad_set_charge_types(struct ideapad_private *priv, + enum power_supply_charge_type charge_type) { - struct ideapad_private *priv = ext_data; unsigned long op1, op2; int err; - switch (val->intval) { + switch (charge_type) { case POWER_SUPPLY_CHARGE_TYPE_FAST: if (WARN_ON(!priv->features.rapid_charge)) return -EINVAL; @@ -2074,22 +2070,19 @@ static int ideapad_psy_ext_set_prop(struct power_supply *psy, return exec_sbmc(priv->adev->handle, op2); } -static int ideapad_psy_ext_get_prop(struct power_supply *psy, - const struct power_supply_ext *ext, - void *ext_data, - enum power_supply_property psp, - union power_supply_propval *val) +static int ideapad_psy_ext_set_prop(struct power_supply *psy, const struct power_supply_ext *ext, + void *ext_data, enum power_supply_property psp, + const union power_supply_propval *val) { struct ideapad_private *priv = ext_data; - bool is_rapid_charge, is_conservation; - unsigned long result; - int err; - scoped_guard(mutex, &priv->gbmd_sbmc_mutex) { - err = eval_gbmd(priv->adev->handle, &result); - if (err) - return err; - } + return ideapad_set_charge_types(priv, val->intval); +} + +static int ideapad_get_charge_types(struct ideapad_private *priv, unsigned long result, + enum power_supply_charge_type *charge_type) +{ + bool is_rapid_charge, is_conservation; is_rapid_charge = (priv->features.rapid_charge && test_bit(GBMD_RAPID_CHARGE_STATE_BIT, &result)); @@ -2102,15 +2095,33 @@ static int ideapad_psy_ext_get_prop(struct power_supply *psy, } if (is_rapid_charge) - val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + *charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST; else if (is_conservation) - val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; + *charge_type = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; else - val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + *charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD; return 0; } +static int ideapad_psy_ext_get_prop(struct power_supply *psy, const struct power_supply_ext *ext, + void *ext_data, enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ideapad_private *priv = ext_data; + unsigned long result; + int err; + + scoped_guard(mutex, &priv->gbmd_sbmc_mutex) { + err = eval_gbmd(priv->adev->handle, &result); + if (err) + return err; + } + + return ideapad_get_charge_types(priv, result, + (enum power_supply_charge_type *)&val->intval); +} + static int ideapad_psy_prop_is_writeable(struct power_supply *psy, const struct power_supply_ext *ext, void *data, -- 2.55.0 It was reported [1] that some unfortunate combinations of BIOS and Lenovo utilities (e.g., Legion Go 2 + Legion Space) may enable Rapid Charge and Conservation Mode at the same time, which is very out of sepc. If both are enabled during a previous Windows boot, ideapad-laptop will see both bits set and correctly shout "unexpected charge_types". A planned BIOS update will address this. Before that, if unexpected charge_types are encountered on probe, enforce [Long_Life] to prevent the device from operating in an out-of-spec state. On check or fix failure, skip registering the battery hook and the conservation_mode attribute. Note: the switches never enable themselves autonomously, so there is no need to fix it during runtime. Fixes: 90430ea98f55 ("platform/x86: ideapad-laptop: Add charge_types:Fast (Rapid Charge)") Cc: stable@vger.kernel.org Closes: https://github.com/ValveSoftware/SteamOS/issues/2876 [1] Signed-off-by: Rong Zhang --- drivers/platform/x86/lenovo/ideapad-laptop.c | 56 +++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c index 40dab4d321a4..31f74a2021b1 100644 --- a/drivers/platform/x86/lenovo/ideapad-laptop.c +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c @@ -2156,6 +2156,50 @@ DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v2, BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) ); +static int ideapad_battery_check(struct ideapad_private *priv) +{ + enum power_supply_charge_type charge_type; + unsigned long val; + int ret; + + /* Not acquiring gbmd_sbmc_mutex as race condition is impossible on init. */ + ret = eval_gbmd(priv->adev->handle, &val); + if (ret) + return ret; + + priv->features.conservation_mode = true; + priv->features.rapid_charge = test_bit(GBMD_RAPID_CHARGE_SUPPORTED_BIT, &val); + + ret = priv->features.rapid_charge ? ideapad_get_charge_types(priv, val, &charge_type) : 0; + if (ret) { + /* + * Some unfortunate combinations of BIOS and Lenovo utilities + * (e.g., Legion Go 2 + Legion Space) may enable Rapid Charge + * and Conservation Mode at the same time, which is very out of + * sepc. + * + * If both are enabled during a previous Windows boot, we will + * see both bits set. + * + * Conservation Mode should override Rapid Charge. Before a BIOS + * update addresses this, enforce it by selecting [Long_Life]. + */ + dev_notice(&priv->platform_device->dev, + "selecting [Long_life] to fix unexpected charge_types\n"); + + ret = ideapad_set_charge_types(priv, POWER_SUPPLY_CHARGE_TYPE_LONGLIFE); + if (ret) { + dev_err(&priv->platform_device->dev, + "failed to fix unexpected charge_types: %d\n", ret); + return ret; + } + + /* Best-effort only. No read back. */ + } + + return 0; +} + 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); @@ -2194,12 +2238,12 @@ 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); - + err = ideapad_battery_check(priv); + if (err) { + /* Continue with no battery hook or conservation_mode attribute. */ + priv->features.conservation_mode = false; + priv->features.rapid_charge = false; + } else { priv->battery_ext = priv->features.rapid_charge ? &ideapad_battery_ext_v2 : &ideapad_battery_ext_v1; -- 2.55.0