From nobody Sun Feb 8 09:27:32 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 1E1D73191C8; Thu, 13 Nov 2025 19:12:23 +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=1763061145; cv=pass; b=tKfWZKsqW7sTOYmJ9cAOi8Q6dopwy/W4G6Y7Tc8vauegwob6nsluz9CmGI8Lmb+8VqBorRJz+58CwAslGPctlKv+b7EWveWzyBIXI7e32cPZV9Sex5dLsoSYcnBJINKQInLbMMxxA8dpkNdh+8yvEJWNIHO0UD1mBNR1vNTb5hc= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763061145; c=relaxed/simple; bh=OOZIm+oyRQLINv+OTXqfEGrVwVrIfsDJqs/Z9yydVmw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GOpnpciR3sE62JnbmCDZnWdipsRb4vRR657ndaGjUPFbyR3LUhh1AatTHFuKVSbJLDF7SI5DvE0oVKiyO/p1ir/kOE0pKG7h55yN6VYDzc/CKPpENifUGa2FCHWFRMb6PPOmwbtgXyOMLlqAKxLvx+uYlL8zL326zDalqJCwa+4= 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=MypowQCs; 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="MypowQCs" ARC-Seal: i=1; a=rsa-sha256; t=1763061122; cv=none; d=zohomail.com; s=zohoarc; b=kxKHWoVdWtFwPvQJXgrEtEmbxYFnVt2PpK6YTsMAWH0chjRDxIxuamAFH+EqKVdfYFTwnP+AIFQL8sxMxHPDxwyqrcp8JtE/c7W5sYjKAHGXeqNlFeYxj6tgtpwbMRNnUnoWAECFh8q//Efu8NuVR9G3492nGuIycfr53KbVJxs= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763061122; 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=cfZh6V0GSsiG0GSXjwqt/gP+zedHVH55yRrrOPg+t3E=; b=A/rOFY1F7kjQuZq7hdCSl8izyh4hPheO4Nqu54o7E/MtA1R05gyisikfe8ELjs8hRNVxTkuMPoYkqK76+f3EoeXR52jMkDYPdmXbp32ZeAyLoP9F/R4VVKOGqKhjWSNzQK4qMIS4nZFAXpRQf/l+UeSzYqV+tQ5kxug4q9EtTRM= 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=1763061122; 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=cfZh6V0GSsiG0GSXjwqt/gP+zedHVH55yRrrOPg+t3E=; b=MypowQCsksBGxiz89tKXeiXMqfntotib+ectiLDpB/hRqvCfpwexju0y8b/A9mdg Ma6LaYyam6/IoW+bOZvCGSwLc58GgqEgdrjEgGBqD/rt5J+bjKpTp+imUlX2aTYERzk bmytoQ6/4T/TqdPhlPBHP+RMKf6pg7cU5hYZLp/M= Received: by mx.zohomail.com with SMTPS id 176306112124094.02439741328124; Thu, 13 Nov 2025 11:12:01 -0800 (PST) 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 v4 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32 Date: Fri, 14 Nov 2025 03:11:44 +0800 Message-ID: <20251113191152.96076-2-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251113191152.96076-1-i@rong.moe> References: <20251113191152.96076-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 >=3D4B 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 v4: - Accept oversized buffer (thanks Armin Wolf) - Use get_unaligned_le32() (ditto) Changes in v2: - New patch (thanks Armin Wolf) --- drivers/platform/x86/lenovo/wmi-helpers.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x= 86/lenovo/wmi-helpers.c index f6fef6296251..ea0b75334c9e 100644 --- a/drivers/platform/x86/lenovo/wmi-helpers.c +++ b/drivers/platform/x86/lenovo/wmi-helpers.c @@ -21,6 +21,7 @@ #include #include #include +#include #include =20 #include "wmi-helpers.h" @@ -59,10 +60,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 buffer when a u32 + * 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 get_unaligned_le32(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 09:27:32 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 4080031A04E; Thu, 13 Nov 2025 19:12:27 +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=1763061150; cv=pass; b=UAGMfv4h1UAlwuAmFlXxwEinAXCekgGtuh+GA0Pn/ySZcHMkWF4MwyFFe7PaKN0pf89FL8FFmJcr66Dxbzn7BtAAsN8UH4MiXN+X+2nED30y1nioXDB7pUWo889JAwx0s0HXdIIZ7xJoSqPZRTzVpzqRJMUU7kp+tFQSRXyegZs= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763061150; c=relaxed/simple; bh=5FXYWr+OZfgBwgGo9Wmi7MNv9yqADJDUZSH0BCk/5oM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=IjfibX7TRquH2p/TNFWZRehZzqN0gE5qudAAh2fo0jgF5KKJkEtAPJv7sBLRnV2jqENzGQjO8SyMxu6TU7m6IFU+S6s7+HZzBKSRMsIora7vsk+2190KmzdCbMjP+xxFaCxvPjkUnAGFu3S434PouU9RVtTy1aNPrvo7NIxswnU= 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=B6qnP+3J; 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="B6qnP+3J" ARC-Seal: i=1; a=rsa-sha256; t=1763061126; cv=none; d=zohomail.com; s=zohoarc; b=ngoSblwD/em9Cz//nayz6NGwQcxGVdaV8AFVsYqGwnJtFZ3mswVNNEOQ3xs11V8esS/6u9XHw6CNd96iQvA1nfjXvwuYec+rAGjoYJOjcqXph19gjqIMf9iBnPP3aJMusPsDlvdUWZEqYQMr3cz/7B61JPw2xGdwH0TSX5tgmSg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763061126; 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=6rXa2Uw9KG3jpoFWXvixAbtuqbaUfxjnuJluUymabLM=; b=ngQ2J0Oj7K2eJgsq6cK3/ozDjpu9wcv+PQQDUcq4D4OoaZfkXrh3AxbEAq+EEhkF6XySGViyBl8P5l/sgLO2b3IIVMB0+Gp8wLwQ47K9vm7L9PN/5JCjZbIW/QGieBBxso4Wd97oqbWSicuOpdum8Ru0UmSviIbnq0c8QFZIlhI= 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=1763061126; 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=6rXa2Uw9KG3jpoFWXvixAbtuqbaUfxjnuJluUymabLM=; b=B6qnP+3Jdcq38cSc0zdAOnUN5+WY2o+7WfQtOtvqUj6j0aa+Difl44w/4/Hu4XW3 Ejhmvuy9r+yHmbHHlyXe33gxSq3ozcYoqk5s5sbD+RAdTKfYW2L75eCDBjf8dnSkrHQ kb1OlhQ8wpLLqchISEByR56C1lEwVtB7wztg6Mzc= Received: by mx.zohomail.com with SMTPS id 176306112415023.270328611634795; Thu, 13 Nov 2025 11:12:04 -0800 (PST) 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 v4 2/7] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Date: Fri, 14 Nov 2025 03:11:45 +0800 Message-ID: <20251113191152.96076-3-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251113191152.96076-1-i@rong.moe> References: <20251113191152.96076-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 d22b774e0236..fb96a0f908f0 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 7b2128e3a214..29014d8c1376 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 fc7e3454e71d..c5e74b2bfeb3 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 bd06c5751f68..2a4746e38ad4 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 2a960b278f11..c6dc1b4cff84 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 09:27:32 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 9327331AF33; Thu, 13 Nov 2025 19:12: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=1763061152; cv=pass; b=qdCusZyvOJaDqKPrQ4Cs0/8lml2uZtjLmrtOtz423AxRahLifty3d+hgSe144vqDp+0hWn0qmyBl9DtKXpLJzcec2jeH/tTp1HF3wladC0pmL8PMPAvEhC+qDbEC9e3wAZjFxFv/LYoi0DPdFtwoooE2X6YY9/26cCn9FEe+T/c= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763061152; c=relaxed/simple; bh=pjbyJ1R8Ceu5HVO4VzthIVsi0wJ1fbCeWCV99xlUDCQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=V58B7gkaRH2yANi31tGpequKQ6p98ZmIb9yR8AAM8DQS1RLmO3NGMFmvFKqhyrhqNZTTv43ugSyYHN6HpCS4tJ1R8BlrFMstPMgFgNHCxNWTwXUWllfrYVDJUJDB5aQBjA+2lRXnMKh0/GWCi26Z8c6UJY/FCrddqP4fwN9jXqw= 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=EPyu3MUw; 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="EPyu3MUw" ARC-Seal: i=1; a=rsa-sha256; t=1763061128; cv=none; d=zohomail.com; s=zohoarc; b=nHtz0ERbxfEURGEFhqYaIK9mRvbFH6z2VNeTW+sa27TpBDnZ0JfkooVlEUl0/vkjf8yjiDhxn897bOhpAqWJCG1q/zLWNdaBhKfH8S1sWk0VvDFELeqj5lT7rOjFj1by4PMThSRIWRcaSbHNJxpsQqv+T0x5EVBGbE1r/sxEi0Y= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763061128; 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=IPgggb4jf/5+PcOYkUS5aC2YLpA8x3l3OTEliGzS5wQ=; b=dnuMyU3YFrAArpzrSBA2ELw67rqyruDjIl0ONNYzP7DSOZgvFrwO2QYaDbYagJv+JHmp9Y0odKoZJcJzmS5EhwAkAKb8sMCvSWYhvgAai5p4TXq3jYtXQ20VDgrUSF1QDY1gaWDXaxfgZjI4zZOiZ9+nQxJ1xOVVBBDaZ8A0r+M= 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=1763061128; 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=IPgggb4jf/5+PcOYkUS5aC2YLpA8x3l3OTEliGzS5wQ=; b=EPyu3MUwIGbB/IQKEj8LHwp2yXhdHpfmpkSYUWEDoJjjTkAQT5p+95nZ6Ix0OXDv QXTjYLchehC/jA1dkm3gGm/x56keIHSStbIc7JKtjpmRyYA8uUB+brVFaybfw7UiFGm i964uJQp6F4YPt2r9uGkDX4QyNndxn4rRu+fKkAs= Received: by mx.zohomail.com with SMTPS id 1763061127085980.9856421657629; Thu, 13 Nov 2025 11:12:07 -0800 (PST) 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 v4 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Date: Fri, 14 Nov 2025 03:11:46 +0800 Message-ID: <20251113191152.96076-4-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251113191152.96076-1-i@rong.moe> References: <20251113191152.96076-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 v4: - Get rid of wmi_has_guid() (thanks Armin Wolf) - More changes in [PATCH v4 6/7] - Prepare for [PATCH v4 6/7] - Move lwmi_cd_match*() forward - Use switch-case in lwmi_cd_remove() Changes in v2: - Fix function parameter 'type' not described in 'lwmi_cd_match' (thanks kernel test bot) --- drivers/platform/x86/lenovo/wmi-capdata.c | 226 +++++++++++++++++----- drivers/platform/x86/lenovo/wmi-capdata.h | 7 +- drivers/platform/x86/lenovo/wmi-other.c | 16 +- 3 files changed, 190 insertions(+), 59 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x= 86/lenovo/wmi-capdata.c index c5e74b2bfeb3..a46208212a98 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,23 @@ #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 { \ + .name =3D #_type, \ + .type =3D _type, \ + } + +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_01), +}; + struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ struct wmi_device *wdev; @@ -44,15 +66,64 @@ 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 +static struct wmi_driver lwmi_cd_driver; + +/** + * lwmi_cd_match() - Match rule for the master driver. + * @dev: Pointer to the capability data parent device. + * @data: Type of the capability data (cast to enum lwmi_cd_type). + * + * Return: int. + */ +static int lwmi_cd_match(struct device *dev, void *data) +{ + enum lwmi_cd_type type =3D (enum lwmi_cd_type)data; + 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 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. + */ +void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr) +{ + int i; + + if (WARN_ON(*matchptr)) + return; + + for (i =3D 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { + component_match_add(master, matchptr, lwmi_cd_match, + (void *)lwmi_cd_table[i].type); + if (IS_ERR(matchptr)) + return; + } +} +EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CD"); + /** * 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 +135,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 +153,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 +192,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 +214,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 +226,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 +257,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 +267,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 +275,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 +335,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,30 +349,58 @@ 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; + switch (info->type) { + case 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; =20 - return component_add(&wdev->dev, &lwmi_cd_component_ops); + ret =3D component_add(&wdev->dev, &lwmi_cd_component_ops); + goto out; + default: + return -EINVAL; + } +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) { - component_del(&wdev->dev, &lwmi_cd_component_ops); + struct lwmi_cd_priv *priv =3D dev_get_drvdata(&wdev->dev); + + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_01: + component_del(&wdev->dev, &lwmi_cd_component_ops); + break; + default: + WARN_ON(1); + } } =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 @@ -283,22 +415,10 @@ static struct wmi_driver lwmi_cd_driver =3D { .no_singleton =3D true, }; =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. - * - * Return: int. - */ -int lwmi_cd01_match(struct device *dev, void *data) -{ - return dev->driver =3D=3D &lwmi_cd_driver.driver; -} -EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD"); - 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 2a4746e38ad4..d326f9d2d165 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; +}; + +void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr); 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_CAPDATA_H_ */ diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86= /lenovo/wmi-other.c index c6dc1b4cff84..f2e1e34d58a9 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 @@ -623,10 +623,13 @@ static int lwmi_other_probe(struct wmi_device *wdev, = const 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 - 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 @@ -639,7 +642,10 @@ 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); + + /* No IDA to free if the driver is never bound to its components. */ + 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 { --=20 2.51.0 From nobody Sun Feb 8 09:27:32 2026 Received: from sender3-op-o15.zoho.com (sender3-op-o15.zoho.com [136.143.184.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 4720F31B832; Thu, 13 Nov 2025 19:12:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.184.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763061160; cv=pass; b=HuIN9MgvDtJeGinkZeCFNUaYODmgdcaYVCg9ZA3bR0j9mVoyoFBpELP24H7oaFmCxjZCvCqaSDd/Z1JNFBVNgNnmuZ8EnA0uphsUTPAYhEnPY1UgWSWCslAneAqui597VE64PQmXAj/mtw9iFwtIeDu1z664D9FLZKt3cXoVxW0= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763061160; c=relaxed/simple; bh=qlQlzWea00zFyvWW2kHQS45Mgsiqe+jytLCL4DSOlS8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Gy1ZC+jeVW3EtMeksZTk5iR792oCJlRUPIqOycOCxxWlbArNLO0qWXsPMnYHf3UhUcgg3S6rseQfvx2sj6srWAQfIF7e64Ue031we8EfF1xSEmBr70n0Ih45vtChgd8BaCzw8+ZqYISrzpbH309qEdEBJZBz+Fa7wwlzLjBVn7g= 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=g+r7xxY4; arc=pass smtp.client-ip=136.143.184.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="g+r7xxY4" ARC-Seal: i=1; a=rsa-sha256; t=1763061132; cv=none; d=zohomail.com; s=zohoarc; b=Fk8yUgh5UsUm+PZkIXVVkw+za8QS4IUjdzJDJ/LT2dNwgbBX6fmdUOhpkmGW/B4L+cRB7IMKfIxJQQ5i9kZ2INpB3KZqr+paR5El9+G/O2VmSjFd+szchk7ODVHoCSf9IuVoJwGpXKpQ0HpIOLczXeJdR1aAfltEYA+pzqA0tBM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763061132; 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=0s1fp5W7YcwJO5Rmhlnf1PX2tCCMSlhSEQV7T20TMNA=; b=Zde2XmDL3M9L4PZ5NdpYc2GIbUFjJ9FUIwGeN0OZ+QY77IITGVMKBSd6z1VTnZ2+J+JkEPkdQA3KWS2yK8gK7Ji0+WGBCQ179Va1nsr6bY8QT+F9fB0kXr2b53RDMuBq5GpSzlJksNmBgB9a106mnrz181FYUgEZshhpQo6Jwro= 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=1763061132; 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=0s1fp5W7YcwJO5Rmhlnf1PX2tCCMSlhSEQV7T20TMNA=; b=g+r7xxY4dULASTPIzI/I2OHchufGiKJq/TRgYvvfikJ323xDhg9tfyZEavzDyRtV 5wAbhzfECn9A8BfsX9mJxLNSrimlISd8kUr2kKHX8ctPmGxsf3VJP6zRn7UvhScmxZ4 Q47MOPH9sHMGJe+qxBcTLRjI4Xfxy0l8RsPI/m+Q= Received: by mx.zohomail.com with SMTPS id 1763061129977593.238755458984; Thu, 13 Nov 2025 11:12:09 -0800 (PST) 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 v4 4/7] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Date: Fri, 14 Nov 2025 03:11:47 +0800 Message-ID: <20251113191152.96076-5-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251113191152.96076-1-i@rong.moe> References: <20251113191152.96076-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. capdata00 always presents on devices with capdata01. lenovo-wmi-other now binds to both (no functional change intended). Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark --- Changes in v4: - Rebase on top of changes made to [PATCH v4 3/7] Changes in v2: - Reword documentation (thanks Derek J. Clark) --- .../wmi/devices/lenovo-wmi-other.rst | 15 ++++++++--- drivers/platform/x86/lenovo/wmi-capdata.c | 25 +++++++++++++++++++ drivers/platform/x86/lenovo/wmi-capdata.h | 8 ++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index d7928b8dfb4b..fcad595d49af 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 a46208212a98..aeeb59c4c982 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 @@ -55,6 +60,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 @@ -70,6 +76,7 @@ struct cd_list { u8 count; =20 union { + DECLARE_FLEX_ARRAY(struct capdata00, cd00); DECLARE_FLEX_ARRAY(struct capdata01, cd01); }; }; @@ -138,6 +145,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; @@ -179,6 +189,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 @@ -197,6 +210,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]); @@ -242,6 +259,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; @@ -354,6 +374,9 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const= void *context) goto out; =20 switch (info->type) { + case LENOVO_CAPABILITY_DATA_00: + ret =3D component_add(&wdev->dev, &lwmi_cd_component_ops); + goto out; case LENOVO_CAPABILITY_DATA_01: priv->acpi_nb.notifier_call =3D lwmi_cd01_notifier_call; =20 @@ -387,6 +410,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev) struct lwmi_cd_priv *priv =3D dev_get_drvdata(&wdev->dev); =20 switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: case LENOVO_CAPABILITY_DATA_01: component_del(&wdev->dev, &lwmi_cd_component_ops); break; @@ -400,6 +424,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 d326f9d2d165..a6d006ef458f 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,10 +27,12 @@ struct capdata01 { }; =20 struct lwmi_cd_binder { + struct cd_list *cd00_list; struct cd_list *cd01_list; }; =20 void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr); +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); =20 #endif /* !_LENOVO_WMI_CAPDATA_H_ */ --=20 2.51.0 From nobody Sun Feb 8 09:27:32 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 289C931A54C; Thu, 13 Nov 2025 19:12: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=1763061160; cv=pass; b=Jt2MZ32tw9jNPuQZPZQk7xoXi4qMfj2llPrEcQ0RJlJ3dWZba6H5Y6lGquzOaQRI1y80BMNRRr624mbQUemvOgdVFyEQcdBcRNHTSvFYNzuDGawlFbsz1s6Lux2em1sEiiB6ejDXQmnHzXaJSYB/kr5dYvbCnbb67szAnilqugM= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763061160; c=relaxed/simple; bh=/fgBP6Zmss/VGBBjzJsFFFkB3Uah4J+hi/HZj6kIY6g=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=uoG0L7X28i6lmq2sKImBDtg0H10hRjejNNPinDR67gzesMhPQ1yFWjU8JRbq+EaZsuo0Hs+L/Y2nXuPsagA0FoBl7SjimJJfnaEt/dSV2W7MdSLdwR14QdfRl5SyU7iyOo5eSpjgB3GmL3xXGtZUI7ToMGy5FXzhUz8I5aJF4wk= 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=NgZSZDnL; 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="NgZSZDnL" ARC-Seal: i=1; a=rsa-sha256; t=1763061134; cv=none; d=zohomail.com; s=zohoarc; b=A3IRmXyVC6K2Q67PFmlDdqBnVWlzSr+J0EmxcGiea0EEENKS/FNb95yqSO1CSiy+BkL7Mm7gYYKdev+wodXB+rWjPwVAr8krYX9d1X/s6yD9MWoGkkcSPJwf+FqpjaERGiQ/cAljc74tuu1bQRPso4TTCW4h7xOwRwDf8Nm9LQc= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763061134; 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=UaD0Zb3S1c53gy9WGmtC8oER6LgmBfW2N9iG00Ktjc4=; b=a2EkqBKeNy8DPUiwVt0MkoG3ivOh0lOt7TSS7zHmSZXvhYTyqcNrn6NBqQqOECzJdhOiNR/9rVh+9hmcCEdOYEPFseUeXjvJDq3HiSPF8VEzd0/+5pSa+ul13Nk2KRDqRTNyhygYjNYFnrVv0xdk3oORuoW0kOFjP4kbhPoI1bk= 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=1763061134; 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=UaD0Zb3S1c53gy9WGmtC8oER6LgmBfW2N9iG00Ktjc4=; b=NgZSZDnLwrRQqV04QJgfnThxl8XqKj3oBfWgOJDJwfkd1Tw8FRmNjwW/sIkr2sDc 0N85GlbLRduZNg2pBy8bZmE6h54rtJj7Rmj+dlP4GWaJIfjVr2awdpzAISbXv5naMKK FCmQdvVqlohxYcd1rnVORpXFlnP9OYFMErUEGg54= Received: by mx.zohomail.com with SMTPS id 1763061133001253.9116945501862; Thu, 13 Nov 2025 11:12:13 -0800 (PST) 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 v4 5/7] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Date: Fri, 14 Nov 2025 03:11:48 +0800 Message-ID: <20251113191152.96076-6-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251113191152.96076-1-i@rong.moe> References: <20251113191152.96076-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. This interface is optional. Hence, it does not bind to lenovo-wmi-other and is not registered as a component for the moment. Appropriate binding will be implemented in the subsequent patch. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark --- Changes in v4: - Rebase on top of changes made to [PATCH v4 3/7] - Do not register it as a component until [PATCH v4 6/7] 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 | 7 ++ 3 files changed, 126 insertions(+) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index fcad595d49af..821282e07d93 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 aeeb59c4c982..8834a2752c97 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) \ @@ -62,6 +68,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 { @@ -78,6 +85,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 @@ -118,6 +126,10 @@ void lwmi_cd_match_add_all(struct device *master, stru= ct component_match **match return; =20 for (i =3D 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { + /* Skip optional interfaces. */ + if (lwmi_cd_table[i].type =3D=3D LENOVO_FAN_TEST_DATA) + continue; + component_match_add(master, matchptr, lwmi_cd_match, (void *)lwmi_cd_table[i].type); if (IS_ERR(matchptr)) @@ -195,6 +207,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. @@ -218,6 +233,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; } @@ -240,6 +258,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. @@ -265,6 +355,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; } @@ -273,6 +369,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; @@ -391,6 +488,8 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const= void *context) =20 ret =3D component_add(&wdev->dev, &lwmi_cd_component_ops); goto out; + case LENOVO_FAN_TEST_DATA: + goto out; default: return -EINVAL; } @@ -414,6 +513,8 @@ static void lwmi_cd_remove(struct wmi_device *wdev) case LENOVO_CAPABILITY_DATA_01: component_del(&wdev->dev, &lwmi_cd_component_ops); break; + case LENOVO_FAN_TEST_DATA: + break; default: WARN_ON(1); } @@ -426,6 +527,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 a6d006ef458f..38af4c4e4ef4 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -26,6 +26,12 @@ 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; @@ -34,5 +40,6 @@ struct lwmi_cd_binder { void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr); 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); =20 #endif /* !_LENOVO_WMI_CAPDATA_H_ */ --=20 2.51.0 From nobody Sun Feb 8 09:27:32 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 73D38319879; Thu, 13 Nov 2025 19:12:40 +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=1763061162; cv=pass; b=HNok5FtRvkzEO46uqmzB7qmuKfG6CgC3sVmQJpouYjmkWGJwmkDBor15t1uW5muefEDIkkhcNG313pURPfGyIlthcoE6YPLQ4abVsBAamwwlV4tgyt1dkOFsmZOeF2TI2cg4+lG97W0hk7Uu6QkITl3jbAKlTe0NbfiF8WvtK9s= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763061162; c=relaxed/simple; bh=5G4eXmI0knMefC6d7JymHvySatAG8ts1cvgOjURPLUU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=QAGjRabxIYAP+x5Qx7PhToc8n57kS6N8LJSyjhtQMypB4q3xhNVpPmLmp01htemQVDq2SfeqyksC6xCcgeA4/5oRGDb8/w9Ep29tXTwJhXouH2m20Q4louJnzchY0jsft+Z2NPpVd0RKYZZud8mKa223n2TMXMukFwckfRquRh8= 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=qgGAsLsx; 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="qgGAsLsx" ARC-Seal: i=1; a=rsa-sha256; t=1763061138; cv=none; d=zohomail.com; s=zohoarc; b=aRtzGt4se9EIkmpTiyP1E1hS1BD1gc4WRDF9tpo9tdqcb+bUXg2WDRalwUgUCdvZ9qGP9eUacsHKDapxqmsppHQioTRQ2dvH1xnP6mOrzDdyt+5/YzoQ8nnUyGT9/niqT4mYNgMRSpwxt2D99fo5ehT68T5f0d7DsougPKYnjR0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763061138; 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=/yCofbZk1flUDsswkITJkQvPjMaFSAHMGzBKZQtl+JU=; b=DYbVOKXxwv6FWrKF8/JpZmOSlnTfRPDk32O0lBPvUosvEShYAfRFK2db3/mA3m4l+Wnrr2L9CzFaFFV4FW9AyztAR3Z3C3OjiXWI6ROee78Cdx0Oc/LqLTtUV0MWTdwg/eJ8i/EJFDV3b/CEeks2nK9aB9h8/QcB5nwO58XPSMs= 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=1763061138; 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=/yCofbZk1flUDsswkITJkQvPjMaFSAHMGzBKZQtl+JU=; b=qgGAsLsxc4URrw0O9+Po16rFa7YMqwmmxhQK0L4reuQsM4IIKGImqQ2TciujU/lT sPwu+ZnXaA4tAJ7YlmjaIEjwN8fU9f3/nazKhVFWVUUxoVaFPJjZrH7mWAGvOOm/WpC dhFH99z7tw2EHPqTtr1zmLNsm6/E6zl4MTbTcyyY= Received: by mx.zohomail.com with SMTPS id 1763061136007799.5474333604368; Thu, 13 Nov 2025 11:12:16 -0800 (PST) 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 v4 6/7] platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data Date: Fri, 14 Nov 2025 03:11:49 +0800 Message-ID: <20251113191152.96076-7-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251113191152.96076-1-i@rong.moe> References: <20251113191152.96076-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" A capdata00 attribute (0x04050000) describes the presence of Fan Test Data. Query it, and bind Fan Test Data as a component of capdata00 accordingly. The component master of capdata00 may pass a callback while binding to retrieve fan info from Fan Test Data. Summarizing this scheme: lenovo-wmi-other <-> capdata00 <-> capdata_fan |- master |- component |- sub-master |- sub-component The callback will be called once both the master and the sub-component are bound to the sub-master (component). This scheme is essential to solve four issues: - The component framework only supports one aggregation per master - A binding is only established until all components are found - The Fan Test Data interface may be missing on some devices - To get rid of queries for the presense of WMI GUIDs Signed-off-by: Rong Zhang --- Changes in v4: - New patch in the series (thanks Armin Wolf's inspiration) - Get rid of wmi_has_guid() (see also [PATCH v4 3/7]) --- drivers/platform/x86/lenovo/wmi-capdata.c | 257 +++++++++++++++++++++- drivers/platform/x86/lenovo/wmi-capdata.h | 19 ++ drivers/platform/x86/lenovo/wmi-other.c | 5 - 3 files changed, 274 insertions(+), 7 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x= 86/lenovo/wmi-capdata.c index 8834a2752c97..3c4d1cbbf312 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -50,10 +50,17 @@ #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 =20 +#define LWMI_FEATURE_ID_FAN_TEST 0x05 + +#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)) + enum lwmi_cd_type { LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01, LENOVO_FAN_TEST_DATA, + CD_TYPE_NONE =3D -1, }; =20 #define LWMI_CD_TABLE_ITEM(_type) \ @@ -75,6 +82,20 @@ struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ struct wmi_device *wdev; struct cd_list *list; + + /* + * A capdata device may be a component master of another capdata device. + * E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan + * |- master |- component + * |- sub-master + * |- sub-component + */ + struct lwmi_cd_sub_master_priv { + struct device *master_dev; + cd_list_cb_t master_cb; + struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-c= omponent. */ + bool registered; /* Has the sub-master been registere= d? */ + } *sub_master; }; =20 struct cd_list { @@ -126,7 +147,7 @@ void lwmi_cd_match_add_all(struct device *master, struc= t component_match **match return; =20 for (i =3D 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { - /* Skip optional interfaces. */ + /* Skip sub-components. */ if (lwmi_cd_table[i].type =3D=3D LENOVO_FAN_TEST_DATA) continue; =20 @@ -138,6 +159,56 @@ void lwmi_cd_match_add_all(struct device *master, stru= ct component_match **match } EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CD"); =20 +/** + * lwmi_cd_call_master_cb() - Call the master callback for the sub-compone= nt. + * @priv: Pointer to the capability data private data. + * + * Call the master callback and pass the sub-component list to it if the + * dependency chain (master <-> sub-master <-> sub-component) is complete. + */ +static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv) +{ + struct cd_list *sub_component_list =3D priv->sub_master->sub_component_li= st; + + /* + * Call the callback only if the dependency chain is ready: + * - Binding between master and sub-master: fills master_dev and master_cb + * - Binding between sub-master and sub-component: fills sub_component_li= st + * + * If a binding has been unbound before the other binding is bound, the + * corresponding members filled by the former are guaranteed to be cleare= d. + * + * This function is only called in bind callbacks, and the component + * framework guarantees bind/unbind callbacks may never execute + * simultaneously, which implies that it's impossible to have a race + * condition. + * + * Hence, this check is sufficient to ensure that the callback is called + * at most once and with the correct state, without relying on a specific + * sequence of binding establishment. + */ + if (!sub_component_list || + !priv->sub_master->master_dev || + !priv->sub_master->master_cb) + return; + + if (PTR_ERR(sub_component_list) =3D=3D -ENODEV) + sub_component_list =3D NULL; + else if (WARN_ON(IS_ERR(sub_component_list))) + return; + + priv->sub_master->master_cb(priv->sub_master->master_dev, + sub_component_list); + + /* + * Prevent "unbind and rebind" sequences from userspace from calling the + * callback twice. + */ + priv->sub_master->master_cb =3D NULL; + priv->sub_master->master_dev =3D NULL; + priv->sub_master->sub_component_list =3D NULL; +} + /** * lwmi_cd_component_bind() - Bind component to master device. * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. @@ -148,6 +219,8 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI= _CD"); * list. This is used to call lwmi_cd*_get_data to look up attribute data * from the lenovo-wmi-other driver. * + * If cd_dev is a sub-master, try to call the master callback. + * * Return: 0 */ static int lwmi_cd_component_bind(struct device *cd_dev, @@ -159,6 +232,11 @@ static int lwmi_cd_component_bind(struct device *cd_de= v, switch (priv->list->type) { case LENOVO_CAPABILITY_DATA_00: binder->cd00_list =3D priv->list; + + priv->sub_master->master_dev =3D om_dev; + priv->sub_master->master_cb =3D binder->cd_fan_list_cb; + lwmi_cd_call_master_cb(priv); + break; case LENOVO_CAPABILITY_DATA_01: binder->cd01_list =3D priv->list; @@ -170,8 +248,163 @@ static int lwmi_cd_component_bind(struct device *cd_d= ev, return 0; } =20 +/** + * lwmi_cd_component_unbind() - Unbind 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: Unused. + * + * If cd_dev is a sub-master, clear the collected data from the master dev= ice to + * prevent the binding establishment between the sub-master and the sub- + * component (if it's about to happen) from calling the master callback. + */ +static void lwmi_cd_component_unbind(struct device *cd_dev, + struct device *om_dev, void *data) +{ + struct lwmi_cd_priv *priv =3D dev_get_drvdata(cd_dev); + + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: + priv->sub_master->master_dev =3D NULL; + priv->sub_master->master_cb =3D NULL; + return; + default: + return; + } +} + static const struct component_ops lwmi_cd_component_ops =3D { .bind =3D lwmi_cd_component_bind, + .unbind =3D lwmi_cd_component_unbind, +}; + +/** + * lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device + * @dev: The sub-master capdata basic device. + * + * Call component_bind_all to bind the sub-component device to the sub-mas= ter + * device. On success, collect the pointer to the sub-component list and t= ry + * to call the master callback. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_cd_sub_master_bind(struct device *dev) +{ + struct lwmi_cd_priv *priv =3D dev_get_drvdata(dev); + struct cd_list *sub_component_list; + int ret; + + ret =3D component_bind_all(dev, &sub_component_list); + if (ret) + return ret; + + priv->sub_master->sub_component_list =3D sub_component_list; + lwmi_cd_call_master_cb(priv); + + return 0; +} + +/** + * lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device + * @dev: The sub-master capdata basic device + * + * Clear the collected pointer to the sub-component list to prevent the bi= nding + * establishment between the sub-master and the sub-component (if it's abo= ut to + * happen) from calling the master callback. Then, call component_unbind_a= ll to + * unbind the sub-component device from the sub-master device. + */ +static void lwmi_cd_sub_master_unbind(struct device *dev) +{ + struct lwmi_cd_priv *priv =3D dev_get_drvdata(dev); + + priv->sub_master->sub_component_list =3D NULL; + + component_unbind_all(dev, NULL); +} + +static const struct component_master_ops lwmi_cd_sub_master_ops =3D { + .bind =3D lwmi_cd_sub_master_bind, + .unbind =3D lwmi_cd_sub_master_unbind, +}; + +/** + * lwmi_cd_sub_master_add() - Register a sub-master with its sub-component + * @priv: Pointer to the sub-master capdata device private data. + * @sub_component_type: Type of the sub-component. + * + * Match the sub-component type and register the current capdata device as= a + * sub-master. If the given sub-component type is CD_TYPE_NONE, mark the s= ub- + * component as non-existent without registering sub-master. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv, + enum lwmi_cd_type sub_component_type) +{ + struct component_match *master_match =3D NULL; + int ret; + + priv->sub_master =3D devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_mas= ter), GFP_KERNEL); + if (!priv->sub_master) + return -ENOMEM; + + if (sub_component_type =3D=3D CD_TYPE_NONE) { + /* The master callback will be called with NULL on bind. */ + priv->sub_master->sub_component_list =3D ERR_PTR(-ENODEV); + priv->sub_master->registered =3D false; + return 0; + } + + component_match_add(&priv->wdev->dev, &master_match, + lwmi_cd_match, (void *)sub_component_type); + if (IS_ERR(master_match)) + return PTR_ERR(master_match); + + ret =3D component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_ma= ster_ops, + master_match); + if (ret) + return ret; + + priv->sub_master->registered =3D true; + return 0; +} + +/** + * lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered + * @priv: Pointer to the sub-master capdata device private data. + */ +static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv) +{ + if (priv->sub_master->registered) { + component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops); + priv->sub_master->registered =3D false; + } +} + +/** + * lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device. + * @sc_dev: Pointer to the sub-component capdata parent device. + * @sm_dev: Pointer to the sub-master capdata parent device. + * @data: Pointer used to return the capability data list pointer. + * + * On sub-master's bind, provide a pointer to the local capdata list. + * This is used by the sub-master to call the master callback. + * + * Return: 0 + */ +static int lwmi_cd_sub_component_bind(struct device *sc_dev, + struct device *sm_dev, void *data) +{ + struct lwmi_cd_priv *priv =3D dev_get_drvdata(sc_dev); + struct cd_list **listp =3D data; + + *listp =3D priv->list; + + return 0; +} + +static const struct component_ops lwmi_cd_sub_component_ops =3D { + .bind =3D lwmi_cd_sub_component_bind, }; =20 /** @@ -471,9 +704,25 @@ static int lwmi_cd_probe(struct wmi_device *wdev, cons= t void *context) goto out; =20 switch (info->type) { - case LENOVO_CAPABILITY_DATA_00: + case LENOVO_CAPABILITY_DATA_00: { + enum lwmi_cd_type sub_component_type =3D LENOVO_FAN_TEST_DATA; + struct capdata00 capdata00; + + ret =3D lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00= ); + if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) { + dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n"); + sub_component_type =3D CD_TYPE_NONE; + } + + /* Sub-master (capdata00) <-> sub-component (capdata_fan) */ + ret =3D lwmi_cd_sub_master_add(priv, sub_component_type); + if (ret) + goto out; + + /* Master (lenovo-wmi-other) <-> sub-master (capdata00) */ ret =3D component_add(&wdev->dev, &lwmi_cd_component_ops); goto out; + } case LENOVO_CAPABILITY_DATA_01: priv->acpi_nb.notifier_call =3D lwmi_cd01_notifier_call; =20 @@ -489,6 +738,7 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const= void *context) ret =3D component_add(&wdev->dev, &lwmi_cd_component_ops); goto out; case LENOVO_FAN_TEST_DATA: + ret =3D component_add(&wdev->dev, &lwmi_cd_sub_component_ops); goto out; default: return -EINVAL; @@ -510,10 +760,13 @@ static void lwmi_cd_remove(struct wmi_device *wdev) =20 switch (priv->list->type) { case LENOVO_CAPABILITY_DATA_00: + lwmi_cd_sub_master_del(priv); + fallthrough; case LENOVO_CAPABILITY_DATA_01: component_del(&wdev->dev, &lwmi_cd_component_ops); break; case LENOVO_FAN_TEST_DATA: + component_del(&wdev->dev, &lwmi_cd_sub_component_ops); break; default: WARN_ON(1); diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x= 86/lenovo/wmi-capdata.h index 38af4c4e4ef4..0b3bb876d768 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -7,6 +7,17 @@ =20 #include =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_ATTR_DEV_ID_MASK GENMASK(31, 24) +#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) +#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) +#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) + +#define LWMI_DEVICE_ID_FAN 0x04 + struct component_match; struct device; struct cd_list; @@ -32,9 +43,17 @@ struct capdata_fan { u32 max_rpm; }; =20 +typedef void (*cd_list_cb_t)(struct device *master_dev, struct cd_list *cd= _list); + struct lwmi_cd_binder { struct cd_list *cd00_list; struct cd_list *cd01_list; + /* + * May be called during or after the bind callback. + * Will be called with NULL if capdata_fan does not exist. + * The pointer is only valid in the callback; never keep it for later use! + */ + cd_list_cb_t cd_fan_list_cb; }; =20 void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr); diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86= /lenovo/wmi-other.c index f2e1e34d58a9..b3adcc2804fa 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -54,11 +54,6 @@ #define LWMI_FEATURE_VALUE_GET 17 #define LWMI_FEATURE_VALUE_SET 18 =20 -#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) -#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) -#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) -#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) - #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" =20 static BLOCKING_NOTIFIER_HEAD(om_chain_head); --=20 2.51.0 From nobody Sun Feb 8 09:27:32 2026 Received: from sender3-op-o15.zoho.com (sender3-op-o15.zoho.com [136.143.184.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 6898431D72C; Thu, 13 Nov 2025 19:12:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.184.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763061166; cv=pass; b=WHCkI7kzE/CCfmYnEbSuCdvvEpeoK2btCzC7KfShMzc52QrjMtzBTQcnn64wNQgssglKExNoiraUddKUBLSPqJe7ampPkewqh/mMDF6kDYC0jL4DOvZMpMEwdbeA9yDo8Y9VYda198rvjhw0tEsVbRr+cDrcTwejBRgBZXrISYE= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763061166; c=relaxed/simple; bh=dwk0g61liSCshe1lGHJL5z7TgcGPuXF6x1876Yj9pF4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kCrNC4KHgXt78oxa/+Dme7PZ0NssJfx8fO6dJ8o3fBtGevqC8I+LCCxUH9Td0kKmADXAtos9rTBfJFUVuJkhcEEPKsphp+yaxiYFZ2Y3pc5bNYiWpNYg9gCn7rm6ixqeR+6PKEhGks9YZhDwl5dNAjyn+NpWhnkr6JrUcvlahL0= 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=eUTLYcdx; arc=pass smtp.client-ip=136.143.184.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="eUTLYcdx" ARC-Seal: i=1; a=rsa-sha256; t=1763061140; cv=none; d=zohomail.com; s=zohoarc; b=bfF7TNOEqdnvXR/l+ZpxH3XI++NmxjyvkBR1mHBy8klR40Lue4wsl3Pq3As+5TiEi8PjVF05oFcSttW5UT3CrUbVj4n1wehhx/Ewu3eVzNl7wCM1eQoDOIvvmZy4kT0nnUR335tKYmmpZ96tw+Pb29oApxxQUxtgi1KsXnzTDkc= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763061140; 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=Pi5nVuZUVIQegvHoCmCVaD8EPVDgPsbm2ZFGkN5PHbQ=; b=a39/Rg/6oD/nZ9koiGfcRz9RGOI5vs3Wc0+akW5pd1aK2Xw7PWXCKIKHmS92kyVWtvMHs2gqu4dB7JzsPbqs11BucPY8TNxRzBpPr0wq56dAyPCk281EN8o4NlOSj+NL3r2ozuyUSI6aDibAQj58v9rZwisq+3eYU/W6jdOpR8Q= 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=1763061140; 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=Pi5nVuZUVIQegvHoCmCVaD8EPVDgPsbm2ZFGkN5PHbQ=; b=eUTLYcdxdrtGMwLrognvK5MGt0nwohSZ1AqBC0grOdWc89xXI0xzKgZgrhjfFJJg YeSbBuV49vR64tpRfmfwcB+tA7TrLwbNAlN2Xn9w3kjUNmI+yXsiOzTQ9BKUF43arm8 kvJHg7rhGNKA970m03+cpA6+Pvb3R0KHmBQqT7Ms= Received: by mx.zohomail.com with SMTPS id 1763061139112100.29108477199611; Thu, 13 Nov 2025 11:12:19 -0800 (PST) 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 v4 7/7] platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning Date: Fri, 14 Nov 2025 03:11:50 +0800 Message-ID: <20251113191152.96076-8-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251113191152.96076-1-i@rong.moe> References: <20251113191152.96076-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 reporting/tuning according to Capability Data 00 (capdata00) and Fan Test Data (capdata_fan) 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) Information from capdata00 and capdata_fan are used to control the visibility and constraints of HWMON attributes. Fan info from capdata00 is collected on bind, while fan info from capdata_fan is collected in a callback. Once all fan info is collected, register the HWMON device. Signed-off-by: Rong Zhang --- Changes in v4: - Rework HWMON registration due to the introduction of [PATCH v4 6/7] - Collect fan into from capdata00 and capdata_fan separately - Use a callback to collect fan info from capdata_fan - Trigger HWMON registration only if all fan info is collected - Do not check 0x04050000.supported, implied by the presense of capdata_fan - Drop Reviewed-by & Tested-by due to the changes, please review & test Changes in v3: - Reword documentation (thanks 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 | 485 +++++++++++++++++- 3 files changed, 487 insertions(+), 10 deletions(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index 821282e07d93..bd1d733ff286 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 +Additionally, 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 fb96a0f908f0..be9af0451146 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 b3adcc2804fa..b811a2fbdb60 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. * + * Additionally, this driver also exports tunable fan speed RPM to HWMON. + * Min/max 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 @@ -49,12 +59,26 @@ #define LWMI_FEATURE_ID_CPU_SPL 0x02 #define LWMI_FEATURE_ID_CPU_FPPT 0x03 =20 +#define LWMI_FEATURE_ID_FAN_RPM 0x03 + #define LWMI_TYPE_ID_NONE 0x00 =20 #define LWMI_FEATURE_VALUE_GET 17 #define LWMI_FEATURE_VALUE_SET 18 =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_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); @@ -71,15 +95,439 @@ 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; + u32 last_target; + long min_rpm; + long max_rpm; + } fan_info[LWMI_FAN_NR]; + struct { + bool capdata00_collected : 1; + bool capdata_fan_collected : 1; + } fan_flags; +}; + +/* + * Visibility of fan channels: + * + * +---------------------+---------+------------------+-------------------= ----+------------+ + * | | default | +expose_all_fans | +relax_fan_constra= int | +both | + * +---------------------+---------+------------------+-------------------= ----+------------+ + * | canonical | RW | RW | RW+relaxed = | RW+relaxed | + * +---------------------+---------+------------------+-------------------= ----+------------+ + * | -capdata_fan[idx] | N | RO | N = | RW+relaxed | + * +---------------------+---------+------------------+-------------------= ----+------------+ + * + * Note: + * 1. LWMI_ATTR_ID_FAN_RPM[idx].supported is always checked before exposin= g a channel. + * 2. -capdata_fan implies -capdata_fan[idx]. + */ +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 an 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) { + if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID)) + return 0; + + 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 if all info is collected + * @priv: Driver private data + */ +static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv) +{ + int i, valid; + + if (WARN_ON(priv->hwmon_dev)) + return; + + if (!priv->fan_flags.capdata00_collected || !priv->fan_flags.capdata_fan_= collected) { + dev_dbg(&priv->wdev->dev, "HWMON registration pending (00: %d, fan: %d)\= n", + priv->fan_flags.capdata00_collected, + priv->fan_flags.capdata_fan_collected); + return; + } + + if (expose_all_fans) + dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n"); + + 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++) { + if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID)) + continue; + + valid++; + + if (!expose_all_fans && + (priv->fan_info[i].min_rpm < 0 || priv->fan_info[i].max_rpm < 0)) { + 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) { + dev_warn(&priv->wdev->dev, + "fan reporting/tuning is unsupported on this device\n"); + return; + } + + priv->hwmon_dev =3D hwmon_device_register_with_info(&priv->wdev->dev, + LWMI_OM_HWMON_NAME, priv, + &lwmi_om_hwmon_chip_info, + NULL); + if (IS_ERR(priv->hwmon_dev)) { + dev_warn(&priv->wdev->dev, "failed to register HWMON device: %ld\n", + PTR_ERR(priv->hwmon_dev)); + priv->hwmon_dev =3D NULL; + return; + } + + dev_dbg(&priv->wdev->dev, "registered HWMON device\n"); +} + +/** + * lwmi_om_hwmon_remove() - Unregister HWMON device + * @priv: Driver private data + * + * Unregisters the HWMON device if applicable. + */ +static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv) +{ + if (!priv->hwmon_dev) + return; + + hwmon_device_unregister(priv->hwmon_dev); + priv->hwmon_dev =3D NULL; +} + +/** + * lwmi_om_fan_info_init() - Initialzie fan info + * @priv: Driver private data + * + * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan() m= ay be + * called in an arbitrary order. Hence, initializion must be done before. + */ +static void lwmi_om_fan_info_init(struct lwmi_om_priv *priv) +{ + int i; + + for (i =3D 0; i < LWMI_FAN_NR; i++) { + priv->fan_info[i] =3D (struct fan_info) { + .supported =3D 0, + /* + * 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 interfac= e. + */ + .last_target =3D 0, + .min_rpm =3D -ENODATA, + .max_rpm =3D -ENODATA, + }; + } + + priv->fan_flags.capdata00_collected =3D false; + priv->fan_flags.capdata_fan_collected =3D false; +} + +/** + * lwmi_om_fan_info_collect_cd00() - Collect fan info from capdata 00 + * @priv: Driver private data + */ +static void lwmi_om_fan_info_collect_cd00(struct lwmi_om_priv *priv) +{ + struct capdata00 capdata00; + int i, err; + + dev_dbg(&priv->wdev->dev, "Collecting fan info from capdata00\n"); + + 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].supported =3D err ? 0 : capdata00.supported; + } + + priv->fan_flags.capdata00_collected =3D true; + lwmi_om_hwmon_add(priv); +} + +/** + * lwmi_om_fan_info_collect_cd_fan() - Collect fan info from capdata fan + * @dev: Pointer to the lenovo-wmi-other device + * @cd_fan_list: Pointer to the capdata fan list + */ +static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_= list *cd_fan_list) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); + struct capdata_fan capdata_fan; + int i, err; + + dev_dbg(dev, "Collecting fan info from capdata_fan\n"); + + if (!cd_fan_list) + goto out; + + for (i =3D 0; i < LWMI_FAN_NR; i++) { + err =3D lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan); + if (err) + continue; + + priv->fan_info[i].min_rpm =3D capdata_fan.min_rpm; + priv->fan_info[i].max_rpm =3D capdata_fan.max_rpm; + } +out: + priv->fan_flags.capdata_fan_collected =3D true; + lwmi_om_hwmon_add(priv); +} + +/* =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; @@ -559,32 +1007,45 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_pr= iv *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, with a callback to collect fan info from + * capdata_fan. On success, assign the capability data list pointers to the + * driver data struct for later access. These pointers are only valid whil= e the + * capdata interfaces exist. Finally, collect fan info from capdata00 and + * register all firmware attribute groups. Note that the HWMON device is + * registered only if all fan info is collected. Hence, it is not register= ed + * here. See lwmi_om_fan_info_collect_cd00() and + * lwmi_om_fan_info_collect_cd_fan(). * * Return: 0 on success, or an error code. */ static int lwmi_om_master_bind(struct device *dev) { struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); - struct lwmi_cd_binder binder =3D { 0 }; + struct lwmi_cd_binder binder =3D { + .cd_fan_list_cb =3D lwmi_om_fan_info_collect_cd_fan, + }; int ret; =20 + lwmi_om_fan_info_init(priv); + ret =3D component_bind_all(dev, &binder); if (ret) return ret; =20 + priv->cd00_list =3D binder.cd00_list; priv->cd01_list =3D binder.cd01_list; - if (!priv->cd01_list) + if (!priv->cd00_list || !priv->cd01_list) return -ENODEV; =20 + lwmi_om_fan_info_collect_cd00(priv); + return lwmi_om_fw_attr_add(priv); } =20 @@ -592,15 +1053,18 @@ static int lwmi_om_master_bind(struct device *dev) * 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 all firmware attribute groups and the HWMON device. Then call + * component_unbind_all to unbind lenovo-wmi-capdata devices 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); + + lwmi_om_hwmon_remove(priv); + component_unbind_all(dev, NULL); } =20 @@ -665,5 +1129,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