From nobody Sun Feb 8 14:59:34 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 B95CE2DFA3B; Thu, 30 Oct 2025 19:40: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=1761853231; cv=pass; b=c269ceP+MoqStI/Y17HByomqv30Y3L8F/lBMvJlBQIsLGNQ+8kPbisCoz/u3WlT4wM116so52SZQlXvJYyFY8pNflmQU2RN64GfmzsEXPyY9UyPrWpT8WlDyIB8v+Q3Kyf6KBwFvwOJsuGEQxkvfB2eAzTT3MQnykVHxCES0n34= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1761853231; c=relaxed/simple; bh=cv1ft3PGhct9I9WBvgDbAmqB37eNL740sjipSv70E6I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=TU7Imq9iOltD94bgsDHaA1pbt+iw77XJ5tmAOYRudnDF0znhpLn9ZzYDsOaIeFof3V0mKAoIcDcLxMSkpFnJrJWIzRwnZhX/u8/XpM69jWRmLUoJt261VzX6Gzd4GAzPe3V85WBJ3+y3Bo8yqGnzaOkUa9ZPuB5BQJ9fRxiSLV4= 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=g1YTeHtq; 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="g1YTeHtq" ARC-Seal: i=1; a=rsa-sha256; t=1761853209; cv=none; d=zohomail.com; s=zohoarc; b=PrTuov8G3SZg+tCWd5Y4rTGXq1p/Yu8iFJR9M/nM2Xhne8GuSO6E9HMzBWBzqm6riknQXqXghG8Vi/BzKy/C+bsjMNxNCXnfr4gSi7l/jfUSCRLH6ilk1CgEPv8NjuVvC4cyghgYFP2sy1hQOwljKM/0TQfVpMH6j2PFrqYteDE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1761853209; 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=xHAE4Kgqjxf5Eh/f0+uiZ3AjA3NIwnCEMp2CLTXX03Q=; b=fgGtgrUbf4lDEljaS7P7qrG3mavYbL/oKm0tm6diMSuk3zX/J6xpX3uZHVqd9nNoHarmsNviqjGLjhpAfMTxAIu9WD1kCkkLao1ozTk4RHjLLdaFwmm7tpOj9xcyd8MQQPVCJH/1y+zlc1m1YOAqaFwXNUQJw34IVMLhhKp27LM= 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=1761853209; 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=xHAE4Kgqjxf5Eh/f0+uiZ3AjA3NIwnCEMp2CLTXX03Q=; b=g1YTeHtqC8kMYP7zOkIm+vC3psC66u2EGmlNauhaEQzzemxghMwWKRhAb9Oda55E 8ds6m2k5w2H1zs19GIDGIZ9Vc1rIKN/C+km2HrH0TC8/JdP4xMsEXnSVpDZSa5DZ/GC QXS8mH0sUIZEKzu0EfC0JEXxVMGIn+V8gvPmmtTM= Received: by mx.zohomail.com with SMTPS id 1761853206712163.04637737631458; Thu, 30 Oct 2025 12:40:06 -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 v2 1/6] platform/x86: lenovo-wmi-helpers: convert returned 4B buffer into u32 Date: Fri, 31 Oct 2025 03:39:39 +0800 Message-ID: <20251030193955.107148-2-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251030193955.107148-1-i@rong.moe> References: <20251030193955.107148-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 Windows WMI-ACPI driver converts all ACPI objects into a common buffer format, so returning a buffer with four bytes will look like an integer for WMI consumers under Windows. Therefore, some devices may simply implement the corresponding ACPI methods to always return a buffer. While lwmi_dev_evaluate_int() expects an integer (u32), convert returned 4-byte buffer into u32 to support these devices. Suggested-by: Armin Wolf Link: https://lore.kernel.org/r/f1787927-b655-4321-b9d9-bc12353c72db@gmx.de/ Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark --- Changes in v2: - New patch (thanks Armin Wolf) --- drivers/platform/x86/lenovo/wmi-helpers.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x= 86/lenovo/wmi-helpers.c index f6fef6296251e..f3bc92ac505ac 100644 --- a/drivers/platform/x86/lenovo/wmi-helpers.c +++ b/drivers/platform/x86/lenovo/wmi-helpers.c @@ -59,10 +59,25 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 i= nstance, u32 method_id, if (!ret_obj) return -ENODATA; =20 - if (ret_obj->type !=3D ACPI_TYPE_INTEGER) - return -ENXIO; + switch (ret_obj->type) { + /* + * The ACPI method may simply return a 4-byte buffer when a u32 + * integer is expected. This is valid on Windows as its WMI-ACPI + * driver converts everything to a common buffer. + */ + case ACPI_TYPE_BUFFER: { + if (ret_obj->buffer.length !=3D 4) + return -ENXIO; =20 - *retval =3D (u32)ret_obj->integer.value; + *retval =3D *((u32 *)ret_obj->buffer.pointer); + return 0; + } + case ACPI_TYPE_INTEGER: + *retval =3D (u32)ret_obj->integer.value; + return 0; + default: + return -ENXIO; + } } =20 return 0; --=20 2.51.0 From nobody Sun Feb 8 14:59:34 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 A84EB3115BD; Thu, 30 Oct 2025 19:40: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=1761853237; cv=pass; b=pLkqYkb2i24vts1NuV7XqLq31EUQ9WqQl9R6G5q+3HhnnypfW3WGArfcM66IGB9EAevPoJ+dLCX6zkYO8tmBxYzbooT/lVw8d+vSqoPY1tAR35i0tedBttC27egmsv1n7FoO1kBuxMCENiCq7Tvk2oT1M8alJbPtfIG6rzG9sH4= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1761853237; c=relaxed/simple; bh=EwYSFip0xkxdisetFsKc41eDAaNkw1CybzBwPukMbfU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=sUCDsgMQZ/A1rWhqw96iVNyScAGwFq62TjdW968eNnhdNRfaC0m09C2c9kEFkyubv0HIBcbJmw/rCqpVdSaRWvYM0fQmU6R8igU8iTxZa/WuHeDksIbgD2CqGzrzdzS4NC1l20KBZ2DJ0D+JyKJ9tlt6srIf+Jd4L3oEt0w9yvY= 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=TBzHyEPt; 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="TBzHyEPt" ARC-Seal: i=1; a=rsa-sha256; t=1761853211; cv=none; d=zohomail.com; s=zohoarc; b=fO8u98XRtsck7+r0IMwlpCOZHX/H2jNkctJiGKn4iuIdKx6hL3QsGIgC1txkAhCKTu+O32BshqrjGcDm7Q5g9J6LqTedKluTWc/ivG6nBi7ryMOrvaP1QS/D5AVti16Xl1eKzChnrbMq+jm2jrLKF9tMWfGi/VWUdaBkZvxBLnY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1761853211; 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=mZgUt+m54r4jkjTBdacgIeZ0G6OG0Fd7S0/uWDZzHYE=; b=kZ61Q8u4la7Zd71DowcYbXcpve/r0TZrL0nSJey0cTEZZfeA1YN3ggdfqsJqgi5TTvgS/7zkKE/2lFHhtaUUcHaCCi1j5/8ekWoGjWCwuqbNIVK0e4Izrc6uYQQxrHIRyAAUdCsfHwH2CTWGUa5x9wG6VCnsOjayOr4GCPwM0cM= 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=1761853211; 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=mZgUt+m54r4jkjTBdacgIeZ0G6OG0Fd7S0/uWDZzHYE=; b=TBzHyEPtC84SwhnDv/s+IFXBLinJRMHTfqVGJdUFUF87Ta5NtV8Mo9uq9L+ira2z cUeHYm/J0VkEIMRqJay6YcF/iuvP/P7pIpe2+wNCl3oTRFIK21UXNhjrSKBOFPDL7Z5 hpRiV6AMyaYPV27pIJYRuqGLtzlPvK0liDilEnLg= Received: by mx.zohomail.com with SMTPS id 1761853209602940.0128653169317; Thu, 30 Oct 2025 12:40:09 -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 v2 2/6] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Date: Fri, 31 Oct 2025 03:39:40 +0800 Message-ID: <20251030193955.107148-3-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251030193955.107148-1-i@rong.moe> References: <20251030193955.107148-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 Tested-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 14:59:34 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 A84623112B2; Thu, 30 Oct 2025 19:40: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=1761853237; cv=pass; b=ChfrNeoIRp4POkLNdUyWzfrD3fNsSMdCdX3wyM/SH5cxdtsBEmxO4lw0PuyBiOnEBWQIaj94ECfJShPiVkaaWuD0VloUGmUT8w1r0h9qnXqUib1VUSZ3woyu7Z3KhJ0arpqeucTQr+0Hw4ssleZTV9E02axOPR8LN8FHu0nJQjg= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1761853237; c=relaxed/simple; bh=FsNU6umgcDHj/E+QMhSy9D5+2ku+Cuqu0zrX/3ZWiqc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YrWKzveM2MULO4COAwrBeL5ffh958s/Q07lOpf5jZ3uhmRC4/ysWJ6/OqGnGnuaAKurljuqdV05XwO8IJi1EDfrR0HzQOae4r6+Q3zXZTwImEtHfYw8QjSnDVnhtGbLjR1q0Tf7tWnpHBrTaSL4s/F/36//QdMRgG2gFcSknh/I= 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=MLBGbahi; 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="MLBGbahi" ARC-Seal: i=1; a=rsa-sha256; t=1761853215; cv=none; d=zohomail.com; s=zohoarc; b=POndMF8xPkWPDMYQJ/H6zOGsOMkjmUO7AV6FYMKij2dRd18GRqAmIzTNGsVN6pQprQHH0gdI56ZkqpREa29D9rNyVTG97OM+f/4TsH79vwb9QqDjc6ykAU8TDbfqmZGrgLOqC/VhY7bLuRrMkQQb0Jeyn7Uo1VcLujbdHUPlqKw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1761853215; 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=XZ/hpgYmAbHclvklyZmF4GdL6yw3mxHYww3bcZ/WOIQ=; b=Hqfl0IossEOsh2D1njWQm8PeqsrBFxSXGOAarSrk91XgpjNuINkEB6Z2u3z1dFoPfbBRE5F5bwdfUC1BsCjRxjnrDNKUt1rjRadMOz8zdXUHgtw/OQ98f++hT6kvmw2WDglSBJsNiBbZ/bKq0MXiz/WAjQkMZEXzcgXDno+Ds5M= 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=1761853215; 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=XZ/hpgYmAbHclvklyZmF4GdL6yw3mxHYww3bcZ/WOIQ=; b=MLBGbahigihmGriK2s1ImyodFt/Uv8v6Qwp3CuFHKh+gDp5I2ktTG4U4EdMg2Za+ rNCAMqFM5r/lyu2zHBBXPZcxy3UQm/mKWZYcb64niEiy/ZTh+zOcgN0epyafuU4r4qX R7GLtwEtZ2FL8scW84gXG9fzTEjIcKV7CBbhfOTI= Received: by mx.zohomail.com with SMTPS id 1761853212345427.92926475222464; Thu, 30 Oct 2025 12:40:12 -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 v2 3/6] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Date: Fri, 31 Oct 2025 03:39:41 +0800 Message-ID: <20251030193955.107148-4-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251030193955.107148-1-i@rong.moe> References: <20251030193955.107148-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 implementation are heavily bound to capdata01. Rewrite it so that it is suitable to utilize other Capability Data as well. No functional change intended. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark --- Changes in v2: - Fix function parameter 'type' not described in 'lwmi_cd_match' (thanks kernel test bot) --- 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..1f7fc09b7c3f3 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. + * @type: 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 14:59:34 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 87F833126C5; Thu, 30 Oct 2025 19:40: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=1761853240; cv=pass; b=CQf6kAzco0zsIEJZFa9gTzFPq4t5PuKHrRIhSnAnZ8TP9AuWr1wvCoWvLetvEmjR3pHVOwmJEGLmFLZnd1V6B8HSfyb2NeTyyKTeiw7bE7eT5Qs5FDi2HrSJVQzANb9s+UfWQ2ISL8yHcGZOrYdyiXtqP5XvCgqNkNxy81Ig188= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1761853240; c=relaxed/simple; bh=6rpCnxSi3VTo56evbz/swMnf5abFzS9PxMvk5BGNWTw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=eo9Dc5jBOHxY55fjBWWHsJb0/dOLnCIgVSpCADEq4ryrgxSUoRAAamLHuOcEuKNBciCiqzvddDqJJVy3nX2JrWOPX5O8BVW6j/FJH7YBgzFhgHbigW+ESfLkD7sn2Ex6dVFkSBAK3Y02JyMJCXA/XzGcARlEbeCnGRKlUhpdBw0= 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=vrasOk0q; 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="vrasOk0q" ARC-Seal: i=1; a=rsa-sha256; t=1761853217; cv=none; d=zohomail.com; s=zohoarc; b=NwzJFCw4l057JcRb1rwJzPPRiJ7ryfjuB85VQVx2v5EenMwhjOo/o8yHdRZRYbz6229cJUfhmPDUBAYj0dCEbbXL9km6yBuRyUESvYpTjG0Igqmi8o6Cd19TWz5y+/pj7Fz4S1vmGLcYgLezCE5FPB9K2TLbIRoZ62MtZCr+7IY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1761853217; 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=U6n4F1lJ7im0nb5+E2keTRxxYloZMzmcQwhCJhwOMOs=; b=jWi/1f4DxzC6vXF2oJW+vJ4R3jlTowc2kU6GRJlXa5j8TXtaqCMTk23bAbGrnx03ZFBtxnmQVCeQ4ULG1X+qsSndeSTTR/yToRja0Oo319pCPcY978c1BRmhOVlqNtKoiVaG/hxOdXCnX0Zg1JavD5eVMz9Jyl3aQR+wqiTU4/o= 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=1761853217; 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=U6n4F1lJ7im0nb5+E2keTRxxYloZMzmcQwhCJhwOMOs=; b=vrasOk0qNEIsnMbLwwdsuKaTNaQHHyOdaL2NcwOCOmC4IU5bJL3bLApVHxVlSvSG wXTo4YxorrirEYMxi2OLaffP3/QOaybWwi1m1QXEuZa5WccJiKT9wMjCpGJq4fUNGJN K6LGelIkKIK1JngVAfE/Iaf/Nfe+LU8S5dQoJolw= Received: by mx.zohomail.com with SMTPS id 1761853215208433.41585020643686; Thu, 30 Oct 2025 12:40:15 -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 v2 4/6] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Date: Fri, 31 Oct 2025 03:39:42 +0800 Message-ID: <20251030193955.107148-5-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251030193955.107148-1-i@rong.moe> References: <20251030193955.107148-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 Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark --- Changes in v2: - Reword documentation (thanks Derek J. Clark) --- .../wmi/devices/lenovo-wmi-other.rst | 15 ++++++++++--- drivers/platform/x86/lenovo/wmi-capdata.c | 21 +++++++++++++++++++ drivers/platform/x86/lenovo/wmi-capdata.h | 8 +++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index d7928b8dfb4b5..fcad595d49af2 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -31,13 +31,22 @@ 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_CAPABILITY_DATA_00 interface provides various information that +does not rely on the gamezone thermal mode. + LENOVO_CAPABILITY_DATA_01 ------------------------- =20 WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154`` =20 -The LENOVO_CAPABILITY_DATA_01 interface provides information on various -power limits of integrated CPU and GPU components. +The LENOVO_CAPABILITY_DATA_01 interface provides various information that +relies on the gamezone thermal mode, including power limits of integrated +CPU and GPU components. =20 Each attribute has the following properties: - current_value @@ -48,7 +57,7 @@ Each attribute has the following properties: - scalar_increment - type =20 -The following attributes are implemented: +The following firmware-attributes are implemented: - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x= 86/lenovo/wmi-capdata.c index 1f7fc09b7c3f3..e8ec30701d883 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 @@ -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 14:59:34 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 F1453304972; Thu, 30 Oct 2025 19:40:41 +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=1761853244; cv=pass; b=MPsMuiHb/jEWWkvcFBbOwvD86PVEHLPeqtBkqEhiVrj3YbFZdYVrYPCz6QR2fSy5vJqJeH39yJ+HxjNARXJ14pCDxYyOdJKqsGptowzGhfsGyXQb1tWhL7kaztgKcNF38Adw/1cV8lGKCgILgh218wzBQp/2IwDUzVsSx87QsFA= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1761853244; c=relaxed/simple; bh=s1r83kHt/+/LaHnHWoWPT4x2C8lUjwfCRTnW+kikaco=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=eLlsbNUssJYYNzl9rpdUePavIiE08X+UhDq0sAe1gmSCj2q4S2HyCFDykrB0sXzyKUiJffkOOkr4eoVDIeikFhIIBTrVHVNGgAynpNBEbzlCNvF45Bh3DOpEsY4uwSj+EwIWahPOyGsIuhIY1zg//lXGIrAQ6hpZaf9Acmnn+uQ= 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=SVHKrpaY; 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="SVHKrpaY" ARC-Seal: i=1; a=rsa-sha256; t=1761853221; cv=none; d=zohomail.com; s=zohoarc; b=bwlE0tInwFhvwIqXeqVGL+gi+JpvkfOG0tj2mQU0Y4ddURPDd2xFjWB3eogBHJfji+lSkmLkGscRVlLNDZ0y9Fepsko9KFS1KMyg8NjNUa6Fl/ZQh2VQAis1xU4Np6S9FcZrsZLqxj2zzc0iARpFICFjoEuw2lcdM9CEMH+4Q1s= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1761853221; 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=G4B7kpjHBTyDedNGbEJG/5gqYLD7SBjd1z7ux9NbJxg=; b=Aaw/GdCGHVYi2j3MA0BpSP5QtUAZ+QncttSpG6XSd9EVqXDStOaC/gK1SJ7khe0AGGsiQ9X7yN8RGRaJZy9XhdLDwpW98jK/BmJPYBGFcAr77XqlMDDit7CItILqtf/aoESJrmOVHZuktp/7tTdQqwOCfHC/bt+Ujvt3iDQFLxk= 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=1761853221; 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=G4B7kpjHBTyDedNGbEJG/5gqYLD7SBjd1z7ux9NbJxg=; b=SVHKrpaY62jP1LP5k5z17KDgJ5hf1aRZhw+DEljyxybWoQyjEJzGPgrnscqNhdE6 E2rbzpzIxYIbcSXZ4fYujtwmOunetKsRjBqwSk8f9vrzyRZ7Gxf9KdXjKGZC14jw9Sb MmChgs09p7+308yWFO8aQ5TqCfx/LZs/FNNHUyPU= Received: by mx.zohomail.com with SMTPS id 1761853218126618.2077188288669; Thu, 30 Oct 2025 12:40:18 -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 v2 5/6] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Date: Fri, 31 Oct 2025 03:39:43 +0800 Message-ID: <20251030193955.107148-6-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251030193955.107148-1-i@rong.moe> References: <20251030193955.107148-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 Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark --- Changes in v2: - Reword documentation --- .../wmi/devices/lenovo-wmi-other.rst | 17 +++ drivers/platform/x86/lenovo/wmi-capdata.c | 102 ++++++++++++++++++ drivers/platform/x86/lenovo/wmi-capdata.h | 8 ++ 3 files changed, 127 insertions(+) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index fcad595d49af2..821282e07d93c 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -62,6 +62,13 @@ The following firmware-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_FAN_TEST_DATA interface provides reference data for self-test of +cooling fans. =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 @@ -115,3 +122,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 e8ec30701d883..e456aace87f24 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -13,6 +13,10 @@ * 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) * @@ -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 14:59:34 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 A88A9327213; Thu, 30 Oct 2025 19:40:43 +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=1761853246; cv=pass; b=ooCDirbxVTJDnPEQ+XShP10bC6GSlAoXxcl/6tvPNqufSWiN7dyBX0tp0TtsxFVpkQVOW4mpdFUq9pjOR+VpypUusrZdsn4Q3ryIuqOivlsSjdoViFrcfOUIAsEcxD6LxD11/HowRPixxyRGyxNp/Yu449htPbeP91fHSFzFkOM= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1761853246; c=relaxed/simple; bh=q6WtKQAoxPCvMTJZdkOGurrVpKvULJLhoj61UqI/9qQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JN/fC0mBUmtOLmT9HL33WZJxR5yCrdsOPlhxIWHtZoCzhzlIWG5z580u6rOu5Ur8yQNbMNQXihBe7OK3uwde941XWe4pTnXv7fVxKG++dyZEnqGMJaK/1sMvhMsiUOBdNvaz37Stev4i63RbqdRRWA/+HnCPBJl9r78DCjgtKOI= 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=YAyQ9LJk; 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="YAyQ9LJk" ARC-Seal: i=1; a=rsa-sha256; t=1761853223; cv=none; d=zohomail.com; s=zohoarc; b=ZhM54fHbdyYa75NOnDxRmZi9JfU9kP1ZMhS3oscjJtEDfzLvnaE8BHsC3mBHSxwNuEDFGhN3xYo7fkXblxVUCRrO28bolYpX28mGIN8UVFTdJxmGe1d7lqjYkGNie8wV8cnALDgR5m4VPLFKA8cbqb9MDJ2pG600FJBcNdzxfJ4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1761853223; 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=EBc5iQ2a7NL60QeclBbv+d71fEVTq+rLL7MfSHy/b5M=; b=MtzzgCbIWh1X0pj2YK4qn42jXaqQbLn9PpgCaQPq8ESnM+FjCcbvX7DClchFuSkrlejiCiFV5D1d6AJoKmR0/kmEhxujuG10nnTVnCuTO7DKQwOatMR+0VTEi0H63WdzeA69B+McWFN3VQU6S3wk+hAMnBpTR1tiyjQ4xfHIfl4= 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=1761853223; 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=EBc5iQ2a7NL60QeclBbv+d71fEVTq+rLL7MfSHy/b5M=; b=YAyQ9LJkDx3T9K2ejbew5VaZvrZXFK+NidlRoxpdlJYjpMtm54960oQd68V0A7co 1hLSrpAGzspW5YmofvcF/zoczKG1wVGon2hoTJvIwxXzk5e3m6T5jzvarCQ+aDWKqGi Qj0tvNWXVJnTpFxCpQ2Wy2CAJ0h/CBgINRj3MqUk= Received: by mx.zohomail.com with SMTPS id 1761853221156673.3177126437446; Thu, 30 Oct 2025 12:40:21 -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 v2 6/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM Date: Fri, 31 Oct 2025 03:39:44 +0800 Message-ID: <20251030193955.107148-7-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251030193955.107148-1-i@rong.moe> References: <20251030193955.107148-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 and Fan Test Data provided by lenovo-wmi-capdata. The corresponding HWMON nodes are: - fanX_enable: enable/disable the fan (tunable) - fanX_input: current RPM - fanX_max: maximum RPM - fanX_min: minimum RPM - fanX_target: target RPM (tunable) Signed-off-by: Rong Zhang Tested-by: Derek J. Clark --- Changes in v2: - Define 4 fan channels instead of 2 (thanks Derek J. Clark) - Squash min/max reporting patch into this one (ditto) - Query 0x04050000 for interface availability (ditto) - New parameter "expose_all_fans" to skip this check - Enforce min/max RPM constraint on set (ditto) - New parameter "relax_fan_constraint" to disable this behavior - Drop parameter "ignore_fan_cap", superseded by the next one - New parameter "expose_all_fans" to expose fans w/o such data - Assume auto mode on probe (ditto) - Reword documentation (ditto) - Do not register HWMON device if no fan can be exposed - fanX_target: Return -EBUSY instead of raw target value when fan stops --- .../wmi/devices/lenovo-wmi-other.rst | 11 + drivers/platform/x86/lenovo/Kconfig | 1 + drivers/platform/x86/lenovo/wmi-other.c | 467 +++++++++++++++++- 3 files changed, 467 insertions(+), 12 deletions(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index 821282e07d93c..10e47c60982e5 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -31,6 +31,8 @@ under the following path: =20 /sys/class/firmware-attributes/lenovo-wmi-other/attributes// =20 +Besides, this driver also exports attributes to HWMON. + LENOVO_CAPABILITY_DATA_00 ------------------------- =20 @@ -39,6 +41,11 @@ WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E`` The LENOVO_CAPABILITY_DATA_00 interface provides various information that does not rely on the gamezone thermal mode. =20 +The following HWMON attributes are implemented: + - fanX_enable: enable/disable the fan (tunable) + - fanX_input: current RPM + - fanX_target: target RPM (tunable) + LENOVO_CAPABILITY_DATA_01 ------------------------- =20 @@ -70,6 +77,10 @@ WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21`` The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of cooling fans. =20 +The following HWMON attributes are implemented: + - fanX_max: maximum RPM + - fanX_min: minimum RPM + 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 =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..e067e55e95562 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -14,7 +14,16 @@ * 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. Min/m= ax RPM + * are also provided for reference. + * * Copyright (C) 2025 Derek J. Clark + * - fw_attributes + * - binding to Capability Data 01 + * + * Copyright (C) 2025 Rong Zhang + * - HWMON + * - binding to Capability Data 00 and Fan */ =20 #include @@ -25,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -43,12 +53,21 @@ =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_FEATURE_ID_FAN_TEST 0x05 + #define LWMI_TYPE_ID_NONE 0x00 =20 #define LWMI_FEATURE_VALUE_GET 17 @@ -59,7 +78,24 @@ #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) =20 +#define LWMI_FAN_ID_BASE 1 +#define LWMI_FAN_NR 4 +#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_ATTR_ID_FAN_TEST \ + (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST) | \ + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_TYPE_ID_NONE)) + +#define LWMI_FAN_STOP_RPM 1 + #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 +112,391 @@ 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; + struct cd_list *cd_fan_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; + u32 last_target; + long min_rpm; + long max_rpm; + } fan_info[LWMI_FAN_NR]; +}; + +/* + * Visibility of fan channels: + * + * +---------------------+---------+------------------+-------------------= ----+------------+ + * | | default | +expose_all_fans | +relax_fan_constra= int | +both | + * +---------------------+---------+------------------+-------------------= ----+------------+ + * | canonical | RW | RW | RW+relaxed = | RW+relaxed | + * +---------------------+---------+------------------+-------------------= ----+------------+ + * | -capdata_fan | N | RO | N = | RW+relaxed | + * +---------------------+---------+------------------+-------------------= ----+------------+ + * | -FAN_TEST.supported | N | RW | N = | RW+relaxed | + * +---------------------+---------+------------------+-------------------= ----+------------+ + * | -both | N | RO | N = | RW+relaxed | + * +---------------------+---------+------------------+-------------------= ----+------------+ + * + * Note: LWMI_ATTR_ID_FAN_RPM[idx].supported is always checked before expo= sing a channel. + */ +static bool expose_all_fans; +module_param(expose_all_fans, bool, 0444); +MODULE_PARM_DESC(expose_all_fans, + "This option skips some capability checks and solely relies on per-channe= l ones " + "to expose fan attributes. Use with caution."); + +static bool relax_fan_constraint; +module_param(relax_fan_constraint, bool, 0444); +MODULE_PARM_DESC(relax_fan_constraint, + "Do not enforce fan RPM constraint (min/max RPM) " + "and enables fan tuning when such data is missing. " + "Enabling this may results in HWMON attributes being out-of-sync. Use wit= h caution."); + +/* =3D=3D=3D=3D=3D=3D=3D=3D HWMON (component: lenovo-wmi-capdata 00 & fan)= =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 RPM. + * + * 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 visible =3D false; + + if (type =3D=3D hwmon_fan) { + switch (attr) { + case hwmon_fan_enable: + case hwmon_fan_target: + if (!(priv->fan_info[channel].supported & LWMI_SUPP_MAY_SET)) + return 0; + + if (relax_fan_constraint || + (priv->fan_info[channel].min_rpm >=3D 0 && + priv->fan_info[channel].max_rpm >=3D 0)) + return 0644; + + /* + * Reaching here implies expose_all_fans is set. + * See lwmi_om_hwmon_add(). + */ + dev_warn_once(&priv->wdev->dev, + "fan tuning disabled due to missing RPM constraint\n"); + return 0; + case hwmon_fan_input: + visible =3D priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET; + break; + case hwmon_fan_min: + visible =3D priv->fan_info[channel].min_rpm >=3D 0; + break; + case hwmon_fan_max: + visible =3D priv->fan_info[channel].max_rpm >=3D 0; + break; + } + } + + return visible ? 0444 : 0; +} + +/** + * 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: + *val =3D priv->fan_info[channel].last_target !=3D LWMI_FAN_STOP_RPM; + return 0; + case hwmon_fan_target: + if (priv->fan_info[channel].last_target =3D=3D LWMI_FAN_STOP_RPM) + return -EBUSY; + + *val =3D priv->fan_info[channel].last_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; + } + } + + 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, min_rpm, max_rpm; + int err; + + if (type =3D=3D hwmon_fan) { + switch (attr) { + case hwmon_fan_enable: + if (val =3D=3D 0) + raw =3D LWMI_FAN_STOP_RPM; + else if (val =3D=3D 1) + raw =3D 0; /* auto */ + else + return -EINVAL; + + goto fan_set; + case hwmon_fan_target: + if (val =3D=3D 0) { + raw =3D 0; + goto fan_set; + } + + min_rpm =3D relax_fan_constraint + ? LWMI_FAN_STOP_RPM + 1 + : priv->fan_info[channel].min_rpm; + max_rpm =3D relax_fan_constraint + ? U16_MAX + : priv->fan_info[channel].max_rpm; + + if (val < min_rpm || val > max_rpm) + return -EDOM; + + raw =3D val; +fan_set: + err =3D lwmi_om_fan_get_set(priv, channel, &raw, true); + if (err) + return err; + + priv->fan_info[channel].last_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_MIN | HWMON_F_MAX, + 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, + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET | + HWMON_F_MIN | HWMON_F_MAX), + NULL }; =20 +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 capdata_fan capdata_fan; + struct capdata00 capdata00; + int i, err, valid; + + if (expose_all_fans) { + dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n"); + } else if (!priv->cd_fan_list) { + goto unsupported; + } else { + err =3D lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_TEST, &capd= ata00); + if (err || !(capdata00.supported & LWMI_SUPP_VALID)) + goto unsupported; + } + + if (relax_fan_constraint) + dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution= \n"); + + valid =3D 0; + for (i =3D 0; i < LWMI_FAN_NR; i++) { + err =3D lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &ca= pdata00); + + priv->fan_info[i] =3D (struct fan_info) { + .supported =3D err ? 0 : capdata00.supported, + /* + * Assume 0 on probe as the EC resets all fans to auto mode on (re)boot. + * + * Note that S0ix (s2idle) preserves the RPM target, so we + * don't need suspend/resume callbacks. This behavior has not + * been tested on S3-capable devices, but I doubt if such devices + * even have this interface. + */ + .last_target =3D 0, + .min_rpm =3D -ENODATA, + .max_rpm =3D -ENODATA, + }; + + if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID)) + continue; + + valid++; + + if (!priv->cd_fan_list) + /* + * Reaching here implies expose_all_fans is set. + * fanX_{target,enable} will be gated by lwmi_om_hwmon_is_visible(), + * unless relax_fan_constraint is also set. + */ + continue; + + err =3D lwmi_cd_fan_get_data(priv->cd_fan_list, LWMI_FAN_ID(i), &capdata= _fan); + if (!err) { + priv->fan_info[i].min_rpm =3D capdata_fan.min_rpm; + priv->fan_info[i].max_rpm =3D capdata_fan.max_rpm; + continue; + } + + if (!expose_all_fans) { + /* + * 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. + */ + dev_dbg(&priv->wdev->dev, "missing RPM constraint for fan%d, hiding\n", + LWMI_FAN_ID(i)); + priv->fan_info[i].supported =3D 0; + valid--; + } + } + + if (valid =3D=3D 0) + goto unsupported; + + priv->hwmon_dev =3D hwmon_device_register_with_info(&priv->wdev->dev, LWM= I_OM_HWMON_NAME, + priv, &lwmi_om_hwmon_chip_info, NULL); + if (IS_ERR(priv->hwmon_dev)) { + err =3D PTR_ERR(priv->hwmon_dev); + priv->hwmon_dev =3D NULL; + return err; + } + return 0; + +unsupported: + dev_warn(&priv->wdev->dev, "fan reporting/tuning is unsupported on this d= evice\n"); + return 0; +} + +/** + * 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) +{ + if (priv->hwmon_dev) + 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; @@ -547,6 +959,7 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *pri= v) =20 err_free_ida: ida_free(&lwmi_om_ida, priv->ida_id); + priv->fw_attr_dev =3D NULL; return err; } =20 @@ -556,6 +969,9 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *pri= v) */ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) { + if (!priv->fw_attr_dev) + return; + for (unsigned int i =3D 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) sysfs_remove_group(&priv->fw_attr_kset->kobj, cd01_attr_groups[i].attr_group); @@ -564,15 +980,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 +1004,45 @@ 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) { + priv->cd_fan_list =3D binder.cd_fan_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_hwmon_remove(priv); lwmi_om_fw_attr_remove(priv); + component_unbind_all(dev, NULL); } =20 @@ -624,6 +1061,9 @@ static int lwmi_other_probe(struct wmi_device *wdev, c= onst 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 +1094,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 +1121,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