Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Rong Zhang and committed by
Ilpo Järvinen
90430ea9 5c54ece0

+86 -13
+86 -13
drivers/platform/x86/lenovo/ideapad-laptop.c
··· 63 63 CFG_OSD_CAM_BIT = 31, 64 64 }; 65 65 66 + /* 67 + * There are two charge modes supported by the GBMD/SBMC interface: 68 + * - "Rapid Charge": increase power to speed up charging 69 + * - "Conservation Mode": stop charging at 60-80% (depends on model) 70 + * 71 + * The interface doesn't prohibit enabling both modes at the same time. 72 + * However, doing so is essentially meaningless, and the manufacturer utilities 73 + * on Windows always make them mutually exclusive. 74 + */ 75 + 66 76 enum { 77 + GBMD_RAPID_CHARGE_STATE_BIT = 2, 67 78 GBMD_CONSERVATION_STATE_BIT = 5, 79 + GBMD_RAPID_CHARGE_SUPPORTED_BIT = 17, 68 80 }; 69 81 70 82 enum { 71 83 SBMC_CONSERVATION_ON = 3, 72 84 SBMC_CONSERVATION_OFF = 5, 85 + SBMC_RAPID_CHARGE_ON = 7, 86 + SBMC_RAPID_CHARGE_OFF = 8, 73 87 }; 74 88 75 89 enum { ··· 186 172 unsigned long cfg; 187 173 unsigned long r_touchpad_val; 188 174 struct { 175 + bool rapid_charge : 1; 189 176 bool conservation_mode : 1; 190 177 bool dytc : 1; 191 178 bool fan_mode : 1; ··· 649 634 return err; 650 635 } 651 636 637 + /* 638 + * For backward compatibility, ignore Rapid Charge while reporting the 639 + * state of Conservation Mode. 640 + */ 652 641 return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); 653 642 } 654 643 ··· 671 652 return err; 672 653 673 654 guard(mutex)(&priv->gbmd_sbmc_mutex); 655 + 656 + /* 657 + * Prevent mutually exclusive modes from being set at the same time, 658 + * but do not disable Rapid Charge while disabling Conservation Mode. 659 + */ 660 + if (priv->features.rapid_charge && state) { 661 + err = exec_sbmc(priv->adev->handle, SBMC_RAPID_CHARGE_OFF); 662 + if (err) 663 + return err; 664 + } 674 665 675 666 err = exec_sbmc(priv->adev->handle, state ? SBMC_CONSERVATION_ON : SBMC_CONSERVATION_OFF); 676 667 if (err) ··· 2046 2017 const union power_supply_propval *val) 2047 2018 { 2048 2019 struct ideapad_private *priv = ext_data; 2049 - unsigned long op; 2020 + unsigned long op1, op2; 2021 + int err; 2050 2022 2051 2023 switch (val->intval) { 2024 + case POWER_SUPPLY_CHARGE_TYPE_FAST: 2025 + if (WARN_ON(!priv->features.rapid_charge)) 2026 + return -EINVAL; 2027 + 2028 + op1 = SBMC_CONSERVATION_OFF; 2029 + op2 = SBMC_RAPID_CHARGE_ON; 2030 + break; 2052 2031 case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE: 2053 - op = SBMC_CONSERVATION_ON; 2032 + op1 = SBMC_RAPID_CHARGE_OFF; 2033 + op2 = SBMC_CONSERVATION_ON; 2054 2034 break; 2055 2035 case POWER_SUPPLY_CHARGE_TYPE_STANDARD: 2056 - op = SBMC_CONSERVATION_OFF; 2036 + op1 = SBMC_RAPID_CHARGE_OFF; 2037 + op2 = SBMC_CONSERVATION_OFF; 2057 2038 break; 2058 2039 default: 2059 2040 return -EINVAL; ··· 2071 2032 2072 2033 guard(mutex)(&priv->gbmd_sbmc_mutex); 2073 2034 2074 - return exec_sbmc(priv->adev->handle, op); 2035 + /* If !rapid_charge, op1 must be SBMC_RAPID_CHARGE_OFF. Skip it. */ 2036 + if (priv->features.rapid_charge) { 2037 + err = exec_sbmc(priv->adev->handle, op1); 2038 + if (err) 2039 + return err; 2040 + } 2041 + 2042 + return exec_sbmc(priv->adev->handle, op2); 2075 2043 } 2076 2044 2077 2045 static int ideapad_psy_ext_get_prop(struct power_supply *psy, ··· 2088 2042 union power_supply_propval *val) 2089 2043 { 2090 2044 struct ideapad_private *priv = ext_data; 2045 + bool is_rapid_charge, is_conservation; 2091 2046 unsigned long result; 2092 2047 int err; 2093 2048 ··· 2098 2051 return err; 2099 2052 } 2100 2053 2101 - if (test_bit(GBMD_CONSERVATION_STATE_BIT, &result)) 2054 + is_rapid_charge = (priv->features.rapid_charge && 2055 + test_bit(GBMD_RAPID_CHARGE_STATE_BIT, &result)); 2056 + is_conservation = test_bit(GBMD_CONSERVATION_STATE_BIT, &result); 2057 + 2058 + if (unlikely(is_rapid_charge && is_conservation)) { 2059 + dev_err(&priv->platform_device->dev, 2060 + "unexpected charge_types: both [Fast] and [Long_Life] are enabled\n"); 2061 + return -EINVAL; 2062 + } 2063 + 2064 + if (is_rapid_charge) 2065 + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; 2066 + else if (is_conservation) 2102 2067 val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; 2103 2068 else 2104 2069 val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; ··· 2143 2084 2144 2085 DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v1, 2145 2086 (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | 2087 + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) 2088 + ); 2089 + 2090 + DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v2, 2091 + (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | 2092 + BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) | 2146 2093 BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) 2147 2094 ); 2148 2095 ··· 2190 2125 priv->features.fan_mode = true; 2191 2126 2192 2127 if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) { 2193 - priv->features.conservation_mode = true; 2128 + /* Not acquiring gbmd_sbmc_mutex as race condition is impossible on init */ 2129 + if (!eval_gbmd(handle, &val)) { 2130 + priv->features.conservation_mode = true; 2131 + priv->features.rapid_charge = test_bit(GBMD_RAPID_CHARGE_SUPPORTED_BIT, 2132 + &val); 2194 2133 2195 - priv->battery_ext = &ideapad_battery_ext_v1; 2134 + priv->battery_ext = priv->features.rapid_charge 2135 + ? &ideapad_battery_ext_v2 2136 + : &ideapad_battery_ext_v1; 2196 2137 2197 - priv->battery_hook.add_battery = ideapad_battery_add; 2198 - priv->battery_hook.remove_battery = ideapad_battery_remove; 2199 - priv->battery_hook.name = "Ideapad Battery Extension"; 2138 + priv->battery_hook.add_battery = ideapad_battery_add; 2139 + priv->battery_hook.remove_battery = ideapad_battery_remove; 2140 + priv->battery_hook.name = "Ideapad Battery Extension"; 2200 2141 2201 - err = devm_battery_hook_register(&priv->platform_device->dev, &priv->battery_hook); 2202 - if (err) 2203 - return err; 2142 + err = devm_battery_hook_register(&priv->platform_device->dev, 2143 + &priv->battery_hook); 2144 + if (err) 2145 + return err; 2146 + } 2204 2147 } 2205 2148 2206 2149 if (acpi_has_method(handle, "DYTC"))