Re: [RFC PATCH 1/3] platform/x86: lenovo-wmi-other: Add Legion Go Full Speed control

Antheas Kapenekakis <[email protected]>
Newsgroups org.kernel.vger.platform-driver-x86,org.kernel.vger.linux-doc,org.kernel.vger.linux-hwmon,org.kernel.vger.linux-kernel
Message-ID <CAGwozwEg060P820Kdi3bZ+nFfH6eGXEpx_5Dsnkn_rDsRoNLcA@mail.gmail.com>
On Fri, 21 Aug 2026 at 23:47, Aditya Dash <[email protected]> wrote:
>
> Selected Legion Go firmware exposes Full Speed as Other Mode feature
> 0x04020000. Capability Data does not describe this feature, so
> lenovo-wmi-other currently ignores it.

Rephrase. Legion Go devices expose .... The current driver does not
handle this ..., add support for the Full Speed attribute ...

> Probe the feature during HWMON setup on those products. If the read
> succeeds and returns a Boolean value, expose it as pwm1_enable. Value 0
> selects Full Speed, and value 2 returns fan control to firmware automatic
> mode. Reject all other values.
>
> Assisted-by: Pi:gpt-5.6-sol
> Signed-off-by: Aditya Dash <[email protected]>
> ---
>  .../wmi/devices/lenovo-wmi-other.rst          |   7 ++
>  drivers/platform/x86/lenovo/wmi-other.c       | 108 +++++++++++++++++-
>  2 files changed, 114 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
> index 011054d64eac..75f2deaaef16 100644
> --- a/Documentation/wmi/devices/lenovo-wmi-other.rst
> +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
> @@ -49,6 +49,13 @@ The following HWMON attributes are implemented:
>  Due to the internal RPM divisor, the current/target RPMs are rounded down to
>  its nearest multiple. The divisor itself is not necessary to be a power of two.
>
> +Legion Go fan controls
> +----------------------
> +
> +On supported Legion Go models, Other Mode feature ``0x04020000`` controls
> +Full Speed mode in firmware. The driver exposes it as ``pwm1_enable``. Value 0
> +enables Full Speed, and value 2 returns fan control to firmware.
> +
>  LENOVO_CAPABILITY_DATA_01
>  -------------------------
>
> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> index fbb32bf404f2..c180933e1d18 100644
> --- a/drivers/platform/x86/lenovo/wmi-other.c
> +++ b/drivers/platform/x86/lenovo/wmi-other.c
> @@ -32,6 +32,7 @@
>  #include <linux/component.h>
>  #include <linux/container_of.h>
>  #include <linux/device.h>
> +#include <linux/dmi.h>
>  #include <linux/export.h>
>  #include <linux/gfp_types.h>
>  #include <linux/hwmon.h>
> @@ -83,6 +84,7 @@ enum lwmi_feature_id_psu {
>         LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR =  0x02,
>  };
>
> +#define LWMI_FEATURE_ID_FAN_FULLSPEED 0x02
>  #define LWMI_FEATURE_ID_FAN_RPM 0x03
>
>  #define LWMI_TYPE_ID_CROSSLOAD 0x01
> @@ -102,6 +104,10 @@ enum lwmi_feature_id_psu {
>  #define LWMI_CHARGE_TYPE_STANDARD      0x00
>  #define LWMI_CHARGE_TYPE_LONGLIFE      0x01
>
> +#define LWMI_ATTR_ID_FAN_FULLSPEED                                     \
> +       lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_FULLSPEED, \
> +                    LWMI_GZ_THERMAL_MODE_NONE, LWMI_TYPE_ID_NONE)
> +
>  #define LWMI_ATTR_ID_FAN_RPM(x)                                   \
>         lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
>                      LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
> @@ -115,6 +121,50 @@ enum lwmi_feature_id_psu {
>
>  static DEFINE_IDA(lwmi_om_ida);
>
> +static const struct dmi_system_id lwmi_fan_dmi_table[] = {
> +       {
> +               .ident = "Lenovo Legion Go 8APU1",
> +               .matches = {
> +                       DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +                       DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> +               },
> +       },
> +       {
> +               .ident = "Lenovo Legion Go S 8APU1",
> +               .matches = {
> +                       DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +                       DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> +               },
> +       },
> +       {
> +               .ident = "Lenovo Legion Go S 8ARP1",
> +               .matches = {
> +                       DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +                       DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> +               },
> +       },
> +       {
> +               .ident = "Lenovo Legion Go 8ASP2",
> +               .matches = {
> +                       DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +                       DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8ASP2"),
> +               },
> +       },
> +       {
> +               .ident = "Lenovo Legion Go 8AHP2",
> +               .matches = {
> +                       DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +                       DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8AHP2"),
> +               },
> +       },
> +       {}
> +};

Why DMI match?

> +
> +static bool lwmi_fan_supported(void)
> +{
> +       return dmi_check_system(lwmi_fan_dmi_table);
> +}

Why not cap match? My version might help you:
https://github.com/anatase-org/patchwork/commit/3d326d36ac8839f5d6f1d3ab1885322bbdf4a3cb

Best,
Antheas

> +
>  enum attribute_property {
>         DEFAULT_VAL,
>         MAX_VAL,
> @@ -144,6 +194,7 @@ struct lwmi_om_priv {
>         int ida_id;
>
>         struct lwmi_fan_info fan_info[LWMI_FAN_NR];
> +       bool fullspeed_supported;
>
>         struct {
>                 bool capdata00_collected : 1;
> @@ -237,6 +288,38 @@ static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val,
>         return (retval == 0 || retval == 1) ? 0 : -EIO;
>  }
>
> +static int lwmi_om_fullspeed_get(struct lwmi_om_priv *priv, long *enable)
> +{
> +       struct wmi_method_args_32 args = {
> +               .arg0 = LWMI_ATTR_ID_FAN_FULLSPEED,
> +       };
> +       u32 value;
> +       int ret;
> +
> +       ret = lwmi_dev_evaluate_int(priv->wdev, 0, LWMI_FEATURE_VALUE_GET,
> +                                   (u8 *)&args, sizeof(args), &value);
> +       if (ret)
> +               return ret;
> +
> +       if (value > 1)
> +               return -ERANGE;
> +
> +       *enable = value ? 0 : 2;
> +       return 0;
> +}
> +
> +static int lwmi_om_fullspeed_set(struct lwmi_om_priv *priv, bool fullspeed)
> +{
> +       struct wmi_method_args_32 args = {
> +               .arg0 = LWMI_ATTR_ID_FAN_FULLSPEED,
> +               .arg1 = fullspeed,
> +       };
> +
> +       /* The WMI method has no return value. */
> +       return lwmi_dev_evaluate_int(priv->wdev, 0, LWMI_FEATURE_VALUE_SET,
> +                                    (u8 *)&args, sizeof(args), NULL);
> +}
> +
>  /**
>   * lwmi_om_hwmon_is_visible() - Determine visibility of HWMON attributes
>   * @drvdata: Driver private data
> @@ -255,6 +338,10 @@ static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_t
>         struct lwmi_om_priv *priv = (struct lwmi_om_priv *)drvdata;
>         bool visible = false;
>
> +       if (type == hwmon_pwm && priv->fullspeed_supported && channel == 0 &&
> +           attr == hwmon_pwm_enable)
> +               return 0644;
> +
>         if (type == hwmon_fan) {
>                 if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID))
>                         return 0;
> @@ -311,6 +398,9 @@ static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
>         u32 retval = 0;
>         int err;
>
> +       if (type == hwmon_pwm && attr == hwmon_pwm_enable && channel == 0)
> +               return lwmi_om_fullspeed_get(priv, val);
> +
>         if (type == hwmon_fan) {
>                 switch (attr) {
>                 /*
> @@ -366,6 +456,17 @@ static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
>         u32 raw, min_rpm, max_rpm;
>         int err;
>
> +       if (type == hwmon_pwm && attr == hwmon_pwm_enable && channel == 0) {
> +               switch (val) {
> +               case 0:
> +                       return lwmi_om_fullspeed_set(priv, true);
> +               case 2:
> +                       return lwmi_om_fullspeed_set(priv, false);
> +               default:
> +                       return -EINVAL;
> +               }
> +       }
> +
>         if (type == hwmon_fan) {
>                 switch (attr) {
>                 case hwmon_fan_target:
> @@ -420,6 +521,7 @@ static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = {
>                            HWMON_F_MIN | HWMON_F_MAX,
>                            HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
>                            HWMON_F_MIN | HWMON_F_MAX),
> +       HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE),
>         NULL
>  };
>
> @@ -440,6 +542,7 @@ static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = {
>   */
>  static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
>  {
> +       long enable;
>         int i, valid;
>
>         if (WARN_ON(priv->hwmon_dev))
> @@ -458,6 +561,9 @@ static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
>         if (relax_fan_constraint)
>                 dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution\n");
>
> +       priv->fullspeed_supported =
> +               lwmi_fan_supported() && !lwmi_om_fullspeed_get(priv, &enable);
> +
>         valid = 0;
>         for (i = 0; i < LWMI_FAN_NR; i++) {
>                 if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID))
> @@ -474,7 +580,7 @@ static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
>                 }
>         }
>
> -       if (valid == 0) {
> +       if (valid == 0 && !priv->fullspeed_supported) {
>                 dev_warn(&priv->wdev->dev,
>                          "fan reporting/tuning is unsupported on this device\n");
>                 return;

This log is a wart. You dmi match anyway. Preferably, you would cap
match. Even then, silently dropping the unsupported features is
preferrable.

> --
> 2.55.0
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.