From nobody Sun Feb 8 11:21:37 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 58B58212F98; Sun, 19 Oct 2025 21:05:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907927; cv=pass; b=e5b3JSMlmw4zaYeb5H5+hMXCFv0Q3XJRsFaZOC0accW+h/mXvegu62LaMYZy+ZMSjkFImYvqLQyUe3OI40+sq5tWpFYbXlQBIdJ8O/cFPZNPlftpzjxLNR7LqX9KjDAP8ehccVPEDszEvCCe6vv2SlbJ8998aSeJnWh7pVJo65Q= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907927; c=relaxed/simple; bh=k7bo9PRwCOQ2W+etc1f8ScUXVKe1VsbZKoGxR9jR+0A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=f80TObFw5CKEQtRK5pKGbm8ElPhAyAEocPqwYBbSmB5ZNDp6J+na0D7ZOyPcexJiViKHpqKNugZDT+EUOLxYPDuY52z256UB1DVM85riY6rBi7xD4x8aXej0DgVTaUZN++7e/uuIqPlb2O5uBEDVHD1yxa7zS+HU9oY2nG3NGgE= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=fmJAP9XO; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="fmJAP9XO" ARC-Seal: i=1; a=rsa-sha256; t=1760907904; cv=none; d=zohomail.com; s=zohoarc; b=bYcIhvc/u3SFdlyHCouMeWBtAZgOKXKFGCUVGY2e2Sizdieu6clceZeEIX2VLC+3iWfoSo1pGTX9hJN3lCFbLUb4CcDrs+y3JSm+aRL6LbLV11YEnSA+ohxHFrs3Q8FjeVSRXf0XVNZ45DJ5uWsmpIYpm6oEFcNFcCmDvJn74sA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1760907904; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=IJV5Js/TF9mqK83N3M6zAR9IOwC90FeMRWvvLTB+umA=; b=Iv8WZfqpSJPzEwmnOFgFJEpLOo/hELAjI/susqF6HVEG8r3fjUO5JdO28w3X+xhoeDTvMFTGrhs818kA6PG+Q5cMNkxA5dEXKT0YfjtMAZWUnpctGAadnuhu5WR/DkNdtcfJC6QeucPUbqltpSxzRwGqHuWfLjhrJ2pSd6i1L5A= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1760907904; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=IJV5Js/TF9mqK83N3M6zAR9IOwC90FeMRWvvLTB+umA=; b=fmJAP9XOAOWlHb6nFhau5VqUReti3e77hf8G4H6iyPRs4Alb/n8itYveEWdZaUTl FKxj8sHcBTPAjfxuwUf0WC0rN8IDJwKPdymEM+Q9hEVHZpX7l6tbS8du4ZcDPpc6nVw PtefEGgpzcOidaOrhelLDk8yR4KvTBs+AI+XW3H4= Received: by mx.zohomail.com with SMTPS id 1760907901158161.83146022522897; Sun, 19 Oct 2025 14:05:01 -0700 (PDT) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH 1/6] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Date: Mon, 20 Oct 2025 05:04:44 +0800 Message-ID: <20251019210450.88830-2-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251019210450.88830-1-i@rong.moe> References: <20251019210450.88830-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" Prepare for the upcoming changes to make it suitable to retrieve and provide other Capability Data as well. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark --- drivers/platform/x86/lenovo/Kconfig | 4 +- drivers/platform/x86/lenovo/Makefile | 2 +- .../lenovo/{wmi-capdata01.c =3D> wmi-capdata.c} | 124 +++++++++--------- .../lenovo/{wmi-capdata01.h =3D> wmi-capdata.h} | 10 +- drivers/platform/x86/lenovo/wmi-other.c | 11 +- 5 files changed, 78 insertions(+), 73 deletions(-) rename drivers/platform/x86/lenovo/{wmi-capdata01.c =3D> wmi-capdata.c} (6= 0%) rename drivers/platform/x86/lenovo/{wmi-capdata01.h =3D> wmi-capdata.h} (6= 0%) diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/len= ovo/Kconfig index d22b774e0236f..fb96a0f908f03 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -233,7 +233,7 @@ config YT2_1380 To compile this driver as a module, choose M here: the module will be called lenovo-yogabook. =20 -config LENOVO_WMI_DATA01 +config LENOVO_WMI_DATA tristate depends on ACPI_WMI =20 @@ -264,7 +264,7 @@ config LENOVO_WMI_TUNING tristate "Lenovo Other Mode WMI Driver" depends on ACPI_WMI select FW_ATTR_CLASS - select LENOVO_WMI_DATA01 + select LENOVO_WMI_DATA select LENOVO_WMI_EVENTS select LENOVO_WMI_HELPERS help diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/le= novo/Makefile index 7b2128e3a2142..29014d8c1376d 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -12,7 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) +=3D ymc.o lenovo-target-$(CONFIG_YOGABOOK) +=3D yogabook.o lenovo-target-$(CONFIG_YT2_1380) +=3D yoga-tab2-pro-1380-fastcharger.o lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) +=3D wmi-camera.o -lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) +=3D wmi-capdata01.o +lenovo-target-$(CONFIG_LENOVO_WMI_DATA) +=3D wmi-capdata.o lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) +=3D wmi-events.o lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) +=3D wmi-helpers.o lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) +=3D wmi-gamezone.o diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.c b/drivers/platform= /x86/lenovo/wmi-capdata.c similarity index 60% rename from drivers/platform/x86/lenovo/wmi-capdata01.c rename to drivers/platform/x86/lenovo/wmi-capdata.c index fc7e3454e71dc..c5e74b2bfeb36 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata01.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -1,14 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Lenovo Capability Data 01 WMI Data Block driver. + * Lenovo Capability Data WMI Data Block driver. * - * Lenovo Capability Data 01 provides information on tunable attributes us= ed by - * the "Other Mode" WMI interface. The data includes if the attribute is - * supported by the hardware, the default_value, max_value, min_value, and= step - * increment. Each attribute has multiple pages, one for each of the therm= al - * modes managed by the Gamezone interface. + * Lenovo Capability Data provides information on tunable attributes used = by + * the "Other Mode" WMI interface. + * + * Capability Data 01 includes if the attribute is supported by the hardwa= re, + * and the default_value, max_value, min_value, and step increment. Each + * attribute has multiple pages, one for each of the thermal modes managed= by + * the Gamezone interface. * * Copyright (C) 2025 Derek J. Clark + * - Initial implementation (formerly named lenovo-wmi-capdata01) */ =20 #include @@ -26,55 +29,55 @@ #include #include =20 -#include "wmi-capdata01.h" +#include "wmi-capdata.h" =20 #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE0181= 54" =20 #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 =20 -struct lwmi_cd01_priv { +struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ struct wmi_device *wdev; - struct cd01_list *list; + struct cd_list *list; }; =20 -struct cd01_list { +struct cd_list { struct mutex list_mutex; /* list R/W mutex */ u8 count; struct capdata01 data[]; }; =20 /** - * lwmi_cd01_component_bind() - Bind component to master device. - * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device. + * lwmi_cd_component_bind() - Bind component to master device. + * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. * @om_dev: Pointer to the lenovo-wmi-other driver parent device. - * @data: capdata01_list object pointer used to return the capability data. + * @data: cd_list object pointer used to return the capability data. * - * On lenovo-wmi-other's master bind, provide a pointer to the local capda= ta01 - * list. This is used to call lwmi_cd01_get_data to look up attribute data + * On lenovo-wmi-other's master bind, provide a pointer to the local capda= ta + * list. This is used to call lwmi_cd*_get_data to look up attribute data * from the lenovo-wmi-other driver. * * Return: 0 */ -static int lwmi_cd01_component_bind(struct device *cd01_dev, - struct device *om_dev, void *data) +static int lwmi_cd_component_bind(struct device *cd_dev, + struct device *om_dev, void *data) { - struct lwmi_cd01_priv *priv =3D dev_get_drvdata(cd01_dev); - struct cd01_list **cd01_list =3D data; + struct lwmi_cd_priv *priv =3D dev_get_drvdata(cd_dev); + struct cd_list **cd_list =3D data; =20 - *cd01_list =3D priv->list; + *cd_list =3D priv->list; =20 return 0; } =20 -static const struct component_ops lwmi_cd01_component_ops =3D { - .bind =3D lwmi_cd01_component_bind, +static const struct component_ops lwmi_cd_component_ops =3D { + .bind =3D lwmi_cd_component_bind, }; =20 /** * lwmi_cd01_get_data - Get the data of the specified attribute - * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct. + * @list: The lenovo-wmi-capdata pointer to its cd_list struct. * @attribute_id: The capdata attribute ID to be found. * @output: Pointer to a capdata01 struct to return the data. * @@ -83,7 +86,7 @@ static const struct component_ops lwmi_cd01_component_ops= =3D { * * Return: 0 on success, or -EINVAL. */ -int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct ca= pdata01 *output) +int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output) { u8 idx; =20 @@ -97,17 +100,17 @@ int lwmi_cd01_get_data(struct cd01_list *list, u32 att= ribute_id, struct capdata0 =20 return -EINVAL; } -EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01"); +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD"); =20 /** - * lwmi_cd01_cache() - Cache all WMI data block information - * @priv: lenovo-wmi-capdata01 driver data. + * lwmi_cd_cache() - Cache all WMI data block information + * @priv: lenovo-wmi-capdata driver data. * * Loop through each WMI data block and cache the data. * * Return: 0 on success, or an error. */ -static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv) +static int lwmi_cd_cache(struct lwmi_cd_priv *priv) { int idx; =20 @@ -131,17 +134,17 @@ static int lwmi_cd01_cache(struct lwmi_cd01_priv *pri= v) } =20 /** - * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata - * @priv: lenovo-wmi-capdata01 driver data. + * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata + * @priv: lenovo-wmi-capdata driver data. * - * Allocate a cd01_list struct large enough to contain data from all WMI d= ata + * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. * * Return: 0 on success, or an error. */ -static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv) +static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) { - struct cd01_list *list; + struct cd_list *list; size_t list_size; int count, ret; =20 @@ -163,28 +166,28 @@ static int lwmi_cd01_alloc(struct lwmi_cd01_priv *pri= v) } =20 /** - * lwmi_cd01_setup() - Cache all WMI data block information - * @priv: lenovo-wmi-capdata01 driver data. + * lwmi_cd_setup() - Cache all WMI data block information + * @priv: lenovo-wmi-capdata driver data. * - * Allocate a cd01_list struct large enough to contain data from all WMI d= ata + * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. Then loop through each data block and * cache the data. * * Return: 0 on success, or an error code. */ -static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv) +static int lwmi_cd_setup(struct lwmi_cd_priv *priv) { int ret; =20 - ret =3D lwmi_cd01_alloc(priv); + ret =3D lwmi_cd_alloc(priv); if (ret) return ret; =20 - return lwmi_cd01_cache(priv); + return lwmi_cd_cache(priv); } =20 /** - * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver= notifier. + * lwmi_cd01_notifier_call() - Call method for cd01 notifier. * block call chain. * @nb: The notifier_block registered to lenovo-wmi-events driver. * @action: Unused. @@ -199,17 +202,17 @@ static int lwmi_cd01_notifier_call(struct notifier_bl= ock *nb, unsigned long acti void *data) { struct acpi_bus_event *event =3D data; - struct lwmi_cd01_priv *priv; + struct lwmi_cd_priv *priv; int ret; =20 if (strcmp(event->device_class, ACPI_AC_CLASS) !=3D 0) return NOTIFY_DONE; =20 - priv =3D container_of(nb, struct lwmi_cd01_priv, acpi_nb); + priv =3D container_of(nb, struct lwmi_cd_priv, acpi_nb); =20 switch (event->type) { case ACPI_AC_NOTIFY_STATUS: - ret =3D lwmi_cd01_cache(priv); + ret =3D lwmi_cd_cache(priv); if (ret) return NOTIFY_BAD; =20 @@ -230,10 +233,9 @@ static void lwmi_cd01_unregister(void *data) unregister_acpi_notifier(acpi_nb); } =20 -static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context) - +static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) { - struct lwmi_cd01_priv *priv; + struct lwmi_cd_priv *priv; int ret; =20 priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); @@ -243,7 +245,7 @@ static int lwmi_cd01_probe(struct wmi_device *wdev, con= st void *context) priv->wdev =3D wdev; dev_set_drvdata(&wdev->dev, priv); =20 - ret =3D lwmi_cd01_setup(priv); + ret =3D lwmi_cd_setup(priv); if (ret) return ret; =20 @@ -257,27 +259,27 @@ static int lwmi_cd01_probe(struct wmi_device *wdev, c= onst void *context) if (ret) return ret; =20 - return component_add(&wdev->dev, &lwmi_cd01_component_ops); + return component_add(&wdev->dev, &lwmi_cd_component_ops); } =20 -static void lwmi_cd01_remove(struct wmi_device *wdev) +static void lwmi_cd_remove(struct wmi_device *wdev) { - component_del(&wdev->dev, &lwmi_cd01_component_ops); + component_del(&wdev->dev, &lwmi_cd_component_ops); } =20 -static const struct wmi_device_id lwmi_cd01_id_table[] =3D { +static const struct wmi_device_id lwmi_cd_id_table[] =3D { { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, {} }; =20 -static struct wmi_driver lwmi_cd01_driver =3D { +static struct wmi_driver lwmi_cd_driver =3D { .driver =3D { - .name =3D "lenovo_wmi_cd01", + .name =3D "lenovo_wmi_cd", .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, }, - .id_table =3D lwmi_cd01_id_table, - .probe =3D lwmi_cd01_probe, - .remove =3D lwmi_cd01_remove, + .id_table =3D lwmi_cd_id_table, + .probe =3D lwmi_cd_probe, + .remove =3D lwmi_cd_remove, .no_singleton =3D true, }; =20 @@ -290,13 +292,13 @@ static struct wmi_driver lwmi_cd01_driver =3D { */ int lwmi_cd01_match(struct device *dev, void *data) { - return dev->driver =3D=3D &lwmi_cd01_driver.driver; + return dev->driver =3D=3D &lwmi_cd_driver.driver; } -EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01"); +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD"); =20 -module_wmi_driver(lwmi_cd01_driver); +module_wmi_driver(lwmi_cd_driver); =20 -MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table); +MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table); MODULE_AUTHOR("Derek J. Clark "); -MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); +MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.h b/drivers/platform= /x86/lenovo/wmi-capdata.h similarity index 60% rename from drivers/platform/x86/lenovo/wmi-capdata01.h rename to drivers/platform/x86/lenovo/wmi-capdata.h index bd06c5751f68b..2a4746e38ad43 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata01.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -2,13 +2,13 @@ =20 /* Copyright (C) 2025 Derek J. Clark */ =20 -#ifndef _LENOVO_WMI_CAPDATA01_H_ -#define _LENOVO_WMI_CAPDATA01_H_ +#ifndef _LENOVO_WMI_CAPDATA_H_ +#define _LENOVO_WMI_CAPDATA_H_ =20 #include =20 struct device; -struct cd01_list; +struct cd_list; =20 struct capdata01 { u32 id; @@ -19,7 +19,7 @@ struct capdata01 { u32 max_value; }; =20 -int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct ca= pdata01 *output); +int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output); int lwmi_cd01_match(struct device *dev, void *data); =20 -#endif /* !_LENOVO_WMI_CAPDATA01_H_ */ +#endif /* !_LENOVO_WMI_CAPDATA_H_ */ diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86= /lenovo/wmi-other.c index 2a960b278f117..c6dc1b4cff841 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -34,7 +34,7 @@ #include #include =20 -#include "wmi-capdata01.h" +#include "wmi-capdata.h" #include "wmi-events.h" #include "wmi-gamezone.h" #include "wmi-helpers.h" @@ -74,7 +74,10 @@ enum attribute_property { =20 struct lwmi_om_priv { struct component_master_ops *ops; - struct cd01_list *cd01_list; /* only valid after capdata01 bind */ + + /* only valid after capdata bind */ + struct cd_list *cd01_list; + struct device *fw_attr_dev; struct kset *fw_attr_kset; struct notifier_block nb; @@ -576,7 +579,7 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv = *priv) static int lwmi_om_master_bind(struct device *dev) { struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); - struct cd01_list *tmp_list; + struct cd_list *tmp_list; int ret; =20 ret =3D component_bind_all(dev, &tmp_list); @@ -657,7 +660,7 @@ static struct wmi_driver lwmi_other_driver =3D { =20 module_wmi_driver(lwmi_other_driver); =20 -MODULE_IMPORT_NS("LENOVO_WMI_CD01"); +MODULE_IMPORT_NS("LENOVO_WMI_CD"); MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); MODULE_AUTHOR("Derek J. Clark "); --=20 2.51.0 From nobody Sun Feb 8 11:21:37 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ECA6523A9AD; Sun, 19 Oct 2025 21:05:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907932; cv=pass; b=QMoD3nsnqtJTDPjpU7Wmvmq1Vui9a6rZE2oQl6KzonnEgy3IEnKkEbLCcYYNq/Yf4OGCCgNjcWaQOn0N0TtBb0JDwOQDZPpgjk+iKeGeE69KZQgQEG6MD2oO5DyFMDy2vBFb4gKv8YkFyRm4TueX2wo08O0Y5taX1wNMexw/QjQ= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907932; c=relaxed/simple; bh=wIoROUIrKmPoNHtdDZCvS/71ZOtJP4VVMr83auSh4lw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SwPTJyzA1RsRY8bZOfgX4PkvCj/DGVDU4S/hIyToiiXBdw2kUsBrsNrjcrbMw5Z1pZnSp/i1TxtSNsLXlNsoExe2zBe1x1aabFjnjYhC9T3qLj2Gu7DcKbB7QcoUTroHnDyvdUyMHJiVWEpb+ab+kSknD6sNhAEaMQGZylNRz6Y= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=MJPSefKJ; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="MJPSefKJ" ARC-Seal: i=1; a=rsa-sha256; t=1760907906; cv=none; d=zohomail.com; s=zohoarc; b=TBxzMIIOEGtl/QKNBS32wTse0rI/wfYxF215mqAJWefsuLJsvxal1qlNdfNLS8am5qiorKrNST+iZOB/M+GffIPU1Q81+i02SN1CK64cIgRRSun003P09ZjA2CdreKONu4jjEqmdGb1hsuSjLM35ZQ/Dp5YiGQHDmFA9Lgd8k74= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1760907906; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=qI+9pn3muc78vYlxbWdy06DKZnCxS7hR7AwdcDEQTgc=; b=LC2+9oXwN+vPrOHQUnanfDlGXYweYN34fqNJZs7/O9X/lLWy3lF7gYh6uNlfwfF4DtfSEn0wRbd8cys38XK7e7U1Zlp8k69xSV9DOS1KnldesZLb0o9KsPe6gFyzbsDs4eLIN1fls9mlPCMIJCyFnGdVQMiRKkmqcwSC6lEiT7Y= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1760907906; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=qI+9pn3muc78vYlxbWdy06DKZnCxS7hR7AwdcDEQTgc=; b=MJPSefKJvOKAqLkV/Hsd0BZLhy74Nts2xbhb3BNmzvhVVkyD+NsqQLoss8Hlw7vp oQBNNapz7ajw8JK9p7q+mqi/nVEy2B7BmNlAWXAyGsvBiZ1zJpzB1WdkW7urGvL0iCT dgI6vV41lFMs4huCInxJ+DIUjnSYzf8ad1+hMB7s= Received: by mx.zohomail.com with SMTPS id 1760907904296760.1956003944582; Sun, 19 Oct 2025 14:05:04 -0700 (PDT) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH 2/6] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Date: Mon, 20 Oct 2025 05:04:45 +0800 Message-ID: <20251019210450.88830-3-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251019210450.88830-1-i@rong.moe> References: <20251019210450.88830-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" The current inplementation are heavily bound to capdata01. Rewrite it so that it is suitable to utilize other Capability Data as well. No functional changes are introduced. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark --- drivers/platform/x86/lenovo/wmi-capdata.c | 208 +++++++++++++++++----- drivers/platform/x86/lenovo/wmi-capdata.h | 7 +- drivers/platform/x86/lenovo/wmi-other.c | 27 ++- 3 files changed, 190 insertions(+), 52 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x= 86/lenovo/wmi-capdata.c index c5e74b2bfeb36..14175fe19247e 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -12,8 +12,13 @@ * * Copyright (C) 2025 Derek J. Clark * - Initial implementation (formerly named lenovo-wmi-capdata01) + * + * Copyright (C) 2025 Rong Zhang + * - Unified implementation */ =20 +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include #include #include @@ -36,6 +41,25 @@ #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 =20 +enum lwmi_cd_type { + LENOVO_CAPABILITY_DATA_01, +}; + +#define LWMI_CD_TABLE_ITEM(_type) \ + [_type] =3D { \ + .guid_string =3D _type##_GUID, \ + .name =3D #_type, \ + .type =3D _type, \ + } + +static const struct lwmi_cd_info { + const char *guid_string; + const char *name; + enum lwmi_cd_type type; +} lwmi_cd_table[] =3D { + LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01), +}; + struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ struct wmi_device *wdev; @@ -44,15 +68,19 @@ struct lwmi_cd_priv { =20 struct cd_list { struct mutex list_mutex; /* list R/W mutex */ + enum lwmi_cd_type type; u8 count; - struct capdata01 data[]; + + union { + DECLARE_FLEX_ARRAY(struct capdata01, cd01); + }; }; =20 /** * lwmi_cd_component_bind() - Bind component to master device. * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. * @om_dev: Pointer to the lenovo-wmi-other driver parent device. - * @data: cd_list object pointer used to return the capability data. + * @data: lwmi_cd_binder object pointer used to return the capability data. * * On lenovo-wmi-other's master bind, provide a pointer to the local capda= ta * list. This is used to call lwmi_cd*_get_data to look up attribute data @@ -64,9 +92,15 @@ static int lwmi_cd_component_bind(struct device *cd_dev, struct device *om_dev, void *data) { struct lwmi_cd_priv *priv =3D dev_get_drvdata(cd_dev); - struct cd_list **cd_list =3D data; + struct lwmi_cd_binder *binder =3D data; =20 - *cd_list =3D priv->list; + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_01: + binder->cd01_list =3D priv->list; + break; + default: + return -EINVAL; + } =20 return 0; } @@ -76,30 +110,33 @@ static const struct component_ops lwmi_cd_component_op= s =3D { }; =20 /** - * lwmi_cd01_get_data - Get the data of the specified attribute + * lwmi_cd*_get_data - Get the data of the specified attribute * @list: The lenovo-wmi-capdata pointer to its cd_list struct. * @attribute_id: The capdata attribute ID to be found. - * @output: Pointer to a capdata01 struct to return the data. + * @output: Pointer to a capdata* struct to return the data. * - * Retrieves the capability data 01 struct pointer for the given - * attribute for its specified thermal mode. + * Retrieves the capability data struct pointer for the given + * attribute. * * Return: 0 on success, or -EINVAL. */ -int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output) -{ - u8 idx; - - guard(mutex)(&list->list_mutex); - for (idx =3D 0; idx < list->count; idx++) { - if (list->data[idx].id !=3D attribute_id) - continue; - memcpy(output, &list->data[idx], sizeof(list->data[idx])); - return 0; +#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \ + int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _outp= ut_t *output) \ + { \ + u8 idx; \ + if (WARN_ON(list->type !=3D _cd_type)) \ + return -EINVAL; \ + guard(mutex)(&list->list_mutex); \ + for (idx =3D 0; idx < list->count; idx++) { \ + if (list->_cdxx[idx].id !=3D attribute_id) \ + continue; \ + memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx])); \ + return 0; \ + } \ + return -EINVAL; \ } =20 - return -EINVAL; -} +DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD"); =20 /** @@ -112,10 +149,21 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_= CD"); */ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) { + size_t size; int idx; + void *p; + + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_01: + p =3D &priv->list->cd01[0]; + size =3D sizeof(priv->list->cd01[0]); + break; + default: + return -EINVAL; + } =20 guard(mutex)(&priv->list->list_mutex); - for (idx =3D 0; idx < priv->list->count; idx++) { + for (idx =3D 0; idx < priv->list->count; idx++, p +=3D size) { union acpi_object *ret_obj __free(kfree) =3D NULL; =20 ret_obj =3D wmidev_block_query(priv->wdev, idx); @@ -123,11 +171,10 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) return -ENODEV; =20 if (ret_obj->type !=3D ACPI_TYPE_BUFFER || - ret_obj->buffer.length < sizeof(priv->list->data[idx])) + ret_obj->buffer.length < size) continue; =20 - memcpy(&priv->list->data[idx], ret_obj->buffer.pointer, - ret_obj->buffer.length); + memcpy(p, ret_obj->buffer.pointer, size); } =20 return 0; @@ -136,20 +183,28 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) /** * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata * @priv: lenovo-wmi-capdata driver data. + * @type: The type of capability data. * * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. * * Return: 0 on success, or an error. */ -static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) +static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) { struct cd_list *list; size_t list_size; int count, ret; =20 count =3D wmidev_instance_count(priv->wdev); - list_size =3D struct_size(list, data, count); + + switch (type) { + case LENOVO_CAPABILITY_DATA_01: + list_size =3D struct_size(list, cd01, count); + break; + default: + return -EINVAL; + } =20 list =3D devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL); if (!list) @@ -159,6 +214,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) if (ret) return ret; =20 + list->type =3D type; list->count =3D count; priv->list =3D list; =20 @@ -168,6 +224,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) /** * lwmi_cd_setup() - Cache all WMI data block information * @priv: lenovo-wmi-capdata driver data. + * @type: The type of capability data. * * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. Then loop through each data block and @@ -175,11 +232,11 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) * * Return: 0 on success, or an error code. */ -static int lwmi_cd_setup(struct lwmi_cd_priv *priv) +static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) { int ret; =20 - ret =3D lwmi_cd_alloc(priv); + ret =3D lwmi_cd_alloc(priv, type); if (ret) return ret; =20 @@ -235,9 +292,13 @@ static void lwmi_cd01_unregister(void *data) =20 static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) { + const struct lwmi_cd_info *info =3D context; struct lwmi_cd_priv *priv; int ret; =20 + if (!info) + return -EINVAL; + priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -245,21 +306,34 @@ static int lwmi_cd_probe(struct wmi_device *wdev, con= st void *context) priv->wdev =3D wdev; dev_set_drvdata(&wdev->dev, priv); =20 - ret =3D lwmi_cd_setup(priv); + ret =3D lwmi_cd_setup(priv, info->type); if (ret) - return ret; + goto out; =20 - priv->acpi_nb.notifier_call =3D lwmi_cd01_notifier_call; + if (info->type =3D=3D LENOVO_CAPABILITY_DATA_01) { + priv->acpi_nb.notifier_call =3D lwmi_cd01_notifier_call; =20 - ret =3D register_acpi_notifier(&priv->acpi_nb); - if (ret) - return ret; + ret =3D register_acpi_notifier(&priv->acpi_nb); + if (ret) + goto out; =20 - ret =3D devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv-= >acpi_nb); - if (ret) - return ret; + ret =3D devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, + &priv->acpi_nb); + if (ret) + goto out; + } + + ret =3D component_add(&wdev->dev, &lwmi_cd_component_ops); =20 - return component_add(&wdev->dev, &lwmi_cd_component_ops); +out: + if (ret) { + dev_err(&wdev->dev, "failed to register %s: %d\n", + info->name, ret); + } else { + dev_info(&wdev->dev, "registered %s with %u items\n", + info->name, priv->list->count); + } + return ret; } =20 static void lwmi_cd_remove(struct wmi_device *wdev) @@ -267,8 +341,12 @@ static void lwmi_cd_remove(struct wmi_device *wdev) component_del(&wdev->dev, &lwmi_cd_component_ops); } =20 +#define LWMI_CD_WDEV_ID(_type) \ + .guid_string =3D _type##_GUID, \ + .context =3D &lwmi_cd_table[_type] + static const struct wmi_device_id lwmi_cd_id_table[] =3D { - { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, + { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) }, {} }; =20 @@ -284,21 +362,61 @@ static struct wmi_driver lwmi_cd_driver =3D { }; =20 /** - * lwmi_cd01_match() - Match rule for the master driver. - * @dev: Pointer to the capability data 01 parent device. - * @data: Unused void pointer for passing match criteria. + * lwmi_cd_match() - Match rule for the master driver. + * @dev: Pointer to the capability data parent device. + * @data: Pointer to capability data type (enum lwmi_cd_type *) to match. * * Return: int. */ -int lwmi_cd01_match(struct device *dev, void *data) +static int lwmi_cd_match(struct device *dev, void *type) +{ + struct lwmi_cd_priv *priv; + + if (dev->driver !=3D &lwmi_cd_driver.driver) + return false; + + priv =3D dev_get_drvdata(dev); + return priv->list->type =3D=3D *(enum lwmi_cd_type *)type; +} + +/** + * lwmi_cd_match_add_all() - Add all match rule for the master driver. + * @master: Pointer to the master device. + * @matchptr: Pointer to the returned component_match pointer. + * + * Adds all component matches to the list stored in @matchptr for the @mas= ter + * device. @matchptr must be initialized to NULL. This matches all availab= le + * capdata types on the machine. + */ +void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr) { - return dev->driver =3D=3D &lwmi_cd_driver.driver; + int i; + + if (WARN_ON(*matchptr)) + return; + + for (i =3D 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { + if (!lwmi_cd_table[i].guid_string || + !wmi_has_guid(lwmi_cd_table[i].guid_string)) + continue; + + component_match_add(master, matchptr, lwmi_cd_match, + (void *)&lwmi_cd_table[i].type); + if (IS_ERR(matchptr)) + return; + } + + if (!*matchptr) { + pr_warn("a master driver requested capability data, but nothing is avail= able\n"); + *matchptr =3D ERR_PTR(-ENODEV); + } } -EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD"); +EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CD"); =20 module_wmi_driver(lwmi_cd_driver); =20 MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table); MODULE_AUTHOR("Derek J. Clark "); +MODULE_AUTHOR("Rong Zhang "); MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x= 86/lenovo/wmi-capdata.h index 2a4746e38ad43..1e5fce7836cbf 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -7,6 +7,7 @@ =20 #include =20 +struct component_match; struct device; struct cd_list; =20 @@ -19,7 +20,11 @@ struct capdata01 { u32 max_value; }; =20 +struct lwmi_cd_binder { + struct cd_list *cd01_list; +}; + int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output); -int lwmi_cd01_match(struct device *dev, void *data); +void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr); =20 #endif /* !_LENOVO_WMI_CAPDATA_H_ */ diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86= /lenovo/wmi-other.c index c6dc1b4cff841..20c6ff0be37a1 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -579,14 +579,14 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_pri= v *priv) static int lwmi_om_master_bind(struct device *dev) { struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); - struct cd_list *tmp_list; + struct lwmi_cd_binder binder =3D { 0 }; int ret; =20 - ret =3D component_bind_all(dev, &tmp_list); + ret =3D component_bind_all(dev, &binder); if (ret) return ret; =20 - priv->cd01_list =3D tmp_list; + priv->cd01_list =3D binder.cd01_list; if (!priv->cd01_list) return -ENODEV; =20 @@ -618,6 +618,7 @@ static int lwmi_other_probe(struct wmi_device *wdev, co= nst void *context) { struct component_match *master_match =3D NULL; struct lwmi_om_priv *priv; + int ret; =20 priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -626,12 +627,26 @@ static int lwmi_other_probe(struct wmi_device *wdev, = const void *context) priv->wdev =3D wdev; dev_set_drvdata(&wdev->dev, priv); =20 - component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL); + lwmi_cd_match_add_all(&wdev->dev, &master_match); if (IS_ERR(master_match)) return PTR_ERR(master_match); =20 - return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops, - master_match); + ret =3D component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops, + master_match); + if (ret) + return ret; + + if (likely(component_master_is_bound(&wdev->dev, &lwmi_om_master_ops))) + return 0; + + /* + * The bind callbacks of both master and components were never called in + * this case - this driver won't work at all. Failing... + */ + dev_err(&wdev->dev, "unbound master; is any component failing to be probe= d?"); + + component_master_del(&wdev->dev, &lwmi_om_master_ops); + return -EXDEV; } =20 static void lwmi_other_remove(struct wmi_device *wdev) --=20 2.51.0 From nobody Sun Feb 8 11:21:37 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AB6AF22D7B6; Sun, 19 Oct 2025 21:05:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907932; cv=pass; b=MwbuJCRSJJKnjPYGuPlkGJfOK0w3HGEv1Wku0EvNRJ44yoN2z1tL72WXuDx49uBTV2HV6GFBfn7HwM6g5cbj+7VZkUFDNaw68G0KJ1G1ZtyArqbfi4KQiikLFRDejIDRzxngpJzRf1IaqAi3d/4TNXubnVlj/bjWom8eA2xChk8= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907932; c=relaxed/simple; bh=y8XfVv9LLUcyg0lrce3+MTbM/zQator+qblF5SJiE3w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JetwhLHmib4GDREcQucowddvW6/UlS9IhmKoaf+2IGL0mdwyATKk83lJKg343VjSXjZ9xYXnR/KuQNk0oB1SReMiqiRYROXHPzTG69/gLmNSCCnB53bKSf3T0GfqBkGa005tnd9RZjfYeK0BMlrsQdK6VRP0bKZoLY12aa4B2lc= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=z5u6zmGS; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="z5u6zmGS" ARC-Seal: i=1; a=rsa-sha256; t=1760907910; cv=none; d=zohomail.com; s=zohoarc; b=RwSZV8+D/Klf9v+AAnW9JGkZvC3j1keZvfxg9uhzHvyov1jfBnWne8wXVMHOPW2DDjNtDVPCOSgAmpSUrrnNo9XP9DCpwPFiuyiGi40tADoOkm6xj5pTeORn+0+h4hJCoimizd1CYFS5V/BzjuJ5OIv4kENBdWWlRNnQt5gjuhI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1760907910; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=BhIp2tdbDW8YVIbRRVL0xruboPT8ZYoMJmZA4xuCPXk=; b=P4ubz2ayRG8RqgbRvaFfyNyLvaSSeP/teYK0DiI3xVsj+TYbzeMQIqNmRy8qcVAdcLygxBFv3fl5gSbwSjXToqmipTuWixXGwvHjctmOwBHVn/tQPOyKWpCgThG4uxR+DXanDBWXv+VnNpZsjuRsClgJ2flVZ6jbQV0p3DG+a7E= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1760907910; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=BhIp2tdbDW8YVIbRRVL0xruboPT8ZYoMJmZA4xuCPXk=; b=z5u6zmGSqTkuaGTyLy7VFAYsmLum9Pb/ivGnxVXtm+YTGQF0PqNApSIbT3TTH/6e a4SBjhnyKXBWaJ5RHYTq2KF0REsJuJytlNDrKWVEGP/lkkilc9NB3BjK+5andn5wiru RFNL4c1ZOSOwmo8whQ5kmXMhJ+cDkz1XWhvXevuI= Received: by mx.zohomail.com with SMTPS id 1760907907432613.0884958227238; Sun, 19 Oct 2025 14:05:07 -0700 (PDT) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH 3/6] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Date: Mon, 20 Oct 2025 05:04:46 +0800 Message-ID: <20251019210450.88830-4-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251019210450.88830-1-i@rong.moe> References: <20251019210450.88830-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" Add support for LENOVO_CAPABILITY_DATA_00 WMI data block that comes on "Other Mode" enabled hardware. Provides an interface for querying if a given attribute is supported by the hardware, as well as its default value. Signed-off-by: Rong Zhang --- .../wmi/devices/lenovo-wmi-other.rst | 8 +++++++ drivers/platform/x86/lenovo/wmi-capdata.c | 23 ++++++++++++++++++- drivers/platform/x86/lenovo/wmi-capdata.h | 8 +++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index d7928b8dfb4b5..adbd7943c6756 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -31,6 +31,14 @@ under the following path: =20 /sys/class/firmware-attributes/lenovo-wmi-other/attributes// =20 +LENOVO_CAPABILITY_DATA_00 +------------------------- + +WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E`` + +The LENOVO-CAPABILITD_DATA_00 interface provides information on whether the +device supports querying or setting fan speed. + LENOVO_CAPABILITY_DATA_01 ------------------------- =20 diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x= 86/lenovo/wmi-capdata.c index 14175fe19247e..6927de409b09d 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -5,6 +5,9 @@ * Lenovo Capability Data provides information on tunable attributes used = by * the "Other Mode" WMI interface. * + * Capability Data 00 includes if the attribute is supported by the hardwa= re, + * and the default_value. All attributes are independent of thermal modes. + * * Capability Data 01 includes if the attribute is supported by the hardwa= re, * and the default_value, max_value, min_value, and step increment. Each * attribute has multiple pages, one for each of the thermal modes managed= by @@ -14,7 +17,7 @@ * - Initial implementation (formerly named lenovo-wmi-capdata01) * * Copyright (C) 2025 Rong Zhang - * - Unified implementation + * - Unified implementation for Capability Data 00 and 01 */ =20 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -36,12 +39,14 @@ =20 #include "wmi-capdata.h" =20 +#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB30= 0E" #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE0181= 54" =20 #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 =20 enum lwmi_cd_type { + LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01, }; =20 @@ -57,6 +62,7 @@ static const struct lwmi_cd_info { const char *name; enum lwmi_cd_type type; } lwmi_cd_table[] =3D { + LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00), LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01), }; =20 @@ -72,6 +78,7 @@ struct cd_list { u8 count; =20 union { + DECLARE_FLEX_ARRAY(struct capdata00, cd00); DECLARE_FLEX_ARRAY(struct capdata01, cd01); }; }; @@ -95,6 +102,9 @@ static int lwmi_cd_component_bind(struct device *cd_dev, struct lwmi_cd_binder *binder =3D data; =20 switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: + binder->cd00_list =3D priv->list; + break; case LENOVO_CAPABILITY_DATA_01: binder->cd01_list =3D priv->list; break; @@ -136,6 +146,9 @@ static const struct component_ops lwmi_cd_component_ops= =3D { return -EINVAL; \ } =20 +DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00); +EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CD"); + DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD"); =20 @@ -154,6 +167,10 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) void *p; =20 switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: + p =3D &priv->list->cd00[0]; + size =3D sizeof(priv->list->cd00[0]); + break; case LENOVO_CAPABILITY_DATA_01: p =3D &priv->list->cd01[0]; size =3D sizeof(priv->list->cd01[0]); @@ -199,6 +216,9 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enu= m lwmi_cd_type type) count =3D wmidev_instance_count(priv->wdev); =20 switch (type) { + case LENOVO_CAPABILITY_DATA_00: + list_size =3D struct_size(list, cd00, count); + break; case LENOVO_CAPABILITY_DATA_01: list_size =3D struct_size(list, cd01, count); break; @@ -346,6 +366,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev) .context =3D &lwmi_cd_table[_type] =20 static const struct wmi_device_id lwmi_cd_id_table[] =3D { + { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) }, { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) }, {} }; diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x= 86/lenovo/wmi-capdata.h index 1e5fce7836cbf..a6f0cb006e745 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -11,6 +11,12 @@ struct component_match; struct device; struct cd_list; =20 +struct capdata00 { + u32 id; + u32 supported; + u32 default_value; +}; + struct capdata01 { u32 id; u32 supported; @@ -21,9 +27,11 @@ struct capdata01 { }; =20 struct lwmi_cd_binder { + struct cd_list *cd00_list; struct cd_list *cd01_list; }; =20 +int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata00 *output); int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output); void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr); =20 --=20 2.51.0 From nobody Sun Feb 8 11:21:37 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4FCDD23D28C; Sun, 19 Oct 2025 21:05:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907937; cv=pass; b=AwEdn8IVoRGkEpLbB0NvICPAYk7a9K8wQBAJPYxsgPJV15vhC3twgTnm1FssDrxjeQCI98z6BkRfGRU3EPIyvXZcXO23ABLkmkklW9vaUNidXaDOpBJLUhHq/bUClSBK//Hk3of7nekohUYa+bSiVoglV25A8jpzVYywOv/z9eU= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907937; c=relaxed/simple; bh=OaprJbwje+Xl9JbayaYn5/GI1tElGDgZnu9rvvgb4Xk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=c7PBfO1jGEY3QUi9mOGksagc10X5+Yd/Hc6lv8gQXvh6e8Qm5VobFBqVUJnMPjrXNfx4dI5kheT/iQl0u3kYuV6IkhcsLHrD3Nbn95SYYHd3c9m0tPAf8jtIjpChfDIYZl3WyjWrQ0WXPpau/6bPI6paSnyDHn0Pp5iyZK6HGwc= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=MSaJ7ZOe; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="MSaJ7ZOe" ARC-Seal: i=1; a=rsa-sha256; t=1760907912; cv=none; d=zohomail.com; s=zohoarc; b=h/2K0aW12uJkaDq2ARDI22kbUc3+E07B19kNRcTUUN+Bd4YBpRsCZEGe6OtRiX4mhX/iJWWPzauRT3zDWvpnvMNcidAm3gYbYkcKvXsF0/a/uouMUIkhyhYvyHhm1rG/0B4pf9GLovdeWigygJsYHk4G9bclnVP2JXsBeiUIWrY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1760907912; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=U2Ir+b9C0zMSpon1Jv8i/FhV+jNWU5a/GVZ8v0qJwbc=; b=UQA+cDlc0QgpSRCU6iPSY4MZoy/PiiXFNrXjSRHPVf8BuYWoTMHAYQKL2nfNTyoeoh65ACT+d1PgraUgb48WFx1axrHy4FHahThPhI1TzPg1jdXgEpUqIxSdHam6FTvw+jzAKqcS8nlFEIwmwNV1undyD7uoD89TNrAJl5sgIYw= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1760907912; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=U2Ir+b9C0zMSpon1Jv8i/FhV+jNWU5a/GVZ8v0qJwbc=; b=MSaJ7ZOe9cEvasIylaa/Bkrqw3qArYB5fcpV/RESp1WKvUZPAQvJ3Ri8HpwMe2Hq SH84r2Fwhb4Qck9Ap9iuFgeZh54ve4gpH2hbeU0HdmDLXbLFKluGIiznaCnoRekn3aE XHUwawZOK9IsXDYBvUT3OQu5oSC6ENeajPnlF4IM= Received: by mx.zohomail.com with SMTPS id 1760907910578695.2158463740977; Sun, 19 Oct 2025 14:05:10 -0700 (PDT) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH 4/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM Date: Mon, 20 Oct 2025 05:04:47 +0800 Message-ID: <20251019210450.88830-5-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251019210450.88830-1-i@rong.moe> References: <20251019210450.88830-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" Register an HWMON device for fan spped RPM according to Capability Data 00 provided by lenovo-wmi-capdata. The corresponding HWMON nodes are: - fanX_enable: enable/disable the fan (tunable) - fanX_input: current RPM - fanX_target: target RPM (tunable) Signed-off-by: Rong Zhang --- .../wmi/devices/lenovo-wmi-other.rst | 5 + drivers/platform/x86/lenovo/Kconfig | 1 + drivers/platform/x86/lenovo/wmi-other.c | 324 +++++++++++++++++- 3 files changed, 317 insertions(+), 13 deletions(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index adbd7943c6756..cb6a9bfe5a79e 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -31,6 +31,11 @@ under the following path: =20 /sys/class/firmware-attributes/lenovo-wmi-other/attributes// =20 +Besides, this driver also exports fan speed RPM to HWMON: + - fanX_enable: enable/disable the fan (tunable) + - fanX_input: current RPM + - fanX_target: target RPM (tunable) + LENOVO_CAPABILITY_DATA_00 ------------------------- =20 diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/len= ovo/Kconfig index fb96a0f908f03..be9af04511462 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -263,6 +263,7 @@ config LENOVO_WMI_GAMEZONE config LENOVO_WMI_TUNING tristate "Lenovo Other Mode WMI Driver" depends on ACPI_WMI + select HWMON select FW_ATTR_CLASS select LENOVO_WMI_DATA select LENOVO_WMI_EVENTS diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86= /lenovo/wmi-other.c index 20c6ff0be37a1..f8771ed3c6642 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -14,7 +14,15 @@ * These attributes typically don't fit anywhere else in the sysfs and are= set * in Windows using one of Lenovo's multiple user applications. * + * Besides, this driver also exports tunable fan speed RPM to HWMON. + * * Copyright (C) 2025 Derek J. Clark + * - fw_attributes + * - binding to Capability Data 01 + * + * Copyright (C) 2025 Rong Zhang + * - HWMON + * - binding to Capability Data 00 */ =20 #include @@ -25,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -43,12 +52,20 @@ =20 #define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" =20 +#define LWMI_SUPP_VALID BIT(0) +#define LWMI_SUPP_MAY_GET (LWMI_SUPP_VALID | BIT(1)) +#define LWMI_SUPP_MAY_SET (LWMI_SUPP_VALID | BIT(2)) + #define LWMI_DEVICE_ID_CPU 0x01 =20 #define LWMI_FEATURE_ID_CPU_SPPT 0x01 #define LWMI_FEATURE_ID_CPU_SPL 0x02 #define LWMI_FEATURE_ID_CPU_FPPT 0x03 =20 +#define LWMI_DEVICE_ID_FAN 0x04 + +#define LWMI_FEATURE_ID_FAN_RPM 0x03 + #define LWMI_TYPE_ID_NONE 0x00 =20 #define LWMI_FEATURE_VALUE_GET 17 @@ -59,7 +76,18 @@ #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) =20 +/* Only fan1 and fan2 are present on supported devices. */ +#define LWMI_FAN_ID_BASE 1 +#define LWMI_FAN_NR 2 +#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE) + +#define LWMI_ATTR_ID_FAN_RPM(x) \ + (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \ + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x))) + #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" +#define LWMI_OM_HWMON_NAME "lenovo_wmi_other" =20 static BLOCKING_NOTIFIER_HEAD(om_chain_head); static DEFINE_IDA(lwmi_om_ida); @@ -76,15 +104,256 @@ struct lwmi_om_priv { struct component_master_ops *ops; =20 /* only valid after capdata bind */ + struct cd_list *cd00_list; struct cd_list *cd01_list; =20 + struct device *hwmon_dev; struct device *fw_attr_dev; struct kset *fw_attr_kset; struct notifier_block nb; struct wmi_device *wdev; int ida_id; + + struct fan_info { + u32 supported; + long target; + } fan_info[LWMI_FAN_NR]; }; =20 +/* =3D=3D=3D=3D=3D=3D=3D=3D HWMON (component: lenovo-wmi-capdata 00) =3D= =3D=3D=3D=3D=3D=3D=3D */ + +/** + * lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan + * @priv: Driver private data structure + * @channel: Fan channel index (0-based) + * @val: Pointer to value (input for set, output for get) + * @set: True to set value, false to get value + * + * Communicates with WMI interface to either retrieve current fan RPM + * or set target fan speed. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32= *val, bool set) +{ + struct wmi_method_args_32 args; + u32 method_id, retval; + int err; + + method_id =3D set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET; + args.arg0 =3D LWMI_ATTR_ID_FAN_RPM(channel); + args.arg1 =3D set ? *val : 0; + + err =3D lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id, + (unsigned char *)&args, sizeof(args), &retval); + if (err) + return err; + + if (!set) + *val =3D retval; + else if (retval !=3D 1) + return -EIO; + + return 0; +} + +/** + * lwmi_om_hwmon_is_visible() - Determine visibility of HWMON attributes + * @drvdata: Driver private data + * @type: Sensor type + * @attr: Attribute identifier + * @channel: Channel index + * + * Determines whether a HWMON attribute should be visible in sysfs + * based on hardware capabilities and current configuration. + * + * Return: permission mode, or 0 if invisible. + */ +static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_se= nsor_types type, + u32 attr, int channel) +{ + struct lwmi_om_priv *priv =3D (struct lwmi_om_priv *)drvdata; + bool r =3D false, w =3D false; + + if (type =3D=3D hwmon_fan) { + switch (attr) { + case hwmon_fan_enable: + case hwmon_fan_target: + r =3D w =3D priv->fan_info[channel].supported & LWMI_SUPP_MAY_SET; + break; + case hwmon_fan_input: + r =3D priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET; + break; + } + } + + if (!r) + return 0; + + return w ? 0644 : 0444; +} + +/** + * lwmi_om_hwmon_read() - Read HWMON sensor data + * @dev: Device pointer + * @type: Sensor type + * @attr: Attribute identifier + * @channel: Channel index + * @val: Pointer to store value + * + * Reads current sensor values from hardware through WMI interface. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types = type, + u32 attr, int channel, long *val) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); + u32 retval =3D 0; + int err; + + if (type =3D=3D hwmon_fan) { + switch (attr) { + case hwmon_fan_input: + err =3D lwmi_om_fan_get_set(priv, channel, &retval, false); + if (err) + return err; + + *val =3D retval; + return 0; + case hwmon_fan_enable: + case hwmon_fan_target: + /* -ENODATA before first set. */ + err =3D (int)priv->fan_info[channel].target; + if (err < 0) + return err; + + if (attr =3D=3D hwmon_fan_enable) + *val =3D priv->fan_info[channel].target !=3D 1; + else + *val =3D priv->fan_info[channel].target; + return 0; + } + } + + return -EOPNOTSUPP; +} + +/** + * lwmi_om_hwmon_write() - Write HWMON sensor data + * @dev: Device pointer + * @type: Sensor type + * @attr: Attribute identifier + * @channel: Channel index + * @val: Value to write + * + * Writes configuration values to hardware through WMI interface. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types= type, + u32 attr, int channel, long val) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); + u32 raw; + int err; + + if (type =3D=3D hwmon_fan) { + switch (attr) { + case hwmon_fan_enable: + case hwmon_fan_target: + if (attr =3D=3D hwmon_fan_enable) { + if (val =3D=3D 0) + raw =3D 1; /* stop */ + else if (val =3D=3D 1) + raw =3D 0; /* auto */ + else + return -EINVAL; + } else { + /* + * val > U16_MAX seems safe but meaningless. + */ + if (val < 0 || val > U16_MAX) + return -EINVAL; + raw =3D val; + } + + err =3D lwmi_om_fan_get_set(priv, channel, &raw, true); + if (err) + return err; + + priv->fan_info[channel].target =3D raw; + return 0; + } + } + + return -EOPNOTSUPP; +} + +static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] =3D { + /* Must match LWMI_FAN_NR. */ + HWMON_CHANNEL_INFO(fan, + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET, + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET), + NULL +}; + +static const struct hwmon_ops lwmi_om_hwmon_ops =3D { + .is_visible =3D lwmi_om_hwmon_is_visible, + .read =3D lwmi_om_hwmon_read, + .write =3D lwmi_om_hwmon_write, +}; + +static const struct hwmon_chip_info lwmi_om_hwmon_chip_info =3D { + .ops =3D &lwmi_om_hwmon_ops, + .info =3D lwmi_om_hwmon_info, +}; + +/** + * lwmi_om_hwmon_add() - Register HWMON device + * @priv: Driver private data + * + * Initializes capability data and registers the HWMON device. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_hwmon_add(struct lwmi_om_priv *priv) +{ + struct capdata00 capdata00; + int i, err; + + for (i =3D 0; i < LWMI_FAN_NR; i++) { + err =3D lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), + &capdata00); + if (err) + continue; + + priv->fan_info[i] =3D (struct fan_info) { + .supported =3D capdata00.supported, + .target =3D -ENODATA, + }; + } + + priv->hwmon_dev =3D hwmon_device_register_with_info(&priv->wdev->dev, LWM= I_OM_HWMON_NAME, + priv, &lwmi_om_hwmon_chip_info, NULL); + + return PTR_ERR_OR_ZERO(priv->hwmon_dev); +} + +/** + * lwmi_om_hwmon_remove() - Unregister HWMON device + * @priv: Driver private data + * + * Unregisters the HWMON device and resets all fans to automatic mode. + * Ensures hardware doesn't remain in manual mode after driver removal. + */ +static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv) +{ + hwmon_device_unregister(priv->hwmon_dev); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D fw_attributes (component: lenovo-wmi-capdata 0= 1) =3D=3D=3D=3D=3D=3D=3D=3D */ + struct tunable_attr_01 { struct capdata01 *capdata; struct device *dev; @@ -564,15 +833,17 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_pri= v *priv) device_unregister(priv->fw_attr_dev); } =20 +/* =3D=3D=3D=3D=3D=3D=3D=3D Self (master: lenovo-wmi-other) =3D=3D=3D=3D= =3D=3D=3D=3D */ + /** * lwmi_om_master_bind() - Bind all components of the other mode driver * @dev: The lenovo-wmi-other driver basic device. * - * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the - * lenovo-wmi-other master driver. On success, assign the capability data = 01 - * list pointer to the driver data struct for later access. This pointer - * is only valid while the capdata01 interface exists. Finally, register a= ll - * firmware attribute groups. + * Call component_bind_all to bind the lenovo-wmi-capdata devices to the + * lenovo-wmi-other master driver. On success, assign the capability data + * list pointers to the driver data struct for later access. These pointers + * are only valid while the capdata interfaces exist. Finally, register the + * HWMON device and all firmware attribute groups. * * Return: 0 on success, or an error code. */ @@ -586,26 +857,47 @@ static int lwmi_om_master_bind(struct device *dev) if (ret) return ret; =20 - priv->cd01_list =3D binder.cd01_list; - if (!priv->cd01_list) + if (!binder.cd00_list && !binder.cd01_list) return -ENODEV; =20 - return lwmi_om_fw_attr_add(priv); + priv->cd00_list =3D binder.cd00_list; + if (priv->cd00_list) { + ret =3D lwmi_om_hwmon_add(priv); + if (ret) + return ret; + } + + priv->cd01_list =3D binder.cd01_list; + if (priv->cd01_list) { + ret =3D lwmi_om_fw_attr_add(priv); + if (ret) { + if (priv->cd00_list) + lwmi_om_hwmon_remove(priv); + return ret; + } + } + + return 0; } =20 /** * lwmi_om_master_unbind() - Unbind all components of the other mode driver * @dev: The lenovo-wmi-other driver basic device * - * Unregister all capability data attribute groups. Then call - * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the - * lenovo-wmi-other master driver. Finally, free the IDA for this device. + * Unregister the HWMON device and all capability data attribute groups. T= hen + * call component_unbind_all to unbind the lenovo-wmi-capdata driver from = the + * lenovo-wmi-other master driver. */ static void lwmi_om_master_unbind(struct device *dev) { struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); =20 - lwmi_om_fw_attr_remove(priv); + if (priv->cd00_list) + lwmi_om_hwmon_remove(priv); + + if (priv->cd01_list) + lwmi_om_fw_attr_remove(priv); + component_unbind_all(dev, NULL); } =20 @@ -624,6 +916,9 @@ static int lwmi_other_probe(struct wmi_device *wdev, co= nst void *context) if (!priv) return -ENOMEM; =20 + /* Sentinel for on-demand ida_free(). */ + priv->ida_id =3D -EIDRM; + priv->wdev =3D wdev; dev_set_drvdata(&wdev->dev, priv); =20 @@ -654,7 +949,9 @@ static void lwmi_other_remove(struct wmi_device *wdev) struct lwmi_om_priv *priv =3D dev_get_drvdata(&wdev->dev); =20 component_master_del(&wdev->dev, &lwmi_om_master_ops); - ida_free(&lwmi_om_ida, priv->ida_id); + + if (priv->ida_id >=3D 0) + ida_free(&lwmi_om_ida, priv->ida_id); } =20 static const struct wmi_device_id lwmi_other_id_table[] =3D { @@ -679,5 +976,6 @@ MODULE_IMPORT_NS("LENOVO_WMI_CD"); MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); MODULE_AUTHOR("Derek J. Clark "); +MODULE_AUTHOR("Rong Zhang "); MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver"); MODULE_LICENSE("GPL"); --=20 2.51.0 From nobody Sun Feb 8 11:21:37 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D43B9239E79; Sun, 19 Oct 2025 21:05:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907940; cv=pass; b=PiFHLaOEjZQ0AdtuKZfDA236AP0U4l8WP8x2Zj91rDpDIQG2UvARXebJYfIZU9g5Wz+aupRzst8534/o+/O2KCqV3NrR96ltwCvlKDEVoWVehid7BXqT8CxAjFQ0zRuZdVaw5dfqXoy3jpYc4OVEVtnUcdUtDW2Utwkbdv5VHXs= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907940; c=relaxed/simple; bh=1If2IpgEUuSY0i0LqD5MWN2VRfH2k74qG/d84vMU+xY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Om+ocGLFqLC3JH3HaZFOgJA29Z47B/y5ri8V7FUy5Yw/SOiF9mwNdVa95gSjPNwhk3d9k/XlqwpP9RgE5UJiQnw4U1tiyt3z/LrPGI05/uVFv3eo91hIdMT7eT7U32FoMv/RqMFd9WH4+86QHwkbf+oACo8olIppaxXZjS5HeWo= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=ABLKUXbX; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="ABLKUXbX" ARC-Seal: i=1; a=rsa-sha256; t=1760907916; cv=none; d=zohomail.com; s=zohoarc; b=VFBFaHJ2Wik1uF6cmRn3com95pJUn2sJAMtL7WIp8HUxWyhcc8NcjHkdVTlFC6n44+RBhFfFe8kBVFLIe+VFooxSraI2kGsWJvH9E8j8PIe2ivGDYnxrRrvbVEU2f2JmJeGCzqCOPxvSk7Cx6gqt4d5B/zZmAl1sgZJ0zRPccrk= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1760907916; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=ce+zgvLE06DFHtkJAjiC6HTjBORK3iPZS0TzMHoUQXI=; b=KOHagQAnxlwsNZJtmHQldYX7D6Xfrhv6v6UVgEwPakNr/109i6BCvl89aVHBx1GxZP+UhUhbuMVuJYnROkYy8cpALWlZ4y5FA7jeqf5D/+aP0ZsYQm4+XPJi64A60//JgZdg/9t94w1p6qnjai4iAulFs5RjBmTkQgxqdAaal+4= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1760907916; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=ce+zgvLE06DFHtkJAjiC6HTjBORK3iPZS0TzMHoUQXI=; b=ABLKUXbX93Bj7rAEWjr9M7SVZ+JzLyBDx/AeC2dpOhDVFed/B7GX2yuZUvg45DVR k0fptjykN3nEGob7Vtb/ejTgDpqE80WukD7Zm2FlnrLNVflOlilNeRoJCKxnczagQp7 Y8GpRSc9J+4iOOgjcPlUHb9P+cTMnfI7/ChqG08k= Received: by mx.zohomail.com with SMTPS id 1760907913708259.65175883917107; Sun, 19 Oct 2025 14:05:13 -0700 (PDT) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH 5/6] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Date: Mon, 20 Oct 2025 05:04:48 +0800 Message-ID: <20251019210450.88830-6-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251019210450.88830-1-i@rong.moe> References: <20251019210450.88830-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" Add support for LENOVO_FAN_TEST_DATA WMI data block. Provides an interface for querying the min/max fan speed RPM (reference data) of a given fan ID. Signed-off-by: Rong Zhang --- .../wmi/devices/lenovo-wmi-other.rst | 17 +++ drivers/platform/x86/lenovo/wmi-capdata.c | 104 +++++++++++++++++- drivers/platform/x86/lenovo/wmi-capdata.h | 8 ++ 3 files changed, 128 insertions(+), 1 deletion(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index cb6a9bfe5a79e..cca96862ae9c4 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -66,6 +66,13 @@ The following attributes are implemented: - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking =20 +LENOVO_FAN_TEST_DATA +------------------------- + +WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21`` + +The LENOVO-LENOVO_FAN_TEST_DATA interface provides reference data for self= -test +of cooling fans, including minimum and maximum fan speed RPM. =20 WMI interface description =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D @@ -119,3 +126,13 @@ data using the `bmfdec `_ utility: [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize; [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize= ")] uint8 DefaultValue[]; }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("De= finition of Fan Test Data"), guid("{B642801B-3D21-45DE-90AE-6E86F164FB21}")] + class LENOVO_FAN_TEST_DATA { + [key, read] string InstanceName; + [read] boolean Active; + [WmiDataId(1), read, Description("Mode.")] uint32 NumOfFans; + [WmiDataId(2), read, Description("Fan ID."), WmiSizeIs("NumOfFans")] u= int32 FanId[]; + [WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("Num= OfFans")] uint32 FanMaxSpeed[]; + [WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("Num= OfFans")] uint32 FanMinSpeed[]; + }; diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x= 86/lenovo/wmi-capdata.c index 6927de409b09d..c861f2dd75c32 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -13,11 +13,15 @@ * attribute has multiple pages, one for each of the thermal modes managed= by * the Gamezone interface. * + * Fan Test Data includes the max/min fan speed RPM for each fan. This is + * reference data for self-test. If the fan is in good condition, it is ca= pable + * to spin faster than max RPM or slower than min RPM. + * * Copyright (C) 2025 Derek J. Clark * - Initial implementation (formerly named lenovo-wmi-capdata01) * * Copyright (C) 2025 Rong Zhang - * - Unified implementation for Capability Data 00 and 01 + * - Unified implementation for Capability Data 00/01 and Fan Test Data */ =20 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -41,6 +45,7 @@ =20 #define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB30= 0E" #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE0181= 54" +#define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21" =20 #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 @@ -48,6 +53,7 @@ enum lwmi_cd_type { LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01, + LENOVO_FAN_TEST_DATA, }; =20 #define LWMI_CD_TABLE_ITEM(_type) \ @@ -64,6 +70,7 @@ static const struct lwmi_cd_info { } lwmi_cd_table[] =3D { LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00), LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01), + LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA), }; =20 struct lwmi_cd_priv { @@ -80,6 +87,7 @@ struct cd_list { union { DECLARE_FLEX_ARRAY(struct capdata00, cd00); DECLARE_FLEX_ARRAY(struct capdata01, cd01); + DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan); }; }; =20 @@ -108,6 +116,14 @@ static int lwmi_cd_component_bind(struct device *cd_de= v, case LENOVO_CAPABILITY_DATA_01: binder->cd01_list =3D priv->list; break; + case LENOVO_FAN_TEST_DATA: + /* + * Do not expose dummy data. + * See also lwmi_cd_fan_list_alloc_cache(). + */ + if (priv->list->count) + binder->cd_fan_list =3D priv->list; + break; default: return -EINVAL; } @@ -152,6 +168,9 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CD= "); DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD"); =20 +DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan); +EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CD"); + /** * lwmi_cd_cache() - Cache all WMI data block information * @priv: lenovo-wmi-capdata driver data. @@ -175,6 +194,9 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) p =3D &priv->list->cd01[0]; size =3D sizeof(priv->list->cd01[0]); break; + case LENOVO_FAN_TEST_DATA: + /* Done by lwmi_cd_alloc() =3D> lwmi_cd_fan_list_alloc_cache(). */ + return 0; default: return -EINVAL; } @@ -197,6 +219,78 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) return 0; } =20 +/** + * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list + * @priv: lenovo-wmi-capdata driver data. + * @listptr: Pointer to returned cd_list pointer. + * + * Return: count of fans found, or an error. + */ +static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct = cd_list **listptr) +{ + u32 count, *fan_ids, *fan_min_rpms, *fan_max_rpms; + union acpi_object *ret_obj __free(kfree) =3D NULL; + struct block { u32 nr; u32 data[]; } *block; + struct cd_list *list; + size_t size; + int idx; + + ret_obj =3D wmidev_block_query(priv->wdev, 0); + if (!ret_obj) + return -ENODEV; + + /* + * This is usually caused by a dummy ACPI method. Do not return an error + * as failing to probe this device will result in master driver being + * unbound - this behavior aligns with lwmi_cd_cache(). + */ + if (ret_obj->type !=3D ACPI_TYPE_BUFFER) { + count =3D 0; + goto alloc; + } + + size =3D ret_obj->buffer.length; + block =3D (struct block *)ret_obj->buffer.pointer; + + count =3D size >=3D sizeof(*block) ? block->nr : 0; + if (size < struct_size(block, data, count * 3)) { + dev_warn(&priv->wdev->dev, + "incomplete fan test data block: %zu < %zu, ignoring\n", + size, struct_size(block, data, count * 3)); + count =3D 0; + } + + if (count =3D=3D 0) + goto alloc; + + if (count > U8_MAX) { + dev_warn(&priv->wdev->dev, + "too many fans reported: %u > %u, truncating\n", + count, U8_MAX); + count =3D U8_MAX; + } + + fan_ids =3D &block->data[0]; + fan_max_rpms =3D &block->data[count]; + fan_min_rpms =3D &block->data[count * 2]; + +alloc: + list =3D devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count),= GFP_KERNEL); + if (!list) + return -ENOMEM; + + for (idx =3D 0; idx < count; idx++) { + list->cd_fan[idx] =3D (struct capdata_fan) { + .id =3D fan_ids[idx], + .min_rpm =3D fan_min_rpms[idx], + .max_rpm =3D fan_max_rpms[idx], + }; + } + + *listptr =3D list; + return count; +} + /** * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata * @priv: lenovo-wmi-capdata driver data. @@ -222,6 +316,12 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, en= um lwmi_cd_type type) case LENOVO_CAPABILITY_DATA_01: list_size =3D struct_size(list, cd01, count); break; + case LENOVO_FAN_TEST_DATA: + count =3D lwmi_cd_fan_list_alloc_cache(priv, &list); + if (count < 0) + return count; + + goto got_list; default: return -EINVAL; } @@ -230,6 +330,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enu= m lwmi_cd_type type) if (!list) return -ENOMEM; =20 +got_list: ret =3D devm_mutex_init(&priv->wdev->dev, &list->list_mutex); if (ret) return ret; @@ -368,6 +469,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev) static const struct wmi_device_id lwmi_cd_id_table[] =3D { { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) }, { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) }, + { LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) }, {} }; =20 diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x= 86/lenovo/wmi-capdata.h index a6f0cb006e745..52bc215ac43d8 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -26,13 +26,21 @@ struct capdata01 { u32 max_value; }; =20 +struct capdata_fan { + u32 id; + u32 min_rpm; + u32 max_rpm; +}; + struct lwmi_cd_binder { struct cd_list *cd00_list; struct cd_list *cd01_list; + struct cd_list *cd_fan_list; }; =20 int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata00 *output); int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output); +int lwmi_cd_fan_get_data(struct cd_list *list, u32 attribute_id, struct ca= pdata_fan *output); void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr); =20 #endif /* !_LENOVO_WMI_CAPDATA_H_ */ --=20 2.51.0 From nobody Sun Feb 8 11:21:37 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5140D23A995; Sun, 19 Oct 2025 21:05:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907942; cv=pass; b=EX/puvGHAqs9tHxlGRGvAagHpQNeBO/TGNnABYtJmR9NfOi96zoNRjp4npe02iwuU/OCla5WPz88vHSP4re4bY0NRRCFuMqVwhP54caltv4P+brlXfxyMnNdcN0UpEdNe2nJz4K2bSHh+phsgssCHd488CNS6fcfgxAoR8+VkjU= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760907942; c=relaxed/simple; bh=s6pqm3zRlTbHHWxcmHvpGQAmgOvd4nI3aEs1DMn+Fqg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nrNR5phfcqO41dU3AS0GtCurZTpdtDYK0k5whi+IHkZX8Uo5IKPJxqHsQsjXXnrjm6YB0dpi8H7HLWc8VueS9qGobNrxJFZkFYHmS/rTHrq00yjScsj8f0fJdT2jyfkI98nqhqQcMNKo993J8Y/cBlOhBBU9/XCzRagMle706HQ= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=La9QjMIt; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="La9QjMIt" ARC-Seal: i=1; a=rsa-sha256; t=1760907918; cv=none; d=zohomail.com; s=zohoarc; b=h8RN2ep8QJ+4weax1siLWIVq/KzqolA6KkBlkDTWznLcof3HPmUgPXzGVFqF2uE8xK65gleoQTI0TFaJC2iFARqqEx2FEKPAXHHoX4l0pnrx3wo8A9pyrCkdbwf39tkCdKxgADhE2s/wagg7zmw+dS2ADP83hlvmIVa3s4+5734= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1760907918; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=88GnnzyCTjv54tzZiuroylaB6WMz0DKGgfQhhvwTf/M=; b=d4URiw/a8zLnX9PYlYfQ89YHPXN6W5Czx46ydkWNWRqj0hlM+MMeGkEyl16BWe7TeCuXG0DERVKF2JnjNKifmWpnDVttappKGgfnJy4NhMGCkQ3n/y/t61xGN+TM8qeFqZgmUb2RuXisqmp79RouDx5+ECo3Uc55rA8qg8OQP2w= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1760907918; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=88GnnzyCTjv54tzZiuroylaB6WMz0DKGgfQhhvwTf/M=; b=La9QjMItfi60ibKtol96DvuhCQGFjAYfo+jUJyu7Ij5Fv3og/xVxHUqGc1D2oMRX kPd4lwsRfwj9o504/XanswOBxQ56oCIAN/9LZOlMRVwiyFSwBFxB+mYPbHDyWSYaZ4Z PsRQvdRUNQv3eElTRjmXRuPRFhrOfKSp0FFTE8fw= Received: by mx.zohomail.com with SMTPS id 1760907916838767.9713060470293; Sun, 19 Oct 2025 14:05:16 -0700 (PDT) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH 6/6] platform/x86: lenovo-wmi-other: Report min/max RPM and hide dummy fans Date: Mon, 20 Oct 2025 05:04:49 +0800 Message-ID: <20251019210450.88830-7-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251019210450.88830-1-i@rong.moe> References: <20251019210450.88830-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" When Fan Test Data is available, make fans without such data invisible by default (opt-out with option ignore_fan_cap). Besides, report extra data for reference: - fanX_max: maximum RPM - fanX_min: minimum RPM Signed-off-by: Rong Zhang --- .../wmi/devices/lenovo-wmi-other.rst | 2 + drivers/platform/x86/lenovo/wmi-other.c | 74 +++++++++++++++++-- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index cca96862ae9c4..06ee7fe77e6ef 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -34,6 +34,8 @@ under the following path: Besides, this driver also exports fan speed RPM to HWMON: - fanX_enable: enable/disable the fan (tunable) - fanX_input: current RPM + - fanX_max: maximum RPM + - fanX_min: minimum RPM - fanX_target: target RPM (tunable) =20 LENOVO_CAPABILITY_DATA_00 diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86= /lenovo/wmi-other.c index f8771ed3c6642..aded709c1ba4a 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -14,7 +14,8 @@ * These attributes typically don't fit anywhere else in the sysfs and are= set * in Windows using one of Lenovo's multiple user applications. * - * Besides, this driver also exports tunable fan speed RPM to HWMON. + * Besides, this driver also exports tunable fan speed RPM to HWMON. Min/m= ax RPM + * are also provided for reference when available. * * Copyright (C) 2025 Derek J. Clark * - fw_attributes @@ -22,7 +23,7 @@ * * Copyright (C) 2025 Rong Zhang * - HWMON - * - binding to Capability Data 00 + * - binding to Capability Data 00 and Fan */ =20 #include @@ -106,6 +107,7 @@ struct lwmi_om_priv { /* only valid after capdata bind */ struct cd_list *cd00_list; struct cd_list *cd01_list; + struct cd_list *cd_fan_list; =20 struct device *hwmon_dev; struct device *fw_attr_dev; @@ -116,11 +118,20 @@ struct lwmi_om_priv { =20 struct fan_info { u32 supported; + long min_rpm; + long max_rpm; long target; } fan_info[LWMI_FAN_NR]; }; =20 -/* =3D=3D=3D=3D=3D=3D=3D=3D HWMON (component: lenovo-wmi-capdata 00) =3D= =3D=3D=3D=3D=3D=3D=3D */ +static bool ignore_fan_cap; +module_param(ignore_fan_cap, bool, 0444); +MODULE_PARM_DESC(ignore_fan_cap, + "Ignore fan capdata. " + "Results in fans missing from capdata keep visible. " + "Effectively disables mix/max fan speed RPM reporting."); + +/* =3D=3D=3D=3D=3D=3D=3D=3D HWMON (component: lenovo-wmi-capdata 00 & fan)= =3D=3D=3D=3D=3D=3D=3D=3D */ =20 /** * lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan @@ -184,6 +195,12 @@ static umode_t lwmi_om_hwmon_is_visible(const void *dr= vdata, enum hwmon_sensor_t case hwmon_fan_input: r =3D priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET; break; + case hwmon_fan_min: + r =3D priv->fan_info[channel].min_rpm >=3D 0; + break; + case hwmon_fan_max: + r =3D priv->fan_info[channel].max_rpm >=3D 0; + break; } } =20 @@ -233,6 +250,12 @@ static int lwmi_om_hwmon_read(struct device *dev, enum= hwmon_sensor_types type, else *val =3D priv->fan_info[channel].target; return 0; + case hwmon_fan_min: + *val =3D priv->fan_info[channel].min_rpm; + return 0; + case hwmon_fan_max: + *val =3D priv->fan_info[channel].max_rpm; + return 0; } } =20 @@ -271,6 +294,9 @@ static int lwmi_om_hwmon_write(struct device *dev, enum= hwmon_sensor_types type, return -EINVAL; } else { /* + * We don't check fan capdata here as it is just + * reference value for self-test. + * * val > U16_MAX seems safe but meaningless. */ if (val < 0 || val > U16_MAX) @@ -293,8 +319,10 @@ static int lwmi_om_hwmon_write(struct device *dev, enu= m hwmon_sensor_types type, static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] =3D { /* Must match LWMI_FAN_NR. */ HWMON_CHANNEL_INFO(fan, - HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET, - HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET), + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET | + HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET | + HWMON_F_MIN | HWMON_F_MAX), NULL }; =20 @@ -319,6 +347,7 @@ static const struct hwmon_chip_info lwmi_om_hwmon_chip_= info =3D { */ static int lwmi_om_hwmon_add(struct lwmi_om_priv *priv) { + struct capdata_fan capdata_fan; struct capdata00 capdata00; int i, err; =20 @@ -330,8 +359,42 @@ static int lwmi_om_hwmon_add(struct lwmi_om_priv *priv) =20 priv->fan_info[i] =3D (struct fan_info) { .supported =3D capdata00.supported, + .min_rpm =3D -ENODATA, + .max_rpm =3D -ENODATA, .target =3D -ENODATA, }; + + if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID)) + continue; + + if (ignore_fan_cap) { + dev_notice_once(&priv->wdev->dev, "fan capdata ignored\n"); + continue; + } + + if (!priv->cd_fan_list) { + dev_notice_once(&priv->wdev->dev, "fan capdata unavailable\n"); + continue; + } + + /* + * Fan attribute from capdata00 may be dummy (i.e., + * get: constant dummy RPM, set: no-op with retval =3D=3D 0). + * + * If fan capdata is available and a fan is missing from it, + * make the fan invisible. + */ + err =3D lwmi_cd_fan_get_data(priv->cd_fan_list, LWMI_FAN_ID(i), &capdata= _fan); + if (err) { + dev_dbg(&priv->wdev->dev, + "fan %d missing from fan capdata, hiding\n", + LWMI_FAN_ID(i)); + priv->fan_info[i].supported =3D 0; + continue; + } + + priv->fan_info[i].min_rpm =3D capdata_fan.min_rpm; + priv->fan_info[i].max_rpm =3D capdata_fan.max_rpm; } =20 priv->hwmon_dev =3D hwmon_device_register_with_info(&priv->wdev->dev, LWM= I_OM_HWMON_NAME, @@ -862,6 +925,7 @@ static int lwmi_om_master_bind(struct device *dev) =20 priv->cd00_list =3D binder.cd00_list; if (priv->cd00_list) { + priv->cd_fan_list =3D binder.cd_fan_list; ret =3D lwmi_om_hwmon_add(priv); if (ret) return ret; --=20 2.51.0