linux-doc.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
@ 2024-12-17 23:06 Derek J. Clark
  2024-12-17 23:06 ` [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers Derek J. Clark
  2024-12-22  8:42 ` John Martens
  0 siblings, 2 replies; 29+ messages in thread
From: Derek J. Clark @ 2024-12-17 23:06 UTC (permalink / raw)
  To: Hans de Goede
  Cc: Ilpo Järvinen, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Derek J . Clark, platform-driver-x86,
	linux-doc, linux-kernel

Adds support for the Lenovo Legion series of laptop hardware to use WMI
interfaces that control various power settings. There are multiple WMI
interfaces that work in concert to provide getting and setting values as
well as validation of input. Currently only the "GameZone", "Other
Method", and "LENOVO_CAPABILITY_DATA_01" interfaces are implemented, but
I attempted to structure the driver so that adding the "Custom Mode",
"Lighting", and the other CAPABILITY_DATA interfaces would be trivial if
desired in a later patch.

This driver is distinct from, but should be considered a replacement for
this patch:
https://lore.kernel.org/all/20241118100503.14228-1-jonmail@163.com/

This driver attempts to standardize the exposed sysfs by mirroring the
asus-armoury driver currently under review. As such, a lot of
inspiration has been drawn from that driver.
https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/

The driver has been tested by me on the Lenovo Legion Go.

Suggested-by: Mario Limonciello <superm1@kernel.org>
Reviewed-by: Luke Jones <luke@ljones.dev>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>

Derek J. Clark (1):
  Add lenovo-legion-wmi drivers

 .../wmi/devices/lenovo-legion-wmi.rst         |  79 ++++
 MAINTAINERS                                   |   9 +
 drivers/platform/x86/Kconfig                  |  35 ++
 drivers/platform/x86/Makefile                 |  21 +-
 .../x86/lenovo-legion-wmi-capdata01.c         | 103 +++++
 .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++
 .../platform/x86/lenovo-legion-wmi-other.c    | 377 ++++++++++++++++++
 drivers/platform/x86/lenovo-legion-wmi.h      | 271 +++++++++++++
 8 files changed, 1119 insertions(+), 9 deletions(-)
 create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst
 create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c
 create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c
 create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c
 create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h

-- 
2.47.0


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-17 23:06 [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers Derek J. Clark
@ 2024-12-17 23:06 ` Derek J. Clark
  2024-12-18  2:39   ` Mario Limonciello
                     ` (2 more replies)
  2024-12-22  8:42 ` John Martens
  1 sibling, 3 replies; 29+ messages in thread
From: Derek J. Clark @ 2024-12-17 23:06 UTC (permalink / raw)
  To: Hans de Goede
  Cc: Ilpo Järvinen, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Derek J . Clark, platform-driver-x86,
	linux-doc, linux-kernel

Adds lenovo-legion-wmi.h which provides templates and some method
implementations used by the lenovo-legion-wmi driver series.

Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo
GameZone WMI interface that comes on Lenovo "Gaming Series" hardware.
Provides ACPI platform profiles over WMI.

Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo
"Other Method" WMI interface that comes on some Lenovo hardware.
Provides a firmware-attributes class which enables the use of tunable
knobs for SPL, SPPT, and FPPT.

Adds lenovo-legion-wmi-capdata01.c which provides a driver for the
LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method"
enabled hardware. Provides an interface for querying if a given
attribute is supported by the hardware, as well as its default_value,
max_value, min_value, and step increment.

Adds lenovo-legion-wmi.rst describing the available drivers and their
function.

Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers.

Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
 .../wmi/devices/lenovo-legion-wmi.rst         |  79 ++++
 MAINTAINERS                                   |   9 +
 drivers/platform/x86/Kconfig                  |  35 ++
 drivers/platform/x86/Makefile                 |  21 +-
 .../x86/lenovo-legion-wmi-capdata01.c         | 103 +++++
 .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++
 .../platform/x86/lenovo-legion-wmi-other.c    | 377 ++++++++++++++++++
 drivers/platform/x86/lenovo-legion-wmi.h      | 271 +++++++++++++
 8 files changed, 1119 insertions(+), 9 deletions(-)
 create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst
 create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c
 create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c
 create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c
 create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h

diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst
new file mode 100644
index 000000000000..37b09c82c980
--- /dev/null
+++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst
@@ -0,0 +1,79 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+======================================================
+Lenovo Legion WMI Interface Driver (lenovo-legion-wmi)
+======================================================
+
+Introduction
+============
+The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that
+require cross-references between GUID's for some functionality. The "Custom
+Mode" interface is a legacy interface for managing and displaying CPU & GPU
+power and hwmon settings and readings. The "Other Mode" interface is a modern
+interface that replaces "Custom Mode" interface methods. The "GameZone"
+interface adds advanced features such as fan profiles and overclocking. The
+"Lighting" interface adds control of various status lights related to different
+hardware components.
+
+Each of these interfaces has a different data structure associated with it that
+provide detailed information about each attribute provided by the interface.
+These data structs are retrieved from an additional WMI device data block GUID:
+ - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00
+ - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01
+ - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02
+
+.. note::
+   Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01
+   interfaces are implemented by this driver.
+
+
+GameZone
+--------
+The GameZone WMI interface provides ACPI platform profile and fan curve
+settings for devices that fall under the "Gaming Series" of Lenovo Legion
+devices.
+
+The following platform profiles are supported:
+ - quiet
+ - balanced
+ - performance
+ - custom
+
+Custom Profile
+~~~~~~~~~~~~~~
+The custom profile is enabled but is not user selectable. This setting
+represents a hardware mode on Lenovo Legion devices that enables user
+modifications to Package Power Tracking settings. When an attribute exposed
+by the "Other Mode" WMI Interface is modified, the GameZone driver will switch
+to this profile automatically.
+
+
+Other Mode
+----------
+The Other Mode WMI interface uses the fw_attributes class to expose various
+WMI functions provided by the interface in the sysfs. This enables CPU and GPU
+power limit tuning as well as various other attributes for devices that fall
+under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by
+the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages
+that allow the driver to probe details about the attribute. Each attibute has
+multiple pages, one for each of the platform profiles managed by the "GameZone"
+interface, so it must be probed prior to returning the current_value. For
+read-only properties, only the "Custom" profile values are reported to ensure
+any userspace applications reading them have accurate tunable value ranges.
+Attributes are exposed in sysfs under the following path:
+/sys/class/firmware-attributes/lenovo-legion-wmi/attributes
+
+Supported Attibutes
+~~~~~~~~~~~~~~~~~~~
+The following attributes are supported:
+ - 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
+
+Each attribute has the following properties:
+ - current_value
+ - default_value
+ - display_name
+ - max_value
+ - min_value
+ - scalar_increment
+ - type
diff --git a/MAINTAINERS b/MAINTAINERS
index baf0eeb9a355..67f7b588aa36 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13034,6 +13034,15 @@ S:	Maintained
 W:	http://legousb.sourceforge.net/
 F:	drivers/usb/misc/legousbtower.c
 
+LENOVO LEGION WMI driver
+M:	Derek J. Clark <derekjohn.clark@gmail.com>
+L:	platform-driver-x86@vger.kernel.org
+S:	Maintained
+F:	drivers/platform/x86/lenovo-legion-wmi-capdata01.c
+F:	drivers/platform/x86/lenovo-legion-wmi-gamezone.c
+F:	drivers/platform/x86/lenovo-legion-wmi-other.c
+F:	drivers/platform/x86/lenovo-legion-wmi.h
+
 LETSKETCH HID TABLET DRIVER
 M:	Hans de Goede <hdegoede@redhat.com>
 L:	linux-input@vger.kernel.org
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 0258dd879d64..a51a1a2fe7ba 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -459,6 +459,41 @@ config IBM_RTL
 	 state = 0 (BIOS SMIs on)
 	 state = 1 (BIOS SMIs off)
 
+config LEGION_GAMEZONE_WMI
+	tristate "Lenovo Legion GameZone WMI Driver"
+	depends on ACPI_WMI
+	select ACPI_PLATFORM_PROFILE
+	help
+	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
+	  platform-profile firmware interface.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called lenovo_legion_wmi_gamezone.
+
+config LEGION_DATA_01_WMI
+	tristate "Lenovo Legion WMI capability Data 01 Driver"
+	depends on ACPI_WMI
+	help
+	  Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series"
+	  line of hardware. This interface is a dependency for exposing tunable power
+	  settings.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called lenovo_legion_wmi_capdata01.
+
+config LEGION_OTHER_WMI
+	tristate "Lenovo Legion Other Method WMI Driver"
+	depends on LEGION_GAMEZONE_WMI
+	depends on LEGION_DATA_01_WMI
+	select FW_ATTR_CLASS
+	help
+	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
+	  firmware_attributes API to control various tunable settings typically exposed by
+	  Lenovo software in Windows.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called lenovo_legion_wmi_other.
+
 config IDEAPAD_LAPTOP
 	tristate "Lenovo IdeaPad Laptop Extras"
 	depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index e1b142947067..838ee568c3f9 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP)	+= hp/
 obj-$(CONFIG_UV_SYSFS)       += uv_sysfs.o
 
 # IBM Thinkpad and Lenovo
-obj-$(CONFIG_IBM_RTL)		+= ibm_rtl.o
-obj-$(CONFIG_IDEAPAD_LAPTOP)	+= ideapad-laptop.o
-obj-$(CONFIG_LENOVO_YMC)	+= lenovo-ymc.o
-obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
-obj-$(CONFIG_THINKPAD_ACPI)	+= thinkpad_acpi.o
-obj-$(CONFIG_THINKPAD_LMI)	+= think-lmi.o
-obj-$(CONFIG_YOGABOOK)		+= lenovo-yogabook.o
-obj-$(CONFIG_YT2_1380)		+= lenovo-yoga-tab2-pro-1380-fastcharger.o
-obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
+obj-$(CONFIG_IBM_RTL)			+= ibm_rtl.o
+obj-$(CONFIG_IDEAPAD_LAPTOP)		+= ideapad-laptop.o
+obj-$(CONFIG_LENOVO_YMC)		+= lenovo-ymc.o
+obj-$(CONFIG_SENSORS_HDAPS)		+= hdaps.o
+obj-$(CONFIG_THINKPAD_ACPI)		+= thinkpad_acpi.o
+obj-$(CONFIG_THINKPAD_LMI)		+= think-lmi.o
+obj-$(CONFIG_YOGABOOK)			+= lenovo-yogabook.o
+obj-$(CONFIG_YT2_1380)			+= lenovo-yoga-tab2-pro-1380-fastcharger.o
+obj-$(CONFIG_LENOVO_WMI_CAMERA)		+= lenovo-wmi-camera.o
+obj-$(CONFIG_LEGION_GAMEZONE_WMI)	+= lenovo-legion-wmi-gamezone.o
+obj-$(CONFIG_LEGION_DATA_01_WMI)	+= lenovo-legion-wmi-capdata01.o
+obj-$(CONFIG_LEGION_OTHER_WMI)		+= lenovo-legion-wmi-other.o
 
 # Intel
 obj-y				+= intel/
diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
new file mode 100644
index 000000000000..99f4f35b7176
--- /dev/null
+++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides
+ * information on tunable attributes used by the "Other Method" WMI interface,
+ * including if it is supported by the hardware, the default_value, max_value,
+ * min_value, and step increment.
+ *
+ * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
+ *
+ */
+
+#include "lenovo-legion-wmi.h"
+
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+
+static const struct wmi_device_id capdata_01_wmi_id_table[] = {
+	{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
+	{}
+};
+
+static struct capdata_wmi cd01_wmi = {
+	.mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex)
+};
+
+int capdata_01_wmi_get(struct om_attribute_id attr_id,
+		       struct capability_data_01 *cap_data)
+{
+	union acpi_object *ret_obj;
+	int count;
+	int instance_id;
+	u32 attribute_id = *(int *)&attr_id;
+
+	mutex_lock(&cd01_wmi.mutex);
+	count = wmidev_instance_count(drvdata.cd01_wmi->wdev);
+	mutex_unlock(&cd01_wmi.mutex);
+	for (instance_id = 0; instance_id < count; instance_id++) {
+		mutex_lock(&cd01_wmi.mutex);
+		ret_obj =
+			wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id);
+		mutex_unlock(&cd01_wmi.mutex);
+		if (!ret_obj) {
+			pr_err("lenovo_legion_wmi_capdata_01: block query failed\n");
+			continue;
+		}
+
+		if (ret_obj->type != ACPI_TYPE_BUFFER) {
+			pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n",
+			       ret_obj->type);
+			kfree(ret_obj);
+			continue;
+		}
+
+		if (ret_obj->buffer.length != sizeof(*cap_data)) {
+			pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n",
+			       ret_obj->buffer.length);
+			kfree(ret_obj);
+			continue;
+		}
+
+		memcpy(cap_data, ret_obj->buffer.pointer,
+		       ret_obj->buffer.length);
+		kfree(ret_obj);
+
+		if (cap_data->id != attribute_id)
+			continue;
+		break;
+	}
+	if (cap_data->id == 0) {
+		pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI");
+
+/* Driver Setup */
+static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+	cd01_wmi.wdev = wdev;
+	drvdata.cd01_wmi = &cd01_wmi;
+	pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n");
+
+	return 0;
+}
+
+static void capdata_01_wmi_remove(struct wmi_device *wdev)
+{
+	pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n");
+}
+
+static struct wmi_driver capdata_01_wmi_driver = {
+	.driver = { .name = "capdata_01_wmi" },
+	.id_table = capdata_01_wmi_id_table,
+	.probe = capdata_01_wmi_probe,
+	.remove = capdata_01_wmi_remove,
+};
+
+module_wmi_driver(capdata_01_wmi_driver);
+
+MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
new file mode 100644
index 000000000000..2f976dc0e367
--- /dev/null
+++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides
+ * platform profile and fan curve settings for devices that fall under the
+ * "Gaming Series" of Lenovo Legion devices.
+ *
+ * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
+ *
+ */
+
+#include "lenovo-legion-wmi.h"
+
+#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
+
+/* Method IDs */
+#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */
+#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */
+#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */
+
+static const struct wmi_device_id gamezone_wmi_id_table[] = {
+	{ LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
+	{}
+};
+
+static struct gamezone_wmi gz_wmi = {
+	.mutex = __MUTEX_INITIALIZER(gz_wmi.mutex)
+};
+
+/* Platform Profile Methods */
+static int
+gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof,
+					int *supported)
+{
+	int ret;
+
+	mutex_lock(&gz_wmi.mutex);
+	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
+					      WMI_METHOD_ID_SMARTFAN_SUPP, 0,
+					      supported);
+	mutex_unlock(&gz_wmi.mutex);
+	return ret;
+}
+
+int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
+				 int *sel_prof)
+{
+	int ret;
+	int supported;
+
+	gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
+						&supported);
+	if (!supported) {
+		pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
+		return -EOPNOTSUPP;
+	}
+	mutex_lock(&gz_wmi.mutex);
+	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
+					      WMI_METHOD_ID_SMARTFAN_GET, 0,
+					      sel_prof);
+	mutex_unlock(&gz_wmi.mutex);
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI");
+
+static int
+gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof,
+				  enum platform_profile_option *profile)
+{
+	int sel_prof;
+	int err;
+
+	err = gamezone_wmi_fan_profile_get(pprof, &sel_prof);
+	if (err)
+		return err;
+
+	switch (sel_prof) {
+	case SMARTFAN_MODE_QUIET:
+		*profile = PLATFORM_PROFILE_QUIET;
+		break;
+	case SMARTFAN_MODE_BALANCED:
+		*profile = PLATFORM_PROFILE_BALANCED;
+		break;
+	case SMARTFAN_MODE_PERFORMANCE:
+		*profile = PLATFORM_PROFILE_PERFORMANCE;
+		break;
+	case SMARTFAN_MODE_CUSTOM:
+		*profile = PLATFORM_PROFILE_CUSTOM;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	drvdata.gz_wmi->current_profile = *profile;
+
+	return 0;
+}
+
+int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
+				      enum platform_profile_option profile)
+{
+	int ret;
+	int sel_prof;
+	int supported;
+
+	gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
+						&supported);
+	if (!supported) {
+		pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
+		return -EOPNOTSUPP;
+	}
+
+	switch (profile) {
+	case PLATFORM_PROFILE_QUIET:
+		sel_prof = SMARTFAN_MODE_QUIET;
+		break;
+	case PLATFORM_PROFILE_BALANCED:
+		sel_prof = SMARTFAN_MODE_BALANCED;
+		break;
+	case PLATFORM_PROFILE_PERFORMANCE:
+		sel_prof = SMARTFAN_MODE_PERFORMANCE;
+		break;
+	case PLATFORM_PROFILE_CUSTOM:
+		sel_prof = SMARTFAN_MODE_CUSTOM;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	mutex_lock(&gz_wmi.mutex);
+	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
+					      WMI_METHOD_ID_SMARTFAN_SET,
+					      sel_prof, NULL);
+	mutex_unlock(&gz_wmi.mutex);
+
+	if (ret) {
+		pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n");
+		return ret;
+	}
+
+	drvdata.gz_wmi->current_profile = profile;
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI");
+
+/* Driver Setup */
+static int platform_profile_setup(struct gamezone_wmi *gz_wmi)
+{
+	int err;
+	int supported;
+
+	gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported);
+
+	gz_wmi->platform_profile_support = supported;
+
+	if (!supported) {
+		pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
+		return -EOPNOTSUPP;
+	}
+
+	gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get;
+	gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set;
+
+	set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices);
+	set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices);
+	set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices);
+	set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices);
+
+	err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof,
+						&gz_wmi->current_profile);
+	if (err) {
+		pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n",
+		       err);
+		return err;
+	}
+
+	err = platform_profile_register(&gz_wmi->pprof);
+	if (err) {
+		pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n",
+		       err);
+		return err;
+	}
+
+	return 0;
+}
+
+static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+	int err;
+
+	gz_wmi.wdev = wdev;
+	drvdata.gz_wmi = &gz_wmi;
+
+	err = platform_profile_setup(&gz_wmi);
+	if (err) {
+		pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n",
+		       err);
+		kfree(&gz_wmi);
+		return err;
+	}
+
+	pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n");
+	return 0;
+}
+
+static void gamezone_wmi_remove(struct wmi_device *wdev)
+{
+	int err;
+
+	mutex_lock(&gz_wmi.mutex);
+	err = platform_profile_remove(&drvdata.gz_wmi->pprof);
+	mutex_unlock(&gz_wmi.mutex);
+
+	if (err) {
+		pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n",
+		       err);
+	} else {
+		pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n");
+	}
+}
+
+static struct wmi_driver gamezone_wmi_driver = {
+	.driver = { .name = "gamezone_wmi" },
+	.id_table = gamezone_wmi_id_table,
+	.probe = gamezone_wmi_probe,
+	.remove = gamezone_wmi_remove,
+};
+
+module_wmi_driver(gamezone_wmi_driver);
+
+MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo GameZone WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c
new file mode 100644
index 000000000000..c09c1848eda7
--- /dev/null
+++ b/drivers/platform/x86/lenovo-legion-wmi-other.c
@@ -0,0 +1,377 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Legion Other Method driver. This driver uses the fw_attributes
+ * class to expose the various WMI functions provided by the "Other Method" WMI
+ * interface. This enables CPU and GPU power limit as well as various other
+ * attributes for devices that fall under the "Gaming Series" of Lenovo Legion
+ * devices. Each attribute exposed by the "Other Method"" interface has a
+ * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to
+ * probe details about the attribute such as set/get support, step, min, max,
+ * and default value. Each attibute has multiple pages, one for each of the
+ * fan profiles managed by the GameZone interface, so it must be probed prior
+ * to returning the current_value.
+ *
+ * These attributes typically don't fit anywhere else in the sysfs and are set
+ * in Windows using one of Lenovo's multiple user applications.
+ *
+ * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
+ *
+ */
+
+#include "lenovo-legion-wmi.h"
+#include "firmware_attributes_class.h"
+
+#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
+
+/* Device IDs */
+#define WMI_DEVICE_ID_CPU 0x01
+
+/* WMI_DEVICE_ID_CPU feature IDs */
+#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */
+#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
+#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */
+
+/* Method IDs */
+#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */
+#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */
+
+static const struct wmi_device_id other_method_wmi_id_table[] = {
+	{ LENOVO_OTHER_METHOD_GUID, NULL },
+	{}
+};
+
+/* Tunable Attributes */
+struct ll_tunables {
+	u32 ppt_pl1_spl;
+	u32 ppt_pl2_sppt;
+	u32 ppt_pl3_fppt;
+};
+
+static const struct class *fw_attr_class;
+
+static struct other_method_wmi om_wmi = {
+	.mutex = __MUTEX_INITIALIZER(om_wmi.mutex)
+};
+
+struct capdata_01_attr_group {
+	const struct attribute_group *attr_group;
+};
+
+/* Simple attribute creation */
+
+/*
+ * att_current_value_store() - Set the current value of the given attribute
+ * @kobj: Pointer to the driver object.
+ * @kobj_attribute: Pointer to the attribute calling this function.
+ * @buf: The buffer to read from, this is parsed to `int` type.
+ * @count: Required by sysfs attribute macros, pass in from the callee attr.
+ * @store_value: Pointer to where the parsed value should be stored.
+ * @device_id: The WMI function Device ID to use.
+ * @feature_id: The WMI function Feature ID to use.
+ *
+ * This function is intended to be generic so it can be called from any
+ * attribute's "current_value_store" which works only with integers. The
+ * integer to be sent to the WMI method is range checked and an error returned
+ * if out of range.
+ *
+ * If the value is valid and WMI is success, then the sysfs attribute is
+ * notified.
+ *
+ * Returns: Either count, or an error.
+ */
+ssize_t attr_current_value_store(struct kobject *kobj,
+				 struct kobj_attribute *attr, const char *buf,
+				 size_t count, u32 *store_value, u8 device_id,
+				 u8 feature_id)
+{
+	struct capability_data_01 cap_data;
+	enum platform_profile_option cust_prof;
+	int err;
+	int sel_prof;
+	u32 value;
+	struct wmi_device *wdev = drvdata.om_wmi->wdev;
+
+	err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
+	if (err) {
+		pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");
+		return -EIO;
+	}
+
+	/* Switch to custom profile if not currently on it. */
+	if (sel_prof != SMARTFAN_MODE_CUSTOM) {
+		pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables.");
+		cust_prof = PLATFORM_PROFILE_CUSTOM;
+		sel_prof = SMARTFAN_MODE_CUSTOM;
+		err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof,
+							cust_prof);
+		if (err) {
+			pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n");
+			return -EIO;
+		}
+	}
+
+	err = kstrtouint(buf, 10, &value);
+	if (err) {
+		pr_err("lenovo_legion_wmi_other: Error converting value to int.\n");
+		return -EIO;
+	}
+
+	/* Construct the attribute id */
+	struct om_attribute_id attr_id = { sel_prof << 8, feature_id,
+					   device_id };
+
+	/* Get min/max from LENOVO_CAPABILITY_DATA_01 */
+	err = capdata_01_wmi_get(attr_id, &cap_data);
+	if (err) {
+		pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
+		return -EIO;
+	}
+	if (cap_data.capability < 1) {
+		pr_err("lenovo_legion_wmi_other: Capability not supported.\n");
+		return -EPERM;
+	}
+
+	if (value < cap_data.min_value || value > cap_data.max_value) {
+		pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n",
+			value, cap_data.min_value, cap_data.max_value);
+		return -EINVAL;
+	}
+
+	mutex_lock(&om_wmi.mutex);
+	err = lenovo_legion_evaluate_method_2(wdev, 0x0,
+					      WMI_METHOD_ID_VALUE_SET,
+					      *(int *)&attr_id, value, NULL);
+	mutex_unlock(&om_wmi.mutex);
+
+	if (err) {
+		pr_err("lenovo_legion_wmi_other: Error setting attribute");
+		return err;
+	}
+
+	if (store_value)
+		*store_value = value;
+
+	sysfs_notify(kobj, NULL, attr->attr.name);
+	return count;
+}
+
+/*
+ * attr_current_value_show() - Get the current value of the given attribute
+ * @kobj: Pointer to the driver object.
+ * @kobj_attribute: Pointer to the attribute calling this function.
+ * @buf: The buffer to write to.
+ * @retval: Pointer to returned data.
+ * @device_id: The WMI function Device ID to use.
+ * @feature_id: The WMI function Feature ID to use.
+ *
+ * This function is intended to be generic so it can be called from any "_show"
+ * attribute which works only with integers.
+ *
+ * If the WMI is success, then the sysfs attribute is notified.
+ *
+ * Returns: Either count, or an error.
+ */
+ssize_t attr_current_value_show(struct kobject *kobj,
+				struct kobj_attribute *attr, char *buf,
+				u8 device_id, u8 feature_id)
+{
+	int sel_prof; /* Current fan profile mode */
+	int err;
+	int retval;
+	struct wmi_device *wdev = drvdata.om_wmi->wdev;
+
+	err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
+
+	if (err) {
+		pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");
+		return err;
+	}
+
+	// Construct the WMI attribute id from the given args.
+	struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
+						device_id };
+
+	mutex_lock(&om_wmi.mutex);
+	err = lenovo_legion_evaluate_method_1(wdev, 0x0,
+					      WMI_METHOD_ID_VALUE_GET,
+					      *(int *)&attribute_id, &retval);
+	mutex_unlock(&om_wmi.mutex);
+
+	if (err) {
+		pr_err("lenovo_legion_wmi_other: Error getting attribute");
+		return err;
+	}
+
+	return sysfs_emit(buf, "%u\n", retval);
+}
+
+/**
+ * attr_capdata_01_show() - Get the value of the specified attribute property
+ * from LENOVO_CAPABILITY_DATA_01.
+ * @kobj: Pointer to the driver object.
+ * @kobj_attribute: Pointer to the attribute calling this function.
+ * @buf: The buffer to write to.
+ * @retval: Pointer to returned data.
+ * @device_id: The WMI functions Device ID to use.
+ * @feature_id: The WMI functions Feature ID to use.
+ * @prop: The property of this attribute to be read.
+ *
+ * This function is intended to be generic so it can be called from any "_show"
+ * attribute which works only with integers.
+ *
+ * If the WMI is success, then the sysfs attribute is notified.
+ *
+ * Returns: Either count, or an error.
+ */
+ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
+			     char *buf, u8 device_id, u8 feature_id,
+			     enum attribute_property prop)
+{
+	struct capability_data_01 cap_data;
+	int err;
+	int retval;
+	int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */
+
+	// Construct the WMI attribute id from the given args.
+	struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
+						device_id };
+
+	err = capdata_01_wmi_get(attribute_id, &cap_data);
+	if (err) {
+		pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
+		return -EIO;
+	}
+
+	switch (prop) {
+	case DEFAULT_VAL:
+		retval = cap_data.default_value;
+		break;
+	case MAX_VAL:
+		retval = cap_data.max_value;
+		break;
+	case MIN_VAL:
+		retval = cap_data.min_value;
+		break;
+	case STEP_VAL:
+		retval = cap_data.step;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return sysfs_emit(buf, "%u\n", retval);
+}
+
+ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU,
+			    WMI_FEATURE_ID_CPU_SPL,
+			    "Set the CPU sustained power limit");
+ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU,
+			    WMI_FEATURE_ID_CPU_SPPT,
+			    "Set the CPU slow package power tracking limit");
+ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU,
+			    WMI_FEATURE_ID_CPU_FPPT,
+			    "Set the CPU fast package power tracking limit");
+
+static const struct capdata_01_attr_group capdata_01_attr_groups[] = {
+	{ &ppt_pl1_spl_attr_group },
+	{ &ppt_pl2_sppt_attr_group },
+	{ &ppt_pl3_fppt_attr_group },
+	{},
+};
+
+static int other_method_fw_attr_add(void)
+{
+	int err, i;
+
+	err = fw_attributes_class_get(&fw_attr_class);
+	if (err) {
+		pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n");
+		return err;
+	}
+
+	om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
+					   NULL, "%s", DRIVER_NAME);
+	if (IS_ERR(om_wmi.fw_attr_dev)) {
+		pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n");
+		err = PTR_ERR(om_wmi.fw_attr_dev);
+		goto fail_class_get;
+	}
+
+	om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL,
+						  &om_wmi.fw_attr_dev->kobj);
+	if (!om_wmi.fw_attr_kset) {
+		pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n");
+		err = -ENOMEM;
+		goto err_destroy_classdev;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) {
+		err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj,
+					 capdata_01_attr_groups[i].attr_group);
+		if (err) {
+			pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n",
+			       capdata_01_attr_groups[i].attr_group->name);
+			goto err_remove_groups;
+		}
+	}
+
+	return 0;
+
+err_remove_groups:
+	while (--i >= 0) {
+		sysfs_remove_group(&om_wmi.fw_attr_kset->kobj,
+				   capdata_01_attr_groups[i].attr_group);
+	}
+err_destroy_classdev:
+	device_destroy(fw_attr_class, MKDEV(0, 0));
+fail_class_get:
+	fw_attributes_class_put();
+	return err;
+}
+
+/* Driver Setup */
+static int other_method_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+	int err;
+
+	om_wmi.wdev = wdev;
+	drvdata.om_wmi = &om_wmi;
+	om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL);
+	if (!om_wmi.ll_tunables)
+		return -ENOMEM;
+
+	err = other_method_fw_attr_add();
+	if (err)
+		return err;
+	pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n");
+
+	return 0;
+}
+
+static void other_method_wmi_remove(struct wmi_device *wdev)
+{
+	mutex_lock(&om_wmi.mutex);
+
+	kset_unregister(om_wmi.fw_attr_kset);
+	device_destroy(fw_attr_class, MKDEV(0, 0));
+	fw_attributes_class_put();
+
+	mutex_unlock(&om_wmi.mutex);
+
+	pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n");
+}
+
+static struct wmi_driver other_method_wmi_driver = {
+	.driver = { .name = "other_method_wmi" },
+	.id_table = other_method_wmi_id_table,
+	.probe = other_method_wmi_probe,
+	.remove = other_method_wmi_remove,
+};
+
+module_wmi_driver(other_method_wmi_driver);
+
+MODULE_IMPORT_NS("GZ_WMI");
+MODULE_IMPORT_NS("CAPDATA_WMI");
+MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Legion Other Method Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h
new file mode 100644
index 000000000000..65baa728f29e
--- /dev/null
+++ b/drivers/platform/x86/lenovo-legion-wmi.h
@@ -0,0 +1,271 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is
+ * broken up into multiple GUID interfaces that require cross-references
+ * between GUID's for some functionality. The "Custom Mode" interface is a
+ * legacy interface for managing and displaying CPU & GPU power and hwmon
+ * settings and readings. The "Other Mode" interface is a modern interface
+ * that replaces or extends the "Custom Mode" interface methods. The "GameZone"
+ * interface adds advanced features such as fan profiles and overclocking.
+ * The "Lighting" interface adds control of various status lights related to
+ * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00
+ * struct for capability information, "Other Mode" uses
+ * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting"
+ * uses LENOVO_CAPABILITY_DATA_02 struct for capability information.
+ *
+ * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
+ *
+ */
+
+#ifndef _LENOVO_LEGION_WMI_H_
+#define _LENOVO_LEGION_WMI_H_
+
+#include <linux/mutex.h>
+#include <linux/platform_profile.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#define DRIVER_NAME "lenovo-legion-wmi"
+
+/* Platform Profile Modes */
+#define SMARTFAN_MODE_QUIET 0x01
+#define SMARTFAN_MODE_BALANCED 0x02
+#define SMARTFAN_MODE_PERFORMANCE 0x03
+#define SMARTFAN_MODE_CUSTOM 0xFF
+
+struct gamezone_wmi {
+	struct wmi_device *wdev;
+	enum platform_profile_option current_profile;
+	struct platform_profile_handler pprof;
+	bool platform_profile_support;
+	struct mutex mutex; /* Ensure single operation on WMI device */
+};
+
+struct other_method_wmi {
+	struct wmi_device *wdev;
+	struct device *fw_attr_dev;
+	struct kset *fw_attr_kset;
+	struct ll_tunables *ll_tunables;
+	struct mutex mutex; /* Ensure single operation on WMI device */
+};
+
+struct capdata_wmi {
+	struct wmi_device *wdev;
+	struct mutex mutex; /* Ensure single operation on WMI device */
+};
+
+struct ll_drvdata {
+	struct other_method_wmi *om_wmi; /* Other method GUID device */
+	struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */
+	struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */
+} drvdata;
+
+struct wmi_method_args {
+	u32 arg0;
+	u32 arg1;
+};
+
+struct om_attribute_id {
+	u32 mode_id : 16; /* Fan profile */
+	u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
+	u32 device_id : 8; /* CPU/GPU */
+} __packed;
+
+enum attribute_property {
+	DEFAULT_VAL = 0,
+	MAX_VAL,
+	MIN_VAL,
+	STEP_VAL,
+	SUPPORTED,
+};
+
+struct capability_data_01 {
+	u32 id;
+	u32 capability;
+	u32 default_value;
+	u32 step;
+	u32 min_value;
+	u32 max_value;
+};
+
+static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance,
+					 u32 method_id, struct acpi_buffer *in,
+					 struct acpi_buffer *out)
+{
+	acpi_status status;
+
+	status = wmidev_evaluate_method(wdev, instance, method_id, in, out);
+
+	if (ACPI_FAILURE(status)) {
+		pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n",
+		       method_id, instance);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
+				    u32 method_id, u32 arg0, u32 arg1,
+				    u32 *retval);
+
+int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
+				    u32 method_id, u32 arg0, u32 arg1,
+				    u32 *retval)
+{
+	int ret;
+	u32 temp_val;
+	struct wmi_method_args args = { arg0, arg1 };
+	struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *ret_obj = NULL;
+
+	ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input,
+					    &output);
+
+	if (ret) {
+		pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n",
+		       method_id, ret);
+		return ret;
+	}
+
+	if (retval) {
+		ret_obj = (union acpi_object *)output.pointer;
+		if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER)
+			temp_val = (u32)ret_obj->integer.value;
+
+		*retval = temp_val;
+	}
+
+	kfree(ret_obj);
+
+	return 0;
+}
+
+int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
+				    u32 method_id, u32 arg0, u32 *retval);
+
+int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
+				    u32 method_id, u32 arg0, u32 *retval)
+{
+	return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0,
+					       0, retval);
+}
+
+int capdata_01_wmi_get(struct om_attribute_id attr_id,
+		       struct capability_data_01 *cap_data);
+
+int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
+				      enum platform_profile_option sel_prof);
+
+int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
+				 int *sel_prof);
+
+/* current_value */
+ssize_t attr_current_value_store(struct kobject *kobj,
+				 struct kobj_attribute *attr, const char *buf,
+				 size_t count, u32 *store_value, u8 device_id,
+				 u8 feature_id);
+
+ssize_t attr_current_value_show(struct kobject *kobj,
+				struct kobj_attribute *attr, char *buf,
+				u8 device_id, u8 feature_id);
+
+/* LENOVO_CAPABILITY_DATA_01 */
+ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
+			     char *buf, u8 device_id, u8 feature_id,
+			     enum attribute_property prop);
+
+ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+		      char *buf);
+
+ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+		      char *buf)
+{
+	return sysfs_emit(buf, "integer\n");
+}
+
+#define __LL_ATTR_RO(_func, _name)                                    \
+	{                                                             \
+		.attr = { .name = __stringify(_name), .mode = 0444 }, \
+		.show = _func##_##_name##_show,                       \
+	}
+
+#define __LL_ATTR_RO_AS(_name, _show)                                 \
+	{                                                             \
+		.attr = { .name = __stringify(_name), .mode = 0444 }, \
+		.show = _show,                                        \
+	}
+
+#define __LL_ATTR_RW(_func, _name) \
+	__ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
+
+/* Shows a formatted static variable */
+#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val)                         \
+	static ssize_t _attrname##_##_prop##_show(                            \
+		struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+	{                                                                     \
+		return sysfs_emit(buf, _fmt, _val);                           \
+	}                                                                     \
+	static struct kobj_attribute attr_##_attrname##_##_prop =             \
+		__LL_ATTR_RO(_attrname, _prop)
+
+/* Attribute current_value show/store */
+#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id)                   \
+	static ssize_t _attrname##_current_value_store(                       \
+		struct kobject *kobj, struct kobj_attribute *attr,            \
+		const char *buf, size_t count)                                \
+	{                                                                     \
+		return attr_current_value_store(                              \
+			kobj, attr, buf, count,                               \
+			&om_wmi.ll_tunables->_attrname, _dev_id, _feat_id);   \
+	}                                                                     \
+	static ssize_t _attrname##_current_value_show(                        \
+		struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+	{                                                                     \
+		return attr_current_value_show(kobj, attr, buf, _dev_id,      \
+					       _feat_id);                     \
+	}                                                                     \
+	static struct kobj_attribute attr_##_attrname##_current_value =       \
+		__LL_ATTR_RW(_attrname, current_value)
+
+/* Attribute property show only */
+#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \
+	static ssize_t _attrname##_##_prop##_show(                             \
+		struct kobject *kobj, struct kobj_attribute *attr, char *buf)  \
+	{                                                                      \
+		return attr_capdata_01_show(kobj, attr, buf, _dev_id,          \
+					    _feat_id, _prop_type);             \
+	}                                                                      \
+	static struct kobj_attribute attr_##_attrname##_##_prop =              \
+		__LL_ATTR_RO(_attrname, _prop)
+
+#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id,    \
+				    _dispname)                                \
+	__LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id);                  \
+	__LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id,    \
+			      DEFAULT_VAL);                                   \
+	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);          \
+	__LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id,        \
+			      MAX_VAL);                                       \
+	__LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id,        \
+			      MIN_VAL);                                       \
+	__LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \
+			      STEP_VAL);                                      \
+	static struct kobj_attribute attr_##_attrname##_type =                \
+		__LL_ATTR_RO_AS(type, int_type_show);                         \
+	static struct attribute *_attrname##_attrs[] = {                      \
+		&attr_##_attrname##_current_value.attr,                       \
+		&attr_##_attrname##_default_value.attr,                       \
+		&attr_##_attrname##_display_name.attr,                        \
+		&attr_##_attrname##_max_value.attr,                           \
+		&attr_##_attrname##_min_value.attr,                           \
+		&attr_##_attrname##_scalar_increment.attr,                    \
+		&attr_##_attrname##_type.attr,                                \
+		NULL,                                                         \
+	};                                                                    \
+	static const struct attribute_group _attrname##_attr_group = {        \
+		.name = _fsname, .attrs = _attrname##_attrs                   \
+	}
+
+#endif /* !_LENOVO_LEGION_WMI_H_ */
-- 
2.47.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-17 23:06 ` [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers Derek J. Clark
@ 2024-12-18  2:39   ` Mario Limonciello
  2024-12-18  3:36     ` Derek J. Clark
  2024-12-25  5:25   ` Cody T.-H. Chiu
  2024-12-27 18:48   ` [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers Antheas Kapenekakis
  2 siblings, 1 reply; 29+ messages in thread
From: Mario Limonciello @ 2024-12-18  2:39 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede
  Cc: Ilpo Järvinen, Jonathan Corbet, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	platform-driver-x86, linux-doc, linux-kernel

On 12/17/2024 17:06, Derek J. Clark wrote:
> Adds lenovo-legion-wmi.h which provides templates and some method
> implementations used by the lenovo-legion-wmi driver series.
> 
> Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo
> GameZone WMI interface that comes on Lenovo "Gaming Series" hardware.
> Provides ACPI platform profiles over WMI.
> 
> Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo
> "Other Method" WMI interface that comes on some Lenovo hardware.
> Provides a firmware-attributes class which enables the use of tunable
> knobs for SPL, SPPT, and FPPT.
> 
> Adds lenovo-legion-wmi-capdata01.c which provides a driver for the
> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method"
> enabled hardware. Provides an interface for querying if a given
> attribute is supported by the hardware, as well as its default_value,
> max_value, min_value, and step increment.
> 
> Adds lenovo-legion-wmi.rst describing the available drivers and their
> function.
> 
> Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers.
> 
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>

Hi Derek,

As a high level first comment; "larger" patches are much harder to review.

It seems that the drivers are logically split as described in your 
commit message already.  For the next version could you split at least 
each driver to it's own patch?

It might also make sense to split up the individual drivers along 
"features".

This is my own personal opinion and not a requirement but I personally 
like to see documentation for something new like this as it's own patch 
at the beginning of the series so we can make sure everyone understands 
and agrees on the design as they review the series and then can make 
sure that the implementation matches the design as the other patches are 
reviewed.

I've got various other comments sprinkled throughout the patch, please 
see them.  I'm not 100% sure on the mutex use yet, we should review that 
after you've got all the cleanups needed done.

> ---
>   .../wmi/devices/lenovo-legion-wmi.rst         |  79 ++++
>   MAINTAINERS                                   |   9 +
>   drivers/platform/x86/Kconfig                  |  35 ++
>   drivers/platform/x86/Makefile                 |  21 +-
>   .../x86/lenovo-legion-wmi-capdata01.c         | 103 +++++
>   .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++
>   .../platform/x86/lenovo-legion-wmi-other.c    | 377 ++++++++++++++++++
>   drivers/platform/x86/lenovo-legion-wmi.h      | 271 +++++++++++++
>   8 files changed, 1119 insertions(+), 9 deletions(-)
>   create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst
>   create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>   create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>   create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c
>   create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h
> 
> diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst
> new file mode 100644
> index 000000000000..37b09c82c980
> --- /dev/null
> +++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst
> @@ -0,0 +1,79 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +======================================================
> +Lenovo Legion WMI Interface Driver (lenovo-legion-wmi)
> +======================================================
> +
> +Introduction
> +============
> +The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that
> +require cross-references between GUID's for some functionality. The "Custom
> +Mode" interface is a legacy interface for managing and displaying CPU & GPU
> +power and hwmon settings and readings. The "Other Mode" interface is a modern
> +interface that replaces "Custom Mode" interface methods. The "GameZone"
> +interface adds advanced features such as fan profiles and overclocking. The
> +"Lighting" interface adds control of various status lights related to different
> +hardware components.
> +
> +Each of these interfaces has a different data structure associated with it that
> +provide detailed information about each attribute provided by the interface.
> +These data structs are retrieved from an additional WMI device data block GUID:
> + - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00
> + - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01
> + - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02
> +
> +.. note::
> +   Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01
> +   interfaces are implemented by this driver.

So this is to say that lighting interface is not implemented right now, 
right?

> +
> +
> +GameZone
> +--------
> +The GameZone WMI interface provides ACPI platform profile and fan curve
> +settings for devices that fall under the "Gaming Series" of Lenovo Legion
> +devices.
> +
> +The following platform profiles are supported:
> + - quiet
> + - balanced
> + - performance
> + - custom
> +
> +Custom Profile
> +~~~~~~~~~~~~~~
> +The custom profile is enabled but is not user selectable. This setting
> +represents a hardware mode on Lenovo Legion devices that enables user
> +modifications to Package Power Tracking settings. When an attribute exposed
> +by the "Other Mode" WMI Interface is modified, the GameZone driver will switch
> +to this profile automatically.

I think you should explicitly mention that it's undone if the user 
selects a fixed platform mode too.  (It does, right?)
> +
> +
> +Other Mode
> +----------
> +The Other Mode WMI interface uses the fw_attributes class to expose various
> +WMI functions provided by the interface in the sysfs. This enables CPU and GPU
> +power limit tuning as well as various other attributes for devices that fall
> +under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by
> +the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages
> +that allow the driver to probe details about the attribute. Each attibute has
> +multiple pages, one for each of the platform profiles managed by the "GameZone"
> +interface, so it must be probed prior to returning the current_value. For
> +read-only properties, only the "Custom" profile values are reported to ensure
> +any userspace applications reading them have accurate tunable value ranges.
> +Attributes are exposed in sysfs under the following path:
> +/sys/class/firmware-attributes/lenovo-legion-wmi/attributes
> +
> +Supported Attibutes
> +~~~~~~~~~~~~~~~~~~~
> +The following attributes are supported:
> + - 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
> +
> +Each attribute has the following properties:
> + - current_value
> + - default_value
> + - display_name
> + - max_value
> + - min_value
> + - scalar_increment
> + - type
> diff --git a/MAINTAINERS b/MAINTAINERS
> index baf0eeb9a355..67f7b588aa36 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13034,6 +13034,15 @@ S:	Maintained
>   W:	http://legousb.sourceforge.net/
>   F:	drivers/usb/misc/legousbtower.c
>   
> +LENOVO LEGION WMI driver
> +M:	Derek J. Clark <derekjohn.clark@gmail.com>
> +L:	platform-driver-x86@vger.kernel.org
> +S:	Maintained
> +F:	drivers/platform/x86/lenovo-legion-wmi-capdata01.c
> +F:	drivers/platform/x86/lenovo-legion-wmi-gamezone.c
> +F:	drivers/platform/x86/lenovo-legion-wmi-other.c
> +F:	drivers/platform/x86/lenovo-legion-wmi.h
> +
>   LETSKETCH HID TABLET DRIVER
>   M:	Hans de Goede <hdegoede@redhat.com>
>   L:	linux-input@vger.kernel.org
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 0258dd879d64..a51a1a2fe7ba 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,41 @@ config IBM_RTL
>   	 state = 0 (BIOS SMIs on)
>   	 state = 1 (BIOS SMIs off)
>   
> +config LEGION_GAMEZONE_WMI
> +	tristate "Lenovo Legion GameZone WMI Driver"
> +	depends on ACPI_WMI
> +	select ACPI_PLATFORM_PROFILE
> +	help
> +	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> +	  platform-profile firmware interface.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called lenovo_legion_wmi_gamezone.
> +
> +config LEGION_DATA_01_WMI
> +	tristate "Lenovo Legion WMI capability Data 01 Driver"
> +	depends on ACPI_WMI
> +	help
> +	  Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series"
> +	  line of hardware. This interface is a dependency for exposing tunable power
> +	  settings.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called lenovo_legion_wmi_capdata01.
> +
> +config LEGION_OTHER_WMI
> +	tristate "Lenovo Legion Other Method WMI Driver"

As a new user coming here, how are they going to know what "other" 
means?  I'm sort of thinking it's better to calls this "CUSTOM_WMI"?  Or 
maybe "CUSTOM_POWER_MODES_WMI"?  Maybe Armin or others have some input 
here too.

> +	depends on LEGION_GAMEZONE_WMI
> +	depends on LEGION_DATA_01_WMI
> +	select FW_ATTR_CLASS
> +	help
> +	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> +	  firmware_attributes API to control various tunable settings typically exposed by
> +	  Lenovo software in Windows.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called lenovo_legion_wmi_other.
> +
>   config IDEAPAD_LAPTOP
>   	tristate "Lenovo IdeaPad Laptop Extras"
>   	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index e1b142947067..838ee568c3f9 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP)	+= hp/
>   obj-$(CONFIG_UV_SYSFS)       += uv_sysfs.o
>   
>   # IBM Thinkpad and Lenovo
> -obj-$(CONFIG_IBM_RTL)		+= ibm_rtl.o
> -obj-$(CONFIG_IDEAPAD_LAPTOP)	+= ideapad-laptop.o
> -obj-$(CONFIG_LENOVO_YMC)	+= lenovo-ymc.o
> -obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
> -obj-$(CONFIG_THINKPAD_ACPI)	+= thinkpad_acpi.o
> -obj-$(CONFIG_THINKPAD_LMI)	+= think-lmi.o
> -obj-$(CONFIG_YOGABOOK)		+= lenovo-yogabook.o
> -obj-$(CONFIG_YT2_1380)		+= lenovo-yoga-tab2-pro-1380-fastcharger.o
> -obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
> +obj-$(CONFIG_IBM_RTL)			+= ibm_rtl.o
> +obj-$(CONFIG_IDEAPAD_LAPTOP)		+= ideapad-laptop.o
> +obj-$(CONFIG_LENOVO_YMC)		+= lenovo-ymc.o
> +obj-$(CONFIG_SENSORS_HDAPS)		+= hdaps.o
> +obj-$(CONFIG_THINKPAD_ACPI)		+= thinkpad_acpi.o
> +obj-$(CONFIG_THINKPAD_LMI)		+= think-lmi.o
> +obj-$(CONFIG_YOGABOOK)			+= lenovo-yogabook.o
> +obj-$(CONFIG_YT2_1380)			+= lenovo-yoga-tab2-pro-1380-fastcharger.o
> +obj-$(CONFIG_LENOVO_WMI_CAMERA)		+= lenovo-wmi-camera.o
> +obj-$(CONFIG_LEGION_GAMEZONE_WMI)	+= lenovo-legion-wmi-gamezone.o
> +obj-$(CONFIG_LEGION_DATA_01_WMI)	+= lenovo-legion-wmi-capdata01.o
> +obj-$(CONFIG_LEGION_OTHER_WMI)		+= lenovo-legion-wmi-other.o

Don't change the whitespace of everything else; especially not in one 
patch.  If the whitespace is wrong, do a patch that fixes it and then 
another patch that introduces a driver.
>   
>   # Intel
>   obj-y				+= intel/
> diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
> new file mode 100644
> index 000000000000..99f4f35b7176
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
> @@ -0,0 +1,103 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides
> + * information on tunable attributes used by the "Other Method" WMI interface,
> + * including if it is supported by the hardware, the default_value, max_value,
> + * min_value, and step increment.
> + *
> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> + *

I don't think you need a newline at the end here.

> + */
> +
> +#include "lenovo-legion-wmi.h"
> +
> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +
> +static const struct wmi_device_id capdata_01_wmi_id_table[] = {
> +	{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> +	{}
> +};
> +
> +static struct capdata_wmi cd01_wmi = {
> +	.mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex)
> +};
> +
> +int capdata_01_wmi_get(struct om_attribute_id attr_id,
> +		       struct capability_data_01 *cap_data)
> +{
> +	union acpi_object *ret_obj;
> +	int count;
> +	int instance_id;
> +	u32 attribute_id = *(int *)&attr_id;

Can please do reverse xmas tree.

> +
> +	mutex_lock(&cd01_wmi.mutex);
> +	count = wmidev_instance_count(drvdata.cd01_wmi->wdev);
> +	mutex_unlock(&cd01_wmi.mutex);

For new mutex use I'd suggest using guard(mutex) instead so you can have 
less lock/unlock/cleanup cases to worry about.

> +	for (instance_id = 0; instance_id < count; instance_id++) {
> +		mutex_lock(&cd01_wmi.mutex);
> +		ret_obj =
> +			wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id);
> +		mutex_unlock(&cd01_wmi.mutex);
> +		if (!ret_obj) {
> +			pr_err("lenovo_legion_wmi_capdata_01: block query failed\n");

With all the error messages you should use #define pr_fmt() at the top 
of the file and then you don't need to do prefixes at all like this.

> +			continue;
> +		}
> +
> +		if (ret_obj->type != ACPI_TYPE_BUFFER) {
> +			pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n",
> +			       ret_obj->type);
> +			kfree(ret_obj);
> +			continue;
> +		}
> +
> +		if (ret_obj->buffer.length != sizeof(*cap_data)) {
> +			pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n",
> +			       ret_obj->buffer.length);
> +			kfree(ret_obj);
> +			continue;
> +		}
> +
> +		memcpy(cap_data, ret_obj->buffer.pointer,
> +		       ret_obj->buffer.length);
> +		kfree(ret_obj);
> +
> +		if (cap_data->id != attribute_id)
> +			continue;
> +		break;
> +	}
> +	if (cap_data->id == 0) {
> +		pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n");
> +		return -EINVAL;
> +	}
> +	return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI");
> +
> +/* Driver Setup */
> +static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context)
> +{
> +	cd01_wmi.wdev = wdev;
> +	drvdata.cd01_wmi = &cd01_wmi;
> +	pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n");
> +

Pretty noisy; no?  I think you probably should lose this message.

> +	return 0;
> +}
> +
> +static void capdata_01_wmi_remove(struct wmi_device *wdev)
> +{
> +	pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n");

Pretty noisy; no?  I think you probably should lose this message.

> +}
> +
> +static struct wmi_driver capdata_01_wmi_driver = {
> +	.driver = { .name = "capdata_01_wmi" },
> +	.id_table = capdata_01_wmi_id_table,
> +	.probe = capdata_01_wmi_probe,
> +	.remove = capdata_01_wmi_remove,
> +};
> +
> +module_wmi_driver(capdata_01_wmi_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
> new file mode 100644
> index 000000000000..2f976dc0e367
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
> @@ -0,0 +1,233 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides
> + * platform profile and fan curve settings for devices that fall under the
> + * "Gaming Series" of Lenovo Legion devices.
> + *
> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
Drop newline here
> + */
> +
> +#include "lenovo-legion-wmi.h"
> +
> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> +
> +/* Method IDs */
> +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */
> +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */
> +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */
> +
> +static const struct wmi_device_id gamezone_wmi_id_table[] = {
> +	{ LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
> +	{}
> +};
> +
> +static struct gamezone_wmi gz_wmi = {
> +	.mutex = __MUTEX_INITIALIZER(gz_wmi.mutex)
> +};
> +
> +/* Platform Profile Methods */
> +static int
> +gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof,
> +					int *supported)
> +{
> +	int ret;
> +
> +	mutex_lock(&gz_wmi.mutex);

I'd use guard(mutex) instead.  By doing that your function becomes a lot 
simpler too.

guard(mutex)(&gz_wmi.mutex);

return lenovo_legion_evaluate_method_1();

> +	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
> +					      WMI_METHOD_ID_SMARTFAN_SUPP, 0,
> +					      supported);
> +	mutex_unlock(&gz_wmi.mutex);
> +	return ret;
> +}
> +
> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
> +				 int *sel_prof)
> +{
> +	int ret;
> +	int supported;
> +
> +	gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
> +						&supported);
> +	if (!supported) {
> +		pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");

Is this error flow real?  I sort of expect that you can avoid 
registering if not supporting it.

> +		return -EOPNOTSUPP;
> +	}
> +	mutex_lock(&gz_wmi.mutex);

guard(mutex) here too.

> +	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
> +					      WMI_METHOD_ID_SMARTFAN_GET, 0,
> +					      sel_prof);
> +	mutex_unlock(&gz_wmi.mutex);
> +	return ret;
> +}
> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI");
> +
> +static int
> +gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof,
> +				  enum platform_profile_option *profile)
> +{
> +	int sel_prof;
> +	int err;
> +
> +	err = gamezone_wmi_fan_profile_get(pprof, &sel_prof);
> +	if (err)
> +		return err;
> +
> +	switch (sel_prof) {
> +	case SMARTFAN_MODE_QUIET:
> +		*profile = PLATFORM_PROFILE_QUIET;
> +		break;
> +	case SMARTFAN_MODE_BALANCED:
> +		*profile = PLATFORM_PROFILE_BALANCED;
> +		break;
> +	case SMARTFAN_MODE_PERFORMANCE:
> +		*profile = PLATFORM_PROFILE_PERFORMANCE;
> +		break;
> +	case SMARTFAN_MODE_CUSTOM:
> +		*profile = PLATFORM_PROFILE_CUSTOM;
> +		break;
> +
Spurious newline.

> +	default:
> +		return -EINVAL;
> +	}
> +	drvdata.gz_wmi->current_profile = *profile;
> +
> +	return 0;
> +}
> +
> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
> +				      enum platform_profile_option profile)
> +{
> +	int ret;
> +	int sel_prof;
> +	int supported;
> +
> +	gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
> +						&supported);
> +	if (!supported) {
> +		pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
> +		return -EOPNOTSUPP;
> +	}

Same question; is this a real error flow?

> +
> +	switch (profile) {
> +	case PLATFORM_PROFILE_QUIET:
> +		sel_prof = SMARTFAN_MODE_QUIET;
> +		break;
> +	case PLATFORM_PROFILE_BALANCED:
> +		sel_prof = SMARTFAN_MODE_BALANCED;
> +		break;
> +	case PLATFORM_PROFILE_PERFORMANCE:
> +		sel_prof = SMARTFAN_MODE_PERFORMANCE;
> +		break;
> +	case PLATFORM_PROFILE_CUSTOM:
> +		sel_prof = SMARTFAN_MODE_CUSTOM;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	mutex_lock(&gz_wmi.mutex);
guard(mutex) here.
> +	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
> +					      WMI_METHOD_ID_SMARTFAN_SET,
> +					      sel_prof, NULL);
> +	mutex_unlock(&gz_wmi.mutex);
> +
> +	if (ret) {
> +		pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n");
> +		return ret;
> +	}
> +
> +	drvdata.gz_wmi->current_profile = profile;
> +	return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI");
> +
> +/* Driver Setup */
> +static int platform_profile_setup(struct gamezone_wmi *gz_wmi)
> +{
> +	int err;
> +	int supported;
> +
> +	gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported);
> +
> +	gz_wmi->platform_profile_support = supported;
> +
> +	if (!supported) {
> +		pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
> +		return -EOPNOTSUPP;
> +	}

Yeah because of this you don't need that other flow I was mentioning above.

IMO I don't think the pr_warn() is really needed, you'll only really 
have one way that you exit -EOPNOTSUPP.

> +
> +	gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get;
> +	gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set;
> +
> +	set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices);
> +	set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices);
> +	set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices);
> +	set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices);
> +
> +	err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof,
> +						&gz_wmi->current_profile);
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n",
> +		       err);

Drop prefix on the error and use pr_fmt().

> +		return err;
> +	}
> +
> +	err = platform_profile_register(&gz_wmi->pprof);
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n",
> +		       err);

Drop prefix on the error and use pr_fmt().

> +		return err;
> +	}
> +
> +	return 0;
> +}
> +
> +static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context)
> +{
> +	int err;
> +
> +	gz_wmi.wdev = wdev;
> +	drvdata.gz_wmi = &gz_wmi;
> +
> +	err = platform_profile_setup(&gz_wmi);
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n",
> +		       err);
> +		kfree(&gz_wmi);

Is this free correct?  It's a global isn't it?  I don't think you should 
be freeing here.

> +		return err;
> +	}
> +
> +	pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n");

Too noisy.

> +	return 0;
> +}
> +
> +static void gamezone_wmi_remove(struct wmi_device *wdev)
> +{
> +	int err;
> +
> +	mutex_lock(&gz_wmi.mutex);
> +	err = platform_profile_remove(&drvdata.gz_wmi->pprof);
> +	mutex_unlock(&gz_wmi.mutex);
> +
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n",
> +		       err);
> +	} else {
> +		pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n");
> +	}

Considering that platform_profile_remove() doesn't really have a failure 
path (it always returns 0). I'd just lose both of these messages and 
make this simple.

guard(mutex)();
platform_profile_remove();

> +}
> +
> +static struct wmi_driver gamezone_wmi_driver = {
> +	.driver = { .name = "gamezone_wmi" },
> +	.id_table = gamezone_wmi_id_table,
> +	.probe = gamezone_wmi_probe,
> +	.remove = gamezone_wmi_remove,
> +};
> +
> +module_wmi_driver(gamezone_wmi_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c
> new file mode 100644
> index 000000000000..c09c1848eda7
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-legion-wmi-other.c
> @@ -0,0 +1,377 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Legion Other Method driver. This driver uses the fw_attributes
> + * class to expose the various WMI functions provided by the "Other Method" WMI
> + * interface. This enables CPU and GPU power limit as well as various other
> + * attributes for devices that fall under the "Gaming Series" of Lenovo Legion
> + * devices. Each attribute exposed by the "Other Method"" interface has a
> + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to
> + * probe details about the attribute such as set/get support, step, min, max,
> + * and default value. Each attibute has multiple pages, one for each of the
> + * fan profiles managed by the GameZone interface, so it must be probed prior
> + * to returning the current_value.
> + *
> + * These attributes typically don't fit anywhere else in the sysfs and are set
> + * in Windows using one of Lenovo's multiple user applications.
> + *
> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
Remove the new line here.
> + */
> +
> +#include "lenovo-legion-wmi.h"
> +#include "firmware_attributes_class.h"
> +
> +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> +
> +/* Device IDs */
> +#define WMI_DEVICE_ID_CPU 0x01
> +
> +/* WMI_DEVICE_ID_CPU feature IDs */
> +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */
> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */
> +
> +/* Method IDs */
> +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */
> +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */
> +
> +static const struct wmi_device_id other_method_wmi_id_table[] = {
> +	{ LENOVO_OTHER_METHOD_GUID, NULL },
> +	{}
> +};
> +
> +/* Tunable Attributes */
> +struct ll_tunables {
> +	u32 ppt_pl1_spl;
> +	u32 ppt_pl2_sppt;
> +	u32 ppt_pl3_fppt;
> +};
> +
> +static const struct class *fw_attr_class;
> +
> +static struct other_method_wmi om_wmi = {
> +	.mutex = __MUTEX_INITIALIZER(om_wmi.mutex)
> +};
> +
> +struct capdata_01_attr_group {
> +	const struct attribute_group *attr_group;
> +};
> +
> +/* Simple attribute creation */
> +
> +/*
> + * att_current_value_store() - Set the current value of the given attribute
> + * @kobj: Pointer to the driver object.
> + * @kobj_attribute: Pointer to the attribute calling this function.
> + * @buf: The buffer to read from, this is parsed to `int` type.
> + * @count: Required by sysfs attribute macros, pass in from the callee attr.
> + * @store_value: Pointer to where the parsed value should be stored.
> + * @device_id: The WMI function Device ID to use.
> + * @feature_id: The WMI function Feature ID to use.
> + *
> + * This function is intended to be generic so it can be called from any
> + * attribute's "current_value_store" which works only with integers. The
> + * integer to be sent to the WMI method is range checked and an error returned
> + * if out of range.
> + *
> + * If the value is valid and WMI is success, then the sysfs attribute is
> + * notified.
> + *
> + * Returns: Either count, or an error.
> + */
> +ssize_t attr_current_value_store(struct kobject *kobj,
> +				 struct kobj_attribute *attr, const char *buf,
> +				 size_t count, u32 *store_value, u8 device_id,
> +				 u8 feature_id)
> +{
> +	struct capability_data_01 cap_data;
> +	enum platform_profile_option cust_prof;
> +	int err;
> +	int sel_prof;
> +	u32 value;
> +	struct wmi_device *wdev = drvdata.om_wmi->wdev;
> +
> +	err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");

Use pr_fmt() for teh file instead of prefix here.

> +		return -EIO;
> +	}
> +
> +	/* Switch to custom profile if not currently on it. */
> +	if (sel_prof != SMARTFAN_MODE_CUSTOM) {
> +		pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables.");

As you do this "for" them, I'd lose the warning.

> +		cust_prof = PLATFORM_PROFILE_CUSTOM;
> +		sel_prof = SMARTFAN_MODE_CUSTOM;
> +		err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof,
> +							cust_prof);
> +		if (err) {
> +			pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n");
> +			return -EIO;
> +		}
> +	}
> +
> +	err = kstrtouint(buf, 10, &value);
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_other: Error converting value to int.\n");
> +		return -EIO;
> +	}
> +
> +	/* Construct the attribute id */
> +	struct om_attribute_id attr_id = { sel_prof << 8, feature_id,
> +					   device_id };
> +
> +	/* Get min/max from LENOVO_CAPABILITY_DATA_01 */
> +	err = capdata_01_wmi_get(attr_id, &cap_data);
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
> +		return -EIO;
> +	}
> +	if (cap_data.capability < 1) {
> +		pr_err("lenovo_legion_wmi_other: Capability not supported.\n");
> +		return -EPERM;
> +	}
> +
> +	if (value < cap_data.min_value || value > cap_data.max_value) {
> +		pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n",
> +			value, cap_data.min_value, cap_data.max_value);
> +		return -EINVAL;
> +	}
> +
> +	mutex_lock(&om_wmi.mutex);
> +	err = lenovo_legion_evaluate_method_2(wdev, 0x0,
> +					      WMI_METHOD_ID_VALUE_SET,
> +					      *(int *)&attr_id, value, NULL);
> +	mutex_unlock(&om_wmi.mutex);
> +
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_other: Error setting attribute");
> +		return err;
> +	}
> +
> +	if (store_value)
> +		*store_value = value;
> +
> +	sysfs_notify(kobj, NULL, attr->attr.name);
> +	return count;
> +}
> +
> +/*
> + * attr_current_value_show() - Get the current value of the given attribute
> + * @kobj: Pointer to the driver object.
> + * @kobj_attribute: Pointer to the attribute calling this function.
> + * @buf: The buffer to write to.
> + * @retval: Pointer to returned data.
> + * @device_id: The WMI function Device ID to use.
> + * @feature_id: The WMI function Feature ID to use.
> + *
> + * This function is intended to be generic so it can be called from any "_show"
> + * attribute which works only with integers.
> + *
> + * If the WMI is success, then the sysfs attribute is notified.
> + *
> + * Returns: Either count, or an error.
> + */
> +ssize_t attr_current_value_show(struct kobject *kobj,
> +				struct kobj_attribute *attr, char *buf,
> +				u8 device_id, u8 feature_id)
> +{
> +	int sel_prof; /* Current fan profile mode */
> +	int err;
> +	int retval;
> +	struct wmi_device *wdev = drvdata.om_wmi->wdev;
> +
> +	err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
> +
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");
> +		return err;
> +	}
> +
> +	// Construct the WMI attribute id from the given args.
> +	struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
> +						device_id };
> +
> +	mutex_lock(&om_wmi.mutex);
> +	err = lenovo_legion_evaluate_method_1(wdev, 0x0,
> +					      WMI_METHOD_ID_VALUE_GET,
> +					      *(int *)&attribute_id, &retval);
> +	mutex_unlock(&om_wmi.mutex);
> +
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_other: Error getting attribute");
> +		return err;
> +	}
> +
> +	return sysfs_emit(buf, "%u\n", retval);
> +}
> +
> +/**
> + * attr_capdata_01_show() - Get the value of the specified attribute property
> + * from LENOVO_CAPABILITY_DATA_01.
> + * @kobj: Pointer to the driver object.
> + * @kobj_attribute: Pointer to the attribute calling this function.
> + * @buf: The buffer to write to.
> + * @retval: Pointer to returned data.
> + * @device_id: The WMI functions Device ID to use.
> + * @feature_id: The WMI functions Feature ID to use.
> + * @prop: The property of this attribute to be read.
> + *
> + * This function is intended to be generic so it can be called from any "_show"
> + * attribute which works only with integers.
> + *
> + * If the WMI is success, then the sysfs attribute is notified.
> + *
> + * Returns: Either count, or an error.
> + */
> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
> +			     char *buf, u8 device_id, u8 feature_id,
> +			     enum attribute_property prop)
> +{
> +	struct capability_data_01 cap_data;
> +	int err;
> +	int retval;
> +	int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */
> +
> +	// Construct the WMI attribute id from the given args.
> +	struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
> +						device_id };
> +
> +	err = capdata_01_wmi_get(attribute_id, &cap_data);
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
> +		return -EIO;
> +	}
> +
> +	switch (prop) {
> +	case DEFAULT_VAL:
> +		retval = cap_data.default_value;
> +		break;
> +	case MAX_VAL:
> +		retval = cap_data.max_value;
> +		break;
> +	case MIN_VAL:
> +		retval = cap_data.min_value;
> +		break;
> +	case STEP_VAL:
> +		retval = cap_data.step;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return sysfs_emit(buf, "%u\n", retval);
> +}
> +
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU,
> +			    WMI_FEATURE_ID_CPU_SPL,
> +			    "Set the CPU sustained power limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU,
> +			    WMI_FEATURE_ID_CPU_SPPT,
> +			    "Set the CPU slow package power tracking limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU,
> +			    WMI_FEATURE_ID_CPU_FPPT,
> +			    "Set the CPU fast package power tracking limit");
> +
> +static const struct capdata_01_attr_group capdata_01_attr_groups[] = {
> +	{ &ppt_pl1_spl_attr_group },
> +	{ &ppt_pl2_sppt_attr_group },
> +	{ &ppt_pl3_fppt_attr_group },
> +	{},
> +};
> +
> +static int other_method_fw_attr_add(void)
> +{
> +	int err, i;
> +
> +	err = fw_attributes_class_get(&fw_attr_class);
> +	if (err) {
> +		pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n");
> +		return err;
> +	}
> +
> +	om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> +					   NULL, "%s", DRIVER_NAME);
> +	if (IS_ERR(om_wmi.fw_attr_dev)) {
> +		pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n");
> +		err = PTR_ERR(om_wmi.fw_attr_dev);
> +		goto fail_class_get;
> +	}
> +
> +	om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL,
> +						  &om_wmi.fw_attr_dev->kobj);
> +	if (!om_wmi.fw_attr_kset) {
> +		pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n");
> +		err = -ENOMEM;
> +		goto err_destroy_classdev;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) {
> +		err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj,
> +					 capdata_01_attr_groups[i].attr_group);
> +		if (err) {
> +			pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n",
> +			       capdata_01_attr_groups[i].attr_group->name);
> +			goto err_remove_groups;
> +		}
> +	}
> +
> +	return 0;
> +
> +err_remove_groups:
> +	while (--i >= 0) {
> +		sysfs_remove_group(&om_wmi.fw_attr_kset->kobj,
> +				   capdata_01_attr_groups[i].attr_group);
> +	}
> +err_destroy_classdev:
> +	device_destroy(fw_attr_class, MKDEV(0, 0));
> +fail_class_get:
> +	fw_attributes_class_put();
> +	return err;
> +}
> +
> +/* Driver Setup */
> +static int other_method_wmi_probe(struct wmi_device *wdev, const void *context)
> +{
> +	int err;
> +
> +	om_wmi.wdev = wdev;
> +	drvdata.om_wmi = &om_wmi;
> +	om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL);
> +	if (!om_wmi.ll_tunables)
> +		return -ENOMEM;
> +
> +	err = other_method_fw_attr_add();
> +	if (err)
> +		return err;
> +	pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n");

too noisy

> +
> +	return 0;
> +}
> +
> +static void other_method_wmi_remove(struct wmi_device *wdev)
> +{
> +	mutex_lock(&om_wmi.mutex);
> +
> +	kset_unregister(om_wmi.fw_attr_kset);
> +	device_destroy(fw_attr_class, MKDEV(0, 0));
> +	fw_attributes_class_put();
> +
> +	mutex_unlock(&om_wmi.mutex);
> +
> +	pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n");
too noisy.

> +}
> +
> +static struct wmi_driver other_method_wmi_driver = {
> +	.driver = { .name = "other_method_wmi" },
> +	.id_table = other_method_wmi_id_table,
> +	.probe = other_method_wmi_probe,
> +	.remove = other_method_wmi_remove,
> +};
> +
> +module_wmi_driver(other_method_wmi_driver);
> +
> +MODULE_IMPORT_NS("GZ_WMI");
> +MODULE_IMPORT_NS("CAPDATA_WMI");
> +MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Legion Other Method Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h
> new file mode 100644
> index 000000000000..65baa728f29e
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-legion-wmi.h
> @@ -0,0 +1,271 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is
> + * broken up into multiple GUID interfaces that require cross-references
> + * between GUID's for some functionality. The "Custom Mode" interface is a
> + * legacy interface for managing and displaying CPU & GPU power and hwmon
> + * settings and readings. The "Other Mode" interface is a modern interface
> + * that replaces or extends the "Custom Mode" interface methods. The "GameZone"
> + * interface adds advanced features such as fan profiles and overclocking.
> + * The "Lighting" interface adds control of various status lights related to
> + * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00
> + * struct for capability information, "Other Mode" uses
> + * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting"
> + * uses LENOVO_CAPABILITY_DATA_02 struct for capability information.
> + *
> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
Lose the newline

> + */
> +
> +#ifndef _LENOVO_LEGION_WMI_H_
> +#define _LENOVO_LEGION_WMI_H_
> +
> +#include <linux/mutex.h>
> +#include <linux/platform_profile.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +#define DRIVER_NAME "lenovo-legion-wmi"

This is only used in one of the drivers, I'd move it there to make it 
clearer.

> +
> +/* Platform Profile Modes */
> +#define SMARTFAN_MODE_QUIET 0x01
> +#define SMARTFAN_MODE_BALANCED 0x02
> +#define SMARTFAN_MODE_PERFORMANCE 0x03
> +#define SMARTFAN_MODE_CUSTOM 0xFF
> +
> +struct gamezone_wmi {
> +	struct wmi_device *wdev;
> +	enum platform_profile_option current_profile;
> +	struct platform_profile_handler pprof;
> +	bool platform_profile_support;
> +	struct mutex mutex; /* Ensure single operation on WMI device */
> +};
> +
> +struct other_method_wmi {
> +	struct wmi_device *wdev;
> +	struct device *fw_attr_dev;
> +	struct kset *fw_attr_kset;
> +	struct ll_tunables *ll_tunables;
> +	struct mutex mutex; /* Ensure single operation on WMI device */
> +};
> +
> +struct capdata_wmi {
> +	struct wmi_device *wdev;
> +	struct mutex mutex; /* Ensure single operation on WMI device */
> +};
> +
> +struct ll_drvdata {
> +	struct other_method_wmi *om_wmi; /* Other method GUID device */
> +	struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */
> +	struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */
> +} drvdata;
> +
> +struct wmi_method_args {
> +	u32 arg0;
> +	u32 arg1;
> +};
> +
> +struct om_attribute_id {
> +	u32 mode_id : 16; /* Fan profile */
> +	u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
> +	u32 device_id : 8; /* CPU/GPU */
> +} __packed;
> +
> +enum attribute_property {
> +	DEFAULT_VAL = 0,
> +	MAX_VAL,
> +	MIN_VAL,
> +	STEP_VAL,
> +	SUPPORTED,
> +};
> +
> +struct capability_data_01 {
> +	u32 id;
> +	u32 capability;
> +	u32 default_value;
> +	u32 step;
> +	u32 min_value;
> +	u32 max_value;
> +};
> +
> +static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance,
> +					 u32 method_id, struct acpi_buffer *in,
> +					 struct acpi_buffer *out)
> +{
> +	acpi_status status;
> +
> +	status = wmidev_evaluate_method(wdev, instance, method_id, in, out);
> +
> +	if (ACPI_FAILURE(status)) {
> +		pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n",
> +		       method_id, instance);
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> +				    u32 method_id, u32 arg0, u32 arg1,
> +				    u32 *retval);
> +
> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> +				    u32 method_id, u32 arg0, u32 arg1,
> +				    u32 *retval)
> +{
> +	int ret;
> +	u32 temp_val;
> +	struct wmi_method_args args = { arg0, arg1 };
> +	struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> +	union acpi_object *ret_obj = NULL;

Reverse xmas tree please.

> +
> +	ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input,
> +					    &output);
> +
> +	if (ret) {
> +		pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n",
> +		       method_id, ret);
> +		return ret;
> +	}
> +
> +	if (retval) {
> +		ret_obj = (union acpi_object *)output.pointer;
> +		if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER)
> +			temp_val = (u32)ret_obj->integer.value;

This is a pretty bad failure if it's not the case, no?  Should you set a 
return value here instead perhaps?

> +
> +		*retval = temp_val

If that above error I mentioned happens then you'll be assigning garbage 
data out.

;
> +	}
> +
> +	kfree(ret_obj);
> +
> +	return 0;
> +}
> +
> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> +				    u32 method_id, u32 arg0, u32 *retval);
> +
> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> +				    u32 method_id, u32 arg0, u32 *retval)
> +{
> +	return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0,
> +					       0, retval);
> +}
> +
> +int capdata_01_wmi_get(struct om_attribute_id attr_id,
> +		       struct capability_data_01 *cap_data);
> +
> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
> +				      enum platform_profile_option sel_prof);
> +
> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
> +				 int *sel_prof);
> +
> +/* current_value */
> +ssize_t attr_current_value_store(struct kobject *kobj,
> +				 struct kobj_attribute *attr, const char *buf,
> +				 size_t count, u32 *store_value, u8 device_id,
> +				 u8 feature_id);
> +
> +ssize_t attr_current_value_show(struct kobject *kobj,
> +				struct kobj_attribute *attr, char *buf,
> +				u8 device_id, u8 feature_id);
> +
> +/* LENOVO_CAPABILITY_DATA_01 */
> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
> +			     char *buf, u8 device_id, u8 feature_id,
> +			     enum attribute_property prop);
> +
> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
> +		      char *buf);
> +
> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
> +		      char *buf)
> +{
> +	return sysfs_emit(buf, "integer\n");
> +}
> +
> +#define __LL_ATTR_RO(_func, _name)                                    \
> +	{                                                             \
> +		.attr = { .name = __stringify(_name), .mode = 0444 }, \
> +		.show = _func##_##_name##_show,                       \
> +	}
> +
> +#define __LL_ATTR_RO_AS(_name, _show)                                 \
> +	{                                                             \
> +		.attr = { .name = __stringify(_name), .mode = 0444 }, \
> +		.show = _show,                                        \
> +	}
> +
> +#define __LL_ATTR_RW(_func, _name) \
> +	__ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
> +
> +/* Shows a formatted static variable */
> +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val)                         \
> +	static ssize_t _attrname##_##_prop##_show(                            \
> +		struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> +	{                                                                     \
> +		return sysfs_emit(buf, _fmt, _val);                           \
> +	}                                                                     \
> +	static struct kobj_attribute attr_##_attrname##_##_prop =             \
> +		__LL_ATTR_RO(_attrname, _prop)
> +
> +/* Attribute current_value show/store */
> +#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id)                   \
> +	static ssize_t _attrname##_current_value_store(                       \
> +		struct kobject *kobj, struct kobj_attribute *attr,            \
> +		const char *buf, size_t count)                                \
> +	{                                                                     \
> +		return attr_current_value_store(                              \
> +			kobj, attr, buf, count,                               \
> +			&om_wmi.ll_tunables->_attrname, _dev_id, _feat_id);   \
> +	}                                                                     \
> +	static ssize_t _attrname##_current_value_show(                        \
> +		struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> +	{                                                                     \
> +		return attr_current_value_show(kobj, attr, buf, _dev_id,      \
> +					       _feat_id);                     \
> +	}                                                                     \
> +	static struct kobj_attribute attr_##_attrname##_current_value =       \
> +		__LL_ATTR_RW(_attrname, current_value)
> +
> +/* Attribute property show only */
> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \
> +	static ssize_t _attrname##_##_prop##_show(                             \
> +		struct kobject *kobj, struct kobj_attribute *attr, char *buf)  \
> +	{                                                                      \
> +		return attr_capdata_01_show(kobj, attr, buf, _dev_id,          \
> +					    _feat_id, _prop_type);             \
> +	}                                                                      \
> +	static struct kobj_attribute attr_##_attrname##_##_prop =              \
> +		__LL_ATTR_RO(_attrname, _prop)
> +
> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id,    \
> +				    _dispname)                                \
> +	__LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id);                  \
> +	__LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id,    \
> +			      DEFAULT_VAL);                                   \
> +	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);          \
> +	__LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id,        \
> +			      MAX_VAL);                                       \
> +	__LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id,        \
> +			      MIN_VAL);                                       \
> +	__LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \
> +			      STEP_VAL);                                      \
> +	static struct kobj_attribute attr_##_attrname##_type =                \
> +		__LL_ATTR_RO_AS(type, int_type_show);                         \
> +	static struct attribute *_attrname##_attrs[] = {                      \
> +		&attr_##_attrname##_current_value.attr,                       \
> +		&attr_##_attrname##_default_value.attr,                       \
> +		&attr_##_attrname##_display_name.attr,                        \
> +		&attr_##_attrname##_max_value.attr,                           \
> +		&attr_##_attrname##_min_value.attr,                           \
> +		&attr_##_attrname##_scalar_increment.attr,                    \
> +		&attr_##_attrname##_type.attr,                                \
> +		NULL,                                                         \
> +	};                                                                    \
> +	static const struct attribute_group _attrname##_attr_group = {        \
> +		.name = _fsname, .attrs = _attrname##_attrs                   \
> +	}
> +
> +#endif /* !_LENOVO_LEGION_WMI_H_ */


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-18  2:39   ` Mario Limonciello
@ 2024-12-18  3:36     ` Derek J. Clark
  2024-12-22 22:55       ` Armin Wolf
  0 siblings, 1 reply; 29+ messages in thread
From: Derek J. Clark @ 2024-12-18  3:36 UTC (permalink / raw)
  To: Mario Limonciello, Hans de Goede
  Cc: Ilpo Järvinen, Jonathan Corbet, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Derek J . Clark, platform-driver-x86, linux-doc, linux-kernel

Hi Mario,

Thank you for taking a look at it so quickly.

>> Adds lenovo-legion-wmi.h which provides templates and some method
>> implementations used by the lenovo-legion-wmi driver series.
>> 
>> Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo
>> GameZone WMI interface that comes on Lenovo "Gaming Series" hardware.
>> Provides ACPI platform profiles over WMI.
>> 
>> Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo
>> "Other Method" WMI interface that comes on some Lenovo hardware.
>> Provides a firmware-attributes class which enables the use of tunable
>> knobs for SPL, SPPT, and FPPT.
>> 
>> Adds lenovo-legion-wmi-capdata01.c which provides a driver for the
>> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method"
>> enabled hardware. Provides an interface for querying if a given
>> attribute is supported by the hardware, as well as its default_value,
>> max_value, min_value, and step increment.
>> 
z>> Adds lenovo-legion-wmi.rst describing the available drivers and their
>> function.
>> 
>> Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers.
>> 
>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>
>Hi Derek,
>
>As a high level first comment; "larger" patches are much harder to review.
>
>It seems that the drivers are logically split as described in your 
>commit message already.  For the next version could you split at least 
>each driver to it's own patch?
>
>It might also make sense to split up the individual drivers along 
>"features".

Can do. It might still make sense to have capdata01 with the other_method
driver, it has no functionality on its own and is a dependency of other_method.
It isn't a problem to have capdata01 as an earlier patch than other_method
though if preferred. I'm not sure how I would break up the drivers further
than that, except the relevant header portions per c file.

>This is my own personal opinion and not a requirement but I personally 
>like to see documentation for something new like this as it's own patch 
>at the beginning of the series so we can make sure everyone understands 
>and agrees on the design as they review the series and then can make 
>sure that the implementation matches the design as the other patches are 
>reviewed.

Acked, will add Documentation as its own 1/ patch.

>I've got various other comments sprinkled throughout the patch, please 
>see them.  I'm not 100% sure on the mutex use yet, we should review that 
>after you've got all the cleanups needed done.
>
>> ---
>>   .../wmi/devices/lenovo-legion-wmi.rst         |  79 ++++
>>   MAINTAINERS                                   |   9 +
>>   drivers/platform/x86/Kconfig                  |  35 ++
>>   drivers/platform/x86/Makefile                 |  21 +-
>>   .../x86/lenovo-legion-wmi-capdata01.c         | 103 +++++
>>   .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++
>>   .../platform/x86/lenovo-legion-wmi-other.c    | 377 ++++++++++++++++++
>>   drivers/platform/x86/lenovo-legion-wmi.h      | 271 +++++++++++++
>>   8 files changed, 1119 insertions(+), 9 deletions(-)
>>   create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst
>>   create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>>   create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>>   create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c
>>   create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h
>> 
>> diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst
>> new file mode 100644
>> index 000000000000..37b09c82c980
>> --- /dev/null
>> +++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst
>> @@ -0,0 +1,79 @@
>> +.. SPDX-License-Identifier: GPL-2.0-or-later
>> +======================================================
>> +Lenovo Legion WMI Interface Driver (lenovo-legion-wmi)
>> +======================================================
>> +
>> +Introduction
>> +============
>> +The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that
>> +require cross-references between GUID's for some functionality. The "Custom
>> +Mode" interface is a legacy interface for managing and displaying CPU & GPU
>> +power and hwmon settings and readings. The "Other Mode" interface is a modern
>> +interface that replaces "Custom Mode" interface methods. The "GameZone"
>> +interface adds advanced features such as fan profiles and overclocking. The
>> +"Lighting" interface adds control of various status lights related to different
>> +hardware components.
>> +
>> +Each of these interfaces has a different data structure associated with it that
>> +provide detailed information about each attribute provided by the interface.
>> +These data structs are retrieved from an additional WMI device data block GUID:
>> + - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00
>> + - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01
>> + - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02
>> +
>> +.. note::
>> +   Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01
>> +   interfaces are implemented by this driver.
>
>So this is to say that lighting interface is not implemented right now, 
>right?

Custom Mode, Lighting, LENOVO_CAPABILITY_DATA_00, and LENOVO_CAPABILITY_DATA_02
are not implemented yet. For now Lenovo are okay with that but may want more
later.

>> +
>> +
>> +GameZone
>> +--------
>> +The GameZone WMI interface provides ACPI platform profile and fan curve
>> +settings for devices that fall under the "Gaming Series" of Lenovo Legion
>> +devices.
>> +
>> +The following platform profiles are supported:
>> + - quiet
>> + - balanced
>> + - performance
>> + - custom
>> +
>> +Custom Profile
>> +~~~~~~~~~~~~~~
>> +The custom profile is enabled but is not user selectable. This setting
>> +represents a hardware mode on Lenovo Legion devices that enables user
>> +modifications to Package Power Tracking settings. When an attribute exposed
>> +by the "Other Mode" WMI Interface is modified, the GameZone driver will switch
>> +to this profile automatically.
>
>I think you should explicitly mention that it's undone if the user 
>selects a fixed platform mode too.  (It does, right?)

It does as a BIOS feature, acked for fix.

>> +
>> +
>> +Other Mode
>> +----------
>> +The Other Mode WMI interface uses the fw_attributes class to expose various
>> +WMI functions provided by the interface in the sysfs. This enables CPU and GPU
>> +power limit tuning as well as various other attributes for devices that fall
>> +under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by
>> +the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages
>> +that allow the driver to probe details about the attribute. Each attibute has
>> +multiple pages, one for each of the platform profiles managed by the "GameZone"
>> +interface, so it must be probed prior to returning the current_value. For
>> +read-only properties, only the "Custom" profile values are reported to ensure
>> +any userspace applications reading them have accurate tunable value ranges.
>> +Attributes are exposed in sysfs under the following path:
>> +/sys/class/firmware-attributes/lenovo-legion-wmi/attributes
>> +
>> +Supported Attibutes
>> +~~~~~~~~~~~~~~~~~~~
>> +The following attributes are supported:
>> + - 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
>> +
>> +Each attribute has the following properties:
>> + - current_value
>> + - default_value
>> + - display_name
>> + - max_value
>> + - min_value
>> + - scalar_increment
>> + - type
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index baf0eeb9a355..67f7b588aa36 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -13034,6 +13034,15 @@ S:	Maintained
>>   W:	http://legousb.sourceforge.net/
>>   F:	drivers/usb/misc/legousbtower.c
>>   
>> +LENOVO LEGION WMI driver
>> +M:	Derek J. Clark <derekjohn.clark@gmail.com>
>> +L:	platform-driver-x86@vger.kernel.org
>> +S:	Maintained
>> +F:	drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>> +F:	drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>> +F:	drivers/platform/x86/lenovo-legion-wmi-other.c
>> +F:	drivers/platform/x86/lenovo-legion-wmi.h
>> +
>>   LETSKETCH HID TABLET DRIVER
>>   M:	Hans de Goede <hdegoede@redhat.com>
>>   L:	linux-input@vger.kernel.org
>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>> index 0258dd879d64..a51a1a2fe7ba 100644
>> --- a/drivers/platform/x86/Kconfig
>> +++ b/drivers/platform/x86/Kconfig
>> @@ -459,6 +459,41 @@ config IBM_RTL
>>   	 state = 0 (BIOS SMIs on)
>>   	 state = 1 (BIOS SMIs off)
>>   
>> +config LEGION_GAMEZONE_WMI
>> +	tristate "Lenovo Legion GameZone WMI Driver"
>> +	depends on ACPI_WMI
>> +	select ACPI_PLATFORM_PROFILE
>> +	help
>> +	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
>> +	  platform-profile firmware interface.
>> +
>> +	  To compile this driver as a module, choose M here: the module will
>> +	  be called lenovo_legion_wmi_gamezone.
>> +
>> +config LEGION_DATA_01_WMI
>> +	tristate "Lenovo Legion WMI capability Data 01 Driver"
>> +	depends on ACPI_WMI
>> +	help
>> +	  Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series"
>> +	  line of hardware. This interface is a dependency for exposing tunable power
>> +	  settings.
>> +
>> +	  To compile this driver as a module, choose M here: the module will
>> +	  be called lenovo_legion_wmi_capdata01.
>> +
>> +config LEGION_OTHER_WMI
>> +	tristate "Lenovo Legion Other Method WMI Driver"
>
>As a new user coming here, how are they going to know what "other" 
>means?  I'm sort of thinking it's better to calls this "CUSTOM_WMI"?  Or 
>maybe "CUSTOM_POWER_MODES_WMI"?  Maybe Armin or others have some input 
>here too.

Other Method is the name Lenovo gave the interface. I'm open to suggestions,
but Custom Method is the name of the older Legion WMI interface so I'd like to
reserve that in case Lenovo wants to add it later.

>> +	depends on LEGION_GAMEZONE_WMI
>> +	depends on LEGION_DATA_01_WMI
>> +	select FW_ATTR_CLASS
>> +	help
>> +	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
>> +	  firmware_attributes API to control various tunable settings typically exposed by
>> +	  Lenovo software in Windows.
>> +
>> +	  To compile this driver as a module, choose M here: the module will
>> +	  be called lenovo_legion_wmi_other.
>> +
>>   config IDEAPAD_LAPTOP
>>   	tristate "Lenovo IdeaPad Laptop Extras"
>>   	depends on ACPI
>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>> index e1b142947067..838ee568c3f9 100644
>> --- a/drivers/platform/x86/Makefile
>> +++ b/drivers/platform/x86/Makefile
>> @@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP)	+= hp/
>>   obj-$(CONFIG_UV_SYSFS)       += uv_sysfs.o
>>   
>>   # IBM Thinkpad and Lenovo
>> -obj-$(CONFIG_IBM_RTL)		+= ibm_rtl.o
>> -obj-$(CONFIG_IDEAPAD_LAPTOP)	+= ideapad-laptop.o
>> -obj-$(CONFIG_LENOVO_YMC)	+= lenovo-ymc.o
>> -obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
>> -obj-$(CONFIG_THINKPAD_ACPI)	+= thinkpad_acpi.o
>> -obj-$(CONFIG_THINKPAD_LMI)	+= think-lmi.o
>> -obj-$(CONFIG_YOGABOOK)		+= lenovo-yogabook.o
>> -obj-$(CONFIG_YT2_1380)		+= lenovo-yoga-tab2-pro-1380-fastcharger.o
>> -obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
>> +obj-$(CONFIG_IBM_RTL)			+= ibm_rtl.o
>> +obj-$(CONFIG_IDEAPAD_LAPTOP)		+= ideapad-laptop.o
>> +obj-$(CONFIG_LENOVO_YMC)		+= lenovo-ymc.o
>> +obj-$(CONFIG_SENSORS_HDAPS)		+= hdaps.o
>> +obj-$(CONFIG_THINKPAD_ACPI)		+= thinkpad_acpi.o
>> +obj-$(CONFIG_THINKPAD_LMI)		+= think-lmi.o
>> +obj-$(CONFIG_YOGABOOK)			+= lenovo-yogabook.o
>> +obj-$(CONFIG_YT2_1380)			+= lenovo-yoga-tab2-pro-1380-fastcharger.o
>> +obj-$(CONFIG_LENOVO_WMI_CAMERA)		+= lenovo-wmi-camera.o
>> +obj-$(CONFIG_LEGION_GAMEZONE_WMI)	+= lenovo-legion-wmi-gamezone.o
>> +obj-$(CONFIG_LEGION_DATA_01_WMI)	+= lenovo-legion-wmi-capdata01.o
>> +obj-$(CONFIG_LEGION_OTHER_WMI)		+= lenovo-legion-wmi-other.o
>
>Don't change the whitespace of everything else; especially not in one 
>patch.  If the whitespace is wrong, do a patch that fixes it and then 
>another patch that introduces a driver.

Only done because the length of the new entries messes up the whitespace of the
rest of the block. I can do as two patches if needed, but the whitespace would
need to be after as it is fine without them.

>>   
>>   # Intel
>>   obj-y				+= intel/
>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>> new file mode 100644
>> index 000000000000..99f4f35b7176
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>> @@ -0,0 +1,103 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides
>> + * information on tunable attributes used by the "Other Method" WMI interface,
>> + * including if it is supported by the hardware, the default_value, max_value,
>> + * min_value, and step increment.
>> + *
>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>> + *
>
>I don't think you need a newline at the end here.

Acked for fix all newline comments. Thanks.

>> + */
>> +
>> +#include "lenovo-legion-wmi.h"
>> +
>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>> +
>> +static const struct wmi_device_id capdata_01_wmi_id_table[] = {
>> +	{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
>> +	{}
>> +};
>> +
>> +static struct capdata_wmi cd01_wmi = {
>> +	.mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex)
>> +};
>> +
>> +int capdata_01_wmi_get(struct om_attribute_id attr_id,
>> +		       struct capability_data_01 *cap_data)
>> +{
>> +	union acpi_object *ret_obj;
>> +	int count;
>> +	int instance_id;
>> +	u32 attribute_id = *(int *)&attr_id;
>
>Can please do reverse xmas tree.
>

Acked for fix all ordering comments. Thanks.

>> +
>> +	mutex_lock(&cd01_wmi.mutex);
>> +	count = wmidev_instance_count(drvdata.cd01_wmi->wdev);
>> +	mutex_unlock(&cd01_wmi.mutex);
>
>For new mutex use I'd suggest using guard(mutex) instead so you can have 
>less lock/unlock/cleanup cases to worry about.
>

Good idea, I wasn't aware of this. Will fix up for v2.

>> +	for (instance_id = 0; instance_id < count; instance_id++) {
>> +		mutex_lock(&cd01_wmi.mutex);
>> +		ret_obj =
>> +			wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id);
>> +		mutex_unlock(&cd01_wmi.mutex);
>> +		if (!ret_obj) {
>> +			pr_err("lenovo_legion_wmi_capdata_01: block query failed\n");
>
>With all the error messages you should use #define pr_fmt() at the top 
>of the file and then you don't need to do prefixes at all like this.
>

Same as above, thanks.

>> +			continue;
>> +		}
>> +
>> +		if (ret_obj->type != ACPI_TYPE_BUFFER) {
>> +			pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n",
>> +			       ret_obj->type);
>> +			kfree(ret_obj);
>> +			continue;
>> +		}
>> +
>> +		if (ret_obj->buffer.length != sizeof(*cap_data)) {
>> +			pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n",
>> +			       ret_obj->buffer.length);
>> +			kfree(ret_obj);
>> +			continue;
>> +		}
>> +
>> +		memcpy(cap_data, ret_obj->buffer.pointer,
>> +		       ret_obj->buffer.length);
>> +		kfree(ret_obj);
>> +
>> +		if (cap_data->id != attribute_id)
>> +			continue;
>> +		break;
>> +	}
>> +	if (cap_data->id == 0) {
>> +		pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n");
>> +		return -EINVAL;
>> +	}
>> +	return 0;
>> +}
>> +EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI");
>> +
>> +/* Driver Setup */
>> +static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context)
>> +{
>> +	cd01_wmi.wdev = wdev;
>> +	drvdata.cd01_wmi = &cd01_wmi;
>> +	pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n");
>> +
>
>Pretty noisy; no?  I think you probably should lose this message.
>

Acked for fix all pr_info comments.

>> +	return 0;
>> +}
>> +
>> +static void capdata_01_wmi_remove(struct wmi_device *wdev)
>> +{
>> +	pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n");
>
>Pretty noisy; no?  I think you probably should lose this message.
>
>> +}
>> +
>> +static struct wmi_driver capdata_01_wmi_driver = {
>> +	.driver = { .name = "capdata_01_wmi" },
>> +	.id_table = capdata_01_wmi_id_table,
>> +	.probe = capdata_01_wmi_probe,
>> +	.remove = capdata_01_wmi_remove,
>> +};
>> +
>> +module_wmi_driver(capdata_01_wmi_driver);
>> +
>> +MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table);
>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>> +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>> new file mode 100644
>> index 000000000000..2f976dc0e367
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>> @@ -0,0 +1,233 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides
>> + * platform profile and fan curve settings for devices that fall under the
>> + * "Gaming Series" of Lenovo Legion devices.
>> + *
>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>> + *
>Drop newline here
>> + */
>> +
>> +#include "lenovo-legion-wmi.h"
>> +
>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>> +
>> +/* Method IDs */
>> +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */
>> +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */
>> +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */
>> +
>> +static const struct wmi_device_id gamezone_wmi_id_table[] = {
>> +	{ LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
>> +	{}
>> +};
>> +
>> +static struct gamezone_wmi gz_wmi = {
>> +	.mutex = __MUTEX_INITIALIZER(gz_wmi.mutex)
>> +};
>> +
>> +/* Platform Profile Methods */
>> +static int
>> +gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof,
>> +					int *supported)
>> +{
>> +	int ret;
>> +
>> +	mutex_lock(&gz_wmi.mutex);
>
>I'd use guard(mutex) instead.  By doing that your function becomes a lot 
>simpler too.
>
>guard(mutex)(&gz_wmi.mutex);
>
>return lenovo_legion_evaluate_method_1();
>
>> +	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
>> +					      WMI_METHOD_ID_SMARTFAN_SUPP, 0,
>> +					      supported);
>> +	mutex_unlock(&gz_wmi.mutex);
>> +	return ret;
>> +}
>> +
>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
>> +				 int *sel_prof)
>> +{
>> +	int ret;
>> +	int supported;
>> +
>> +	gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
>> +						&supported);
>> +	if (!supported) {
>> +		pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
>
>Is this error flow real?  I sort of expect that you can avoid 
>registering if not supporting it.
>

This method is an exported symbol in GZ_WMI. I'm not aware of any hardware
without the GameZone interface that does implement the Other Method interface,
but if it does exist I was concerned about calling on an interface that isn't
registered. Perhaps a null pointer check on gz_wmi or gz_wmi.pprof->supported
check in the other method calls to this would be better? I didn't want to rely
on pprof exising for the check. I do now realize that this would call on a WMI
interface that doesn't exist if it was the case this hardware exists.

>> +		return -EOPNOTSUPP;
>> +	}
>> +	mutex_lock(&gz_wmi.mutex);
>
>guard(mutex) here too.
>
>> +	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
>> +					      WMI_METHOD_ID_SMARTFAN_GET, 0,
>> +					      sel_prof);
>> +	mutex_unlock(&gz_wmi.mutex);
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI");
>> +
>> +static int
>> +gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof,
>> +				  enum platform_profile_option *profile)
>> +{
>> +	int sel_prof;
>> +	int err;
>> +
>> +	err = gamezone_wmi_fan_profile_get(pprof, &sel_prof);
>> +	if (err)
>> +		return err;
>> +
>> +	switch (sel_prof) {
>> +	case SMARTFAN_MODE_QUIET:
>> +		*profile = PLATFORM_PROFILE_QUIET;
>> +		break;
>> +	case SMARTFAN_MODE_BALANCED:
>> +		*profile = PLATFORM_PROFILE_BALANCED;
>> +		break;
>> +	case SMARTFAN_MODE_PERFORMANCE:
>> +		*profile = PLATFORM_PROFILE_PERFORMANCE;
>> +		break;
>> +	case SMARTFAN_MODE_CUSTOM:
>> +		*profile = PLATFORM_PROFILE_CUSTOM;
>> +		break;
>> +
>Spurious newline.
>
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +	drvdata.gz_wmi->current_profile = *profile;
>> +
>> +	return 0;
>> +}
>> +
>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
>> +				      enum platform_profile_option profile)
>> +{
>> +	int ret;
>> +	int sel_prof;
>> +	int supported;
>> +
>> +	gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
>> +						&supported);
>> +	if (!supported) {
>> +		pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
>> +		return -EOPNOTSUPP;
>> +	}
>
>Same question; is this a real error flow?
>

Also an exported symbol in GZ_WMI. Will find another way to do these checks in
Other Method.

>> +
>> +	switch (profile) {
>> +	case PLATFORM_PROFILE_QUIET:
>> +		sel_prof = SMARTFAN_MODE_QUIET;
>> +		break;
>> +	case PLATFORM_PROFILE_BALANCED:
>> +		sel_prof = SMARTFAN_MODE_BALANCED;
>> +		break;
>> +	case PLATFORM_PROFILE_PERFORMANCE:
>> +		sel_prof = SMARTFAN_MODE_PERFORMANCE;
>> +		break;
>> +	case PLATFORM_PROFILE_CUSTOM:
>> +		sel_prof = SMARTFAN_MODE_CUSTOM;
>> +		break;
>> +	default:
>> +		return -EOPNOTSUPP;
>> +	}
>> +
>> +	mutex_lock(&gz_wmi.mutex);
>guard(mutex) here.
>> +	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
>> +					      WMI_METHOD_ID_SMARTFAN_SET,
>> +					      sel_prof, NULL);
>> +	mutex_unlock(&gz_wmi.mutex);
>> +
>> +	if (ret) {
>> +		pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n");
>> +		return ret;
>> +	}
>> +
>> +	drvdata.gz_wmi->current_profile = profile;
>> +	return 0;
>> +}
>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI");
>> +
>> +/* Driver Setup */
>> +static int platform_profile_setup(struct gamezone_wmi *gz_wmi)
>> +{
>> +	int err;
>> +	int supported;
>> +
>> +	gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported);
>> +
>> +	gz_wmi->platform_profile_support = supported;
>> +
>> +	if (!supported) {
>> +		pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
>> +		return -EOPNOTSUPP;
>> +	}
>
>Yeah because of this you don't need that other flow I was mentioning above.
>
>IMO I don't think the pr_warn() is really needed, you'll only really 
>have one way that you exit -EOPNOTSUPP.
>

Will remove warn, thanks.

>> +
>> +	gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get;
>> +	gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set;
>> +
>> +	set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices);
>> +	set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices);
>> +	set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices);
>> +	set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices);
>> +
>> +	err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof,
>> +						&gz_wmi->current_profile);
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n",
>> +		       err);
>
>Drop prefix on the error and use pr_fmt().
>
>> +		return err;
>> +	}
>> +
>> +	err = platform_profile_register(&gz_wmi->pprof);
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n",
>> +		       err);
>
>Drop prefix on the error and use pr_fmt().
>
>> +		return err;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context)
>> +{
>> +	int err;
>> +
>> +	gz_wmi.wdev = wdev;
>> +	drvdata.gz_wmi = &gz_wmi;
>> +
>> +	err = platform_profile_setup(&gz_wmi);
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n",
>> +		       err);
>> +		kfree(&gz_wmi);
>
>Is this free correct?  It's a global isn't it?  I don't think you should 
>be freeing here.
>

I'll just return the error.

>> +		return err;
>> +	}
>> +
>> +	pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n");
>
>Too noisy.
>
>> +	return 0;
>> +}
>> +
>> +static void gamezone_wmi_remove(struct wmi_device *wdev)
>> +{
>> +	int err;
>> +
>> +	mutex_lock(&gz_wmi.mutex);
>> +	err = platform_profile_remove(&drvdata.gz_wmi->pprof);
>> +	mutex_unlock(&gz_wmi.mutex);
>> +
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n",
>> +		       err);
>> +	} else {
>> +		pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n");
>> +	}
>
>Considering that platform_profile_remove() doesn't really have a failure 
>path (it always returns 0). I'd just lose both of these messages and 
>make this simple.
>
>guard(mutex)();
>platform_profile_remove();
>

Acked for fix.

>> +}
>> +
>> +static struct wmi_driver gamezone_wmi_driver = {
>> +	.driver = { .name = "gamezone_wmi" },
>> +	.id_table = gamezone_wmi_id_table,
>> +	.probe = gamezone_wmi_probe,
>> +	.remove = gamezone_wmi_remove,
>> +};
>> +
>> +module_wmi_driver(gamezone_wmi_driver);
>> +
>> +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table);
>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>> +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c
>> new file mode 100644
>> index 000000000000..c09c1848eda7
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-legion-wmi-other.c
>> @@ -0,0 +1,377 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Lenovo Legion Other Method driver. This driver uses the fw_attributes
>> + * class to expose the various WMI functions provided by the "Other Method" WMI
>> + * interface. This enables CPU and GPU power limit as well as various other
>> + * attributes for devices that fall under the "Gaming Series" of Lenovo Legion
>> + * devices. Each attribute exposed by the "Other Method"" interface has a
>> + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to
>> + * probe details about the attribute such as set/get support, step, min, max,
>> + * and default value. Each attibute has multiple pages, one for each of the
>> + * fan profiles managed by the GameZone interface, so it must be probed prior
>> + * to returning the current_value.
>> + *
>> + * These attributes typically don't fit anywhere else in the sysfs and are set
>> + * in Windows using one of Lenovo's multiple user applications.
>> + *
>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>> + *
>Remove the new line here.
>> + */
>> +
>> +#include "lenovo-legion-wmi.h"
>> +#include "firmware_attributes_class.h"
>> +
>> +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
>> +
>> +/* Device IDs */
>> +#define WMI_DEVICE_ID_CPU 0x01
>> +
>> +/* WMI_DEVICE_ID_CPU feature IDs */
>> +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */
>> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
>> +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */
>> +
>> +/* Method IDs */
>> +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */
>> +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */
>> +
>> +static const struct wmi_device_id other_method_wmi_id_table[] = {
>> +	{ LENOVO_OTHER_METHOD_GUID, NULL },
>> +	{}
>> +};
>> +
>> +/* Tunable Attributes */
>> +struct ll_tunables {
>> +	u32 ppt_pl1_spl;
>> +	u32 ppt_pl2_sppt;
>> +	u32 ppt_pl3_fppt;
>> +};
>> +
>> +static const struct class *fw_attr_class;
>> +
>> +static struct other_method_wmi om_wmi = {
>> +	.mutex = __MUTEX_INITIALIZER(om_wmi.mutex)
>> +};
>> +
>> +struct capdata_01_attr_group {
>> +	const struct attribute_group *attr_group;
>> +};
>> +
>> +/* Simple attribute creation */
>> +
>> +/*
>> + * att_current_value_store() - Set the current value of the given attribute
>> + * @kobj: Pointer to the driver object.
>> + * @kobj_attribute: Pointer to the attribute calling this function.
>> + * @buf: The buffer to read from, this is parsed to `int` type.
>> + * @count: Required by sysfs attribute macros, pass in from the callee attr.
>> + * @store_value: Pointer to where the parsed value should be stored.
>> + * @device_id: The WMI function Device ID to use.
>> + * @feature_id: The WMI function Feature ID to use.
>> + *
>> + * This function is intended to be generic so it can be called from any
>> + * attribute's "current_value_store" which works only with integers. The
>> + * integer to be sent to the WMI method is range checked and an error returned
>> + * if out of range.
>> + *
>> + * If the value is valid and WMI is success, then the sysfs attribute is
>> + * notified.
>> + *
>> + * Returns: Either count, or an error.
>> + */
>> +ssize_t attr_current_value_store(struct kobject *kobj,
>> +				 struct kobj_attribute *attr, const char *buf,
>> +				 size_t count, u32 *store_value, u8 device_id,
>> +				 u8 feature_id)
>> +{
>> +	struct capability_data_01 cap_data;
>> +	enum platform_profile_option cust_prof;
>> +	int err;
>> +	int sel_prof;
>> +	u32 value;
>> +	struct wmi_device *wdev = drvdata.om_wmi->wdev;
>> +
>> +	err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");
>
>Use pr_fmt() for teh file instead of prefix here.
>
>> +		return -EIO;
>> +	}
>> +
>> +	/* Switch to custom profile if not currently on it. */
>> +	if (sel_prof != SMARTFAN_MODE_CUSTOM) {
>> +		pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables.");
>
>As you do this "for" them, I'd lose the warning.
>

Acked for fix. Leftover from an earlier version that didn't set the profile.

>> +		cust_prof = PLATFORM_PROFILE_CUSTOM;
>> +		sel_prof = SMARTFAN_MODE_CUSTOM;
>> +		err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof,
>> +							cust_prof);
>> +		if (err) {
>> +			pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n");
>> +			return -EIO;
>> +		}
>> +	}
>> +
>> +	err = kstrtouint(buf, 10, &value);
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_other: Error converting value to int.\n");
>> +		return -EIO;
>> +	}
>> +
>> +	/* Construct the attribute id */
>> +	struct om_attribute_id attr_id = { sel_prof << 8, feature_id,
>> +					   device_id };
>> +
>> +	/* Get min/max from LENOVO_CAPABILITY_DATA_01 */
>> +	err = capdata_01_wmi_get(attr_id, &cap_data);
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
>> +		return -EIO;
>> +	}
>> +	if (cap_data.capability < 1) {
>> +		pr_err("lenovo_legion_wmi_other: Capability not supported.\n");
>> +		return -EPERM;
>> +	}
>> +
>> +	if (value < cap_data.min_value || value > cap_data.max_value) {
>> +		pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n",
>> +			value, cap_data.min_value, cap_data.max_value);
>> +		return -EINVAL;
>> +	}
>> +
>> +	mutex_lock(&om_wmi.mutex);
>> +	err = lenovo_legion_evaluate_method_2(wdev, 0x0,
>> +					      WMI_METHOD_ID_VALUE_SET,
>> +					      *(int *)&attr_id, value, NULL);
>> +	mutex_unlock(&om_wmi.mutex);
>> +
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_other: Error setting attribute");
>> +		return err;
>> +	}
>> +
>> +	if (store_value)
>> +		*store_value = value;
>> +
>> +	sysfs_notify(kobj, NULL, attr->attr.name);
>> +	return count;
>> +}
>> +
>> +/*
>> + * attr_current_value_show() - Get the current value of the given attribute
>> + * @kobj: Pointer to the driver object.
>> + * @kobj_attribute: Pointer to the attribute calling this function.
>> + * @buf: The buffer to write to.
>> + * @retval: Pointer to returned data.
>> + * @device_id: The WMI function Device ID to use.
>> + * @feature_id: The WMI function Feature ID to use.
>> + *
>> + * This function is intended to be generic so it can be called from any "_show"
>> + * attribute which works only with integers.
>> + *
>> + * If the WMI is success, then the sysfs attribute is notified.
>> + *
>> + * Returns: Either count, or an error.
>> + */
>> +ssize_t attr_current_value_show(struct kobject *kobj,
>> +				struct kobj_attribute *attr, char *buf,
>> +				u8 device_id, u8 feature_id)
>> +{
>> +	int sel_prof; /* Current fan profile mode */
>> +	int err;
>> +	int retval;
>> +	struct wmi_device *wdev = drvdata.om_wmi->wdev;
>> +
>> +	err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
>> +
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");
>> +		return err;
>> +	}
>> +
>> +	// Construct the WMI attribute id from the given args.
>> +	struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
>> +						device_id };
>> +
>> +	mutex_lock(&om_wmi.mutex);
>> +	err = lenovo_legion_evaluate_method_1(wdev, 0x0,
>> +					      WMI_METHOD_ID_VALUE_GET,
>> +					      *(int *)&attribute_id, &retval);
>> +	mutex_unlock(&om_wmi.mutex);
>> +
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_other: Error getting attribute");
>> +		return err;
>> +	}
>> +
>> +	return sysfs_emit(buf, "%u\n", retval);
>> +}
>> +
>> +/**
>> + * attr_capdata_01_show() - Get the value of the specified attribute property
>> + * from LENOVO_CAPABILITY_DATA_01.
>> + * @kobj: Pointer to the driver object.
>> + * @kobj_attribute: Pointer to the attribute calling this function.
>> + * @buf: The buffer to write to.
>> + * @retval: Pointer to returned data.
>> + * @device_id: The WMI functions Device ID to use.
>> + * @feature_id: The WMI functions Feature ID to use.
>> + * @prop: The property of this attribute to be read.
>> + *
>> + * This function is intended to be generic so it can be called from any "_show"
>> + * attribute which works only with integers.
>> + *
>> + * If the WMI is success, then the sysfs attribute is notified.
>> + *
>> + * Returns: Either count, or an error.
>> + */
>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
>> +			     char *buf, u8 device_id, u8 feature_id,
>> +			     enum attribute_property prop)
>> +{
>> +	struct capability_data_01 cap_data;
>> +	int err;
>> +	int retval;
>> +	int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */
>> +
>> +	// Construct the WMI attribute id from the given args.
>> +	struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
>> +						device_id };
>> +
>> +	err = capdata_01_wmi_get(attribute_id, &cap_data);
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
>> +		return -EIO;
>> +	}
>> +
>> +	switch (prop) {
>> +	case DEFAULT_VAL:
>> +		retval = cap_data.default_value;
>> +		break;
>> +	case MAX_VAL:
>> +		retval = cap_data.max_value;
>> +		break;
>> +	case MIN_VAL:
>> +		retval = cap_data.min_value;
>> +		break;
>> +	case STEP_VAL:
>> +		retval = cap_data.step;
>> +		break;
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +	return sysfs_emit(buf, "%u\n", retval);
>> +}
>> +
>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU,
>> +			    WMI_FEATURE_ID_CPU_SPL,
>> +			    "Set the CPU sustained power limit");
>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU,
>> +			    WMI_FEATURE_ID_CPU_SPPT,
>> +			    "Set the CPU slow package power tracking limit");
>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU,
>> +			    WMI_FEATURE_ID_CPU_FPPT,
>> +			    "Set the CPU fast package power tracking limit");
>> +
>> +static const struct capdata_01_attr_group capdata_01_attr_groups[] = {
>> +	{ &ppt_pl1_spl_attr_group },
>> +	{ &ppt_pl2_sppt_attr_group },
>> +	{ &ppt_pl3_fppt_attr_group },
>> +	{},
>> +};
>> +
>> +static int other_method_fw_attr_add(void)
>> +{
>> +	int err, i;
>> +
>> +	err = fw_attributes_class_get(&fw_attr_class);
>> +	if (err) {
>> +		pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n");
>> +		return err;
>> +	}
>> +
>> +	om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
>> +					   NULL, "%s", DRIVER_NAME);
>> +	if (IS_ERR(om_wmi.fw_attr_dev)) {
>> +		pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n");
>> +		err = PTR_ERR(om_wmi.fw_attr_dev);
>> +		goto fail_class_get;
>> +	}
>> +
>> +	om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL,
>> +						  &om_wmi.fw_attr_dev->kobj);
>> +	if (!om_wmi.fw_attr_kset) {
>> +		pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n");
>> +		err = -ENOMEM;
>> +		goto err_destroy_classdev;
>> +	}
>> +
>> +	for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) {
>> +		err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj,
>> +					 capdata_01_attr_groups[i].attr_group);
>> +		if (err) {
>> +			pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n",
>> +			       capdata_01_attr_groups[i].attr_group->name);
>> +			goto err_remove_groups;
>> +		}
>> +	}
>> +
>> +	return 0;
>> +
>> +err_remove_groups:
>> +	while (--i >= 0) {
>> +		sysfs_remove_group(&om_wmi.fw_attr_kset->kobj,
>> +				   capdata_01_attr_groups[i].attr_group);
>> +	}
>> +err_destroy_classdev:
>> +	device_destroy(fw_attr_class, MKDEV(0, 0));
>> +fail_class_get:
>> +	fw_attributes_class_put();
>> +	return err;
>> +}
>> +
>> +/* Driver Setup */
>> +static int other_method_wmi_probe(struct wmi_device *wdev, const void *context)
>> +{
>> +	int err;
>> +
>> +	om_wmi.wdev = wdev;
>> +	drvdata.om_wmi = &om_wmi;
>> +	om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL);
>> +	if (!om_wmi.ll_tunables)
>> +		return -ENOMEM;
>> +
>> +	err = other_method_fw_attr_add();
>> +	if (err)
>> +		return err;
>> +	pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n");
>
>too noisy
>
>> +
>> +	return 0;
>> +}
>> +
>> +static void other_method_wmi_remove(struct wmi_device *wdev)
>> +{
>> +	mutex_lock(&om_wmi.mutex);
>> +
>> +	kset_unregister(om_wmi.fw_attr_kset);
>> +	device_destroy(fw_attr_class, MKDEV(0, 0));
>> +	fw_attributes_class_put();
>> +
>> +	mutex_unlock(&om_wmi.mutex);
>> +
>> +	pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n");
>too noisy.
>
>> +}
>> +
>> +static struct wmi_driver other_method_wmi_driver = {
>> +	.driver = { .name = "other_method_wmi" },
>> +	.id_table = other_method_wmi_id_table,
>> +	.probe = other_method_wmi_probe,
>> +	.remove = other_method_wmi_remove,
>> +};
>> +
>> +module_wmi_driver(other_method_wmi_driver);
>> +
>> +MODULE_IMPORT_NS("GZ_WMI");
>> +MODULE_IMPORT_NS("CAPDATA_WMI");
>> +MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table);
>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>> +MODULE_DESCRIPTION("Lenovo Legion Other Method Driver");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h
>> new file mode 100644
>> index 000000000000..65baa728f29e
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-legion-wmi.h
>> @@ -0,0 +1,271 @@
>> +/* SPDX-License-Identifier: GPL-2.0-or-later
>> + *
>> + * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is
>> + * broken up into multiple GUID interfaces that require cross-references
>> + * between GUID's for some functionality. The "Custom Mode" interface is a
>> + * legacy interface for managing and displaying CPU & GPU power and hwmon
>> + * settings and readings. The "Other Mode" interface is a modern interface
>> + * that replaces or extends the "Custom Mode" interface methods. The "GameZone"
>> + * interface adds advanced features such as fan profiles and overclocking.
>> + * The "Lighting" interface adds control of various status lights related to
>> + * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00
>> + * struct for capability information, "Other Mode" uses
>> + * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting"
>> + * uses LENOVO_CAPABILITY_DATA_02 struct for capability information.
>> + *
>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>> + *
>Lose the newline
>
>> + */
>> +
>> +#ifndef _LENOVO_LEGION_WMI_H_
>> +#define _LENOVO_LEGION_WMI_H_
>> +
>> +#include <linux/mutex.h>
>> +#include <linux/platform_profile.h>
>> +#include <linux/types.h>
>> +#include <linux/wmi.h>
>> +
>> +#define DRIVER_NAME "lenovo-legion-wmi"
>
>This is only used in one of the drivers, I'd move it there to make it 
>clearer.
>

Acked for fix. 

>> +
>> +/* Platform Profile Modes */
>> +#define SMARTFAN_MODE_QUIET 0x01
>> +#define SMARTFAN_MODE_BALANCED 0x02
>> +#define SMARTFAN_MODE_PERFORMANCE 0x03
>> +#define SMARTFAN_MODE_CUSTOM 0xFF
>> +
>> +struct gamezone_wmi {
>> +	struct wmi_device *wdev;
>> +	enum platform_profile_option current_profile;
>> +	struct platform_profile_handler pprof;
>> +	bool platform_profile_support;
>> +	struct mutex mutex; /* Ensure single operation on WMI device */
>> +};
>> +
>> +struct other_method_wmi {
>> +	struct wmi_device *wdev;
>> +	struct device *fw_attr_dev;
>> +	struct kset *fw_attr_kset;
>> +	struct ll_tunables *ll_tunables;
>> +	struct mutex mutex; /* Ensure single operation on WMI device */
>> +};
>> +
>> +struct capdata_wmi {
>> +	struct wmi_device *wdev;
>> +	struct mutex mutex; /* Ensure single operation on WMI device */
>> +};
>> +
>> +struct ll_drvdata {
>> +	struct other_method_wmi *om_wmi; /* Other method GUID device */
>> +	struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */
>> +	struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */
>> +} drvdata;
>> +
>> +struct wmi_method_args {
>> +	u32 arg0;
>> +	u32 arg1;
>> +};
>> +
>> +struct om_attribute_id {
>> +	u32 mode_id : 16; /* Fan profile */
>> +	u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
>> +	u32 device_id : 8; /* CPU/GPU */
>> +} __packed;
>> +
>> +enum attribute_property {
>> +	DEFAULT_VAL = 0,
>> +	MAX_VAL,
>> +	MIN_VAL,
>> +	STEP_VAL,
>> +	SUPPORTED,
>> +};
>> +
>> +struct capability_data_01 {
>> +	u32 id;
>> +	u32 capability;
>> +	u32 default_value;
>> +	u32 step;
>> +	u32 min_value;
>> +	u32 max_value;
>> +};
>> +
>> +static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance,
>> +					 u32 method_id, struct acpi_buffer *in,
>> +					 struct acpi_buffer *out)
>> +{
>> +	acpi_status status;
>> +
>> +	status = wmidev_evaluate_method(wdev, instance, method_id, in, out);
>> +
>> +	if (ACPI_FAILURE(status)) {
>> +		pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n",
>> +		       method_id, instance);
>> +		return -EIO;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>> +				    u32 method_id, u32 arg0, u32 arg1,
>> +				    u32 *retval);
>> +
>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>> +				    u32 method_id, u32 arg0, u32 arg1,
>> +				    u32 *retval)
>> +{
>> +	int ret;
>> +	u32 temp_val;
>> +	struct wmi_method_args args = { arg0, arg1 };
>> +	struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
>> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>> +	union acpi_object *ret_obj = NULL;
>
>Reverse xmas tree please.
>
>> +
>> +	ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input,
>> +					    &output);
>> +
>> +	if (ret) {
>> +		pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n",
>> +		       method_id, ret);
>> +		return ret;
>> +	}
>> +
>> +	if (retval) {
>> +		ret_obj = (union acpi_object *)output.pointer;
>> +		if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER)
>> +			temp_val = (u32)ret_obj->integer.value;
>
>This is a pretty bad failure if it's not the case, no?  Should you set a 
>return value here instead perhaps?
>
>> +
>> +		*retval = temp_val
>
>If that above error I mentioned happens then you'll be assigning garbage 
>data out.
>
>;

True, good catch. Someone built with clang+lto and it warned about this section
as well as temp val is not initialized. Will fix both.

>> +	}
>> +
>> +	kfree(ret_obj);
>> +
>> +	return 0;
>> +}
>> +
>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>> +				    u32 method_id, u32 arg0, u32 *retval);
>> +
>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>> +				    u32 method_id, u32 arg0, u32 *retval)
>> +{
>> +	return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0,
>> +					       0, retval);
>> +}
>> +
>> +int capdata_01_wmi_get(struct om_attribute_id attr_id,
>> +		       struct capability_data_01 *cap_data);
>> +
>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
>> +				      enum platform_profile_option sel_prof);
>> +
>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
>> +				 int *sel_prof);
>> +
>> +/* current_value */
>> +ssize_t attr_current_value_store(struct kobject *kobj,
>> +				 struct kobj_attribute *attr, const char *buf,
>> +				 size_t count, u32 *store_value, u8 device_id,
>> +				 u8 feature_id);
>> +
>> +ssize_t attr_current_value_show(struct kobject *kobj,
>> +				struct kobj_attribute *attr, char *buf,
>> +				u8 device_id, u8 feature_id);
>> +
>> +/* LENOVO_CAPABILITY_DATA_01 */
>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
>> +			     char *buf, u8 device_id, u8 feature_id,
>> +			     enum attribute_property prop);
>> +
>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
>> +		      char *buf);
>> +
>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
>> +		      char *buf)
>> +{
>> +	return sysfs_emit(buf, "integer\n");
>> +}
>> +
>> +#define __LL_ATTR_RO(_func, _name)                                    \
>> +	{                                                             \
>> +		.attr = { .name = __stringify(_name), .mode = 0444 }, \
>> +		.show = _func##_##_name##_show,                       \
>> +	}
>> +
>> +#define __LL_ATTR_RO_AS(_name, _show)                                 \
>> +	{                                                             \
>> +		.attr = { .name = __stringify(_name), .mode = 0444 }, \
>> +		.show = _show,                                        \
>> +	}
>> +
>> +#define __LL_ATTR_RW(_func, _name) \
>> +	__ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
>> +
>> +/* Shows a formatted static variable */
>> +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val)                         \
>> +	static ssize_t _attrname##_##_prop##_show(                            \
>> +		struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
>> +	{                                                                     \
>> +		return sysfs_emit(buf, _fmt, _val);                           \
>> +	}                                                                     \
>> +	static struct kobj_attribute attr_##_attrname##_##_prop =             \
>> +		__LL_ATTR_RO(_attrname, _prop)
>> +
>> +/* Attribute current_value show/store */
>> +#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id)                   \
>> +	static ssize_t _attrname##_current_value_store(                       \
>> +		struct kobject *kobj, struct kobj_attribute *attr,            \
>> +		const char *buf, size_t count)                                \
>> +	{                                                                     \
>> +		return attr_current_value_store(                              \
>> +			kobj, attr, buf, count,                               \
>> +			&om_wmi.ll_tunables->_attrname, _dev_id, _feat_id);   \
>> +	}                                                                     \
>> +	static ssize_t _attrname##_current_value_show(                        \
>> +		struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
>> +	{                                                                     \
>> +		return attr_current_value_show(kobj, attr, buf, _dev_id,      \
>> +					       _feat_id);                     \
>> +	}                                                                     \
>> +	static struct kobj_attribute attr_##_attrname##_current_value =       \
>> +		__LL_ATTR_RW(_attrname, current_value)
>> +
>> +/* Attribute property show only */
>> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \
>> +	static ssize_t _attrname##_##_prop##_show(                             \
>> +		struct kobject *kobj, struct kobj_attribute *attr, char *buf)  \
>> +	{                                                                      \
>> +		return attr_capdata_01_show(kobj, attr, buf, _dev_id,          \
>> +					    _feat_id, _prop_type);             \
>> +	}                                                                      \
>> +	static struct kobj_attribute attr_##_attrname##_##_prop =              \
>> +		__LL_ATTR_RO(_attrname, _prop)
>> +
>> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id,    \
>> +				    _dispname)                                \
>> +	__LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id);                  \
>> +	__LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id,    \
>> +			      DEFAULT_VAL);                                   \
>> +	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);          \
>> +	__LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id,        \
>> +			      MAX_VAL);                                       \
>> +	__LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id,        \
>> +			      MIN_VAL);                                       \
>> +	__LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \
>> +			      STEP_VAL);                                      \
>> +	static struct kobj_attribute attr_##_attrname##_type =                \
>> +		__LL_ATTR_RO_AS(type, int_type_show);                         \
>> +	static struct attribute *_attrname##_attrs[] = {                      \
>> +		&attr_##_attrname##_current_value.attr,                       \
>> +		&attr_##_attrname##_default_value.attr,                       \
>> +		&attr_##_attrname##_display_name.attr,                        \
>> +		&attr_##_attrname##_max_value.attr,                           \
>> +		&attr_##_attrname##_min_value.attr,                           \
>> +		&attr_##_attrname##_scalar_increment.attr,                    \
>> +		&attr_##_attrname##_type.attr,                                \
>> +		NULL,                                                         \
>> +	};                                                                    \
>> +	static const struct attribute_group _attrname##_attr_group = {        \
>> +		.name = _fsname, .attrs = _attrname##_attrs                   \
>> +	}
>> +
>> +#endif /* !_LENOVO_LEGION_WMI_H_ */

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-17 23:06 [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers Derek J. Clark
  2024-12-17 23:06 ` [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers Derek J. Clark
@ 2024-12-22  8:42 ` John Martens
       [not found]   ` <CAFqHKT==PiLb_VuFPHJzgfrprukGGig+iUzcih+3TJ1VYt94Dw@mail.gmail.com>
  2024-12-22 23:05   ` Armin Wolf
  1 sibling, 2 replies; 29+ messages in thread
From: John Martens @ 2024-12-22  8:42 UTC (permalink / raw)
  To: derekjohn.clark
  Cc: corbet, hdegoede, ilpo.jarvinen, linux-doc, linux-kernel, luke,
	mpearson-lenovo, nijs1, pgriffais, platform-driver-x86, shaohz1,
	superm1, zhangzx36

> Adds support for the Lenovo Legion series of laptop hardware to use WMI
> interfaces that control various power settings. 

Note that there already is a driver for Lenovo Legion laptops that I 
wanted to merge upstream.

https://github.com/johnfanv2/LenovoLegionLinux

Compared to the proposed patch, it has the following
advantages:
1. already popular and tested by thousands of users
    - many stars and discussions on github
    - patched into multiple kernels of gaming-related distros
    - packaged as dkms module for almost all relevant Linux 
      distributions including Debian by other developers
2. supports many different Lenovo Legion models starting from 2020/2021
3. supports a lot of more functions, including fan control, which is the
  most requested feature
4. supports the many changes between different in the WMI/ACPI method
5. actually shares some credtis with persons who revere engineered it :)
6. support by GUI tool to configure it all

On the other hand, my driver has the following disadvantages:
1. The version of master on github is the most recent one and contains
   a lot of debug output that has to be removed (often indicated by TODO)
2. It is all in one large c file instead of organizing it neatly into
   multiple files.
3. It was modeled after the ideapad driver instead of the newer ASUS
   driver.

A few notes regarding the many changes of the WMI methods that I tried 
to deal with in my driver: note that in almost every new model a new 
WMI method is used to control the same functionality (e.g. fan control
 or powermode). Additionally, often the constants or the unit changes
: e.g. percent or rpm for fan speed.

> The driver has been tested by me on the Lenovo Legion Go.

The driver on github has been tested by thousands of users.

I suggest that we maybe combine the two drivers before merging them,
since Derek seems to have more kernel patching knowledge and I seem
to have more worked on all the Legion laptops.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
       [not found]   ` <CAFqHKT==PiLb_VuFPHJzgfrprukGGig+iUzcih+3TJ1VYt94Dw@mail.gmail.com>
@ 2024-12-22 20:34     ` Derek John Clark
  0 siblings, 0 replies; 29+ messages in thread
From: Derek John Clark @ 2024-12-22 20:34 UTC (permalink / raw)
  To: John Martens
  Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, linux-doc,
	open list, Luke Jones, Mark Pearson, Xino JS1 Ni,
	Pierre-Loup Griffais, open list:AMD PMF DRIVER, Mia HZ1 Shao,
	Mario Limonciello, Zhixin ZX36 Zhang

All.

Sorry for the spam. My mobile client switched to HTML mode and I
didn't catch it yesterday.
Resending my last response plain text so it gets posted to LKML properly.

>> Adds support for the Lenovo Legion series of laptop hardware to use WMI
>> interfaces that control various power settings.

>Note that there already is a driver for Lenovo Legion laptops that I
>wanted to merge upstream.

>https://github.com/johnfanv2/LenovoLegionLinux

Hi John.

I'm a fan of your work and did consider working on getting your driver
upstream-able before moving forward with my own. After reviewing it I
found there were too many changes needed which would have ultimately
required a full rewrite. For this initial driver MR I only intended to
add the features and models that Lenovo specifically asked me to
implement. I'm treating it as a platform baseline and I plan on adding
additional features in later patch series'. I fully encourage
community involvement in expanding this driver as I won't be able to
do it all on my own.

>Compared to the proposed patch, it has the following
>advantages:
>1. already popular and tested by thousands of users
>    - many stars and discussions on github
>    - patched into multiple kernels of gaming-related distros
>    - packaged as dkms module for almost all relevant Linux
>      distributions including Debian by other developers

Those DKMS packages will need to blacklist these drivers anyway, so
they should remain functional for anyone who prefers the out of tree
driver until there is feature parity.

>2. supports many different Lenovo Legion models starting from 2020/2021

I would gladly take any assistance with hardware I don't have access
to. I have structured the driver such that adding the additional
interfaces and features should be simple. Since a lot of the
historical data is available I expect a fairly rapid development
timeline going forward.

>3. supports a lot of more functions, including fan control, which is the
>  most requested feature

I'm aware of the user's need for fan control, this is planned as the
next feature once this patch series is accepted. Other things I have
planned are hwmon sensors and lighting control. I don't think feature
parity in the initial driver is a reasonable goal as that's
essentially a moving target that would take many months to write and
test. As I'm sure you're aware, maintaining a kernel patch through
multiple kernel versions takes a lot of effort if the subsystem is
active enough.

>4. supports the many changes between different in the WMI/ACPI method
>5. actually shares some credtis with persons who revere engineered it :)

I appreciate those efforts, many talented folks have done some amazing
work and I don't want to take anything away from them. I just want to
note this driver isn't reverse engineered as Lenovo have provided me
with the full WMI spec to create it.

>6. support by GUI tool to configure it all

>On the other hand, my driver has the following disadvantages:
>1. The version of master on github is the most recent one and contains
>   a lot of debug output that has to be removed (often indicated by TODO)
>2. It is all in one large c file instead of organizing it neatly into
>   multiple files.
>3. It was modeled after the ideapad driver instead of the newer ASUS
>   driver.

>A few notes regarding the many changes of the WMI methods that I tried
>to deal with in my driver: note that in almost every new model a new
>WMI method is used to control the same functionality (e.g. fan control
> or powermode). Additionally, often the constants or the unit changes
>: e.g. percent or rpm for fan speed.


>> The driver has been tested by me on the Lenovo Legion Go.

>The driver on github has been tested by thousands of users.

>I suggest that we maybe combine the two drivers before merging them,
>since Derek seems to have more kernel patching knowledge and I seem
>to have more worked on all the Legion laptops.

Based on the sheer size of your driver, I think a change as large as
that will be too much for a reasonable review on the LKML. Your driver
is over 6K lines and that will expand a bit when doing all the
necessary things to break it into multiple files and switch from
depreciated methods, add additional safety checks, etc. In my opinion
it would be better to add each interface as its own patch series (e.g.
custom mode and lighting, dmi specific functions, etc.) as that will
be much more manageable for review and easier to catch bugs.

All that being said, I'm certainly open to collaboration on the future
of this driver. If you want to contact me separately, we can discuss
it.

On Sun, Dec 22, 2024 at 2:09 AM Derek John Clark
<derekjohn.clark@gmail.com> wrote:
>
> >> Adds support for the Lenovo Legion series of laptop hardware to use WMI
> >> interfaces that control various power settings.
>
> >Note that there already is a driver for Lenovo Legion laptops that I
> >wanted to merge upstream.
>
> >https://github.com/johnfanv2/LenovoLegionLinux
>
> Hi John.
>
> I'm a fan of your work and did consider working on getting your driver upstreamable before moving forward with my own. After reviewing it I found there were too many changes needed which would have ultimately required a full rewrite. For this initial driver MR I only intended to add the features and models that Lenovo specifically asked me to implement. I'm treating it as a platform baseline and I do plan on adding additional features in later patch series'. I fully encourage community involvement in expanding this driver as I won't be able to do it all on my own.
>
> >Compared to the proposed patch, it has the following
> >advantages:
> >1. already popular and tested by thousands of users
> >    - many stars and discussions on github
> >    - patched into multiple kernels of gaming-related distros
> >    - packaged as dkms module for almost all relevant Linux
> >      distributions including Debian by other developers
>
> Those DKMS packages will need to blacklist these drivers anyway, so they should remain functional for anyone who prefers the out of tree driver until there is feature parity.
>
> >2. supports many different Lenovo Legion models starting from 2020/2021
>
> I would gladly take any assistance with hardware I don't have access to. I have structured the driver such that adding the additional interfaces and features should be simple. Since a lot of the historical data is available I expect a fairly rapid development timeline going forward.
>
> >3. supports a lot of more functions, including fan control, which is the
> >  most requested feature
>
> I'm aware of the user need for fan control, this is planned as the next feature once this patch series is accepted. Other things I have planned are hwmon sensors and lighting control. I don't think feature parity in the initial driver is a reasonable goal as that's essentially a moving target that would take many months to write and test. As I'm sure you're aware, maintaining a kernel patch through multiple kernel versions takes a lot of effort if the subsystem is active enough.
>
> >4. supports the many changes between different in the WMI/ACPI method
> >5. actually shares some credtis with persons who revere engineered it :)
>
> I appreciate those efforts, many talented folks have done some amazing work and I don't want to take anything away from them. I just want to note this driver isn't reverse engineered as Lenovo have provided me with the full WMI spec to create it.
>
> >6. support by GUI tool to configure it all
>
> >On the other hand, my driver has the following disadvantages:
> >1. The version of master on github is the most recent one and contains
> >   a lot of debug output that has to be removed (often indicated by TODO)
> >2. It is all in one large c file instead of organizing it neatly into
> >   multiple files.
> >3. It was modeled after the ideapad driver instead of the newer ASUS
> >   driver.
>
> >A few notes regarding the many changes of the WMI methods that I tried
> >to deal with in my driver: note that in almost every new model a new
> >WMI method is used to control the same functionality (e.g. fan control
> > or powermode). Additionally, often the constants or the unit changes
> >: e.g. percent or rpm for fan speed.
>
>
> >> The driver has been tested by me on the Lenovo Legion Go.
>
> >The driver on github has been tested by thousands of users.
>
> >I suggest that we maybe combine the two drivers before merging them,
> >since Derek seems to have more kernel patching knowledge and I seem
> >to have more worked on all the Legion laptops.
>
> Based on the sheer size of your driver, I think a change as large as that will be too much for a reasonable review on the LKML. Your driver is over 6K lines and that will expand a bit when doing all the necessary things to break it into multiple files and switch from depreciated methods, add additional safety checks, etc. In my opinion it would be better to add each interface as it's own patch series (e.g. custom mode and lighting, dmi specific functions, etc.) as that will be much more manageable for review and easier to catch bugs.
>
> All that being said, I'm certainly open to collaboration on the future of this driver. if you want to contact me separately we can discuss it.
>
> On Sun, Dec 22, 2024, 00:42 John Martens <johnfanv2@gmail.com> wrote:
>>
>> > Adds support for the Lenovo Legion series of laptop hardware to use WMI
>> > interfaces that control various power settings.
>>
>> Note that there already is a driver for Lenovo Legion laptops that I
>> wanted to merge upstream.
>>
>> https://github.com/johnfanv2/LenovoLegionLinux
>>
>> Compared to the proposed patch, it has the following
>> advantages:
>> 1. already popular and tested by thousands of users
>>     - many stars and discussions on github
>>     - patched into multiple kernels of gaming-related distros
>>     - packaged as dkms module for almost all relevant Linux
>>       distributions including Debian by other developers
>> 2. supports many different Lenovo Legion models starting from 2020/2021
>> 3. supports a lot of more functions, including fan control, which is the
>>   most requested feature
>> 4. supports the many changes between different in the WMI/ACPI method
>> 5. actually shares some credtis with persons who revere engineered it :)
>> 6. support by GUI tool to configure it all
>>
>> On the other hand, my driver has the following disadvantages:
>> 1. The version of master on github is the most recent one and contains
>>    a lot of debug output that has to be removed (often indicated by TODO)
>> 2. It is all in one large c file instead of organizing it neatly into
>>    multiple files.
>> 3. It was modeled after the ideapad driver instead of the newer ASUS
>>    driver.
>>
>> A few notes regarding the many changes of the WMI methods that I tried
>> to deal with in my driver: note that in almost every new model a new
>> WMI method is used to control the same functionality (e.g. fan control
>>  or powermode). Additionally, often the constants or the unit changes
>> : e.g. percent or rpm for fan speed.
>>
>> > The driver has been tested by me on the Lenovo Legion Go.
>>
>> The driver on github has been tested by thousands of users.
>>
>> I suggest that we maybe combine the two drivers before merging them,
>> since Derek seems to have more kernel patching knowledge and I seem
>> to have more worked on all the Legion laptops.
>>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-18  3:36     ` Derek J. Clark
@ 2024-12-22 22:55       ` Armin Wolf
  2024-12-23 15:47         ` Derek John Clark
  0 siblings, 1 reply; 29+ messages in thread
From: Armin Wolf @ 2024-12-22 22:55 UTC (permalink / raw)
  To: Derek J. Clark, Mario Limonciello, Hans de Goede
  Cc: Ilpo Järvinen, Jonathan Corbet, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	platform-driver-x86, linux-doc, linux-kernel

Am 18.12.24 um 04:36 schrieb Derek J. Clark:

> Hi Mario,
>
> Thank you for taking a look at it so quickly.
>
>>> Adds lenovo-legion-wmi.h which provides templates and some method
>>> implementations used by the lenovo-legion-wmi driver series.
>>>
>>> Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo
>>> GameZone WMI interface that comes on Lenovo "Gaming Series" hardware.
>>> Provides ACPI platform profiles over WMI.
>>>
>>> Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo
>>> "Other Method" WMI interface that comes on some Lenovo hardware.
>>> Provides a firmware-attributes class which enables the use of tunable
>>> knobs for SPL, SPPT, and FPPT.
>>>
>>> Adds lenovo-legion-wmi-capdata01.c which provides a driver for the
>>> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method"
>>> enabled hardware. Provides an interface for querying if a given
>>> attribute is supported by the hardware, as well as its default_value,
>>> max_value, min_value, and step increment.
>>>
> z>> Adds lenovo-legion-wmi.rst describing the available drivers and their
>>> function.
>>>
>>> Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers.
>>>
>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>> Hi Derek,
>>
>> As a high level first comment; "larger" patches are much harder to review.
>>
>> It seems that the drivers are logically split as described in your
>> commit message already.  For the next version could you split at least
>> each driver to it's own patch?
>>
>> It might also make sense to split up the individual drivers along
>> "features".
> Can do. It might still make sense to have capdata01 with the other_method
> driver, it has no functionality on its own and is a dependency of other_method.
> It isn't a problem to have capdata01 as an earlier patch than other_method
> though if preferred. I'm not sure how I would break up the drivers further
> than that, except the relevant header portions per c file.

I think the gamezone driver should  be totally independent of the other driver.

For the capdata01 and the other-guid driver i think the component framework could be handy,
see https://docs.kernel.org/driver-api/component.html for details. Basically you will have
a driver for capdata01 and another driver for other-guid which will both register a component.
The firmware-attributes part will only get loaded if both components are present.

Please keep in mind that new WMI drivers need to be instantiated multiple times, see
https://docs.kernel.org/wmi/driver-development-guide.html for details.

>> This is my own personal opinion and not a requirement but I personally
>> like to see documentation for something new like this as it's own patch
>> at the beginning of the series so we can make sure everyone understands
>> and agrees on the design as they review the series and then can make
>> sure that the implementation matches the design as the other patches are
>> reviewed.
> Acked, will add Documentation as its own 1/ patch.
>
>> I've got various other comments sprinkled throughout the patch, please
>> see them.  I'm not 100% sure on the mutex use yet, we should review that
>> after you've got all the cleanups needed done.
>>
>>> ---
>>>    .../wmi/devices/lenovo-legion-wmi.rst         |  79 ++++
>>>    MAINTAINERS                                   |   9 +
>>>    drivers/platform/x86/Kconfig                  |  35 ++
>>>    drivers/platform/x86/Makefile                 |  21 +-
>>>    .../x86/lenovo-legion-wmi-capdata01.c         | 103 +++++
>>>    .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++
>>>    .../platform/x86/lenovo-legion-wmi-other.c    | 377 ++++++++++++++++++
>>>    drivers/platform/x86/lenovo-legion-wmi.h      | 271 +++++++++++++
>>>    8 files changed, 1119 insertions(+), 9 deletions(-)
>>>    create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst
>>>    create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>>>    create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>>>    create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c
>>>    create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h
>>>
>>> diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst
>>> new file mode 100644
>>> index 000000000000..37b09c82c980
>>> --- /dev/null
>>> +++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst
>>> @@ -0,0 +1,79 @@
>>> +.. SPDX-License-Identifier: GPL-2.0-or-later
>>> +======================================================
>>> +Lenovo Legion WMI Interface Driver (lenovo-legion-wmi)
>>> +======================================================
>>> +
>>> +Introduction
>>> +============
>>> +The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that
>>> +require cross-references between GUID's for some functionality. The "Custom
>>> +Mode" interface is a legacy interface for managing and displaying CPU & GPU
>>> +power and hwmon settings and readings. The "Other Mode" interface is a modern
>>> +interface that replaces "Custom Mode" interface methods. The "GameZone"
>>> +interface adds advanced features such as fan profiles and overclocking. The
>>> +"Lighting" interface adds control of various status lights related to different
>>> +hardware components.
>>> +
>>> +Each of these interfaces has a different data structure associated with it that
>>> +provide detailed information about each attribute provided by the interface.
>>> +These data structs are retrieved from an additional WMI device data block GUID:
>>> + - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00
>>> + - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01
>>> + - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02
>>> +
>>> +.. note::
>>> +   Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01
>>> +   interfaces are implemented by this driver.
>> So this is to say that lighting interface is not implemented right now,
>> right?
> Custom Mode, Lighting, LENOVO_CAPABILITY_DATA_00, and LENOVO_CAPABILITY_DATA_02
> are not implemented yet. For now Lenovo are okay with that but may want more
> later.

>>> +
>>> +
>>> +GameZone
>>> +--------
>>> +The GameZone WMI interface provides ACPI platform profile and fan curve
>>> +settings for devices that fall under the "Gaming Series" of Lenovo Legion
>>> +devices.
>>> +
>>> +The following platform profiles are supported:
>>> + - quiet
>>> + - balanced
>>> + - performance
>>> + - custom
>>> +
>>> +Custom Profile
>>> +~~~~~~~~~~~~~~
>>> +The custom profile is enabled but is not user selectable. This setting
>>> +represents a hardware mode on Lenovo Legion devices that enables user
>>> +modifications to Package Power Tracking settings. When an attribute exposed
>>> +by the "Other Mode" WMI Interface is modified, the GameZone driver will switch
>>> +to this profile automatically.
>> I think you should explicitly mention that it's undone if the user
>> selects a fixed platform mode too.  (It does, right?)
> It does as a BIOS feature, acked for fix.

So in order to use the "Other GUID" settings, custom mode has to be selected first, right?

Is it really necessary to do this inside the other-guid driver, or can this be done in userspace?
The reason for this is that i want to avoid having to couple the other-guid driver with the
gamezone guid driver.

>
>>> +
>>> +
>>> +Other Mode
>>> +----------
>>> +The Other Mode WMI interface uses the fw_attributes class to expose various
>>> +WMI functions provided by the interface in the sysfs. This enables CPU and GPU
>>> +power limit tuning as well as various other attributes for devices that fall
>>> +under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by
>>> +the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages
>>> +that allow the driver to probe details about the attribute. Each attibute has
>>> +multiple pages, one for each of the platform profiles managed by the "GameZone"
>>> +interface, so it must be probed prior to returning the current_value. For
>>> +read-only properties, only the "Custom" profile values are reported to ensure
>>> +any userspace applications reading them have accurate tunable value ranges.
>>> +Attributes are exposed in sysfs under the following path:
>>> +/sys/class/firmware-attributes/lenovo-legion-wmi/attributes
>>> +
>>> +Supported Attibutes
>>> +~~~~~~~~~~~~~~~~~~~
>>> +The following attributes are supported:
>>> + - 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
>>> +
>>> +Each attribute has the following properties:
>>> + - current_value
>>> + - default_value
>>> + - display_name
>>> + - max_value
>>> + - min_value
>>> + - scalar_increment
>>> + - type
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index baf0eeb9a355..67f7b588aa36 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13034,6 +13034,15 @@ S:	Maintained
>>>    W:	http://legousb.sourceforge.net/
>>>    F:	drivers/usb/misc/legousbtower.c
>>>
>>> +LENOVO LEGION WMI driver
>>> +M:	Derek J. Clark <derekjohn.clark@gmail.com>
>>> +L:	platform-driver-x86@vger.kernel.org
>>> +S:	Maintained
>>> +F:	drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>>> +F:	drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>>> +F:	drivers/platform/x86/lenovo-legion-wmi-other.c
>>> +F:	drivers/platform/x86/lenovo-legion-wmi.h
>>> +
>>>    LETSKETCH HID TABLET DRIVER
>>>    M:	Hans de Goede <hdegoede@redhat.com>
>>>    L:	linux-input@vger.kernel.org
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 0258dd879d64..a51a1a2fe7ba 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -459,6 +459,41 @@ config IBM_RTL
>>>    	 state = 0 (BIOS SMIs on)
>>>    	 state = 1 (BIOS SMIs off)
>>>
>>> +config LEGION_GAMEZONE_WMI
>>> +	tristate "Lenovo Legion GameZone WMI Driver"
>>> +	depends on ACPI_WMI
>>> +	select ACPI_PLATFORM_PROFILE
>>> +	help
>>> +	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
>>> +	  platform-profile firmware interface.
>>> +
>>> +	  To compile this driver as a module, choose M here: the module will
>>> +	  be called lenovo_legion_wmi_gamezone.
>>> +
>>> +config LEGION_DATA_01_WMI
>>> +	tristate "Lenovo Legion WMI capability Data 01 Driver"
>>> +	depends on ACPI_WMI
>>> +	help
>>> +	  Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series"
>>> +	  line of hardware. This interface is a dependency for exposing tunable power
>>> +	  settings.
>>> +
>>> +	  To compile this driver as a module, choose M here: the module will
>>> +	  be called lenovo_legion_wmi_capdata01.
>>> +
>>> +config LEGION_OTHER_WMI
>>> +	tristate "Lenovo Legion Other Method WMI Driver"
>> As a new user coming here, how are they going to know what "other"
>> means?  I'm sort of thinking it's better to calls this "CUSTOM_WMI"?  Or
>> maybe "CUSTOM_POWER_MODES_WMI"?  Maybe Armin or others have some input
>> here too.
> Other Method is the name Lenovo gave the interface. I'm open to suggestions,
> but Custom Method is the name of the older Legion WMI interface so I'd like to
> reserve that in case Lenovo wants to add it later.

What is the intended purpose of this "Other Method" interface? If its primary purpose
is to provide tuning settings to the system, then you can call it something like:

- LENOVO_WMI_TUNING_SETTINGS

- LENOVO_WMI_TUNING

- ...

>
>>> +	depends on LEGION_GAMEZONE_WMI
>>> +	depends on LEGION_DATA_01_WMI
>>> +	select FW_ATTR_CLASS
>>> +	help
>>> +	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
>>> +	  firmware_attributes API to control various tunable settings typically exposed by
>>> +	  Lenovo software in Windows.
>>> +
>>> +	  To compile this driver as a module, choose M here: the module will
>>> +	  be called lenovo_legion_wmi_other.
>>> +
>>>    config IDEAPAD_LAPTOP
>>>    	tristate "Lenovo IdeaPad Laptop Extras"
>>>    	depends on ACPI
>>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>>> index e1b142947067..838ee568c3f9 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP)	+= hp/
>>>    obj-$(CONFIG_UV_SYSFS)       += uv_sysfs.o
>>>
>>>    # IBM Thinkpad and Lenovo
>>> -obj-$(CONFIG_IBM_RTL)		+= ibm_rtl.o
>>> -obj-$(CONFIG_IDEAPAD_LAPTOP)	+= ideapad-laptop.o
>>> -obj-$(CONFIG_LENOVO_YMC)	+= lenovo-ymc.o
>>> -obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
>>> -obj-$(CONFIG_THINKPAD_ACPI)	+= thinkpad_acpi.o
>>> -obj-$(CONFIG_THINKPAD_LMI)	+= think-lmi.o
>>> -obj-$(CONFIG_YOGABOOK)		+= lenovo-yogabook.o
>>> -obj-$(CONFIG_YT2_1380)		+= lenovo-yoga-tab2-pro-1380-fastcharger.o
>>> -obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
>>> +obj-$(CONFIG_IBM_RTL)			+= ibm_rtl.o
>>> +obj-$(CONFIG_IDEAPAD_LAPTOP)		+= ideapad-laptop.o
>>> +obj-$(CONFIG_LENOVO_YMC)		+= lenovo-ymc.o
>>> +obj-$(CONFIG_SENSORS_HDAPS)		+= hdaps.o
>>> +obj-$(CONFIG_THINKPAD_ACPI)		+= thinkpad_acpi.o
>>> +obj-$(CONFIG_THINKPAD_LMI)		+= think-lmi.o
>>> +obj-$(CONFIG_YOGABOOK)			+= lenovo-yogabook.o
>>> +obj-$(CONFIG_YT2_1380)			+= lenovo-yoga-tab2-pro-1380-fastcharger.o
>>> +obj-$(CONFIG_LENOVO_WMI_CAMERA)		+= lenovo-wmi-camera.o
>>> +obj-$(CONFIG_LEGION_GAMEZONE_WMI)	+= lenovo-legion-wmi-gamezone.o
>>> +obj-$(CONFIG_LEGION_DATA_01_WMI)	+= lenovo-legion-wmi-capdata01.o
>>> +obj-$(CONFIG_LEGION_OTHER_WMI)		+= lenovo-legion-wmi-other.o
>> Don't change the whitespace of everything else; especially not in one
>> patch.  If the whitespace is wrong, do a patch that fixes it and then
>> another patch that introduces a driver.
> Only done because the length of the new entries messes up the whitespace of the
> rest of the block. I can do as two patches if needed, but the whitespace would
> need to be after as it is fine without them.
>
>>>
>>>    # Intel
>>>    obj-y				+= intel/
>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>>> new file mode 100644
>>> index 000000000000..99f4f35b7176
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>>> @@ -0,0 +1,103 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides
>>> + * information on tunable attributes used by the "Other Method" WMI interface,
>>> + * including if it is supported by the hardware, the default_value, max_value,
>>> + * min_value, and step increment.
>>> + *
>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + *
>> I don't think you need a newline at the end here.
> Acked for fix all newline comments. Thanks.
>
>>> + */
>>> +
>>> +#include "lenovo-legion-wmi.h"
>>> +
>>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>>> +
>>> +static const struct wmi_device_id capdata_01_wmi_id_table[] = {
>>> +	{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
>>> +	{}
>>> +};
>>> +
>>> +static struct capdata_wmi cd01_wmi = {
>>> +	.mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex)
>>> +};
>>> +
>>> +int capdata_01_wmi_get(struct om_attribute_id attr_id,
>>> +		       struct capability_data_01 *cap_data)
>>> +{
>>> +	union acpi_object *ret_obj;
>>> +	int count;
>>> +	int instance_id;
>>> +	u32 attribute_id = *(int *)&attr_id;
>> Can please do reverse xmas tree.
>>
> Acked for fix all ordering comments. Thanks.
>
>>> +
>>> +	mutex_lock(&cd01_wmi.mutex);
>>> +	count = wmidev_instance_count(drvdata.cd01_wmi->wdev);
>>> +	mutex_unlock(&cd01_wmi.mutex);
>> For new mutex use I'd suggest using guard(mutex) instead so you can have
>> less lock/unlock/cleanup cases to worry about.
>>
> Good idea, I wasn't aware of this. Will fix up for v2.
>
>>> +	for (instance_id = 0; instance_id < count; instance_id++) {
>>> +		mutex_lock(&cd01_wmi.mutex);
>>> +		ret_obj =
>>> +			wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id);
>>> +		mutex_unlock(&cd01_wmi.mutex);
>>> +		if (!ret_obj) {
>>> +			pr_err("lenovo_legion_wmi_capdata_01: block query failed\n");
>> With all the error messages you should use #define pr_fmt() at the top
>> of the file and then you don't need to do prefixes at all like this.
>>
> Same as above, thanks.
>
>>> +			continue;
>>> +		}
>>> +
>>> +		if (ret_obj->type != ACPI_TYPE_BUFFER) {
>>> +			pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n",
>>> +			       ret_obj->type);
>>> +			kfree(ret_obj);
>>> +			continue;
>>> +		}
>>> +
>>> +		if (ret_obj->buffer.length != sizeof(*cap_data)) {
>>> +			pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n",
>>> +			       ret_obj->buffer.length);
>>> +			kfree(ret_obj);
>>> +			continue;
>>> +		}
>>> +
>>> +		memcpy(cap_data, ret_obj->buffer.pointer,
>>> +		       ret_obj->buffer.length);
>>> +		kfree(ret_obj);
>>> +
>>> +		if (cap_data->id != attribute_id)
>>> +			continue;
>>> +		break;
>>> +	}
>>> +	if (cap_data->id == 0) {
>>> +		pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n");
>>> +		return -EINVAL;
>>> +	}
>>> +	return 0;
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI");
>>> +
>>> +/* Driver Setup */
>>> +static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context)
>>> +{
>>> +	cd01_wmi.wdev = wdev;
>>> +	drvdata.cd01_wmi = &cd01_wmi;
>>> +	pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n");
>>> +
>> Pretty noisy; no?  I think you probably should lose this message.
>>
> Acked for fix all pr_info comments.
>
>>> +	return 0;
>>> +}
>>> +
>>> +static void capdata_01_wmi_remove(struct wmi_device *wdev)
>>> +{
>>> +	pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n");
>> Pretty noisy; no?  I think you probably should lose this message.
>>
>>> +}
>>> +
>>> +static struct wmi_driver capdata_01_wmi_driver = {
>>> +	.driver = { .name = "capdata_01_wmi" },
>>> +	.id_table = capdata_01_wmi_id_table,
>>> +	.probe = capdata_01_wmi_probe,
>>> +	.remove = capdata_01_wmi_remove,
>>> +};
>>> +
>>> +module_wmi_driver(capdata_01_wmi_driver);
>>> +
>>> +MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table);
>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>> +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>>> new file mode 100644
>>> index 000000000000..2f976dc0e367
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>>> @@ -0,0 +1,233 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides
>>> + * platform profile and fan curve settings for devices that fall under the
>>> + * "Gaming Series" of Lenovo Legion devices.
>>> + *
>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + *
>> Drop newline here
>>> + */
>>> +
>>> +#include "lenovo-legion-wmi.h"
>>> +
>>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>>> +
>>> +/* Method IDs */
>>> +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */
>>> +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */
>>> +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */
>>> +
>>> +static const struct wmi_device_id gamezone_wmi_id_table[] = {
>>> +	{ LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
>>> +	{}
>>> +};
>>> +
>>> +static struct gamezone_wmi gz_wmi = {
>>> +	.mutex = __MUTEX_INITIALIZER(gz_wmi.mutex)
>>> +};
>>> +
>>> +/* Platform Profile Methods */
>>> +static int
>>> +gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof,
>>> +					int *supported)
>>> +{
>>> +	int ret;
>>> +
>>> +	mutex_lock(&gz_wmi.mutex);
>> I'd use guard(mutex) instead.  By doing that your function becomes a lot
>> simpler too.
>>
>> guard(mutex)(&gz_wmi.mutex);
>>
>> return lenovo_legion_evaluate_method_1();
>>
>>> +	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
>>> +					      WMI_METHOD_ID_SMARTFAN_SUPP, 0,
>>> +					      supported);
>>> +	mutex_unlock(&gz_wmi.mutex);
>>> +	return ret;
>>> +}
>>> +
>>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
>>> +				 int *sel_prof)
>>> +{
>>> +	int ret;
>>> +	int supported;
>>> +
>>> +	gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
>>> +						&supported);
>>> +	if (!supported) {
>>> +		pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
>> Is this error flow real?  I sort of expect that you can avoid
>> registering if not supporting it.
>>
> This method is an exported symbol in GZ_WMI. I'm not aware of any hardware
> without the GameZone interface that does implement the Other Method interface,
> but if it does exist I was concerned about calling on an interface that isn't
> registered. Perhaps a null pointer check on gz_wmi or gz_wmi.pprof->supported
> check in the other method calls to this would be better? I didn't want to rely
> on pprof exising for the check. I do now realize that this would call on a WMI
> interface that doesn't exist if it was the case this hardware exists.

In general passing WMI devices between different drivers will likely result in device lifetime issues.

I suggest that you decouple both drivers as much as possible and rely on userspace for selecting
custom mode before changing any tuning settings.

See my above comment for more details.

Thanks,
Armin Wolf

>
>>> +		return -EOPNOTSUPP;
>>> +	}
>>> +	mutex_lock(&gz_wmi.mutex);
>> guard(mutex) here too.
>>
>>> +	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
>>> +					      WMI_METHOD_ID_SMARTFAN_GET, 0,
>>> +					      sel_prof);
>>> +	mutex_unlock(&gz_wmi.mutex);
>>> +	return ret;
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI");
>>> +
>>> +static int
>>> +gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof,
>>> +				  enum platform_profile_option *profile)
>>> +{
>>> +	int sel_prof;
>>> +	int err;
>>> +
>>> +	err = gamezone_wmi_fan_profile_get(pprof, &sel_prof);
>>> +	if (err)
>>> +		return err;
>>> +
>>> +	switch (sel_prof) {
>>> +	case SMARTFAN_MODE_QUIET:
>>> +		*profile = PLATFORM_PROFILE_QUIET;
>>> +		break;
>>> +	case SMARTFAN_MODE_BALANCED:
>>> +		*profile = PLATFORM_PROFILE_BALANCED;
>>> +		break;
>>> +	case SMARTFAN_MODE_PERFORMANCE:
>>> +		*profile = PLATFORM_PROFILE_PERFORMANCE;
>>> +		break;
>>> +	case SMARTFAN_MODE_CUSTOM:
>>> +		*profile = PLATFORM_PROFILE_CUSTOM;
>>> +		break;
>>> +
>> Spurious newline.
>>
>>> +	default:
>>> +		return -EINVAL;
>>> +	}
>>> +	drvdata.gz_wmi->current_profile = *profile;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
>>> +				      enum platform_profile_option profile)
>>> +{
>>> +	int ret;
>>> +	int sel_prof;
>>> +	int supported;
>>> +
>>> +	gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
>>> +						&supported);
>>> +	if (!supported) {
>>> +		pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
>>> +		return -EOPNOTSUPP;
>>> +	}
>> Same question; is this a real error flow?
>>
> Also an exported symbol in GZ_WMI. Will find another way to do these checks in
> Other Method.
>
>>> +
>>> +	switch (profile) {
>>> +	case PLATFORM_PROFILE_QUIET:
>>> +		sel_prof = SMARTFAN_MODE_QUIET;
>>> +		break;
>>> +	case PLATFORM_PROFILE_BALANCED:
>>> +		sel_prof = SMARTFAN_MODE_BALANCED;
>>> +		break;
>>> +	case PLATFORM_PROFILE_PERFORMANCE:
>>> +		sel_prof = SMARTFAN_MODE_PERFORMANCE;
>>> +		break;
>>> +	case PLATFORM_PROFILE_CUSTOM:
>>> +		sel_prof = SMARTFAN_MODE_CUSTOM;
>>> +		break;
>>> +	default:
>>> +		return -EOPNOTSUPP;
>>> +	}
>>> +
>>> +	mutex_lock(&gz_wmi.mutex);
>> guard(mutex) here.
>>> +	ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
>>> +					      WMI_METHOD_ID_SMARTFAN_SET,
>>> +					      sel_prof, NULL);
>>> +	mutex_unlock(&gz_wmi.mutex);
>>> +
>>> +	if (ret) {
>>> +		pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	drvdata.gz_wmi->current_profile = profile;
>>> +	return 0;
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI");
>>> +
>>> +/* Driver Setup */
>>> +static int platform_profile_setup(struct gamezone_wmi *gz_wmi)
>>> +{
>>> +	int err;
>>> +	int supported;
>>> +
>>> +	gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported);
>>> +
>>> +	gz_wmi->platform_profile_support = supported;
>>> +
>>> +	if (!supported) {
>>> +		pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
>>> +		return -EOPNOTSUPP;
>>> +	}
>> Yeah because of this you don't need that other flow I was mentioning above.
>>
>> IMO I don't think the pr_warn() is really needed, you'll only really
>> have one way that you exit -EOPNOTSUPP.
>>
> Will remove warn, thanks.
>
>>> +
>>> +	gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get;
>>> +	gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set;
>>> +
>>> +	set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices);
>>> +	set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices);
>>> +	set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices);
>>> +	set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices);
>>> +
>>> +	err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof,
>>> +						&gz_wmi->current_profile);
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n",
>>> +		       err);
>> Drop prefix on the error and use pr_fmt().
>>
>>> +		return err;
>>> +	}
>>> +
>>> +	err = platform_profile_register(&gz_wmi->pprof);
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n",
>>> +		       err);
>> Drop prefix on the error and use pr_fmt().
>>
>>> +		return err;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context)
>>> +{
>>> +	int err;
>>> +
>>> +	gz_wmi.wdev = wdev;
>>> +	drvdata.gz_wmi = &gz_wmi;
>>> +
>>> +	err = platform_profile_setup(&gz_wmi);
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n",
>>> +		       err);
>>> +		kfree(&gz_wmi);
>> Is this free correct?  It's a global isn't it?  I don't think you should
>> be freeing here.
>>
> I'll just return the error.
>
>>> +		return err;
>>> +	}
>>> +
>>> +	pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n");
>> Too noisy.
>>
>>> +	return 0;
>>> +}
>>> +
>>> +static void gamezone_wmi_remove(struct wmi_device *wdev)
>>> +{
>>> +	int err;
>>> +
>>> +	mutex_lock(&gz_wmi.mutex);
>>> +	err = platform_profile_remove(&drvdata.gz_wmi->pprof);
>>> +	mutex_unlock(&gz_wmi.mutex);
>>> +
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n",
>>> +		       err);
>>> +	} else {
>>> +		pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n");
>>> +	}
>> Considering that platform_profile_remove() doesn't really have a failure
>> path (it always returns 0). I'd just lose both of these messages and
>> make this simple.
>>
>> guard(mutex)();
>> platform_profile_remove();
>>
> Acked for fix.
>
>>> +}
>>> +
>>> +static struct wmi_driver gamezone_wmi_driver = {
>>> +	.driver = { .name = "gamezone_wmi" },
>>> +	.id_table = gamezone_wmi_id_table,
>>> +	.probe = gamezone_wmi_probe,
>>> +	.remove = gamezone_wmi_remove,
>>> +};
>>> +
>>> +module_wmi_driver(gamezone_wmi_driver);
>>> +
>>> +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table);
>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>> +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c
>>> new file mode 100644
>>> index 000000000000..c09c1848eda7
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-legion-wmi-other.c
>>> @@ -0,0 +1,377 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * Lenovo Legion Other Method driver. This driver uses the fw_attributes
>>> + * class to expose the various WMI functions provided by the "Other Method" WMI
>>> + * interface. This enables CPU and GPU power limit as well as various other
>>> + * attributes for devices that fall under the "Gaming Series" of Lenovo Legion
>>> + * devices. Each attribute exposed by the "Other Method"" interface has a
>>> + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to
>>> + * probe details about the attribute such as set/get support, step, min, max,
>>> + * and default value. Each attibute has multiple pages, one for each of the
>>> + * fan profiles managed by the GameZone interface, so it must be probed prior
>>> + * to returning the current_value.
>>> + *
>>> + * These attributes typically don't fit anywhere else in the sysfs and are set
>>> + * in Windows using one of Lenovo's multiple user applications.
>>> + *
>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + *
>> Remove the new line here.
>>> + */
>>> +
>>> +#include "lenovo-legion-wmi.h"
>>> +#include "firmware_attributes_class.h"
>>> +
>>> +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
>>> +
>>> +/* Device IDs */
>>> +#define WMI_DEVICE_ID_CPU 0x01
>>> +
>>> +/* WMI_DEVICE_ID_CPU feature IDs */
>>> +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */
>>> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
>>> +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */
>>> +
>>> +/* Method IDs */
>>> +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */
>>> +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */
>>> +
>>> +static const struct wmi_device_id other_method_wmi_id_table[] = {
>>> +	{ LENOVO_OTHER_METHOD_GUID, NULL },
>>> +	{}
>>> +};
>>> +
>>> +/* Tunable Attributes */
>>> +struct ll_tunables {
>>> +	u32 ppt_pl1_spl;
>>> +	u32 ppt_pl2_sppt;
>>> +	u32 ppt_pl3_fppt;
>>> +};
>>> +
>>> +static const struct class *fw_attr_class;
>>> +
>>> +static struct other_method_wmi om_wmi = {
>>> +	.mutex = __MUTEX_INITIALIZER(om_wmi.mutex)
>>> +};
>>> +
>>> +struct capdata_01_attr_group {
>>> +	const struct attribute_group *attr_group;
>>> +};
>>> +
>>> +/* Simple attribute creation */
>>> +
>>> +/*
>>> + * att_current_value_store() - Set the current value of the given attribute
>>> + * @kobj: Pointer to the driver object.
>>> + * @kobj_attribute: Pointer to the attribute calling this function.
>>> + * @buf: The buffer to read from, this is parsed to `int` type.
>>> + * @count: Required by sysfs attribute macros, pass in from the callee attr.
>>> + * @store_value: Pointer to where the parsed value should be stored.
>>> + * @device_id: The WMI function Device ID to use.
>>> + * @feature_id: The WMI function Feature ID to use.
>>> + *
>>> + * This function is intended to be generic so it can be called from any
>>> + * attribute's "current_value_store" which works only with integers. The
>>> + * integer to be sent to the WMI method is range checked and an error returned
>>> + * if out of range.
>>> + *
>>> + * If the value is valid and WMI is success, then the sysfs attribute is
>>> + * notified.
>>> + *
>>> + * Returns: Either count, or an error.
>>> + */
>>> +ssize_t attr_current_value_store(struct kobject *kobj,
>>> +				 struct kobj_attribute *attr, const char *buf,
>>> +				 size_t count, u32 *store_value, u8 device_id,
>>> +				 u8 feature_id)
>>> +{
>>> +	struct capability_data_01 cap_data;
>>> +	enum platform_profile_option cust_prof;
>>> +	int err;
>>> +	int sel_prof;
>>> +	u32 value;
>>> +	struct wmi_device *wdev = drvdata.om_wmi->wdev;
>>> +
>>> +	err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");
>> Use pr_fmt() for teh file instead of prefix here.
>>
>>> +		return -EIO;
>>> +	}
>>> +
>>> +	/* Switch to custom profile if not currently on it. */
>>> +	if (sel_prof != SMARTFAN_MODE_CUSTOM) {
>>> +		pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables.");
>> As you do this "for" them, I'd lose the warning.
>>
> Acked for fix. Leftover from an earlier version that didn't set the profile.
>
>>> +		cust_prof = PLATFORM_PROFILE_CUSTOM;
>>> +		sel_prof = SMARTFAN_MODE_CUSTOM;
>>> +		err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof,
>>> +							cust_prof);
>>> +		if (err) {
>>> +			pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n");
>>> +			return -EIO;
>>> +		}
>>> +	}
>>> +
>>> +	err = kstrtouint(buf, 10, &value);
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_other: Error converting value to int.\n");
>>> +		return -EIO;
>>> +	}
>>> +
>>> +	/* Construct the attribute id */
>>> +	struct om_attribute_id attr_id = { sel_prof << 8, feature_id,
>>> +					   device_id };
>>> +
>>> +	/* Get min/max from LENOVO_CAPABILITY_DATA_01 */
>>> +	err = capdata_01_wmi_get(attr_id, &cap_data);
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
>>> +		return -EIO;
>>> +	}
>>> +	if (cap_data.capability < 1) {
>>> +		pr_err("lenovo_legion_wmi_other: Capability not supported.\n");
>>> +		return -EPERM;
>>> +	}
>>> +
>>> +	if (value < cap_data.min_value || value > cap_data.max_value) {
>>> +		pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n",
>>> +			value, cap_data.min_value, cap_data.max_value);
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	mutex_lock(&om_wmi.mutex);
>>> +	err = lenovo_legion_evaluate_method_2(wdev, 0x0,
>>> +					      WMI_METHOD_ID_VALUE_SET,
>>> +					      *(int *)&attr_id, value, NULL);
>>> +	mutex_unlock(&om_wmi.mutex);
>>> +
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_other: Error setting attribute");
>>> +		return err;
>>> +	}
>>> +
>>> +	if (store_value)
>>> +		*store_value = value;
>>> +
>>> +	sysfs_notify(kobj, NULL, attr->attr.name);
>>> +	return count;
>>> +}
>>> +
>>> +/*
>>> + * attr_current_value_show() - Get the current value of the given attribute
>>> + * @kobj: Pointer to the driver object.
>>> + * @kobj_attribute: Pointer to the attribute calling this function.
>>> + * @buf: The buffer to write to.
>>> + * @retval: Pointer to returned data.
>>> + * @device_id: The WMI function Device ID to use.
>>> + * @feature_id: The WMI function Feature ID to use.
>>> + *
>>> + * This function is intended to be generic so it can be called from any "_show"
>>> + * attribute which works only with integers.
>>> + *
>>> + * If the WMI is success, then the sysfs attribute is notified.
>>> + *
>>> + * Returns: Either count, or an error.
>>> + */
>>> +ssize_t attr_current_value_show(struct kobject *kobj,
>>> +				struct kobj_attribute *attr, char *buf,
>>> +				u8 device_id, u8 feature_id)
>>> +{
>>> +	int sel_prof; /* Current fan profile mode */
>>> +	int err;
>>> +	int retval;
>>> +	struct wmi_device *wdev = drvdata.om_wmi->wdev;
>>> +
>>> +	err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
>>> +
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");
>>> +		return err;
>>> +	}
>>> +
>>> +	// Construct the WMI attribute id from the given args.
>>> +	struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
>>> +						device_id };
>>> +
>>> +	mutex_lock(&om_wmi.mutex);
>>> +	err = lenovo_legion_evaluate_method_1(wdev, 0x0,
>>> +					      WMI_METHOD_ID_VALUE_GET,
>>> +					      *(int *)&attribute_id, &retval);
>>> +	mutex_unlock(&om_wmi.mutex);
>>> +
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_other: Error getting attribute");
>>> +		return err;
>>> +	}
>>> +
>>> +	return sysfs_emit(buf, "%u\n", retval);
>>> +}
>>> +
>>> +/**
>>> + * attr_capdata_01_show() - Get the value of the specified attribute property
>>> + * from LENOVO_CAPABILITY_DATA_01.
>>> + * @kobj: Pointer to the driver object.
>>> + * @kobj_attribute: Pointer to the attribute calling this function.
>>> + * @buf: The buffer to write to.
>>> + * @retval: Pointer to returned data.
>>> + * @device_id: The WMI functions Device ID to use.
>>> + * @feature_id: The WMI functions Feature ID to use.
>>> + * @prop: The property of this attribute to be read.
>>> + *
>>> + * This function is intended to be generic so it can be called from any "_show"
>>> + * attribute which works only with integers.
>>> + *
>>> + * If the WMI is success, then the sysfs attribute is notified.
>>> + *
>>> + * Returns: Either count, or an error.
>>> + */
>>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
>>> +			     char *buf, u8 device_id, u8 feature_id,
>>> +			     enum attribute_property prop)
>>> +{
>>> +	struct capability_data_01 cap_data;
>>> +	int err;
>>> +	int retval;
>>> +	int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */
>>> +
>>> +	// Construct the WMI attribute id from the given args.
>>> +	struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
>>> +						device_id };
>>> +
>>> +	err = capdata_01_wmi_get(attribute_id, &cap_data);
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
>>> +		return -EIO;
>>> +	}
>>> +
>>> +	switch (prop) {
>>> +	case DEFAULT_VAL:
>>> +		retval = cap_data.default_value;
>>> +		break;
>>> +	case MAX_VAL:
>>> +		retval = cap_data.max_value;
>>> +		break;
>>> +	case MIN_VAL:
>>> +		retval = cap_data.min_value;
>>> +		break;
>>> +	case STEP_VAL:
>>> +		retval = cap_data.step;
>>> +		break;
>>> +	default:
>>> +		return -EINVAL;
>>> +	}
>>> +	return sysfs_emit(buf, "%u\n", retval);
>>> +}
>>> +
>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU,
>>> +			    WMI_FEATURE_ID_CPU_SPL,
>>> +			    "Set the CPU sustained power limit");
>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU,
>>> +			    WMI_FEATURE_ID_CPU_SPPT,
>>> +			    "Set the CPU slow package power tracking limit");
>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU,
>>> +			    WMI_FEATURE_ID_CPU_FPPT,
>>> +			    "Set the CPU fast package power tracking limit");
>>> +
>>> +static const struct capdata_01_attr_group capdata_01_attr_groups[] = {
>>> +	{ &ppt_pl1_spl_attr_group },
>>> +	{ &ppt_pl2_sppt_attr_group },
>>> +	{ &ppt_pl3_fppt_attr_group },
>>> +	{},
>>> +};
>>> +
>>> +static int other_method_fw_attr_add(void)
>>> +{
>>> +	int err, i;
>>> +
>>> +	err = fw_attributes_class_get(&fw_attr_class);
>>> +	if (err) {
>>> +		pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n");
>>> +		return err;
>>> +	}
>>> +
>>> +	om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
>>> +					   NULL, "%s", DRIVER_NAME);
>>> +	if (IS_ERR(om_wmi.fw_attr_dev)) {
>>> +		pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n");
>>> +		err = PTR_ERR(om_wmi.fw_attr_dev);
>>> +		goto fail_class_get;
>>> +	}
>>> +
>>> +	om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL,
>>> +						  &om_wmi.fw_attr_dev->kobj);
>>> +	if (!om_wmi.fw_attr_kset) {
>>> +		pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n");
>>> +		err = -ENOMEM;
>>> +		goto err_destroy_classdev;
>>> +	}
>>> +
>>> +	for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) {
>>> +		err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj,
>>> +					 capdata_01_attr_groups[i].attr_group);
>>> +		if (err) {
>>> +			pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n",
>>> +			       capdata_01_attr_groups[i].attr_group->name);
>>> +			goto err_remove_groups;
>>> +		}
>>> +	}
>>> +
>>> +	return 0;
>>> +
>>> +err_remove_groups:
>>> +	while (--i >= 0) {
>>> +		sysfs_remove_group(&om_wmi.fw_attr_kset->kobj,
>>> +				   capdata_01_attr_groups[i].attr_group);
>>> +	}
>>> +err_destroy_classdev:
>>> +	device_destroy(fw_attr_class, MKDEV(0, 0));
>>> +fail_class_get:
>>> +	fw_attributes_class_put();
>>> +	return err;
>>> +}
>>> +
>>> +/* Driver Setup */
>>> +static int other_method_wmi_probe(struct wmi_device *wdev, const void *context)
>>> +{
>>> +	int err;
>>> +
>>> +	om_wmi.wdev = wdev;
>>> +	drvdata.om_wmi = &om_wmi;
>>> +	om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL);
>>> +	if (!om_wmi.ll_tunables)
>>> +		return -ENOMEM;
>>> +
>>> +	err = other_method_fw_attr_add();
>>> +	if (err)
>>> +		return err;
>>> +	pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n");
>> too noisy
>>
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static void other_method_wmi_remove(struct wmi_device *wdev)
>>> +{
>>> +	mutex_lock(&om_wmi.mutex);
>>> +
>>> +	kset_unregister(om_wmi.fw_attr_kset);
>>> +	device_destroy(fw_attr_class, MKDEV(0, 0));
>>> +	fw_attributes_class_put();
>>> +
>>> +	mutex_unlock(&om_wmi.mutex);
>>> +
>>> +	pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n");
>> too noisy.
>>
>>> +}
>>> +
>>> +static struct wmi_driver other_method_wmi_driver = {
>>> +	.driver = { .name = "other_method_wmi" },
>>> +	.id_table = other_method_wmi_id_table,
>>> +	.probe = other_method_wmi_probe,
>>> +	.remove = other_method_wmi_remove,
>>> +};
>>> +
>>> +module_wmi_driver(other_method_wmi_driver);
>>> +
>>> +MODULE_IMPORT_NS("GZ_WMI");
>>> +MODULE_IMPORT_NS("CAPDATA_WMI");
>>> +MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table);
>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>> +MODULE_DESCRIPTION("Lenovo Legion Other Method Driver");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h
>>> new file mode 100644
>>> index 000000000000..65baa728f29e
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-legion-wmi.h
>>> @@ -0,0 +1,271 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-or-later
>>> + *
>>> + * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is
>>> + * broken up into multiple GUID interfaces that require cross-references
>>> + * between GUID's for some functionality. The "Custom Mode" interface is a
>>> + * legacy interface for managing and displaying CPU & GPU power and hwmon
>>> + * settings and readings. The "Other Mode" interface is a modern interface
>>> + * that replaces or extends the "Custom Mode" interface methods. The "GameZone"
>>> + * interface adds advanced features such as fan profiles and overclocking.
>>> + * The "Lighting" interface adds control of various status lights related to
>>> + * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00
>>> + * struct for capability information, "Other Mode" uses
>>> + * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting"
>>> + * uses LENOVO_CAPABILITY_DATA_02 struct for capability information.
>>> + *
>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + *
>> Lose the newline
>>
>>> + */
>>> +
>>> +#ifndef _LENOVO_LEGION_WMI_H_
>>> +#define _LENOVO_LEGION_WMI_H_
>>> +
>>> +#include <linux/mutex.h>
>>> +#include <linux/platform_profile.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +
>>> +#define DRIVER_NAME "lenovo-legion-wmi"
>> This is only used in one of the drivers, I'd move it there to make it
>> clearer.
>>
> Acked for fix.
>
>>> +
>>> +/* Platform Profile Modes */
>>> +#define SMARTFAN_MODE_QUIET 0x01
>>> +#define SMARTFAN_MODE_BALANCED 0x02
>>> +#define SMARTFAN_MODE_PERFORMANCE 0x03
>>> +#define SMARTFAN_MODE_CUSTOM 0xFF
>>> +
>>> +struct gamezone_wmi {
>>> +	struct wmi_device *wdev;
>>> +	enum platform_profile_option current_profile;
>>> +	struct platform_profile_handler pprof;
>>> +	bool platform_profile_support;
>>> +	struct mutex mutex; /* Ensure single operation on WMI device */
>>> +};
>>> +
>>> +struct other_method_wmi {
>>> +	struct wmi_device *wdev;
>>> +	struct device *fw_attr_dev;
>>> +	struct kset *fw_attr_kset;
>>> +	struct ll_tunables *ll_tunables;
>>> +	struct mutex mutex; /* Ensure single operation on WMI device */
>>> +};
>>> +
>>> +struct capdata_wmi {
>>> +	struct wmi_device *wdev;
>>> +	struct mutex mutex; /* Ensure single operation on WMI device */
>>> +};
>>> +
>>> +struct ll_drvdata {
>>> +	struct other_method_wmi *om_wmi; /* Other method GUID device */
>>> +	struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */
>>> +	struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */
>>> +} drvdata;
>>> +
>>> +struct wmi_method_args {
>>> +	u32 arg0;
>>> +	u32 arg1;
>>> +};
>>> +
>>> +struct om_attribute_id {
>>> +	u32 mode_id : 16; /* Fan profile */
>>> +	u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
>>> +	u32 device_id : 8; /* CPU/GPU */
>>> +} __packed;
>>> +
>>> +enum attribute_property {
>>> +	DEFAULT_VAL = 0,
>>> +	MAX_VAL,
>>> +	MIN_VAL,
>>> +	STEP_VAL,
>>> +	SUPPORTED,
>>> +};
>>> +
>>> +struct capability_data_01 {
>>> +	u32 id;
>>> +	u32 capability;
>>> +	u32 default_value;
>>> +	u32 step;
>>> +	u32 min_value;
>>> +	u32 max_value;
>>> +};
>>> +
>>> +static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance,
>>> +					 u32 method_id, struct acpi_buffer *in,
>>> +					 struct acpi_buffer *out)
>>> +{
>>> +	acpi_status status;
>>> +
>>> +	status = wmidev_evaluate_method(wdev, instance, method_id, in, out);
>>> +
>>> +	if (ACPI_FAILURE(status)) {
>>> +		pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n",
>>> +		       method_id, instance);
>>> +		return -EIO;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>>> +				    u32 method_id, u32 arg0, u32 arg1,
>>> +				    u32 *retval);
>>> +
>>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>>> +				    u32 method_id, u32 arg0, u32 arg1,
>>> +				    u32 *retval)
>>> +{
>>> +	int ret;
>>> +	u32 temp_val;
>>> +	struct wmi_method_args args = { arg0, arg1 };
>>> +	struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
>>> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>>> +	union acpi_object *ret_obj = NULL;
>> Reverse xmas tree please.
>>
>>> +
>>> +	ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input,
>>> +					    &output);
>>> +
>>> +	if (ret) {
>>> +		pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n",
>>> +		       method_id, ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	if (retval) {
>>> +		ret_obj = (union acpi_object *)output.pointer;
>>> +		if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER)
>>> +			temp_val = (u32)ret_obj->integer.value;
>> This is a pretty bad failure if it's not the case, no?  Should you set a
>> return value here instead perhaps?
>>
>>> +
>>> +		*retval = temp_val
>> If that above error I mentioned happens then you'll be assigning garbage
>> data out.
>>
>> ;
> True, good catch. Someone built with clang+lto and it warned about this section
> as well as temp val is not initialized. Will fix both.
>
>>> +	}
>>> +
>>> +	kfree(ret_obj);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>>> +				    u32 method_id, u32 arg0, u32 *retval);
>>> +
>>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>>> +				    u32 method_id, u32 arg0, u32 *retval)
>>> +{
>>> +	return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0,
>>> +					       0, retval);
>>> +}
>>> +
>>> +int capdata_01_wmi_get(struct om_attribute_id attr_id,
>>> +		       struct capability_data_01 *cap_data);
>>> +
>>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
>>> +				      enum platform_profile_option sel_prof);
>>> +
>>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
>>> +				 int *sel_prof);
>>> +
>>> +/* current_value */
>>> +ssize_t attr_current_value_store(struct kobject *kobj,
>>> +				 struct kobj_attribute *attr, const char *buf,
>>> +				 size_t count, u32 *store_value, u8 device_id,
>>> +				 u8 feature_id);
>>> +
>>> +ssize_t attr_current_value_show(struct kobject *kobj,
>>> +				struct kobj_attribute *attr, char *buf,
>>> +				u8 device_id, u8 feature_id);
>>> +
>>> +/* LENOVO_CAPABILITY_DATA_01 */
>>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
>>> +			     char *buf, u8 device_id, u8 feature_id,
>>> +			     enum attribute_property prop);
>>> +
>>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
>>> +		      char *buf);
>>> +
>>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
>>> +		      char *buf)
>>> +{
>>> +	return sysfs_emit(buf, "integer\n");
>>> +}
>>> +
>>> +#define __LL_ATTR_RO(_func, _name)                                    \
>>> +	{                                                             \
>>> +		.attr = { .name = __stringify(_name), .mode = 0444 }, \
>>> +		.show = _func##_##_name##_show,                       \
>>> +	}
>>> +
>>> +#define __LL_ATTR_RO_AS(_name, _show)                                 \
>>> +	{                                                             \
>>> +		.attr = { .name = __stringify(_name), .mode = 0444 }, \
>>> +		.show = _show,                                        \
>>> +	}
>>> +
>>> +#define __LL_ATTR_RW(_func, _name) \
>>> +	__ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
>>> +
>>> +/* Shows a formatted static variable */
>>> +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val)                         \
>>> +	static ssize_t _attrname##_##_prop##_show(                            \
>>> +		struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
>>> +	{                                                                     \
>>> +		return sysfs_emit(buf, _fmt, _val);                           \
>>> +	}                                                                     \
>>> +	static struct kobj_attribute attr_##_attrname##_##_prop =             \
>>> +		__LL_ATTR_RO(_attrname, _prop)
>>> +
>>> +/* Attribute current_value show/store */
>>> +#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id)                   \
>>> +	static ssize_t _attrname##_current_value_store(                       \
>>> +		struct kobject *kobj, struct kobj_attribute *attr,            \
>>> +		const char *buf, size_t count)                                \
>>> +	{                                                                     \
>>> +		return attr_current_value_store(                              \
>>> +			kobj, attr, buf, count,                               \
>>> +			&om_wmi.ll_tunables->_attrname, _dev_id, _feat_id);   \
>>> +	}                                                                     \
>>> +	static ssize_t _attrname##_current_value_show(                        \
>>> +		struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
>>> +	{                                                                     \
>>> +		return attr_current_value_show(kobj, attr, buf, _dev_id,      \
>>> +					       _feat_id);                     \
>>> +	}                                                                     \
>>> +	static struct kobj_attribute attr_##_attrname##_current_value =       \
>>> +		__LL_ATTR_RW(_attrname, current_value)
>>> +
>>> +/* Attribute property show only */
>>> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \
>>> +	static ssize_t _attrname##_##_prop##_show(                             \
>>> +		struct kobject *kobj, struct kobj_attribute *attr, char *buf)  \
>>> +	{                                                                      \
>>> +		return attr_capdata_01_show(kobj, attr, buf, _dev_id,          \
>>> +					    _feat_id, _prop_type);             \
>>> +	}                                                                      \
>>> +	static struct kobj_attribute attr_##_attrname##_##_prop =              \
>>> +		__LL_ATTR_RO(_attrname, _prop)
>>> +
>>> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id,    \
>>> +				    _dispname)                                \
>>> +	__LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id);                  \
>>> +	__LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id,    \
>>> +			      DEFAULT_VAL);                                   \
>>> +	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);          \
>>> +	__LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id,        \
>>> +			      MAX_VAL);                                       \
>>> +	__LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id,        \
>>> +			      MIN_VAL);                                       \
>>> +	__LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \
>>> +			      STEP_VAL);                                      \
>>> +	static struct kobj_attribute attr_##_attrname##_type =                \
>>> +		__LL_ATTR_RO_AS(type, int_type_show);                         \
>>> +	static struct attribute *_attrname##_attrs[] = {                      \
>>> +		&attr_##_attrname##_current_value.attr,                       \
>>> +		&attr_##_attrname##_default_value.attr,                       \
>>> +		&attr_##_attrname##_display_name.attr,                        \
>>> +		&attr_##_attrname##_max_value.attr,                           \
>>> +		&attr_##_attrname##_min_value.attr,                           \
>>> +		&attr_##_attrname##_scalar_increment.attr,                    \
>>> +		&attr_##_attrname##_type.attr,                                \
>>> +		NULL,                                                         \
>>> +	};                                                                    \
>>> +	static const struct attribute_group _attrname##_attr_group = {        \
>>> +		.name = _fsname, .attrs = _attrname##_attrs                   \
>>> +	}
>>> +
>>> +#endif /* !_LENOVO_LEGION_WMI_H_ */

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-22  8:42 ` John Martens
       [not found]   ` <CAFqHKT==PiLb_VuFPHJzgfrprukGGig+iUzcih+3TJ1VYt94Dw@mail.gmail.com>
@ 2024-12-22 23:05   ` Armin Wolf
  2024-12-26  0:18     ` John Martens
  1 sibling, 1 reply; 29+ messages in thread
From: Armin Wolf @ 2024-12-22 23:05 UTC (permalink / raw)
  To: John Martens, derekjohn.clark
  Cc: corbet, hdegoede, ilpo.jarvinen, linux-doc, linux-kernel, luke,
	mpearson-lenovo, nijs1, pgriffais, platform-driver-x86, shaohz1,
	superm1, zhangzx36

Am 22.12.24 um 09:42 schrieb John Martens:

>> Adds support for the Lenovo Legion series of laptop hardware to use WMI
>> interfaces that control various power settings.
> Note that there already is a driver for Lenovo Legion laptops that I
> wanted to merge upstream.
>
> https://github.com/johnfanv2/LenovoLegionLinux
>
> Compared to the proposed patch, it has the following
> advantages:
> 1. already popular and tested by thousands of users
>      - many stars and discussions on github
>      - patched into multiple kernels of gaming-related distros
>      - packaged as dkms module for almost all relevant Linux
>        distributions including Debian by other developers
> 2. supports many different Lenovo Legion models starting from 2020/2021
> 3. supports a lot of more functions, including fan control, which is the
>    most requested feature
> 4. supports the many changes between different in the WMI/ACPI method
> 5. actually shares some credtis with persons who revere engineered it :)
> 6. support by GUI tool to configure it all
>
> On the other hand, my driver has the following disadvantages:
> 1. The version of master on github is the most recent one and contains
>     a lot of debug output that has to be removed (often indicated by TODO)
> 2. It is all in one large c file instead of organizing it neatly into
>     multiple files.
> 3. It was modeled after the ideapad driver instead of the newer ASUS
>     driver.
>
> A few notes regarding the many changes of the WMI methods that I tried
> to deal with in my driver: note that in almost every new model a new
> WMI method is used to control the same functionality (e.g. fan control
>   or powermode). Additionally, often the constants or the unit changes
> : e.g. percent or rpm for fan speed.
>
>> The driver has been tested by me on the Lenovo Legion Go.
> The driver on github has been tested by thousands of users.
>
> I suggest that we maybe combine the two drivers before merging them,
> since Derek seems to have more kernel patching knowledge and I seem
> to have more worked on all the Legion laptops.
>
I agree in combining both drivers.

I suggest that we first upstream the bare-minimum for supporting the gamezone GUID
(no fancy features, just the basics) so that:

1. More features can be added later.

2. People can work on the other-guid drivers.

Sadly your WMI driver needs some work too since it uses the deprecated GUID-based WMI interface.
Because of this i suggest that we first upstream Dereks driver for the gamezone GUID, which can
then be extended by you step-by-step.

Thanks,
Armin Wolf


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-22 22:55       ` Armin Wolf
@ 2024-12-23 15:47         ` Derek John Clark
  2024-12-23 16:50           ` Armin Wolf
  0 siblings, 1 reply; 29+ messages in thread
From: Derek John Clark @ 2024-12-23 15:47 UTC (permalink / raw)
  To: Armin Wolf
  Cc: Mario Limonciello, Hans de Goede, Ilpo Järvinen,
	Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
	Mark Pearson, Pierre-Loup A . Griffais, platform-driver-x86,
	linux-doc, linux-kernel

>> Can do. It might still make sense to have capdata01 with the other_method
>> driver, it has no functionality on its own and is a dependency of other_method.
>> It isn't a problem to have capdata01 as an earlier patch than other_method
>> though if preferred. I'm not sure how I would break up the drivers further
>> than that, except the relevant header portions per c file.
>
>I think the gamezone driver should  be totally independent of the other driver.
>
>For the capdata01 and the other-guid driver i think the component framework could be handy,
>see https://docs.kernel.org/driver-api/component.html for details. Basically you will have
>a driver for capdata01 and another driver for other-guid which will both register a component.
>The firmware-attributes part will only get loaded if both components are present.

This is really cool and just what I need. I'll look into it, thanks.

>Please keep in mind that new WMI drivers need to be instantiated multiple times, see
>https://docs.kernel.org/wmi/driver-development-guide.html for details.

I must have missed that. I'll incorporate it into v2, thanks.

>> It does as a BIOS feature, acked for fix.
>
>So in order to use the "Other GUID" settings, custom mode has to be selected first, right?
>
>Is it really necessary to do this inside the other-guid driver, or can this be done in userspace?
>The reason for this is that i want to avoid having to couple the other-guid driver with the
>gamezone guid driver.
>

The only major issue is there is (currently) no way to actually set custom mode
from userspace. As designed, "custom" is not selectable through the
platform_profile sysfs file descriptor even when enabled as any attempt to write
"custom" to it returns "Invalid argument". This will need to be changed if
we're going to move forward with the interfaces decoupled, unless
there is another
way to handle this I'm not aware of.

Another, not very severe issue, is the OM interface will always permit
a write to any
page even if the gamezone interface isn't set to custom mode. I did some testing
and there doesn't seem to be any effect on the hardware when this happens, but
having no effect when writing to the attribute endpoints could lead to erroneous
bug reports from uninformed users. I suppose that is better than bug
reports from
null pointer reference if something goes wrong with the gamezone interface.

I did consider trying to grab a pointer to platform_profile to watch
the state instead
of gamezone, but that isn't reliable either as it will report "custom"
any time two
providers disagree. Since amd-pmf uses "low-power" and Lenovo uses "quiet" as
their respective lowest settings, platform_profile will report
"custom" any time the
hardware is on the lowest profile. The obvious answer there might be to enable
the low-power enum instead of quiet on the gamezone interface, but that also
assumes no other provider will ever register with another value. Do
you think this
is worth pursuing?

>> Other Method is the name Lenovo gave the interface. I'm open to suggestions,
>> but Custom Method is the name of the older Legion WMI interface so I'd like to
>> reserve that in case Lenovo wants to add it later.
>
>What is the intended purpose of this "Other Method" interface? If its primary purpose
>is to provide tuning settings to the system, then you can call it something like:
>
>- LENOVO_WMI_TUNING_SETTINGS
>
>- LENOVO_WMI_TUNING
>
>- ...

I think we're of a similar mind here as I changed it to LENOVO_WMI_TUNABLES in
my working branch already. I like LENOVO_WMI_TUNING more though.

>In general passing WMI devices between different drivers will likely result in device lifetime issues.
>
>I suggest that you decouple both drivers as much as possible and rely on userspace for selecting
>custom mode before changing any tuning settings.
>
>See my above comment for more details.
>
>Thanks,
>Armin Wolf

Thanks for taking a look Armin. I will be away for the next few days with only
my mobile phone which seems to be having issues sending plain text. I might not
be able to respond again until the end of the week.

Derek

On Sun, Dec 22, 2024 at 2:55 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 18.12.24 um 04:36 schrieb Derek J. Clark:
>
> > Hi Mario,
> >
> > Thank you for taking a look at it so quickly.
> >
> >>> Adds lenovo-legion-wmi.h which provides templates and some method
> >>> implementations used by the lenovo-legion-wmi driver series.
> >>>
> >>> Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo
> >>> GameZone WMI interface that comes on Lenovo "Gaming Series" hardware.
> >>> Provides ACPI platform profiles over WMI.
> >>>
> >>> Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo
> >>> "Other Method" WMI interface that comes on some Lenovo hardware.
> >>> Provides a firmware-attributes class which enables the use of tunable
> >>> knobs for SPL, SPPT, and FPPT.
> >>>
> >>> Adds lenovo-legion-wmi-capdata01.c which provides a driver for the
> >>> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method"
> >>> enabled hardware. Provides an interface for querying if a given
> >>> attribute is supported by the hardware, as well as its default_value,
> >>> max_value, min_value, and step increment.
> >>>
> > z>> Adds lenovo-legion-wmi.rst describing the available drivers and their
> >>> function.
> >>>
> >>> Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers.
> >>>
> >>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> >> Hi Derek,
> >>
> >> As a high level first comment; "larger" patches are much harder to review.
> >>
> >> It seems that the drivers are logically split as described in your
> >> commit message already.  For the next version could you split at least
> >> each driver to it's own patch?
> >>
> >> It might also make sense to split up the individual drivers along
> >> "features".
> > Can do. It might still make sense to have capdata01 with the other_method
> > driver, it has no functionality on its own and is a dependency of other_method.
> > It isn't a problem to have capdata01 as an earlier patch than other_method
> > though if preferred. I'm not sure how I would break up the drivers further
> > than that, except the relevant header portions per c file.
>
> I think the gamezone driver should  be totally independent of the other driver.
>
> For the capdata01 and the other-guid driver i think the component framework could be handy,
> see https://docs.kernel.org/driver-api/component.html for details. Basically you will have
> a driver for capdata01 and another driver for other-guid which will both register a component.
> The firmware-attributes part will only get loaded if both components are present.
>
> Please keep in mind that new WMI drivers need to be instantiated multiple times, see
> https://docs.kernel.org/wmi/driver-development-guide.html for details.
>
> >> This is my own personal opinion and not a requirement but I personally
> >> like to see documentation for something new like this as it's own patch
> >> at the beginning of the series so we can make sure everyone understands
> >> and agrees on the design as they review the series and then can make
> >> sure that the implementation matches the design as the other patches are
> >> reviewed.
> > Acked, will add Documentation as its own 1/ patch.
> >
> >> I've got various other comments sprinkled throughout the patch, please
> >> see them.  I'm not 100% sure on the mutex use yet, we should review that
> >> after you've got all the cleanups needed done.
> >>
> >>> ---
> >>>    .../wmi/devices/lenovo-legion-wmi.rst         |  79 ++++
> >>>    MAINTAINERS                                   |   9 +
> >>>    drivers/platform/x86/Kconfig                  |  35 ++
> >>>    drivers/platform/x86/Makefile                 |  21 +-
> >>>    .../x86/lenovo-legion-wmi-capdata01.c         | 103 +++++
> >>>    .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++
> >>>    .../platform/x86/lenovo-legion-wmi-other.c    | 377 ++++++++++++++++++
> >>>    drivers/platform/x86/lenovo-legion-wmi.h      | 271 +++++++++++++
> >>>    8 files changed, 1119 insertions(+), 9 deletions(-)
> >>>    create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst
> >>>    create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c
> >>>    create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c
> >>>    create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c
> >>>    create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h
> >>>
> >>> diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst
> >>> new file mode 100644
> >>> index 000000000000..37b09c82c980
> >>> --- /dev/null
> >>> +++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst
> >>> @@ -0,0 +1,79 @@
> >>> +.. SPDX-License-Identifier: GPL-2.0-or-later
> >>> +======================================================
> >>> +Lenovo Legion WMI Interface Driver (lenovo-legion-wmi)
> >>> +======================================================
> >>> +
> >>> +Introduction
> >>> +============
> >>> +The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that
> >>> +require cross-references between GUID's for some functionality. The "Custom
> >>> +Mode" interface is a legacy interface for managing and displaying CPU & GPU
> >>> +power and hwmon settings and readings. The "Other Mode" interface is a modern
> >>> +interface that replaces "Custom Mode" interface methods. The "GameZone"
> >>> +interface adds advanced features such as fan profiles and overclocking. The
> >>> +"Lighting" interface adds control of various status lights related to different
> >>> +hardware components.
> >>> +
> >>> +Each of these interfaces has a different data structure associated with it that
> >>> +provide detailed information about each attribute provided by the interface.
> >>> +These data structs are retrieved from an additional WMI device data block GUID:
> >>> + - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00
> >>> + - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01
> >>> + - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02
> >>> +
> >>> +.. note::
> >>> +   Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01
> >>> +   interfaces are implemented by this driver.
> >> So this is to say that lighting interface is not implemented right now,
> >> right?
> > Custom Mode, Lighting, LENOVO_CAPABILITY_DATA_00, and LENOVO_CAPABILITY_DATA_02
> > are not implemented yet. For now Lenovo are okay with that but may want more
> > later.
>
> >>> +
> >>> +
> >>> +GameZone
> >>> +--------
> >>> +The GameZone WMI interface provides ACPI platform profile and fan curve
> >>> +settings for devices that fall under the "Gaming Series" of Lenovo Legion
> >>> +devices.
> >>> +
> >>> +The following platform profiles are supported:
> >>> + - quiet
> >>> + - balanced
> >>> + - performance
> >>> + - custom
> >>> +
> >>> +Custom Profile
> >>> +~~~~~~~~~~~~~~
> >>> +The custom profile is enabled but is not user selectable. This setting
> >>> +represents a hardware mode on Lenovo Legion devices that enables user
> >>> +modifications to Package Power Tracking settings. When an attribute exposed
> >>> +by the "Other Mode" WMI Interface is modified, the GameZone driver will switch
> >>> +to this profile automatically.
> >> I think you should explicitly mention that it's undone if the user
> >> selects a fixed platform mode too.  (It does, right?)
> > It does as a BIOS feature, acked for fix.
>
> So in order to use the "Other GUID" settings, custom mode has to be selected first, right?
>
> Is it really necessary to do this inside the other-guid driver, or can this be done in userspace?
> The reason for this is that i want to avoid having to couple the other-guid driver with the
> gamezone guid driver.
>
> >
> >>> +
> >>> +
> >>> +Other Mode
> >>> +----------
> >>> +The Other Mode WMI interface uses the fw_attributes class to expose various
> >>> +WMI functions provided by the interface in the sysfs. This enables CPU and GPU
> >>> +power limit tuning as well as various other attributes for devices that fall
> >>> +under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by
> >>> +the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages
> >>> +that allow the driver to probe details about the attribute. Each attibute has
> >>> +multiple pages, one for each of the platform profiles managed by the "GameZone"
> >>> +interface, so it must be probed prior to returning the current_value. For
> >>> +read-only properties, only the "Custom" profile values are reported to ensure
> >>> +any userspace applications reading them have accurate tunable value ranges.
> >>> +Attributes are exposed in sysfs under the following path:
> >>> +/sys/class/firmware-attributes/lenovo-legion-wmi/attributes
> >>> +
> >>> +Supported Attibutes
> >>> +~~~~~~~~~~~~~~~~~~~
> >>> +The following attributes are supported:
> >>> + - 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
> >>> +
> >>> +Each attribute has the following properties:
> >>> + - current_value
> >>> + - default_value
> >>> + - display_name
> >>> + - max_value
> >>> + - min_value
> >>> + - scalar_increment
> >>> + - type
> >>> diff --git a/MAINTAINERS b/MAINTAINERS
> >>> index baf0eeb9a355..67f7b588aa36 100644
> >>> --- a/MAINTAINERS
> >>> +++ b/MAINTAINERS
> >>> @@ -13034,6 +13034,15 @@ S: Maintained
> >>>    W:       http://legousb.sourceforge.net/
> >>>    F:       drivers/usb/misc/legousbtower.c
> >>>
> >>> +LENOVO LEGION WMI driver
> >>> +M: Derek J. Clark <derekjohn.clark@gmail.com>
> >>> +L: platform-driver-x86@vger.kernel.org
> >>> +S: Maintained
> >>> +F: drivers/platform/x86/lenovo-legion-wmi-capdata01.c
> >>> +F: drivers/platform/x86/lenovo-legion-wmi-gamezone.c
> >>> +F: drivers/platform/x86/lenovo-legion-wmi-other.c
> >>> +F: drivers/platform/x86/lenovo-legion-wmi.h
> >>> +
> >>>    LETSKETCH HID TABLET DRIVER
> >>>    M:       Hans de Goede <hdegoede@redhat.com>
> >>>    L:       linux-input@vger.kernel.org
> >>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> >>> index 0258dd879d64..a51a1a2fe7ba 100644
> >>> --- a/drivers/platform/x86/Kconfig
> >>> +++ b/drivers/platform/x86/Kconfig
> >>> @@ -459,6 +459,41 @@ config IBM_RTL
> >>>      state = 0 (BIOS SMIs on)
> >>>      state = 1 (BIOS SMIs off)
> >>>
> >>> +config LEGION_GAMEZONE_WMI
> >>> +   tristate "Lenovo Legion GameZone WMI Driver"
> >>> +   depends on ACPI_WMI
> >>> +   select ACPI_PLATFORM_PROFILE
> >>> +   help
> >>> +     Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> >>> +     platform-profile firmware interface.
> >>> +
> >>> +     To compile this driver as a module, choose M here: the module will
> >>> +     be called lenovo_legion_wmi_gamezone.
> >>> +
> >>> +config LEGION_DATA_01_WMI
> >>> +   tristate "Lenovo Legion WMI capability Data 01 Driver"
> >>> +   depends on ACPI_WMI
> >>> +   help
> >>> +     Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series"
> >>> +     line of hardware. This interface is a dependency for exposing tunable power
> >>> +     settings.
> >>> +
> >>> +     To compile this driver as a module, choose M here: the module will
> >>> +     be called lenovo_legion_wmi_capdata01.
> >>> +
> >>> +config LEGION_OTHER_WMI
> >>> +   tristate "Lenovo Legion Other Method WMI Driver"
> >> As a new user coming here, how are they going to know what "other"
> >> means?  I'm sort of thinking it's better to calls this "CUSTOM_WMI"?  Or
> >> maybe "CUSTOM_POWER_MODES_WMI"?  Maybe Armin or others have some input
> >> here too.
> > Other Method is the name Lenovo gave the interface. I'm open to suggestions,
> > but Custom Method is the name of the older Legion WMI interface so I'd like to
> > reserve that in case Lenovo wants to add it later.
>
> What is the intended purpose of this "Other Method" interface? If its primary purpose
> is to provide tuning settings to the system, then you can call it something like:
>
> - LENOVO_WMI_TUNING_SETTINGS
>
> - LENOVO_WMI_TUNING
>
> - ...
>
> >
> >>> +   depends on LEGION_GAMEZONE_WMI
> >>> +   depends on LEGION_DATA_01_WMI
> >>> +   select FW_ATTR_CLASS
> >>> +   help
> >>> +     Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> >>> +     firmware_attributes API to control various tunable settings typically exposed by
> >>> +     Lenovo software in Windows.
> >>> +
> >>> +     To compile this driver as a module, choose M here: the module will
> >>> +     be called lenovo_legion_wmi_other.
> >>> +
> >>>    config IDEAPAD_LAPTOP
> >>>     tristate "Lenovo IdeaPad Laptop Extras"
> >>>     depends on ACPI
> >>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> >>> index e1b142947067..838ee568c3f9 100644
> >>> --- a/drivers/platform/x86/Makefile
> >>> +++ b/drivers/platform/x86/Makefile
> >>> @@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP)   += hp/
> >>>    obj-$(CONFIG_UV_SYSFS)       += uv_sysfs.o
> >>>
> >>>    # IBM Thinkpad and Lenovo
> >>> -obj-$(CONFIG_IBM_RTL)              += ibm_rtl.o
> >>> -obj-$(CONFIG_IDEAPAD_LAPTOP)       += ideapad-laptop.o
> >>> -obj-$(CONFIG_LENOVO_YMC)   += lenovo-ymc.o
> >>> -obj-$(CONFIG_SENSORS_HDAPS)        += hdaps.o
> >>> -obj-$(CONFIG_THINKPAD_ACPI)        += thinkpad_acpi.o
> >>> -obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> >>> -obj-$(CONFIG_YOGABOOK)             += lenovo-yogabook.o
> >>> -obj-$(CONFIG_YT2_1380)             += lenovo-yoga-tab2-pro-1380-fastcharger.o
> >>> -obj-$(CONFIG_LENOVO_WMI_CAMERA)    += lenovo-wmi-camera.o
> >>> +obj-$(CONFIG_IBM_RTL)                      += ibm_rtl.o
> >>> +obj-$(CONFIG_IDEAPAD_LAPTOP)               += ideapad-laptop.o
> >>> +obj-$(CONFIG_LENOVO_YMC)           += lenovo-ymc.o
> >>> +obj-$(CONFIG_SENSORS_HDAPS)                += hdaps.o
> >>> +obj-$(CONFIG_THINKPAD_ACPI)                += thinkpad_acpi.o
> >>> +obj-$(CONFIG_THINKPAD_LMI)         += think-lmi.o
> >>> +obj-$(CONFIG_YOGABOOK)                     += lenovo-yogabook.o
> >>> +obj-$(CONFIG_YT2_1380)                     += lenovo-yoga-tab2-pro-1380-fastcharger.o
> >>> +obj-$(CONFIG_LENOVO_WMI_CAMERA)            += lenovo-wmi-camera.o
> >>> +obj-$(CONFIG_LEGION_GAMEZONE_WMI)  += lenovo-legion-wmi-gamezone.o
> >>> +obj-$(CONFIG_LEGION_DATA_01_WMI)   += lenovo-legion-wmi-capdata01.o
> >>> +obj-$(CONFIG_LEGION_OTHER_WMI)             += lenovo-legion-wmi-other.o
> >> Don't change the whitespace of everything else; especially not in one
> >> patch.  If the whitespace is wrong, do a patch that fixes it and then
> >> another patch that introduces a driver.
> > Only done because the length of the new entries messes up the whitespace of the
> > rest of the block. I can do as two patches if needed, but the whitespace would
> > need to be after as it is fine without them.
> >
> >>>
> >>>    # Intel
> >>>    obj-y                            += intel/
> >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
> >>> new file mode 100644
> >>> index 000000000000..99f4f35b7176
> >>> --- /dev/null
> >>> +++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
> >>> @@ -0,0 +1,103 @@
> >>> +// SPDX-License-Identifier: GPL-2.0-or-later
> >>> +/*
> >>> + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides
> >>> + * information on tunable attributes used by the "Other Method" WMI interface,
> >>> + * including if it is supported by the hardware, the default_value, max_value,
> >>> + * min_value, and step increment.
> >>> + *
> >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> >>> + *
> >> I don't think you need a newline at the end here.
> > Acked for fix all newline comments. Thanks.
> >
> >>> + */
> >>> +
> >>> +#include "lenovo-legion-wmi.h"
> >>> +
> >>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> >>> +
> >>> +static const struct wmi_device_id capdata_01_wmi_id_table[] = {
> >>> +   { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> >>> +   {}
> >>> +};
> >>> +
> >>> +static struct capdata_wmi cd01_wmi = {
> >>> +   .mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex)
> >>> +};
> >>> +
> >>> +int capdata_01_wmi_get(struct om_attribute_id attr_id,
> >>> +                  struct capability_data_01 *cap_data)
> >>> +{
> >>> +   union acpi_object *ret_obj;
> >>> +   int count;
> >>> +   int instance_id;
> >>> +   u32 attribute_id = *(int *)&attr_id;
> >> Can please do reverse xmas tree.
> >>
> > Acked for fix all ordering comments. Thanks.
> >
> >>> +
> >>> +   mutex_lock(&cd01_wmi.mutex);
> >>> +   count = wmidev_instance_count(drvdata.cd01_wmi->wdev);
> >>> +   mutex_unlock(&cd01_wmi.mutex);
> >> For new mutex use I'd suggest using guard(mutex) instead so you can have
> >> less lock/unlock/cleanup cases to worry about.
> >>
> > Good idea, I wasn't aware of this. Will fix up for v2.
> >
> >>> +   for (instance_id = 0; instance_id < count; instance_id++) {
> >>> +           mutex_lock(&cd01_wmi.mutex);
> >>> +           ret_obj =
> >>> +                   wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id);
> >>> +           mutex_unlock(&cd01_wmi.mutex);
> >>> +           if (!ret_obj) {
> >>> +                   pr_err("lenovo_legion_wmi_capdata_01: block query failed\n");
> >> With all the error messages you should use #define pr_fmt() at the top
> >> of the file and then you don't need to do prefixes at all like this.
> >>
> > Same as above, thanks.
> >
> >>> +                   continue;
> >>> +           }
> >>> +
> >>> +           if (ret_obj->type != ACPI_TYPE_BUFFER) {
> >>> +                   pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n",
> >>> +                          ret_obj->type);
> >>> +                   kfree(ret_obj);
> >>> +                   continue;
> >>> +           }
> >>> +
> >>> +           if (ret_obj->buffer.length != sizeof(*cap_data)) {
> >>> +                   pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n",
> >>> +                          ret_obj->buffer.length);
> >>> +                   kfree(ret_obj);
> >>> +                   continue;
> >>> +           }
> >>> +
> >>> +           memcpy(cap_data, ret_obj->buffer.pointer,
> >>> +                  ret_obj->buffer.length);
> >>> +           kfree(ret_obj);
> >>> +
> >>> +           if (cap_data->id != attribute_id)
> >>> +                   continue;
> >>> +           break;
> >>> +   }
> >>> +   if (cap_data->id == 0) {
> >>> +           pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n");
> >>> +           return -EINVAL;
> >>> +   }
> >>> +   return 0;
> >>> +}
> >>> +EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI");
> >>> +
> >>> +/* Driver Setup */
> >>> +static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context)
> >>> +{
> >>> +   cd01_wmi.wdev = wdev;
> >>> +   drvdata.cd01_wmi = &cd01_wmi;
> >>> +   pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n");
> >>> +
> >> Pretty noisy; no?  I think you probably should lose this message.
> >>
> > Acked for fix all pr_info comments.
> >
> >>> +   return 0;
> >>> +}
> >>> +
> >>> +static void capdata_01_wmi_remove(struct wmi_device *wdev)
> >>> +{
> >>> +   pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n");
> >> Pretty noisy; no?  I think you probably should lose this message.
> >>
> >>> +}
> >>> +
> >>> +static struct wmi_driver capdata_01_wmi_driver = {
> >>> +   .driver = { .name = "capdata_01_wmi" },
> >>> +   .id_table = capdata_01_wmi_id_table,
> >>> +   .probe = capdata_01_wmi_probe,
> >>> +   .remove = capdata_01_wmi_remove,
> >>> +};
> >>> +
> >>> +module_wmi_driver(capdata_01_wmi_driver);
> >>> +
> >>> +MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table);
> >>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> >>> +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
> >>> +MODULE_LICENSE("GPL");
> >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
> >>> new file mode 100644
> >>> index 000000000000..2f976dc0e367
> >>> --- /dev/null
> >>> +++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
> >>> @@ -0,0 +1,233 @@
> >>> +// SPDX-License-Identifier: GPL-2.0-or-later
> >>> +/*
> >>> + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides
> >>> + * platform profile and fan curve settings for devices that fall under the
> >>> + * "Gaming Series" of Lenovo Legion devices.
> >>> + *
> >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> >>> + *
> >> Drop newline here
> >>> + */
> >>> +
> >>> +#include "lenovo-legion-wmi.h"
> >>> +
> >>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> >>> +
> >>> +/* Method IDs */
> >>> +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */
> >>> +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */
> >>> +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */
> >>> +
> >>> +static const struct wmi_device_id gamezone_wmi_id_table[] = {
> >>> +   { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
> >>> +   {}
> >>> +};
> >>> +
> >>> +static struct gamezone_wmi gz_wmi = {
> >>> +   .mutex = __MUTEX_INITIALIZER(gz_wmi.mutex)
> >>> +};
> >>> +
> >>> +/* Platform Profile Methods */
> >>> +static int
> >>> +gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof,
> >>> +                                   int *supported)
> >>> +{
> >>> +   int ret;
> >>> +
> >>> +   mutex_lock(&gz_wmi.mutex);
> >> I'd use guard(mutex) instead.  By doing that your function becomes a lot
> >> simpler too.
> >>
> >> guard(mutex)(&gz_wmi.mutex);
> >>
> >> return lenovo_legion_evaluate_method_1();
> >>
> >>> +   ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
> >>> +                                         WMI_METHOD_ID_SMARTFAN_SUPP, 0,
> >>> +                                         supported);
> >>> +   mutex_unlock(&gz_wmi.mutex);
> >>> +   return ret;
> >>> +}
> >>> +
> >>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
> >>> +                            int *sel_prof)
> >>> +{
> >>> +   int ret;
> >>> +   int supported;
> >>> +
> >>> +   gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
> >>> +                                           &supported);
> >>> +   if (!supported) {
> >>> +           pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
> >> Is this error flow real?  I sort of expect that you can avoid
> >> registering if not supporting it.
> >>
> > This method is an exported symbol in GZ_WMI. I'm not aware of any hardware
> > without the GameZone interface that does implement the Other Method interface,
> > but if it does exist I was concerned about calling on an interface that isn't
> > registered. Perhaps a null pointer check on gz_wmi or gz_wmi.pprof->supported
> > check in the other method calls to this would be better? I didn't want to rely
> > on pprof exising for the check. I do now realize that this would call on a WMI
> > interface that doesn't exist if it was the case this hardware exists.
>
> In general passing WMI devices between different drivers will likely result in device lifetime issues.
>
> I suggest that you decouple both drivers as much as possible and rely on userspace for selecting
> custom mode before changing any tuning settings.
>
> See my above comment for more details.
>
> Thanks,
> Armin Wolf
>
> >
> >>> +           return -EOPNOTSUPP;
> >>> +   }
> >>> +   mutex_lock(&gz_wmi.mutex);
> >> guard(mutex) here too.
> >>
> >>> +   ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
> >>> +                                         WMI_METHOD_ID_SMARTFAN_GET, 0,
> >>> +                                         sel_prof);
> >>> +   mutex_unlock(&gz_wmi.mutex);
> >>> +   return ret;
> >>> +}
> >>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI");
> >>> +
> >>> +static int
> >>> +gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof,
> >>> +                             enum platform_profile_option *profile)
> >>> +{
> >>> +   int sel_prof;
> >>> +   int err;
> >>> +
> >>> +   err = gamezone_wmi_fan_profile_get(pprof, &sel_prof);
> >>> +   if (err)
> >>> +           return err;
> >>> +
> >>> +   switch (sel_prof) {
> >>> +   case SMARTFAN_MODE_QUIET:
> >>> +           *profile = PLATFORM_PROFILE_QUIET;
> >>> +           break;
> >>> +   case SMARTFAN_MODE_BALANCED:
> >>> +           *profile = PLATFORM_PROFILE_BALANCED;
> >>> +           break;
> >>> +   case SMARTFAN_MODE_PERFORMANCE:
> >>> +           *profile = PLATFORM_PROFILE_PERFORMANCE;
> >>> +           break;
> >>> +   case SMARTFAN_MODE_CUSTOM:
> >>> +           *profile = PLATFORM_PROFILE_CUSTOM;
> >>> +           break;
> >>> +
> >> Spurious newline.
> >>
> >>> +   default:
> >>> +           return -EINVAL;
> >>> +   }
> >>> +   drvdata.gz_wmi->current_profile = *profile;
> >>> +
> >>> +   return 0;
> >>> +}
> >>> +
> >>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
> >>> +                                 enum platform_profile_option profile)
> >>> +{
> >>> +   int ret;
> >>> +   int sel_prof;
> >>> +   int supported;
> >>> +
> >>> +   gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
> >>> +                                           &supported);
> >>> +   if (!supported) {
> >>> +           pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
> >>> +           return -EOPNOTSUPP;
> >>> +   }
> >> Same question; is this a real error flow?
> >>
> > Also an exported symbol in GZ_WMI. Will find another way to do these checks in
> > Other Method.
> >
> >>> +
> >>> +   switch (profile) {
> >>> +   case PLATFORM_PROFILE_QUIET:
> >>> +           sel_prof = SMARTFAN_MODE_QUIET;
> >>> +           break;
> >>> +   case PLATFORM_PROFILE_BALANCED:
> >>> +           sel_prof = SMARTFAN_MODE_BALANCED;
> >>> +           break;
> >>> +   case PLATFORM_PROFILE_PERFORMANCE:
> >>> +           sel_prof = SMARTFAN_MODE_PERFORMANCE;
> >>> +           break;
> >>> +   case PLATFORM_PROFILE_CUSTOM:
> >>> +           sel_prof = SMARTFAN_MODE_CUSTOM;
> >>> +           break;
> >>> +   default:
> >>> +           return -EOPNOTSUPP;
> >>> +   }
> >>> +
> >>> +   mutex_lock(&gz_wmi.mutex);
> >> guard(mutex) here.
> >>> +   ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
> >>> +                                         WMI_METHOD_ID_SMARTFAN_SET,
> >>> +                                         sel_prof, NULL);
> >>> +   mutex_unlock(&gz_wmi.mutex);
> >>> +
> >>> +   if (ret) {
> >>> +           pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n");
> >>> +           return ret;
> >>> +   }
> >>> +
> >>> +   drvdata.gz_wmi->current_profile = profile;
> >>> +   return 0;
> >>> +}
> >>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI");
> >>> +
> >>> +/* Driver Setup */
> >>> +static int platform_profile_setup(struct gamezone_wmi *gz_wmi)
> >>> +{
> >>> +   int err;
> >>> +   int supported;
> >>> +
> >>> +   gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported);
> >>> +
> >>> +   gz_wmi->platform_profile_support = supported;
> >>> +
> >>> +   if (!supported) {
> >>> +           pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
> >>> +           return -EOPNOTSUPP;
> >>> +   }
> >> Yeah because of this you don't need that other flow I was mentioning above.
> >>
> >> IMO I don't think the pr_warn() is really needed, you'll only really
> >> have one way that you exit -EOPNOTSUPP.
> >>
> > Will remove warn, thanks.
> >
> >>> +
> >>> +   gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get;
> >>> +   gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set;
> >>> +
> >>> +   set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices);
> >>> +   set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices);
> >>> +   set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices);
> >>> +   set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices);
> >>> +
> >>> +   err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof,
> >>> +                                           &gz_wmi->current_profile);
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n",
> >>> +                  err);
> >> Drop prefix on the error and use pr_fmt().
> >>
> >>> +           return err;
> >>> +   }
> >>> +
> >>> +   err = platform_profile_register(&gz_wmi->pprof);
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n",
> >>> +                  err);
> >> Drop prefix on the error and use pr_fmt().
> >>
> >>> +           return err;
> >>> +   }
> >>> +
> >>> +   return 0;
> >>> +}
> >>> +
> >>> +static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context)
> >>> +{
> >>> +   int err;
> >>> +
> >>> +   gz_wmi.wdev = wdev;
> >>> +   drvdata.gz_wmi = &gz_wmi;
> >>> +
> >>> +   err = platform_profile_setup(&gz_wmi);
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n",
> >>> +                  err);
> >>> +           kfree(&gz_wmi);
> >> Is this free correct?  It's a global isn't it?  I don't think you should
> >> be freeing here.
> >>
> > I'll just return the error.
> >
> >>> +           return err;
> >>> +   }
> >>> +
> >>> +   pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n");
> >> Too noisy.
> >>
> >>> +   return 0;
> >>> +}
> >>> +
> >>> +static void gamezone_wmi_remove(struct wmi_device *wdev)
> >>> +{
> >>> +   int err;
> >>> +
> >>> +   mutex_lock(&gz_wmi.mutex);
> >>> +   err = platform_profile_remove(&drvdata.gz_wmi->pprof);
> >>> +   mutex_unlock(&gz_wmi.mutex);
> >>> +
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n",
> >>> +                  err);
> >>> +   } else {
> >>> +           pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n");
> >>> +   }
> >> Considering that platform_profile_remove() doesn't really have a failure
> >> path (it always returns 0). I'd just lose both of these messages and
> >> make this simple.
> >>
> >> guard(mutex)();
> >> platform_profile_remove();
> >>
> > Acked for fix.
> >
> >>> +}
> >>> +
> >>> +static struct wmi_driver gamezone_wmi_driver = {
> >>> +   .driver = { .name = "gamezone_wmi" },
> >>> +   .id_table = gamezone_wmi_id_table,
> >>> +   .probe = gamezone_wmi_probe,
> >>> +   .remove = gamezone_wmi_remove,
> >>> +};
> >>> +
> >>> +module_wmi_driver(gamezone_wmi_driver);
> >>> +
> >>> +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table);
> >>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> >>> +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver");
> >>> +MODULE_LICENSE("GPL");
> >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c
> >>> new file mode 100644
> >>> index 000000000000..c09c1848eda7
> >>> --- /dev/null
> >>> +++ b/drivers/platform/x86/lenovo-legion-wmi-other.c
> >>> @@ -0,0 +1,377 @@
> >>> +// SPDX-License-Identifier: GPL-2.0-or-later
> >>> +/*
> >>> + * Lenovo Legion Other Method driver. This driver uses the fw_attributes
> >>> + * class to expose the various WMI functions provided by the "Other Method" WMI
> >>> + * interface. This enables CPU and GPU power limit as well as various other
> >>> + * attributes for devices that fall under the "Gaming Series" of Lenovo Legion
> >>> + * devices. Each attribute exposed by the "Other Method"" interface has a
> >>> + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to
> >>> + * probe details about the attribute such as set/get support, step, min, max,
> >>> + * and default value. Each attibute has multiple pages, one for each of the
> >>> + * fan profiles managed by the GameZone interface, so it must be probed prior
> >>> + * to returning the current_value.
> >>> + *
> >>> + * These attributes typically don't fit anywhere else in the sysfs and are set
> >>> + * in Windows using one of Lenovo's multiple user applications.
> >>> + *
> >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> >>> + *
> >> Remove the new line here.
> >>> + */
> >>> +
> >>> +#include "lenovo-legion-wmi.h"
> >>> +#include "firmware_attributes_class.h"
> >>> +
> >>> +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> >>> +
> >>> +/* Device IDs */
> >>> +#define WMI_DEVICE_ID_CPU 0x01
> >>> +
> >>> +/* WMI_DEVICE_ID_CPU feature IDs */
> >>> +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */
> >>> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> >>> +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */
> >>> +
> >>> +/* Method IDs */
> >>> +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */
> >>> +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */
> >>> +
> >>> +static const struct wmi_device_id other_method_wmi_id_table[] = {
> >>> +   { LENOVO_OTHER_METHOD_GUID, NULL },
> >>> +   {}
> >>> +};
> >>> +
> >>> +/* Tunable Attributes */
> >>> +struct ll_tunables {
> >>> +   u32 ppt_pl1_spl;
> >>> +   u32 ppt_pl2_sppt;
> >>> +   u32 ppt_pl3_fppt;
> >>> +};
> >>> +
> >>> +static const struct class *fw_attr_class;
> >>> +
> >>> +static struct other_method_wmi om_wmi = {
> >>> +   .mutex = __MUTEX_INITIALIZER(om_wmi.mutex)
> >>> +};
> >>> +
> >>> +struct capdata_01_attr_group {
> >>> +   const struct attribute_group *attr_group;
> >>> +};
> >>> +
> >>> +/* Simple attribute creation */
> >>> +
> >>> +/*
> >>> + * att_current_value_store() - Set the current value of the given attribute
> >>> + * @kobj: Pointer to the driver object.
> >>> + * @kobj_attribute: Pointer to the attribute calling this function.
> >>> + * @buf: The buffer to read from, this is parsed to `int` type.
> >>> + * @count: Required by sysfs attribute macros, pass in from the callee attr.
> >>> + * @store_value: Pointer to where the parsed value should be stored.
> >>> + * @device_id: The WMI function Device ID to use.
> >>> + * @feature_id: The WMI function Feature ID to use.
> >>> + *
> >>> + * This function is intended to be generic so it can be called from any
> >>> + * attribute's "current_value_store" which works only with integers. The
> >>> + * integer to be sent to the WMI method is range checked and an error returned
> >>> + * if out of range.
> >>> + *
> >>> + * If the value is valid and WMI is success, then the sysfs attribute is
> >>> + * notified.
> >>> + *
> >>> + * Returns: Either count, or an error.
> >>> + */
> >>> +ssize_t attr_current_value_store(struct kobject *kobj,
> >>> +                            struct kobj_attribute *attr, const char *buf,
> >>> +                            size_t count, u32 *store_value, u8 device_id,
> >>> +                            u8 feature_id)
> >>> +{
> >>> +   struct capability_data_01 cap_data;
> >>> +   enum platform_profile_option cust_prof;
> >>> +   int err;
> >>> +   int sel_prof;
> >>> +   u32 value;
> >>> +   struct wmi_device *wdev = drvdata.om_wmi->wdev;
> >>> +
> >>> +   err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");
> >> Use pr_fmt() for teh file instead of prefix here.
> >>
> >>> +           return -EIO;
> >>> +   }
> >>> +
> >>> +   /* Switch to custom profile if not currently on it. */
> >>> +   if (sel_prof != SMARTFAN_MODE_CUSTOM) {
> >>> +           pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables.");
> >> As you do this "for" them, I'd lose the warning.
> >>
> > Acked for fix. Leftover from an earlier version that didn't set the profile.
> >
> >>> +           cust_prof = PLATFORM_PROFILE_CUSTOM;
> >>> +           sel_prof = SMARTFAN_MODE_CUSTOM;
> >>> +           err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof,
> >>> +                                                   cust_prof);
> >>> +           if (err) {
> >>> +                   pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n");
> >>> +                   return -EIO;
> >>> +           }
> >>> +   }
> >>> +
> >>> +   err = kstrtouint(buf, 10, &value);
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_other: Error converting value to int.\n");
> >>> +           return -EIO;
> >>> +   }
> >>> +
> >>> +   /* Construct the attribute id */
> >>> +   struct om_attribute_id attr_id = { sel_prof << 8, feature_id,
> >>> +                                      device_id };
> >>> +
> >>> +   /* Get min/max from LENOVO_CAPABILITY_DATA_01 */
> >>> +   err = capdata_01_wmi_get(attr_id, &cap_data);
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
> >>> +           return -EIO;
> >>> +   }
> >>> +   if (cap_data.capability < 1) {
> >>> +           pr_err("lenovo_legion_wmi_other: Capability not supported.\n");
> >>> +           return -EPERM;
> >>> +   }
> >>> +
> >>> +   if (value < cap_data.min_value || value > cap_data.max_value) {
> >>> +           pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n",
> >>> +                   value, cap_data.min_value, cap_data.max_value);
> >>> +           return -EINVAL;
> >>> +   }
> >>> +
> >>> +   mutex_lock(&om_wmi.mutex);
> >>> +   err = lenovo_legion_evaluate_method_2(wdev, 0x0,
> >>> +                                         WMI_METHOD_ID_VALUE_SET,
> >>> +                                         *(int *)&attr_id, value, NULL);
> >>> +   mutex_unlock(&om_wmi.mutex);
> >>> +
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_other: Error setting attribute");
> >>> +           return err;
> >>> +   }
> >>> +
> >>> +   if (store_value)
> >>> +           *store_value = value;
> >>> +
> >>> +   sysfs_notify(kobj, NULL, attr->attr.name);
> >>> +   return count;
> >>> +}
> >>> +
> >>> +/*
> >>> + * attr_current_value_show() - Get the current value of the given attribute
> >>> + * @kobj: Pointer to the driver object.
> >>> + * @kobj_attribute: Pointer to the attribute calling this function.
> >>> + * @buf: The buffer to write to.
> >>> + * @retval: Pointer to returned data.
> >>> + * @device_id: The WMI function Device ID to use.
> >>> + * @feature_id: The WMI function Feature ID to use.
> >>> + *
> >>> + * This function is intended to be generic so it can be called from any "_show"
> >>> + * attribute which works only with integers.
> >>> + *
> >>> + * If the WMI is success, then the sysfs attribute is notified.
> >>> + *
> >>> + * Returns: Either count, or an error.
> >>> + */
> >>> +ssize_t attr_current_value_show(struct kobject *kobj,
> >>> +                           struct kobj_attribute *attr, char *buf,
> >>> +                           u8 device_id, u8 feature_id)
> >>> +{
> >>> +   int sel_prof; /* Current fan profile mode */
> >>> +   int err;
> >>> +   int retval;
> >>> +   struct wmi_device *wdev = drvdata.om_wmi->wdev;
> >>> +
> >>> +   err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
> >>> +
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");
> >>> +           return err;
> >>> +   }
> >>> +
> >>> +   // Construct the WMI attribute id from the given args.
> >>> +   struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
> >>> +                                           device_id };
> >>> +
> >>> +   mutex_lock(&om_wmi.mutex);
> >>> +   err = lenovo_legion_evaluate_method_1(wdev, 0x0,
> >>> +                                         WMI_METHOD_ID_VALUE_GET,
> >>> +                                         *(int *)&attribute_id, &retval);
> >>> +   mutex_unlock(&om_wmi.mutex);
> >>> +
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_other: Error getting attribute");
> >>> +           return err;
> >>> +   }
> >>> +
> >>> +   return sysfs_emit(buf, "%u\n", retval);
> >>> +}
> >>> +
> >>> +/**
> >>> + * attr_capdata_01_show() - Get the value of the specified attribute property
> >>> + * from LENOVO_CAPABILITY_DATA_01.
> >>> + * @kobj: Pointer to the driver object.
> >>> + * @kobj_attribute: Pointer to the attribute calling this function.
> >>> + * @buf: The buffer to write to.
> >>> + * @retval: Pointer to returned data.
> >>> + * @device_id: The WMI functions Device ID to use.
> >>> + * @feature_id: The WMI functions Feature ID to use.
> >>> + * @prop: The property of this attribute to be read.
> >>> + *
> >>> + * This function is intended to be generic so it can be called from any "_show"
> >>> + * attribute which works only with integers.
> >>> + *
> >>> + * If the WMI is success, then the sysfs attribute is notified.
> >>> + *
> >>> + * Returns: Either count, or an error.
> >>> + */
> >>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
> >>> +                        char *buf, u8 device_id, u8 feature_id,
> >>> +                        enum attribute_property prop)
> >>> +{
> >>> +   struct capability_data_01 cap_data;
> >>> +   int err;
> >>> +   int retval;
> >>> +   int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */
> >>> +
> >>> +   // Construct the WMI attribute id from the given args.
> >>> +   struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
> >>> +                                           device_id };
> >>> +
> >>> +   err = capdata_01_wmi_get(attribute_id, &cap_data);
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
> >>> +           return -EIO;
> >>> +   }
> >>> +
> >>> +   switch (prop) {
> >>> +   case DEFAULT_VAL:
> >>> +           retval = cap_data.default_value;
> >>> +           break;
> >>> +   case MAX_VAL:
> >>> +           retval = cap_data.max_value;
> >>> +           break;
> >>> +   case MIN_VAL:
> >>> +           retval = cap_data.min_value;
> >>> +           break;
> >>> +   case STEP_VAL:
> >>> +           retval = cap_data.step;
> >>> +           break;
> >>> +   default:
> >>> +           return -EINVAL;
> >>> +   }
> >>> +   return sysfs_emit(buf, "%u\n", retval);
> >>> +}
> >>> +
> >>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU,
> >>> +                       WMI_FEATURE_ID_CPU_SPL,
> >>> +                       "Set the CPU sustained power limit");
> >>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU,
> >>> +                       WMI_FEATURE_ID_CPU_SPPT,
> >>> +                       "Set the CPU slow package power tracking limit");
> >>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU,
> >>> +                       WMI_FEATURE_ID_CPU_FPPT,
> >>> +                       "Set the CPU fast package power tracking limit");
> >>> +
> >>> +static const struct capdata_01_attr_group capdata_01_attr_groups[] = {
> >>> +   { &ppt_pl1_spl_attr_group },
> >>> +   { &ppt_pl2_sppt_attr_group },
> >>> +   { &ppt_pl3_fppt_attr_group },
> >>> +   {},
> >>> +};
> >>> +
> >>> +static int other_method_fw_attr_add(void)
> >>> +{
> >>> +   int err, i;
> >>> +
> >>> +   err = fw_attributes_class_get(&fw_attr_class);
> >>> +   if (err) {
> >>> +           pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n");
> >>> +           return err;
> >>> +   }
> >>> +
> >>> +   om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> >>> +                                      NULL, "%s", DRIVER_NAME);
> >>> +   if (IS_ERR(om_wmi.fw_attr_dev)) {
> >>> +           pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n");
> >>> +           err = PTR_ERR(om_wmi.fw_attr_dev);
> >>> +           goto fail_class_get;
> >>> +   }
> >>> +
> >>> +   om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL,
> >>> +                                             &om_wmi.fw_attr_dev->kobj);
> >>> +   if (!om_wmi.fw_attr_kset) {
> >>> +           pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n");
> >>> +           err = -ENOMEM;
> >>> +           goto err_destroy_classdev;
> >>> +   }
> >>> +
> >>> +   for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) {
> >>> +           err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj,
> >>> +                                    capdata_01_attr_groups[i].attr_group);
> >>> +           if (err) {
> >>> +                   pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n",
> >>> +                          capdata_01_attr_groups[i].attr_group->name);
> >>> +                   goto err_remove_groups;
> >>> +           }
> >>> +   }
> >>> +
> >>> +   return 0;
> >>> +
> >>> +err_remove_groups:
> >>> +   while (--i >= 0) {
> >>> +           sysfs_remove_group(&om_wmi.fw_attr_kset->kobj,
> >>> +                              capdata_01_attr_groups[i].attr_group);
> >>> +   }
> >>> +err_destroy_classdev:
> >>> +   device_destroy(fw_attr_class, MKDEV(0, 0));
> >>> +fail_class_get:
> >>> +   fw_attributes_class_put();
> >>> +   return err;
> >>> +}
> >>> +
> >>> +/* Driver Setup */
> >>> +static int other_method_wmi_probe(struct wmi_device *wdev, const void *context)
> >>> +{
> >>> +   int err;
> >>> +
> >>> +   om_wmi.wdev = wdev;
> >>> +   drvdata.om_wmi = &om_wmi;
> >>> +   om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL);
> >>> +   if (!om_wmi.ll_tunables)
> >>> +           return -ENOMEM;
> >>> +
> >>> +   err = other_method_fw_attr_add();
> >>> +   if (err)
> >>> +           return err;
> >>> +   pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n");
> >> too noisy
> >>
> >>> +
> >>> +   return 0;
> >>> +}
> >>> +
> >>> +static void other_method_wmi_remove(struct wmi_device *wdev)
> >>> +{
> >>> +   mutex_lock(&om_wmi.mutex);
> >>> +
> >>> +   kset_unregister(om_wmi.fw_attr_kset);
> >>> +   device_destroy(fw_attr_class, MKDEV(0, 0));
> >>> +   fw_attributes_class_put();
> >>> +
> >>> +   mutex_unlock(&om_wmi.mutex);
> >>> +
> >>> +   pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n");
> >> too noisy.
> >>
> >>> +}
> >>> +
> >>> +static struct wmi_driver other_method_wmi_driver = {
> >>> +   .driver = { .name = "other_method_wmi" },
> >>> +   .id_table = other_method_wmi_id_table,
> >>> +   .probe = other_method_wmi_probe,
> >>> +   .remove = other_method_wmi_remove,
> >>> +};
> >>> +
> >>> +module_wmi_driver(other_method_wmi_driver);
> >>> +
> >>> +MODULE_IMPORT_NS("GZ_WMI");
> >>> +MODULE_IMPORT_NS("CAPDATA_WMI");
> >>> +MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table);
> >>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> >>> +MODULE_DESCRIPTION("Lenovo Legion Other Method Driver");
> >>> +MODULE_LICENSE("GPL");
> >>> diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h
> >>> new file mode 100644
> >>> index 000000000000..65baa728f29e
> >>> --- /dev/null
> >>> +++ b/drivers/platform/x86/lenovo-legion-wmi.h
> >>> @@ -0,0 +1,271 @@
> >>> +/* SPDX-License-Identifier: GPL-2.0-or-later
> >>> + *
> >>> + * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is
> >>> + * broken up into multiple GUID interfaces that require cross-references
> >>> + * between GUID's for some functionality. The "Custom Mode" interface is a
> >>> + * legacy interface for managing and displaying CPU & GPU power and hwmon
> >>> + * settings and readings. The "Other Mode" interface is a modern interface
> >>> + * that replaces or extends the "Custom Mode" interface methods. The "GameZone"
> >>> + * interface adds advanced features such as fan profiles and overclocking.
> >>> + * The "Lighting" interface adds control of various status lights related to
> >>> + * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00
> >>> + * struct for capability information, "Other Mode" uses
> >>> + * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting"
> >>> + * uses LENOVO_CAPABILITY_DATA_02 struct for capability information.
> >>> + *
> >>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
> >>> + *
> >> Lose the newline
> >>
> >>> + */
> >>> +
> >>> +#ifndef _LENOVO_LEGION_WMI_H_
> >>> +#define _LENOVO_LEGION_WMI_H_
> >>> +
> >>> +#include <linux/mutex.h>
> >>> +#include <linux/platform_profile.h>
> >>> +#include <linux/types.h>
> >>> +#include <linux/wmi.h>
> >>> +
> >>> +#define DRIVER_NAME "lenovo-legion-wmi"
> >> This is only used in one of the drivers, I'd move it there to make it
> >> clearer.
> >>
> > Acked for fix.
> >
> >>> +
> >>> +/* Platform Profile Modes */
> >>> +#define SMARTFAN_MODE_QUIET 0x01
> >>> +#define SMARTFAN_MODE_BALANCED 0x02
> >>> +#define SMARTFAN_MODE_PERFORMANCE 0x03
> >>> +#define SMARTFAN_MODE_CUSTOM 0xFF
> >>> +
> >>> +struct gamezone_wmi {
> >>> +   struct wmi_device *wdev;
> >>> +   enum platform_profile_option current_profile;
> >>> +   struct platform_profile_handler pprof;
> >>> +   bool platform_profile_support;
> >>> +   struct mutex mutex; /* Ensure single operation on WMI device */
> >>> +};
> >>> +
> >>> +struct other_method_wmi {
> >>> +   struct wmi_device *wdev;
> >>> +   struct device *fw_attr_dev;
> >>> +   struct kset *fw_attr_kset;
> >>> +   struct ll_tunables *ll_tunables;
> >>> +   struct mutex mutex; /* Ensure single operation on WMI device */
> >>> +};
> >>> +
> >>> +struct capdata_wmi {
> >>> +   struct wmi_device *wdev;
> >>> +   struct mutex mutex; /* Ensure single operation on WMI device */
> >>> +};
> >>> +
> >>> +struct ll_drvdata {
> >>> +   struct other_method_wmi *om_wmi; /* Other method GUID device */
> >>> +   struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */
> >>> +   struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */
> >>> +} drvdata;
> >>> +
> >>> +struct wmi_method_args {
> >>> +   u32 arg0;
> >>> +   u32 arg1;
> >>> +};
> >>> +
> >>> +struct om_attribute_id {
> >>> +   u32 mode_id : 16; /* Fan profile */
> >>> +   u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
> >>> +   u32 device_id : 8; /* CPU/GPU */
> >>> +} __packed;
> >>> +
> >>> +enum attribute_property {
> >>> +   DEFAULT_VAL = 0,
> >>> +   MAX_VAL,
> >>> +   MIN_VAL,
> >>> +   STEP_VAL,
> >>> +   SUPPORTED,
> >>> +};
> >>> +
> >>> +struct capability_data_01 {
> >>> +   u32 id;
> >>> +   u32 capability;
> >>> +   u32 default_value;
> >>> +   u32 step;
> >>> +   u32 min_value;
> >>> +   u32 max_value;
> >>> +};
> >>> +
> >>> +static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance,
> >>> +                                    u32 method_id, struct acpi_buffer *in,
> >>> +                                    struct acpi_buffer *out)
> >>> +{
> >>> +   acpi_status status;
> >>> +
> >>> +   status = wmidev_evaluate_method(wdev, instance, method_id, in, out);
> >>> +
> >>> +   if (ACPI_FAILURE(status)) {
> >>> +           pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n",
> >>> +                  method_id, instance);
> >>> +           return -EIO;
> >>> +   }
> >>> +
> >>> +   return 0;
> >>> +}
> >>> +
> >>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> >>> +                               u32 method_id, u32 arg0, u32 arg1,
> >>> +                               u32 *retval);
> >>> +
> >>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
> >>> +                               u32 method_id, u32 arg0, u32 arg1,
> >>> +                               u32 *retval)
> >>> +{
> >>> +   int ret;
> >>> +   u32 temp_val;
> >>> +   struct wmi_method_args args = { arg0, arg1 };
> >>> +   struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
> >>> +   struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> >>> +   union acpi_object *ret_obj = NULL;
> >> Reverse xmas tree please.
> >>
> >>> +
> >>> +   ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input,
> >>> +                                       &output);
> >>> +
> >>> +   if (ret) {
> >>> +           pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n",
> >>> +                  method_id, ret);
> >>> +           return ret;
> >>> +   }
> >>> +
> >>> +   if (retval) {
> >>> +           ret_obj = (union acpi_object *)output.pointer;
> >>> +           if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER)
> >>> +                   temp_val = (u32)ret_obj->integer.value;
> >> This is a pretty bad failure if it's not the case, no?  Should you set a
> >> return value here instead perhaps?
> >>
> >>> +
> >>> +           *retval = temp_val
> >> If that above error I mentioned happens then you'll be assigning garbage
> >> data out.
> >>
> >> ;
> > True, good catch. Someone built with clang+lto and it warned about this section
> > as well as temp val is not initialized. Will fix both.
> >
> >>> +   }
> >>> +
> >>> +   kfree(ret_obj);
> >>> +
> >>> +   return 0;
> >>> +}
> >>> +
> >>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> >>> +                               u32 method_id, u32 arg0, u32 *retval);
> >>> +
> >>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
> >>> +                               u32 method_id, u32 arg0, u32 *retval)
> >>> +{
> >>> +   return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0,
> >>> +                                          0, retval);
> >>> +}
> >>> +
> >>> +int capdata_01_wmi_get(struct om_attribute_id attr_id,
> >>> +                  struct capability_data_01 *cap_data);
> >>> +
> >>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
> >>> +                                 enum platform_profile_option sel_prof);
> >>> +
> >>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
> >>> +                            int *sel_prof);
> >>> +
> >>> +/* current_value */
> >>> +ssize_t attr_current_value_store(struct kobject *kobj,
> >>> +                            struct kobj_attribute *attr, const char *buf,
> >>> +                            size_t count, u32 *store_value, u8 device_id,
> >>> +                            u8 feature_id);
> >>> +
> >>> +ssize_t attr_current_value_show(struct kobject *kobj,
> >>> +                           struct kobj_attribute *attr, char *buf,
> >>> +                           u8 device_id, u8 feature_id);
> >>> +
> >>> +/* LENOVO_CAPABILITY_DATA_01 */
> >>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
> >>> +                        char *buf, u8 device_id, u8 feature_id,
> >>> +                        enum attribute_property prop);
> >>> +
> >>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
> >>> +                 char *buf);
> >>> +
> >>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
> >>> +                 char *buf)
> >>> +{
> >>> +   return sysfs_emit(buf, "integer\n");
> >>> +}
> >>> +
> >>> +#define __LL_ATTR_RO(_func, _name)                                    \
> >>> +   {                                                             \
> >>> +           .attr = { .name = __stringify(_name), .mode = 0444 }, \
> >>> +           .show = _func##_##_name##_show,                       \
> >>> +   }
> >>> +
> >>> +#define __LL_ATTR_RO_AS(_name, _show)                                 \
> >>> +   {                                                             \
> >>> +           .attr = { .name = __stringify(_name), .mode = 0444 }, \
> >>> +           .show = _show,                                        \
> >>> +   }
> >>> +
> >>> +#define __LL_ATTR_RW(_func, _name) \
> >>> +   __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
> >>> +
> >>> +/* Shows a formatted static variable */
> >>> +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val)                         \
> >>> +   static ssize_t _attrname##_##_prop##_show(                            \
> >>> +           struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> >>> +   {                                                                     \
> >>> +           return sysfs_emit(buf, _fmt, _val);                           \
> >>> +   }                                                                     \
> >>> +   static struct kobj_attribute attr_##_attrname##_##_prop =             \
> >>> +           __LL_ATTR_RO(_attrname, _prop)
> >>> +
> >>> +/* Attribute current_value show/store */
> >>> +#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id)                   \
> >>> +   static ssize_t _attrname##_current_value_store(                       \
> >>> +           struct kobject *kobj, struct kobj_attribute *attr,            \
> >>> +           const char *buf, size_t count)                                \
> >>> +   {                                                                     \
> >>> +           return attr_current_value_store(                              \
> >>> +                   kobj, attr, buf, count,                               \
> >>> +                   &om_wmi.ll_tunables->_attrname, _dev_id, _feat_id);   \
> >>> +   }                                                                     \
> >>> +   static ssize_t _attrname##_current_value_show(                        \
> >>> +           struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
> >>> +   {                                                                     \
> >>> +           return attr_current_value_show(kobj, attr, buf, _dev_id,      \
> >>> +                                          _feat_id);                     \
> >>> +   }                                                                     \
> >>> +   static struct kobj_attribute attr_##_attrname##_current_value =       \
> >>> +           __LL_ATTR_RW(_attrname, current_value)
> >>> +
> >>> +/* Attribute property show only */
> >>> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \
> >>> +   static ssize_t _attrname##_##_prop##_show(                             \
> >>> +           struct kobject *kobj, struct kobj_attribute *attr, char *buf)  \
> >>> +   {                                                                      \
> >>> +           return attr_capdata_01_show(kobj, attr, buf, _dev_id,          \
> >>> +                                       _feat_id, _prop_type);             \
> >>> +   }                                                                      \
> >>> +   static struct kobj_attribute attr_##_attrname##_##_prop =              \
> >>> +           __LL_ATTR_RO(_attrname, _prop)
> >>> +
> >>> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id,    \
> >>> +                               _dispname)                                \
> >>> +   __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id);                  \
> >>> +   __LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id,    \
> >>> +                         DEFAULT_VAL);                                   \
> >>> +   __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);          \
> >>> +   __LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id,        \
> >>> +                         MAX_VAL);                                       \
> >>> +   __LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id,        \
> >>> +                         MIN_VAL);                                       \
> >>> +   __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \
> >>> +                         STEP_VAL);                                      \
> >>> +   static struct kobj_attribute attr_##_attrname##_type =                \
> >>> +           __LL_ATTR_RO_AS(type, int_type_show);                         \
> >>> +   static struct attribute *_attrname##_attrs[] = {                      \
> >>> +           &attr_##_attrname##_current_value.attr,                       \
> >>> +           &attr_##_attrname##_default_value.attr,                       \
> >>> +           &attr_##_attrname##_display_name.attr,                        \
> >>> +           &attr_##_attrname##_max_value.attr,                           \
> >>> +           &attr_##_attrname##_min_value.attr,                           \
> >>> +           &attr_##_attrname##_scalar_increment.attr,                    \
> >>> +           &attr_##_attrname##_type.attr,                                \
> >>> +           NULL,                                                         \
> >>> +   };                                                                    \
> >>> +   static const struct attribute_group _attrname##_attr_group = {        \
> >>> +           .name = _fsname, .attrs = _attrname##_attrs                   \
> >>> +   }
> >>> +
> >>> +#endif /* !_LENOVO_LEGION_WMI_H_ */

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-23 15:47         ` Derek John Clark
@ 2024-12-23 16:50           ` Armin Wolf
  0 siblings, 0 replies; 29+ messages in thread
From: Armin Wolf @ 2024-12-23 16:50 UTC (permalink / raw)
  To: Derek John Clark
  Cc: Mario Limonciello, Hans de Goede, Ilpo Järvinen,
	Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
	Mark Pearson, Pierre-Loup A . Griffais, platform-driver-x86,
	linux-doc, linux-kernel

Am 23.12.24 um 16:47 schrieb Derek John Clark:

>>> Can do. It might still make sense to have capdata01 with the other_method
>>> driver, it has no functionality on its own and is a dependency of other_method.
>>> It isn't a problem to have capdata01 as an earlier patch than other_method
>>> though if preferred. I'm not sure how I would break up the drivers further
>>> than that, except the relevant header portions per c file.
>> I think the gamezone driver should  be totally independent of the other driver.
>>
>> For the capdata01 and the other-guid driver i think the component framework could be handy,
>> see https://docs.kernel.org/driver-api/component.html for details. Basically you will have
>> a driver for capdata01 and another driver for other-guid which will both register a component.
>> The firmware-attributes part will only get loaded if both components are present.
> This is really cool and just what I need. I'll look into it, thanks.
>
>> Please keep in mind that new WMI drivers need to be instantiated multiple times, see
>> https://docs.kernel.org/wmi/driver-development-guide.html for details.
> I must have missed that. I'll incorporate it into v2, thanks.
>
>>> It does as a BIOS feature, acked for fix.
>> So in order to use the "Other GUID" settings, custom mode has to be selected first, right?
>>
>> Is it really necessary to do this inside the other-guid driver, or can this be done in userspace?
>> The reason for this is that i want to avoid having to couple the other-guid driver with the
>> gamezone guid driver.
>>
> The only major issue is there is (currently) no way to actually set custom mode
> from userspace. As designed, "custom" is not selectable through the
> platform_profile sysfs file descriptor even when enabled as any attempt to write
> "custom" to it returns "Invalid argument". This will need to be changed if
> we're going to move forward with the interfaces decoupled, unless
> there is another
> way to handle this I'm not aware of.

The new platform-profile class interface allows you to select "custom". Only the legacy global sysfs
interface does not allow you to do that.

>
> Another, not very severe issue, is the OM interface will always permit
> a write to any
> page even if the gamezone interface isn't set to custom mode. I did some testing
> and there doesn't seem to be any effect on the hardware when this happens, but
> having no effect when writing to the attribute endpoints could lead to erroneous
> bug reports from uninformed users. I suppose that is better than bug
> reports from
> null pointer reference if something goes wrong with the gamezone interface.

Yes, i suggest that we document this when documenting the firmware attribute interface.

>
> I did consider trying to grab a pointer to platform_profile to watch
> the state instead
> of gamezone, but that isn't reliable either as it will report "custom"
> any time two
> providers disagree. Since amd-pmf uses "low-power" and Lenovo uses "quiet" as
> their respective lowest settings, platform_profile will report
> "custom" any time the
> hardware is on the lowest profile. The obvious answer there might be to enable
> the low-power enum instead of quiet on the gamezone interface, but that also
> assumes no other provider will ever register with another value. Do
> you think this
> is worth pursuing?
>
I do not think this is a reliable approach. IMHO documenting this extra requirement for the
tuning values to have an effect should be enough since users needs to read the documentation
anyway to properly use the tuning settings.

Also people will probably write a userspace utility for that, so the special handling can happen there.

>>> Other Method is the name Lenovo gave the interface. I'm open to suggestions,
>>> but Custom Method is the name of the older Legion WMI interface so I'd like to
>>> reserve that in case Lenovo wants to add it later.
>> What is the intended purpose of this "Other Method" interface? If its primary purpose
>> is to provide tuning settings to the system, then you can call it something like:
>>
>> - LENOVO_WMI_TUNING_SETTINGS
>>
>> - LENOVO_WMI_TUNING
>>
>> - ...
> I think we're of a similar mind here as I changed it to LENOVO_WMI_TUNABLES in
> my working branch already. I like LENOVO_WMI_TUNING more though.

I am OK with both names, you can pick the one which sounds best to you.

>> In general passing WMI devices between different drivers will likely result in device lifetime issues.
>>
>> I suggest that you decouple both drivers as much as possible and rely on userspace for selecting
>> custom mode before changing any tuning settings.
>>
>> See my above comment for more details.
>>
>> Thanks,
>> Armin Wolf
> Thanks for taking a look Armin. I will be away for the next few days with only
> my mobile phone which seems to be having issues sending plain text. I might not
> be able to respond again until the end of the week.
>
> Derek

That is totally fine, i wish you happy holidays :).

Thanks,
Armin Wolf

>
> On Sun, Dec 22, 2024 at 2:55 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 18.12.24 um 04:36 schrieb Derek J. Clark:
>>
>>> Hi Mario,
>>>
>>> Thank you for taking a look at it so quickly.
>>>
>>>>> Adds lenovo-legion-wmi.h which provides templates and some method
>>>>> implementations used by the lenovo-legion-wmi driver series.
>>>>>
>>>>> Adds lenovo-legion-wmi-gamezone.c which provides a driver for the Lenovo
>>>>> GameZone WMI interface that comes on Lenovo "Gaming Series" hardware.
>>>>> Provides ACPI platform profiles over WMI.
>>>>>
>>>>> Adds lenovo-legion-wmi-other.c which provides a driver for the Lenovo
>>>>> "Other Method" WMI interface that comes on some Lenovo hardware.
>>>>> Provides a firmware-attributes class which enables the use of tunable
>>>>> knobs for SPL, SPPT, and FPPT.
>>>>>
>>>>> Adds lenovo-legion-wmi-capdata01.c which provides a driver for the
>>>>> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Method"
>>>>> enabled hardware. Provides an interface for querying if a given
>>>>> attribute is supported by the hardware, as well as its default_value,
>>>>> max_value, min_value, and step increment.
>>>>>
>>> z>> Adds lenovo-legion-wmi.rst describing the available drivers and their
>>>>> function.
>>>>>
>>>>> Updates Kconfig, Makefile, and MAINTAINERS to include the new drivers.
>>>>>
>>>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>>> Hi Derek,
>>>>
>>>> As a high level first comment; "larger" patches are much harder to review.
>>>>
>>>> It seems that the drivers are logically split as described in your
>>>> commit message already.  For the next version could you split at least
>>>> each driver to it's own patch?
>>>>
>>>> It might also make sense to split up the individual drivers along
>>>> "features".
>>> Can do. It might still make sense to have capdata01 with the other_method
>>> driver, it has no functionality on its own and is a dependency of other_method.
>>> It isn't a problem to have capdata01 as an earlier patch than other_method
>>> though if preferred. I'm not sure how I would break up the drivers further
>>> than that, except the relevant header portions per c file.
>> I think the gamezone driver should  be totally independent of the other driver.
>>
>> For the capdata01 and the other-guid driver i think the component framework could be handy,
>> see https://docs.kernel.org/driver-api/component.html for details. Basically you will have
>> a driver for capdata01 and another driver for other-guid which will both register a component.
>> The firmware-attributes part will only get loaded if both components are present.
>>
>> Please keep in mind that new WMI drivers need to be instantiated multiple times, see
>> https://docs.kernel.org/wmi/driver-development-guide.html for details.
>>
>>>> This is my own personal opinion and not a requirement but I personally
>>>> like to see documentation for something new like this as it's own patch
>>>> at the beginning of the series so we can make sure everyone understands
>>>> and agrees on the design as they review the series and then can make
>>>> sure that the implementation matches the design as the other patches are
>>>> reviewed.
>>> Acked, will add Documentation as its own 1/ patch.
>>>
>>>> I've got various other comments sprinkled throughout the patch, please
>>>> see them.  I'm not 100% sure on the mutex use yet, we should review that
>>>> after you've got all the cleanups needed done.
>>>>
>>>>> ---
>>>>>     .../wmi/devices/lenovo-legion-wmi.rst         |  79 ++++
>>>>>     MAINTAINERS                                   |   9 +
>>>>>     drivers/platform/x86/Kconfig                  |  35 ++
>>>>>     drivers/platform/x86/Makefile                 |  21 +-
>>>>>     .../x86/lenovo-legion-wmi-capdata01.c         | 103 +++++
>>>>>     .../platform/x86/lenovo-legion-wmi-gamezone.c | 233 +++++++++++
>>>>>     .../platform/x86/lenovo-legion-wmi-other.c    | 377 ++++++++++++++++++
>>>>>     drivers/platform/x86/lenovo-legion-wmi.h      | 271 +++++++++++++
>>>>>     8 files changed, 1119 insertions(+), 9 deletions(-)
>>>>>     create mode 100644 Documentation/wmi/devices/lenovo-legion-wmi.rst
>>>>>     create mode 100644 drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>>>>>     create mode 100644 drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>>>>>     create mode 100644 drivers/platform/x86/lenovo-legion-wmi-other.c
>>>>>     create mode 100644 drivers/platform/x86/lenovo-legion-wmi.h
>>>>>
>>>>> diff --git a/Documentation/wmi/devices/lenovo-legion-wmi.rst b/Documentation/wmi/devices/lenovo-legion-wmi.rst
>>>>> new file mode 100644
>>>>> index 000000000000..37b09c82c980
>>>>> --- /dev/null
>>>>> +++ b/Documentation/wmi/devices/lenovo-legion-wmi.rst
>>>>> @@ -0,0 +1,79 @@
>>>>> +.. SPDX-License-Identifier: GPL-2.0-or-later
>>>>> +======================================================
>>>>> +Lenovo Legion WMI Interface Driver (lenovo-legion-wmi)
>>>>> +======================================================
>>>>> +
>>>>> +Introduction
>>>>> +============
>>>>> +The Lenovo Legion WMI interface is broken up into multiple GUID interfaces that
>>>>> +require cross-references between GUID's for some functionality. The "Custom
>>>>> +Mode" interface is a legacy interface for managing and displaying CPU & GPU
>>>>> +power and hwmon settings and readings. The "Other Mode" interface is a modern
>>>>> +interface that replaces "Custom Mode" interface methods. The "GameZone"
>>>>> +interface adds advanced features such as fan profiles and overclocking. The
>>>>> +"Lighting" interface adds control of various status lights related to different
>>>>> +hardware components.
>>>>> +
>>>>> +Each of these interfaces has a different data structure associated with it that
>>>>> +provide detailed information about each attribute provided by the interface.
>>>>> +These data structs are retrieved from an additional WMI device data block GUID:
>>>>> + - The "Custom Mode" interface uses LENOVO_CAPABILITY_DATA_00
>>>>> + - The "Other Mode" interface uses LENOVO_CAPABILITY_DATA_01
>>>>> + - The "Lighting" interface uses LENOVO_CAPABILITY_DATA_02
>>>>> +
>>>>> +.. note::
>>>>> +   Currently only the "Gamezone", "Other Mode", and LENOVO_CAPABILITY_DATA_01
>>>>> +   interfaces are implemented by this driver.
>>>> So this is to say that lighting interface is not implemented right now,
>>>> right?
>>> Custom Mode, Lighting, LENOVO_CAPABILITY_DATA_00, and LENOVO_CAPABILITY_DATA_02
>>> are not implemented yet. For now Lenovo are okay with that but may want more
>>> later.
>>>>> +
>>>>> +
>>>>> +GameZone
>>>>> +--------
>>>>> +The GameZone WMI interface provides ACPI platform profile and fan curve
>>>>> +settings for devices that fall under the "Gaming Series" of Lenovo Legion
>>>>> +devices.
>>>>> +
>>>>> +The following platform profiles are supported:
>>>>> + - quiet
>>>>> + - balanced
>>>>> + - performance
>>>>> + - custom
>>>>> +
>>>>> +Custom Profile
>>>>> +~~~~~~~~~~~~~~
>>>>> +The custom profile is enabled but is not user selectable. This setting
>>>>> +represents a hardware mode on Lenovo Legion devices that enables user
>>>>> +modifications to Package Power Tracking settings. When an attribute exposed
>>>>> +by the "Other Mode" WMI Interface is modified, the GameZone driver will switch
>>>>> +to this profile automatically.
>>>> I think you should explicitly mention that it's undone if the user
>>>> selects a fixed platform mode too.  (It does, right?)
>>> It does as a BIOS feature, acked for fix.
>> So in order to use the "Other GUID" settings, custom mode has to be selected first, right?
>>
>> Is it really necessary to do this inside the other-guid driver, or can this be done in userspace?
>> The reason for this is that i want to avoid having to couple the other-guid driver with the
>> gamezone guid driver.
>>
>>>>> +
>>>>> +
>>>>> +Other Mode
>>>>> +----------
>>>>> +The Other Mode WMI interface uses the fw_attributes class to expose various
>>>>> +WMI functions provided by the interface in the sysfs. This enables CPU and GPU
>>>>> +power limit tuning as well as various other attributes for devices that fall
>>>>> +under the "Gaming Series" of Lenovo Legion devices. Each attribute exposed by
>>>>> +the "Other Method" interface has corresponding LENOVO_CAPABILITY_DATA_01 pages
>>>>> +that allow the driver to probe details about the attribute. Each attibute has
>>>>> +multiple pages, one for each of the platform profiles managed by the "GameZone"
>>>>> +interface, so it must be probed prior to returning the current_value. For
>>>>> +read-only properties, only the "Custom" profile values are reported to ensure
>>>>> +any userspace applications reading them have accurate tunable value ranges.
>>>>> +Attributes are exposed in sysfs under the following path:
>>>>> +/sys/class/firmware-attributes/lenovo-legion-wmi/attributes
>>>>> +
>>>>> +Supported Attibutes
>>>>> +~~~~~~~~~~~~~~~~~~~
>>>>> +The following attributes are supported:
>>>>> + - 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
>>>>> +
>>>>> +Each attribute has the following properties:
>>>>> + - current_value
>>>>> + - default_value
>>>>> + - display_name
>>>>> + - max_value
>>>>> + - min_value
>>>>> + - scalar_increment
>>>>> + - type
>>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>>> index baf0eeb9a355..67f7b588aa36 100644
>>>>> --- a/MAINTAINERS
>>>>> +++ b/MAINTAINERS
>>>>> @@ -13034,6 +13034,15 @@ S: Maintained
>>>>>     W:       http://legousb.sourceforge.net/
>>>>>     F:       drivers/usb/misc/legousbtower.c
>>>>>
>>>>> +LENOVO LEGION WMI driver
>>>>> +M: Derek J. Clark <derekjohn.clark@gmail.com>
>>>>> +L: platform-driver-x86@vger.kernel.org
>>>>> +S: Maintained
>>>>> +F: drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>>>>> +F: drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>>>>> +F: drivers/platform/x86/lenovo-legion-wmi-other.c
>>>>> +F: drivers/platform/x86/lenovo-legion-wmi.h
>>>>> +
>>>>>     LETSKETCH HID TABLET DRIVER
>>>>>     M:       Hans de Goede <hdegoede@redhat.com>
>>>>>     L:       linux-input@vger.kernel.org
>>>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>>>> index 0258dd879d64..a51a1a2fe7ba 100644
>>>>> --- a/drivers/platform/x86/Kconfig
>>>>> +++ b/drivers/platform/x86/Kconfig
>>>>> @@ -459,6 +459,41 @@ config IBM_RTL
>>>>>       state = 0 (BIOS SMIs on)
>>>>>       state = 1 (BIOS SMIs off)
>>>>>
>>>>> +config LEGION_GAMEZONE_WMI
>>>>> +   tristate "Lenovo Legion GameZone WMI Driver"
>>>>> +   depends on ACPI_WMI
>>>>> +   select ACPI_PLATFORM_PROFILE
>>>>> +   help
>>>>> +     Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
>>>>> +     platform-profile firmware interface.
>>>>> +
>>>>> +     To compile this driver as a module, choose M here: the module will
>>>>> +     be called lenovo_legion_wmi_gamezone.
>>>>> +
>>>>> +config LEGION_DATA_01_WMI
>>>>> +   tristate "Lenovo Legion WMI capability Data 01 Driver"
>>>>> +   depends on ACPI_WMI
>>>>> +   help
>>>>> +     Say Y here if you have a WMI aware Lenovo Legion device in the "Gaming Series"
>>>>> +     line of hardware. This interface is a dependency for exposing tunable power
>>>>> +     settings.
>>>>> +
>>>>> +     To compile this driver as a module, choose M here: the module will
>>>>> +     be called lenovo_legion_wmi_capdata01.
>>>>> +
>>>>> +config LEGION_OTHER_WMI
>>>>> +   tristate "Lenovo Legion Other Method WMI Driver"
>>>> As a new user coming here, how are they going to know what "other"
>>>> means?  I'm sort of thinking it's better to calls this "CUSTOM_WMI"?  Or
>>>> maybe "CUSTOM_POWER_MODES_WMI"?  Maybe Armin or others have some input
>>>> here too.
>>> Other Method is the name Lenovo gave the interface. I'm open to suggestions,
>>> but Custom Method is the name of the older Legion WMI interface so I'd like to
>>> reserve that in case Lenovo wants to add it later.
>> What is the intended purpose of this "Other Method" interface? If its primary purpose
>> is to provide tuning settings to the system, then you can call it something like:
>>
>> - LENOVO_WMI_TUNING_SETTINGS
>>
>> - LENOVO_WMI_TUNING
>>
>> - ...
>>
>>>>> +   depends on LEGION_GAMEZONE_WMI
>>>>> +   depends on LEGION_DATA_01_WMI
>>>>> +   select FW_ATTR_CLASS
>>>>> +   help
>>>>> +     Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
>>>>> +     firmware_attributes API to control various tunable settings typically exposed by
>>>>> +     Lenovo software in Windows.
>>>>> +
>>>>> +     To compile this driver as a module, choose M here: the module will
>>>>> +     be called lenovo_legion_wmi_other.
>>>>> +
>>>>>     config IDEAPAD_LAPTOP
>>>>>      tristate "Lenovo IdeaPad Laptop Extras"
>>>>>      depends on ACPI
>>>>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>>>>> index e1b142947067..838ee568c3f9 100644
>>>>> --- a/drivers/platform/x86/Makefile
>>>>> +++ b/drivers/platform/x86/Makefile
>>>>> @@ -59,15 +59,18 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP)   += hp/
>>>>>     obj-$(CONFIG_UV_SYSFS)       += uv_sysfs.o
>>>>>
>>>>>     # IBM Thinkpad and Lenovo
>>>>> -obj-$(CONFIG_IBM_RTL)              += ibm_rtl.o
>>>>> -obj-$(CONFIG_IDEAPAD_LAPTOP)       += ideapad-laptop.o
>>>>> -obj-$(CONFIG_LENOVO_YMC)   += lenovo-ymc.o
>>>>> -obj-$(CONFIG_SENSORS_HDAPS)        += hdaps.o
>>>>> -obj-$(CONFIG_THINKPAD_ACPI)        += thinkpad_acpi.o
>>>>> -obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
>>>>> -obj-$(CONFIG_YOGABOOK)             += lenovo-yogabook.o
>>>>> -obj-$(CONFIG_YT2_1380)             += lenovo-yoga-tab2-pro-1380-fastcharger.o
>>>>> -obj-$(CONFIG_LENOVO_WMI_CAMERA)    += lenovo-wmi-camera.o
>>>>> +obj-$(CONFIG_IBM_RTL)                      += ibm_rtl.o
>>>>> +obj-$(CONFIG_IDEAPAD_LAPTOP)               += ideapad-laptop.o
>>>>> +obj-$(CONFIG_LENOVO_YMC)           += lenovo-ymc.o
>>>>> +obj-$(CONFIG_SENSORS_HDAPS)                += hdaps.o
>>>>> +obj-$(CONFIG_THINKPAD_ACPI)                += thinkpad_acpi.o
>>>>> +obj-$(CONFIG_THINKPAD_LMI)         += think-lmi.o
>>>>> +obj-$(CONFIG_YOGABOOK)                     += lenovo-yogabook.o
>>>>> +obj-$(CONFIG_YT2_1380)                     += lenovo-yoga-tab2-pro-1380-fastcharger.o
>>>>> +obj-$(CONFIG_LENOVO_WMI_CAMERA)            += lenovo-wmi-camera.o
>>>>> +obj-$(CONFIG_LEGION_GAMEZONE_WMI)  += lenovo-legion-wmi-gamezone.o
>>>>> +obj-$(CONFIG_LEGION_DATA_01_WMI)   += lenovo-legion-wmi-capdata01.o
>>>>> +obj-$(CONFIG_LEGION_OTHER_WMI)             += lenovo-legion-wmi-other.o
>>>> Don't change the whitespace of everything else; especially not in one
>>>> patch.  If the whitespace is wrong, do a patch that fixes it and then
>>>> another patch that introduces a driver.
>>> Only done because the length of the new entries messes up the whitespace of the
>>> rest of the block. I can do as two patches if needed, but the whitespace would
>>> need to be after as it is fine without them.
>>>
>>>>>     # Intel
>>>>>     obj-y                            += intel/
>>>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-capdata01.c b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>>>>> new file mode 100644
>>>>> index 000000000000..99f4f35b7176
>>>>> --- /dev/null
>>>>> +++ b/drivers/platform/x86/lenovo-legion-wmi-capdata01.c
>>>>> @@ -0,0 +1,103 @@
>>>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>>>> +/*
>>>>> + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides
>>>>> + * information on tunable attributes used by the "Other Method" WMI interface,
>>>>> + * including if it is supported by the hardware, the default_value, max_value,
>>>>> + * min_value, and step increment.
>>>>> + *
>>>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>>>>> + *
>>>> I don't think you need a newline at the end here.
>>> Acked for fix all newline comments. Thanks.
>>>
>>>>> + */
>>>>> +
>>>>> +#include "lenovo-legion-wmi.h"
>>>>> +
>>>>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>>>>> +
>>>>> +static const struct wmi_device_id capdata_01_wmi_id_table[] = {
>>>>> +   { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
>>>>> +   {}
>>>>> +};
>>>>> +
>>>>> +static struct capdata_wmi cd01_wmi = {
>>>>> +   .mutex = __MUTEX_INITIALIZER(cd01_wmi.mutex)
>>>>> +};
>>>>> +
>>>>> +int capdata_01_wmi_get(struct om_attribute_id attr_id,
>>>>> +                  struct capability_data_01 *cap_data)
>>>>> +{
>>>>> +   union acpi_object *ret_obj;
>>>>> +   int count;
>>>>> +   int instance_id;
>>>>> +   u32 attribute_id = *(int *)&attr_id;
>>>> Can please do reverse xmas tree.
>>>>
>>> Acked for fix all ordering comments. Thanks.
>>>
>>>>> +
>>>>> +   mutex_lock(&cd01_wmi.mutex);
>>>>> +   count = wmidev_instance_count(drvdata.cd01_wmi->wdev);
>>>>> +   mutex_unlock(&cd01_wmi.mutex);
>>>> For new mutex use I'd suggest using guard(mutex) instead so you can have
>>>> less lock/unlock/cleanup cases to worry about.
>>>>
>>> Good idea, I wasn't aware of this. Will fix up for v2.
>>>
>>>>> +   for (instance_id = 0; instance_id < count; instance_id++) {
>>>>> +           mutex_lock(&cd01_wmi.mutex);
>>>>> +           ret_obj =
>>>>> +                   wmidev_block_query(drvdata.cd01_wmi->wdev, instance_id);
>>>>> +           mutex_unlock(&cd01_wmi.mutex);
>>>>> +           if (!ret_obj) {
>>>>> +                   pr_err("lenovo_legion_wmi_capdata_01: block query failed\n");
>>>> With all the error messages you should use #define pr_fmt() at the top
>>>> of the file and then you don't need to do prefixes at all like this.
>>>>
>>> Same as above, thanks.
>>>
>>>>> +                   continue;
>>>>> +           }
>>>>> +
>>>>> +           if (ret_obj->type != ACPI_TYPE_BUFFER) {
>>>>> +                   pr_err("lenovo_legion_wmi_capdata_01: block query returned type: %u\n",
>>>>> +                          ret_obj->type);
>>>>> +                   kfree(ret_obj);
>>>>> +                   continue;
>>>>> +           }
>>>>> +
>>>>> +           if (ret_obj->buffer.length != sizeof(*cap_data)) {
>>>>> +                   pr_err("lenovo_legion_wmi_capdata_01: bad buffer length, %d\n",
>>>>> +                          ret_obj->buffer.length);
>>>>> +                   kfree(ret_obj);
>>>>> +                   continue;
>>>>> +           }
>>>>> +
>>>>> +           memcpy(cap_data, ret_obj->buffer.pointer,
>>>>> +                  ret_obj->buffer.length);
>>>>> +           kfree(ret_obj);
>>>>> +
>>>>> +           if (cap_data->id != attribute_id)
>>>>> +                   continue;
>>>>> +           break;
>>>>> +   }
>>>>> +   if (cap_data->id == 0) {
>>>>> +           pr_err("lenovo_legion_wmi_capdata_01: Failed to get capability data.\n");
>>>>> +           return -EINVAL;
>>>>> +   }
>>>>> +   return 0;
>>>>> +}
>>>>> +EXPORT_SYMBOL_NS_GPL(capdata_01_wmi_get, "CAPDATA_WMI");
>>>>> +
>>>>> +/* Driver Setup */
>>>>> +static int capdata_01_wmi_probe(struct wmi_device *wdev, const void *context)
>>>>> +{
>>>>> +   cd01_wmi.wdev = wdev;
>>>>> +   drvdata.cd01_wmi = &cd01_wmi;
>>>>> +   pr_info("lenovo_legion_wmi_capdata_01: Added Lenovo Capability Data 01 WMI interface.\n");
>>>>> +
>>>> Pretty noisy; no?  I think you probably should lose this message.
>>>>
>>> Acked for fix all pr_info comments.
>>>
>>>>> +   return 0;
>>>>> +}
>>>>> +
>>>>> +static void capdata_01_wmi_remove(struct wmi_device *wdev)
>>>>> +{
>>>>> +   pr_info("lenovo_legion_wmi_capdata_01: Removed Lenovo Capability Data 01 WMI interface.\n");
>>>> Pretty noisy; no?  I think you probably should lose this message.
>>>>
>>>>> +}
>>>>> +
>>>>> +static struct wmi_driver capdata_01_wmi_driver = {
>>>>> +   .driver = { .name = "capdata_01_wmi" },
>>>>> +   .id_table = capdata_01_wmi_id_table,
>>>>> +   .probe = capdata_01_wmi_probe,
>>>>> +   .remove = capdata_01_wmi_remove,
>>>>> +};
>>>>> +
>>>>> +module_wmi_driver(capdata_01_wmi_driver);
>>>>> +
>>>>> +MODULE_DEVICE_TABLE(wmi, capdata_01_wmi_id_table);
>>>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>>>> +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
>>>>> +MODULE_LICENSE("GPL");
>>>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-gamezone.c b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>>>>> new file mode 100644
>>>>> index 000000000000..2f976dc0e367
>>>>> --- /dev/null
>>>>> +++ b/drivers/platform/x86/lenovo-legion-wmi-gamezone.c
>>>>> @@ -0,0 +1,233 @@
>>>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>>>> +/*
>>>>> + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provides
>>>>> + * platform profile and fan curve settings for devices that fall under the
>>>>> + * "Gaming Series" of Lenovo Legion devices.
>>>>> + *
>>>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>>>>> + *
>>>> Drop newline here
>>>>> + */
>>>>> +
>>>>> +#include "lenovo-legion-wmi.h"
>>>>> +
>>>>> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>>>>> +
>>>>> +/* Method IDs */
>>>>> +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */
>>>>> +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */
>>>>> +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */
>>>>> +
>>>>> +static const struct wmi_device_id gamezone_wmi_id_table[] = {
>>>>> +   { LENOVO_GAMEZONE_GUID, NULL }, /* LENOVO_GAMEZONE_DATA */
>>>>> +   {}
>>>>> +};
>>>>> +
>>>>> +static struct gamezone_wmi gz_wmi = {
>>>>> +   .mutex = __MUTEX_INITIALIZER(gz_wmi.mutex)
>>>>> +};
>>>>> +
>>>>> +/* Platform Profile Methods */
>>>>> +static int
>>>>> +gamezone_wmi_platform_profile_supported(struct platform_profile_handler *pprof,
>>>>> +                                   int *supported)
>>>>> +{
>>>>> +   int ret;
>>>>> +
>>>>> +   mutex_lock(&gz_wmi.mutex);
>>>> I'd use guard(mutex) instead.  By doing that your function becomes a lot
>>>> simpler too.
>>>>
>>>> guard(mutex)(&gz_wmi.mutex);
>>>>
>>>> return lenovo_legion_evaluate_method_1();
>>>>
>>>>> +   ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
>>>>> +                                         WMI_METHOD_ID_SMARTFAN_SUPP, 0,
>>>>> +                                         supported);
>>>>> +   mutex_unlock(&gz_wmi.mutex);
>>>>> +   return ret;
>>>>> +}
>>>>> +
>>>>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
>>>>> +                            int *sel_prof)
>>>>> +{
>>>>> +   int ret;
>>>>> +   int supported;
>>>>> +
>>>>> +   gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
>>>>> +                                           &supported);
>>>>> +   if (!supported) {
>>>>> +           pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
>>>> Is this error flow real?  I sort of expect that you can avoid
>>>> registering if not supporting it.
>>>>
>>> This method is an exported symbol in GZ_WMI. I'm not aware of any hardware
>>> without the GameZone interface that does implement the Other Method interface,
>>> but if it does exist I was concerned about calling on an interface that isn't
>>> registered. Perhaps a null pointer check on gz_wmi or gz_wmi.pprof->supported
>>> check in the other method calls to this would be better? I didn't want to rely
>>> on pprof exising for the check. I do now realize that this would call on a WMI
>>> interface that doesn't exist if it was the case this hardware exists.
>> In general passing WMI devices between different drivers will likely result in device lifetime issues.
>>
>> I suggest that you decouple both drivers as much as possible and rely on userspace for selecting
>> custom mode before changing any tuning settings.
>>
>> See my above comment for more details.
>>
>> Thanks,
>> Armin Wolf
>>
>>>>> +           return -EOPNOTSUPP;
>>>>> +   }
>>>>> +   mutex_lock(&gz_wmi.mutex);
>>>> guard(mutex) here too.
>>>>
>>>>> +   ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
>>>>> +                                         WMI_METHOD_ID_SMARTFAN_GET, 0,
>>>>> +                                         sel_prof);
>>>>> +   mutex_unlock(&gz_wmi.mutex);
>>>>> +   return ret;
>>>>> +}
>>>>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_fan_profile_get, "GZ_WMI");
>>>>> +
>>>>> +static int
>>>>> +gamezone_wmi_platform_profile_get(struct platform_profile_handler *pprof,
>>>>> +                             enum platform_profile_option *profile)
>>>>> +{
>>>>> +   int sel_prof;
>>>>> +   int err;
>>>>> +
>>>>> +   err = gamezone_wmi_fan_profile_get(pprof, &sel_prof);
>>>>> +   if (err)
>>>>> +           return err;
>>>>> +
>>>>> +   switch (sel_prof) {
>>>>> +   case SMARTFAN_MODE_QUIET:
>>>>> +           *profile = PLATFORM_PROFILE_QUIET;
>>>>> +           break;
>>>>> +   case SMARTFAN_MODE_BALANCED:
>>>>> +           *profile = PLATFORM_PROFILE_BALANCED;
>>>>> +           break;
>>>>> +   case SMARTFAN_MODE_PERFORMANCE:
>>>>> +           *profile = PLATFORM_PROFILE_PERFORMANCE;
>>>>> +           break;
>>>>> +   case SMARTFAN_MODE_CUSTOM:
>>>>> +           *profile = PLATFORM_PROFILE_CUSTOM;
>>>>> +           break;
>>>>> +
>>>> Spurious newline.
>>>>
>>>>> +   default:
>>>>> +           return -EINVAL;
>>>>> +   }
>>>>> +   drvdata.gz_wmi->current_profile = *profile;
>>>>> +
>>>>> +   return 0;
>>>>> +}
>>>>> +
>>>>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
>>>>> +                                 enum platform_profile_option profile)
>>>>> +{
>>>>> +   int ret;
>>>>> +   int sel_prof;
>>>>> +   int supported;
>>>>> +
>>>>> +   gamezone_wmi_platform_profile_supported(&drvdata.gz_wmi->pprof,
>>>>> +                                           &supported);
>>>>> +   if (!supported) {
>>>>> +           pr_err("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
>>>>> +           return -EOPNOTSUPP;
>>>>> +   }
>>>> Same question; is this a real error flow?
>>>>
>>> Also an exported symbol in GZ_WMI. Will find another way to do these checks in
>>> Other Method.
>>>
>>>>> +
>>>>> +   switch (profile) {
>>>>> +   case PLATFORM_PROFILE_QUIET:
>>>>> +           sel_prof = SMARTFAN_MODE_QUIET;
>>>>> +           break;
>>>>> +   case PLATFORM_PROFILE_BALANCED:
>>>>> +           sel_prof = SMARTFAN_MODE_BALANCED;
>>>>> +           break;
>>>>> +   case PLATFORM_PROFILE_PERFORMANCE:
>>>>> +           sel_prof = SMARTFAN_MODE_PERFORMANCE;
>>>>> +           break;
>>>>> +   case PLATFORM_PROFILE_CUSTOM:
>>>>> +           sel_prof = SMARTFAN_MODE_CUSTOM;
>>>>> +           break;
>>>>> +   default:
>>>>> +           return -EOPNOTSUPP;
>>>>> +   }
>>>>> +
>>>>> +   mutex_lock(&gz_wmi.mutex);
>>>> guard(mutex) here.
>>>>> +   ret = lenovo_legion_evaluate_method_1(drvdata.gz_wmi->wdev, 0x0,
>>>>> +                                         WMI_METHOD_ID_SMARTFAN_SET,
>>>>> +                                         sel_prof, NULL);
>>>>> +   mutex_unlock(&gz_wmi.mutex);
>>>>> +
>>>>> +   if (ret) {
>>>>> +           pr_err("lenovo_legion_wmi_gamezone: Failed to set platform profile.\n");
>>>>> +           return ret;
>>>>> +   }
>>>>> +
>>>>> +   drvdata.gz_wmi->current_profile = profile;
>>>>> +   return 0;
>>>>> +}
>>>>> +EXPORT_SYMBOL_NS_GPL(gamezone_wmi_platform_profile_set, "GZ_WMI");
>>>>> +
>>>>> +/* Driver Setup */
>>>>> +static int platform_profile_setup(struct gamezone_wmi *gz_wmi)
>>>>> +{
>>>>> +   int err;
>>>>> +   int supported;
>>>>> +
>>>>> +   gamezone_wmi_platform_profile_supported(&gz_wmi->pprof, &supported);
>>>>> +
>>>>> +   gz_wmi->platform_profile_support = supported;
>>>>> +
>>>>> +   if (!supported) {
>>>>> +           pr_warn("lenovo_legion_wmi_gamezone: Platform profiles are not supported by this device.\n");
>>>>> +           return -EOPNOTSUPP;
>>>>> +   }
>>>> Yeah because of this you don't need that other flow I was mentioning above.
>>>>
>>>> IMO I don't think the pr_warn() is really needed, you'll only really
>>>> have one way that you exit -EOPNOTSUPP.
>>>>
>>> Will remove warn, thanks.
>>>
>>>>> +
>>>>> +   gz_wmi->pprof.profile_get = gamezone_wmi_platform_profile_get;
>>>>> +   gz_wmi->pprof.profile_set = gamezone_wmi_platform_profile_set;
>>>>> +
>>>>> +   set_bit(PLATFORM_PROFILE_QUIET, gz_wmi->pprof.choices);
>>>>> +   set_bit(PLATFORM_PROFILE_BALANCED, gz_wmi->pprof.choices);
>>>>> +   set_bit(PLATFORM_PROFILE_PERFORMANCE, gz_wmi->pprof.choices);
>>>>> +   set_bit(PLATFORM_PROFILE_CUSTOM, gz_wmi->pprof.choices);
>>>>> +
>>>>> +   err = gamezone_wmi_platform_profile_get(&gz_wmi->pprof,
>>>>> +                                           &gz_wmi->current_profile);
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_gamezone: Failed to get current platform profile: %d\n",
>>>>> +                  err);
>>>> Drop prefix on the error and use pr_fmt().
>>>>
>>>>> +           return err;
>>>>> +   }
>>>>> +
>>>>> +   err = platform_profile_register(&gz_wmi->pprof);
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_gamezone: Failed to register platform profile support: %d\n",
>>>>> +                  err);
>>>> Drop prefix on the error and use pr_fmt().
>>>>
>>>>> +           return err;
>>>>> +   }
>>>>> +
>>>>> +   return 0;
>>>>> +}
>>>>> +
>>>>> +static int gamezone_wmi_probe(struct wmi_device *wdev, const void *context)
>>>>> +{
>>>>> +   int err;
>>>>> +
>>>>> +   gz_wmi.wdev = wdev;
>>>>> +   drvdata.gz_wmi = &gz_wmi;
>>>>> +
>>>>> +   err = platform_profile_setup(&gz_wmi);
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_gamezone: Failed to add platform profile: %d\n",
>>>>> +                  err);
>>>>> +           kfree(&gz_wmi);
>>>> Is this free correct?  It's a global isn't it?  I don't think you should
>>>> be freeing here.
>>>>
>>> I'll just return the error.
>>>
>>>>> +           return err;
>>>>> +   }
>>>>> +
>>>>> +   pr_info("lenovo_legion_wmi_gamezone: Added platform profile support.\n");
>>>> Too noisy.
>>>>
>>>>> +   return 0;
>>>>> +}
>>>>> +
>>>>> +static void gamezone_wmi_remove(struct wmi_device *wdev)
>>>>> +{
>>>>> +   int err;
>>>>> +
>>>>> +   mutex_lock(&gz_wmi.mutex);
>>>>> +   err = platform_profile_remove(&drvdata.gz_wmi->pprof);
>>>>> +   mutex_unlock(&gz_wmi.mutex);
>>>>> +
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_gamezone: Failed to remove platform profile: %d\n",
>>>>> +                  err);
>>>>> +   } else {
>>>>> +           pr_info("lenovo_legion_wmi_gamezone: Removed platform profile support.\n");
>>>>> +   }
>>>> Considering that platform_profile_remove() doesn't really have a failure
>>>> path (it always returns 0). I'd just lose both of these messages and
>>>> make this simple.
>>>>
>>>> guard(mutex)();
>>>> platform_profile_remove();
>>>>
>>> Acked for fix.
>>>
>>>>> +}
>>>>> +
>>>>> +static struct wmi_driver gamezone_wmi_driver = {
>>>>> +   .driver = { .name = "gamezone_wmi" },
>>>>> +   .id_table = gamezone_wmi_id_table,
>>>>> +   .probe = gamezone_wmi_probe,
>>>>> +   .remove = gamezone_wmi_remove,
>>>>> +};
>>>>> +
>>>>> +module_wmi_driver(gamezone_wmi_driver);
>>>>> +
>>>>> +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table);
>>>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>>>> +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver");
>>>>> +MODULE_LICENSE("GPL");
>>>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi-other.c b/drivers/platform/x86/lenovo-legion-wmi-other.c
>>>>> new file mode 100644
>>>>> index 000000000000..c09c1848eda7
>>>>> --- /dev/null
>>>>> +++ b/drivers/platform/x86/lenovo-legion-wmi-other.c
>>>>> @@ -0,0 +1,377 @@
>>>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>>>> +/*
>>>>> + * Lenovo Legion Other Method driver. This driver uses the fw_attributes
>>>>> + * class to expose the various WMI functions provided by the "Other Method" WMI
>>>>> + * interface. This enables CPU and GPU power limit as well as various other
>>>>> + * attributes for devices that fall under the "Gaming Series" of Lenovo Legion
>>>>> + * devices. Each attribute exposed by the "Other Method"" interface has a
>>>>> + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to
>>>>> + * probe details about the attribute such as set/get support, step, min, max,
>>>>> + * and default value. Each attibute has multiple pages, one for each of the
>>>>> + * fan profiles managed by the GameZone interface, so it must be probed prior
>>>>> + * to returning the current_value.
>>>>> + *
>>>>> + * These attributes typically don't fit anywhere else in the sysfs and are set
>>>>> + * in Windows using one of Lenovo's multiple user applications.
>>>>> + *
>>>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>>>>> + *
>>>> Remove the new line here.
>>>>> + */
>>>>> +
>>>>> +#include "lenovo-legion-wmi.h"
>>>>> +#include "firmware_attributes_class.h"
>>>>> +
>>>>> +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
>>>>> +
>>>>> +/* Device IDs */
>>>>> +#define WMI_DEVICE_ID_CPU 0x01
>>>>> +
>>>>> +/* WMI_DEVICE_ID_CPU feature IDs */
>>>>> +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */
>>>>> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
>>>>> +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */
>>>>> +
>>>>> +/* Method IDs */
>>>>> +#define WMI_METHOD_ID_VALUE_GET 17 /* Other Method Getter */
>>>>> +#define WMI_METHOD_ID_VALUE_SET 18 /* Other Method Setter */
>>>>> +
>>>>> +static const struct wmi_device_id other_method_wmi_id_table[] = {
>>>>> +   { LENOVO_OTHER_METHOD_GUID, NULL },
>>>>> +   {}
>>>>> +};
>>>>> +
>>>>> +/* Tunable Attributes */
>>>>> +struct ll_tunables {
>>>>> +   u32 ppt_pl1_spl;
>>>>> +   u32 ppt_pl2_sppt;
>>>>> +   u32 ppt_pl3_fppt;
>>>>> +};
>>>>> +
>>>>> +static const struct class *fw_attr_class;
>>>>> +
>>>>> +static struct other_method_wmi om_wmi = {
>>>>> +   .mutex = __MUTEX_INITIALIZER(om_wmi.mutex)
>>>>> +};
>>>>> +
>>>>> +struct capdata_01_attr_group {
>>>>> +   const struct attribute_group *attr_group;
>>>>> +};
>>>>> +
>>>>> +/* Simple attribute creation */
>>>>> +
>>>>> +/*
>>>>> + * att_current_value_store() - Set the current value of the given attribute
>>>>> + * @kobj: Pointer to the driver object.
>>>>> + * @kobj_attribute: Pointer to the attribute calling this function.
>>>>> + * @buf: The buffer to read from, this is parsed to `int` type.
>>>>> + * @count: Required by sysfs attribute macros, pass in from the callee attr.
>>>>> + * @store_value: Pointer to where the parsed value should be stored.
>>>>> + * @device_id: The WMI function Device ID to use.
>>>>> + * @feature_id: The WMI function Feature ID to use.
>>>>> + *
>>>>> + * This function is intended to be generic so it can be called from any
>>>>> + * attribute's "current_value_store" which works only with integers. The
>>>>> + * integer to be sent to the WMI method is range checked and an error returned
>>>>> + * if out of range.
>>>>> + *
>>>>> + * If the value is valid and WMI is success, then the sysfs attribute is
>>>>> + * notified.
>>>>> + *
>>>>> + * Returns: Either count, or an error.
>>>>> + */
>>>>> +ssize_t attr_current_value_store(struct kobject *kobj,
>>>>> +                            struct kobj_attribute *attr, const char *buf,
>>>>> +                            size_t count, u32 *store_value, u8 device_id,
>>>>> +                            u8 feature_id)
>>>>> +{
>>>>> +   struct capability_data_01 cap_data;
>>>>> +   enum platform_profile_option cust_prof;
>>>>> +   int err;
>>>>> +   int sel_prof;
>>>>> +   u32 value;
>>>>> +   struct wmi_device *wdev = drvdata.om_wmi->wdev;
>>>>> +
>>>>> +   err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");
>>>> Use pr_fmt() for teh file instead of prefix here.
>>>>
>>>>> +           return -EIO;
>>>>> +   }
>>>>> +
>>>>> +   /* Switch to custom profile if not currently on it. */
>>>>> +   if (sel_prof != SMARTFAN_MODE_CUSTOM) {
>>>>> +           pr_warn("lenovo_legion_wmi_other: Device must be in CUSTOM profile to set tunables.");
>>>> As you do this "for" them, I'd lose the warning.
>>>>
>>> Acked for fix. Leftover from an earlier version that didn't set the profile.
>>>
>>>>> +           cust_prof = PLATFORM_PROFILE_CUSTOM;
>>>>> +           sel_prof = SMARTFAN_MODE_CUSTOM;
>>>>> +           err = gamezone_wmi_platform_profile_set(&drvdata.gz_wmi->pprof,
>>>>> +                                                   cust_prof);
>>>>> +           if (err) {
>>>>> +                   pr_err("lenovo_legion_wmi_other: Error setting gamezone fan profile.\n");
>>>>> +                   return -EIO;
>>>>> +           }
>>>>> +   }
>>>>> +
>>>>> +   err = kstrtouint(buf, 10, &value);
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_other: Error converting value to int.\n");
>>>>> +           return -EIO;
>>>>> +   }
>>>>> +
>>>>> +   /* Construct the attribute id */
>>>>> +   struct om_attribute_id attr_id = { sel_prof << 8, feature_id,
>>>>> +                                      device_id };
>>>>> +
>>>>> +   /* Get min/max from LENOVO_CAPABILITY_DATA_01 */
>>>>> +   err = capdata_01_wmi_get(attr_id, &cap_data);
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
>>>>> +           return -EIO;
>>>>> +   }
>>>>> +   if (cap_data.capability < 1) {
>>>>> +           pr_err("lenovo_legion_wmi_other: Capability not supported.\n");
>>>>> +           return -EPERM;
>>>>> +   }
>>>>> +
>>>>> +   if (value < cap_data.min_value || value > cap_data.max_value) {
>>>>> +           pr_warn("lenovo_legion_wmi_other: Value %d is not between %d and %d.\n",
>>>>> +                   value, cap_data.min_value, cap_data.max_value);
>>>>> +           return -EINVAL;
>>>>> +   }
>>>>> +
>>>>> +   mutex_lock(&om_wmi.mutex);
>>>>> +   err = lenovo_legion_evaluate_method_2(wdev, 0x0,
>>>>> +                                         WMI_METHOD_ID_VALUE_SET,
>>>>> +                                         *(int *)&attr_id, value, NULL);
>>>>> +   mutex_unlock(&om_wmi.mutex);
>>>>> +
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_other: Error setting attribute");
>>>>> +           return err;
>>>>> +   }
>>>>> +
>>>>> +   if (store_value)
>>>>> +           *store_value = value;
>>>>> +
>>>>> +   sysfs_notify(kobj, NULL, attr->attr.name);
>>>>> +   return count;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * attr_current_value_show() - Get the current value of the given attribute
>>>>> + * @kobj: Pointer to the driver object.
>>>>> + * @kobj_attribute: Pointer to the attribute calling this function.
>>>>> + * @buf: The buffer to write to.
>>>>> + * @retval: Pointer to returned data.
>>>>> + * @device_id: The WMI function Device ID to use.
>>>>> + * @feature_id: The WMI function Feature ID to use.
>>>>> + *
>>>>> + * This function is intended to be generic so it can be called from any "_show"
>>>>> + * attribute which works only with integers.
>>>>> + *
>>>>> + * If the WMI is success, then the sysfs attribute is notified.
>>>>> + *
>>>>> + * Returns: Either count, or an error.
>>>>> + */
>>>>> +ssize_t attr_current_value_show(struct kobject *kobj,
>>>>> +                           struct kobj_attribute *attr, char *buf,
>>>>> +                           u8 device_id, u8 feature_id)
>>>>> +{
>>>>> +   int sel_prof; /* Current fan profile mode */
>>>>> +   int err;
>>>>> +   int retval;
>>>>> +   struct wmi_device *wdev = drvdata.om_wmi->wdev;
>>>>> +
>>>>> +   err = gamezone_wmi_fan_profile_get(&drvdata.gz_wmi->pprof, &sel_prof);
>>>>> +
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_other: Error getting gamezone fan profile.\n");
>>>>> +           return err;
>>>>> +   }
>>>>> +
>>>>> +   // Construct the WMI attribute id from the given args.
>>>>> +   struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
>>>>> +                                           device_id };
>>>>> +
>>>>> +   mutex_lock(&om_wmi.mutex);
>>>>> +   err = lenovo_legion_evaluate_method_1(wdev, 0x0,
>>>>> +                                         WMI_METHOD_ID_VALUE_GET,
>>>>> +                                         *(int *)&attribute_id, &retval);
>>>>> +   mutex_unlock(&om_wmi.mutex);
>>>>> +
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_other: Error getting attribute");
>>>>> +           return err;
>>>>> +   }
>>>>> +
>>>>> +   return sysfs_emit(buf, "%u\n", retval);
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * attr_capdata_01_show() - Get the value of the specified attribute property
>>>>> + * from LENOVO_CAPABILITY_DATA_01.
>>>>> + * @kobj: Pointer to the driver object.
>>>>> + * @kobj_attribute: Pointer to the attribute calling this function.
>>>>> + * @buf: The buffer to write to.
>>>>> + * @retval: Pointer to returned data.
>>>>> + * @device_id: The WMI functions Device ID to use.
>>>>> + * @feature_id: The WMI functions Feature ID to use.
>>>>> + * @prop: The property of this attribute to be read.
>>>>> + *
>>>>> + * This function is intended to be generic so it can be called from any "_show"
>>>>> + * attribute which works only with integers.
>>>>> + *
>>>>> + * If the WMI is success, then the sysfs attribute is notified.
>>>>> + *
>>>>> + * Returns: Either count, or an error.
>>>>> + */
>>>>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
>>>>> +                        char *buf, u8 device_id, u8 feature_id,
>>>>> +                        enum attribute_property prop)
>>>>> +{
>>>>> +   struct capability_data_01 cap_data;
>>>>> +   int err;
>>>>> +   int retval;
>>>>> +   int sel_prof = SMARTFAN_MODE_CUSTOM; /* Only show CUSTOM mode values */
>>>>> +
>>>>> +   // Construct the WMI attribute id from the given args.
>>>>> +   struct om_attribute_id attribute_id = { sel_prof << 8, feature_id,
>>>>> +                                           device_id };
>>>>> +
>>>>> +   err = capdata_01_wmi_get(attribute_id, &cap_data);
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_other: Failed to get capability data.\n");
>>>>> +           return -EIO;
>>>>> +   }
>>>>> +
>>>>> +   switch (prop) {
>>>>> +   case DEFAULT_VAL:
>>>>> +           retval = cap_data.default_value;
>>>>> +           break;
>>>>> +   case MAX_VAL:
>>>>> +           retval = cap_data.max_value;
>>>>> +           break;
>>>>> +   case MIN_VAL:
>>>>> +           retval = cap_data.min_value;
>>>>> +           break;
>>>>> +   case STEP_VAL:
>>>>> +           retval = cap_data.step;
>>>>> +           break;
>>>>> +   default:
>>>>> +           return -EINVAL;
>>>>> +   }
>>>>> +   return sysfs_emit(buf, "%u\n", retval);
>>>>> +}
>>>>> +
>>>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", WMI_DEVICE_ID_CPU,
>>>>> +                       WMI_FEATURE_ID_CPU_SPL,
>>>>> +                       "Set the CPU sustained power limit");
>>>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", WMI_DEVICE_ID_CPU,
>>>>> +                       WMI_FEATURE_ID_CPU_SPPT,
>>>>> +                       "Set the CPU slow package power tracking limit");
>>>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", WMI_DEVICE_ID_CPU,
>>>>> +                       WMI_FEATURE_ID_CPU_FPPT,
>>>>> +                       "Set the CPU fast package power tracking limit");
>>>>> +
>>>>> +static const struct capdata_01_attr_group capdata_01_attr_groups[] = {
>>>>> +   { &ppt_pl1_spl_attr_group },
>>>>> +   { &ppt_pl2_sppt_attr_group },
>>>>> +   { &ppt_pl3_fppt_attr_group },
>>>>> +   {},
>>>>> +};
>>>>> +
>>>>> +static int other_method_fw_attr_add(void)
>>>>> +{
>>>>> +   int err, i;
>>>>> +
>>>>> +   err = fw_attributes_class_get(&fw_attr_class);
>>>>> +   if (err) {
>>>>> +           pr_err("lenovo_legion_wmi_other: Failed to get firmware_attributes_class.\n");
>>>>> +           return err;
>>>>> +   }
>>>>> +
>>>>> +   om_wmi.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
>>>>> +                                      NULL, "%s", DRIVER_NAME);
>>>>> +   if (IS_ERR(om_wmi.fw_attr_dev)) {
>>>>> +           pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class device.\n");
>>>>> +           err = PTR_ERR(om_wmi.fw_attr_dev);
>>>>> +           goto fail_class_get;
>>>>> +   }
>>>>> +
>>>>> +   om_wmi.fw_attr_kset = kset_create_and_add("attributes", NULL,
>>>>> +                                             &om_wmi.fw_attr_dev->kobj);
>>>>> +   if (!om_wmi.fw_attr_kset) {
>>>>> +           pr_err("lenovo_legion_wmi_other: Failed to create firmware_attributes_class kset.\n");
>>>>> +           err = -ENOMEM;
>>>>> +           goto err_destroy_classdev;
>>>>> +   }
>>>>> +
>>>>> +   for (i = 0; i < ARRAY_SIZE(capdata_01_attr_groups) - 1; i++) {
>>>>> +           err = sysfs_create_group(&om_wmi.fw_attr_kset->kobj,
>>>>> +                                    capdata_01_attr_groups[i].attr_group);
>>>>> +           if (err) {
>>>>> +                   pr_err("lenovo_legion_wmi_other: Failed to create sysfs-group for %s\n",
>>>>> +                          capdata_01_attr_groups[i].attr_group->name);
>>>>> +                   goto err_remove_groups;
>>>>> +           }
>>>>> +   }
>>>>> +
>>>>> +   return 0;
>>>>> +
>>>>> +err_remove_groups:
>>>>> +   while (--i >= 0) {
>>>>> +           sysfs_remove_group(&om_wmi.fw_attr_kset->kobj,
>>>>> +                              capdata_01_attr_groups[i].attr_group);
>>>>> +   }
>>>>> +err_destroy_classdev:
>>>>> +   device_destroy(fw_attr_class, MKDEV(0, 0));
>>>>> +fail_class_get:
>>>>> +   fw_attributes_class_put();
>>>>> +   return err;
>>>>> +}
>>>>> +
>>>>> +/* Driver Setup */
>>>>> +static int other_method_wmi_probe(struct wmi_device *wdev, const void *context)
>>>>> +{
>>>>> +   int err;
>>>>> +
>>>>> +   om_wmi.wdev = wdev;
>>>>> +   drvdata.om_wmi = &om_wmi;
>>>>> +   om_wmi.ll_tunables = kzalloc(sizeof(*om_wmi.ll_tunables), GFP_KERNEL);
>>>>> +   if (!om_wmi.ll_tunables)
>>>>> +           return -ENOMEM;
>>>>> +
>>>>> +   err = other_method_fw_attr_add();
>>>>> +   if (err)
>>>>> +           return err;
>>>>> +   pr_info("lenovo_legion_wmi_other: Firmware attributes added.\n");
>>>> too noisy
>>>>
>>>>> +
>>>>> +   return 0;
>>>>> +}
>>>>> +
>>>>> +static void other_method_wmi_remove(struct wmi_device *wdev)
>>>>> +{
>>>>> +   mutex_lock(&om_wmi.mutex);
>>>>> +
>>>>> +   kset_unregister(om_wmi.fw_attr_kset);
>>>>> +   device_destroy(fw_attr_class, MKDEV(0, 0));
>>>>> +   fw_attributes_class_put();
>>>>> +
>>>>> +   mutex_unlock(&om_wmi.mutex);
>>>>> +
>>>>> +   pr_info("lenovo_legion_wmi_other: Firmware attributes removed.\n");
>>>> too noisy.
>>>>
>>>>> +}
>>>>> +
>>>>> +static struct wmi_driver other_method_wmi_driver = {
>>>>> +   .driver = { .name = "other_method_wmi" },
>>>>> +   .id_table = other_method_wmi_id_table,
>>>>> +   .probe = other_method_wmi_probe,
>>>>> +   .remove = other_method_wmi_remove,
>>>>> +};
>>>>> +
>>>>> +module_wmi_driver(other_method_wmi_driver);
>>>>> +
>>>>> +MODULE_IMPORT_NS("GZ_WMI");
>>>>> +MODULE_IMPORT_NS("CAPDATA_WMI");
>>>>> +MODULE_DEVICE_TABLE(wmi, other_method_wmi_id_table);
>>>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>>>> +MODULE_DESCRIPTION("Lenovo Legion Other Method Driver");
>>>>> +MODULE_LICENSE("GPL");
>>>>> diff --git a/drivers/platform/x86/lenovo-legion-wmi.h b/drivers/platform/x86/lenovo-legion-wmi.h
>>>>> new file mode 100644
>>>>> index 000000000000..65baa728f29e
>>>>> --- /dev/null
>>>>> +++ b/drivers/platform/x86/lenovo-legion-wmi.h
>>>>> @@ -0,0 +1,271 @@
>>>>> +/* SPDX-License-Identifier: GPL-2.0-or-later
>>>>> + *
>>>>> + * Lenovo Legion WMI interface driver. The Lenovo Legion WMI interface is
>>>>> + * broken up into multiple GUID interfaces that require cross-references
>>>>> + * between GUID's for some functionality. The "Custom Mode" interface is a
>>>>> + * legacy interface for managing and displaying CPU & GPU power and hwmon
>>>>> + * settings and readings. The "Other Mode" interface is a modern interface
>>>>> + * that replaces or extends the "Custom Mode" interface methods. The "GameZone"
>>>>> + * interface adds advanced features such as fan profiles and overclocking.
>>>>> + * The "Lighting" interface adds control of various status lights related to
>>>>> + * different hardware components. "Custom Mode" uses LENOVO_CAPABILITY_DATA_00
>>>>> + * struct for capability information, "Other Mode" uses
>>>>> + * LENOVO_CAPABILITY_DATA_01 struct for capability information, and "Lighting"
>>>>> + * uses LENOVO_CAPABILITY_DATA_02 struct for capability information.
>>>>> + *
>>>>> + * Copyright(C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
>>>>> + *
>>>> Lose the newline
>>>>
>>>>> + */
>>>>> +
>>>>> +#ifndef _LENOVO_LEGION_WMI_H_
>>>>> +#define _LENOVO_LEGION_WMI_H_
>>>>> +
>>>>> +#include <linux/mutex.h>
>>>>> +#include <linux/platform_profile.h>
>>>>> +#include <linux/types.h>
>>>>> +#include <linux/wmi.h>
>>>>> +
>>>>> +#define DRIVER_NAME "lenovo-legion-wmi"
>>>> This is only used in one of the drivers, I'd move it there to make it
>>>> clearer.
>>>>
>>> Acked for fix.
>>>
>>>>> +
>>>>> +/* Platform Profile Modes */
>>>>> +#define SMARTFAN_MODE_QUIET 0x01
>>>>> +#define SMARTFAN_MODE_BALANCED 0x02
>>>>> +#define SMARTFAN_MODE_PERFORMANCE 0x03
>>>>> +#define SMARTFAN_MODE_CUSTOM 0xFF
>>>>> +
>>>>> +struct gamezone_wmi {
>>>>> +   struct wmi_device *wdev;
>>>>> +   enum platform_profile_option current_profile;
>>>>> +   struct platform_profile_handler pprof;
>>>>> +   bool platform_profile_support;
>>>>> +   struct mutex mutex; /* Ensure single operation on WMI device */
>>>>> +};
>>>>> +
>>>>> +struct other_method_wmi {
>>>>> +   struct wmi_device *wdev;
>>>>> +   struct device *fw_attr_dev;
>>>>> +   struct kset *fw_attr_kset;
>>>>> +   struct ll_tunables *ll_tunables;
>>>>> +   struct mutex mutex; /* Ensure single operation on WMI device */
>>>>> +};
>>>>> +
>>>>> +struct capdata_wmi {
>>>>> +   struct wmi_device *wdev;
>>>>> +   struct mutex mutex; /* Ensure single operation on WMI device */
>>>>> +};
>>>>> +
>>>>> +struct ll_drvdata {
>>>>> +   struct other_method_wmi *om_wmi; /* Other method GUID device */
>>>>> +   struct gamezone_wmi *gz_wmi; /* Gamezone GUID device */
>>>>> +   struct capdata_wmi *cd01_wmi; /* Capability Data 01 GUID device */
>>>>> +} drvdata;
>>>>> +
>>>>> +struct wmi_method_args {
>>>>> +   u32 arg0;
>>>>> +   u32 arg1;
>>>>> +};
>>>>> +
>>>>> +struct om_attribute_id {
>>>>> +   u32 mode_id : 16; /* Fan profile */
>>>>> +   u32 feature_id : 8; /* Attribute (SPL/SPPT/...) */
>>>>> +   u32 device_id : 8; /* CPU/GPU */
>>>>> +} __packed;
>>>>> +
>>>>> +enum attribute_property {
>>>>> +   DEFAULT_VAL = 0,
>>>>> +   MAX_VAL,
>>>>> +   MIN_VAL,
>>>>> +   STEP_VAL,
>>>>> +   SUPPORTED,
>>>>> +};
>>>>> +
>>>>> +struct capability_data_01 {
>>>>> +   u32 id;
>>>>> +   u32 capability;
>>>>> +   u32 default_value;
>>>>> +   u32 step;
>>>>> +   u32 min_value;
>>>>> +   u32 max_value;
>>>>> +};
>>>>> +
>>>>> +static int lenovo_legion_evaluate_method(struct wmi_device *wdev, u8 instance,
>>>>> +                                    u32 method_id, struct acpi_buffer *in,
>>>>> +                                    struct acpi_buffer *out)
>>>>> +{
>>>>> +   acpi_status status;
>>>>> +
>>>>> +   status = wmidev_evaluate_method(wdev, instance, method_id, in, out);
>>>>> +
>>>>> +   if (ACPI_FAILURE(status)) {
>>>>> +           pr_err("lenovo_legion_wmi: wmidev_evaluate_method failed for method_id %u instance %u.\n",
>>>>> +                  method_id, instance);
>>>>> +           return -EIO;
>>>>> +   }
>>>>> +
>>>>> +   return 0;
>>>>> +}
>>>>> +
>>>>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>>>>> +                               u32 method_id, u32 arg0, u32 arg1,
>>>>> +                               u32 *retval);
>>>>> +
>>>>> +int lenovo_legion_evaluate_method_2(struct wmi_device *wdev, u8 instance,
>>>>> +                               u32 method_id, u32 arg0, u32 arg1,
>>>>> +                               u32 *retval)
>>>>> +{
>>>>> +   int ret;
>>>>> +   u32 temp_val;
>>>>> +   struct wmi_method_args args = { arg0, arg1 };
>>>>> +   struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
>>>>> +   struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>>>>> +   union acpi_object *ret_obj = NULL;
>>>> Reverse xmas tree please.
>>>>
>>>>> +
>>>>> +   ret = lenovo_legion_evaluate_method(wdev, instance, method_id, &input,
>>>>> +                                       &output);
>>>>> +
>>>>> +   if (ret) {
>>>>> +           pr_err("lenovo_legion_wmi: Attempt to get method_id %u value failed with error: %u\n",
>>>>> +                  method_id, ret);
>>>>> +           return ret;
>>>>> +   }
>>>>> +
>>>>> +   if (retval) {
>>>>> +           ret_obj = (union acpi_object *)output.pointer;
>>>>> +           if (ret_obj && ret_obj->type == ACPI_TYPE_INTEGER)
>>>>> +                   temp_val = (u32)ret_obj->integer.value;
>>>> This is a pretty bad failure if it's not the case, no?  Should you set a
>>>> return value here instead perhaps?
>>>>
>>>>> +
>>>>> +           *retval = temp_val
>>>> If that above error I mentioned happens then you'll be assigning garbage
>>>> data out.
>>>>
>>>> ;
>>> True, good catch. Someone built with clang+lto and it warned about this section
>>> as well as temp val is not initialized. Will fix both.
>>>
>>>>> +   }
>>>>> +
>>>>> +   kfree(ret_obj);
>>>>> +
>>>>> +   return 0;
>>>>> +}
>>>>> +
>>>>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>>>>> +                               u32 method_id, u32 arg0, u32 *retval);
>>>>> +
>>>>> +int lenovo_legion_evaluate_method_1(struct wmi_device *wdev, u8 instance,
>>>>> +                               u32 method_id, u32 arg0, u32 *retval)
>>>>> +{
>>>>> +   return lenovo_legion_evaluate_method_2(wdev, instance, method_id, arg0,
>>>>> +                                          0, retval);
>>>>> +}
>>>>> +
>>>>> +int capdata_01_wmi_get(struct om_attribute_id attr_id,
>>>>> +                  struct capability_data_01 *cap_data);
>>>>> +
>>>>> +int gamezone_wmi_platform_profile_set(struct platform_profile_handler *pprof,
>>>>> +                                 enum platform_profile_option sel_prof);
>>>>> +
>>>>> +int gamezone_wmi_fan_profile_get(struct platform_profile_handler *pprof,
>>>>> +                            int *sel_prof);
>>>>> +
>>>>> +/* current_value */
>>>>> +ssize_t attr_current_value_store(struct kobject *kobj,
>>>>> +                            struct kobj_attribute *attr, const char *buf,
>>>>> +                            size_t count, u32 *store_value, u8 device_id,
>>>>> +                            u8 feature_id);
>>>>> +
>>>>> +ssize_t attr_current_value_show(struct kobject *kobj,
>>>>> +                           struct kobj_attribute *attr, char *buf,
>>>>> +                           u8 device_id, u8 feature_id);
>>>>> +
>>>>> +/* LENOVO_CAPABILITY_DATA_01 */
>>>>> +ssize_t attr_capdata_01_show(struct kobject *kobj, struct kobj_attribute *attr,
>>>>> +                        char *buf, u8 device_id, u8 feature_id,
>>>>> +                        enum attribute_property prop);
>>>>> +
>>>>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
>>>>> +                 char *buf);
>>>>> +
>>>>> +ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
>>>>> +                 char *buf)
>>>>> +{
>>>>> +   return sysfs_emit(buf, "integer\n");
>>>>> +}
>>>>> +
>>>>> +#define __LL_ATTR_RO(_func, _name)                                    \
>>>>> +   {                                                             \
>>>>> +           .attr = { .name = __stringify(_name), .mode = 0444 }, \
>>>>> +           .show = _func##_##_name##_show,                       \
>>>>> +   }
>>>>> +
>>>>> +#define __LL_ATTR_RO_AS(_name, _show)                                 \
>>>>> +   {                                                             \
>>>>> +           .attr = { .name = __stringify(_name), .mode = 0444 }, \
>>>>> +           .show = _show,                                        \
>>>>> +   }
>>>>> +
>>>>> +#define __LL_ATTR_RW(_func, _name) \
>>>>> +   __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
>>>>> +
>>>>> +/* Shows a formatted static variable */
>>>>> +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val)                         \
>>>>> +   static ssize_t _attrname##_##_prop##_show(                            \
>>>>> +           struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
>>>>> +   {                                                                     \
>>>>> +           return sysfs_emit(buf, _fmt, _val);                           \
>>>>> +   }                                                                     \
>>>>> +   static struct kobj_attribute attr_##_attrname##_##_prop =             \
>>>>> +           __LL_ATTR_RO(_attrname, _prop)
>>>>> +
>>>>> +/* Attribute current_value show/store */
>>>>> +#define __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id)                   \
>>>>> +   static ssize_t _attrname##_current_value_store(                       \
>>>>> +           struct kobject *kobj, struct kobj_attribute *attr,            \
>>>>> +           const char *buf, size_t count)                                \
>>>>> +   {                                                                     \
>>>>> +           return attr_current_value_store(                              \
>>>>> +                   kobj, attr, buf, count,                               \
>>>>> +                   &om_wmi.ll_tunables->_attrname, _dev_id, _feat_id);   \
>>>>> +   }                                                                     \
>>>>> +   static ssize_t _attrname##_current_value_show(                        \
>>>>> +           struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
>>>>> +   {                                                                     \
>>>>> +           return attr_current_value_show(kobj, attr, buf, _dev_id,      \
>>>>> +                                          _feat_id);                     \
>>>>> +   }                                                                     \
>>>>> +   static struct kobj_attribute attr_##_attrname##_current_value =       \
>>>>> +           __LL_ATTR_RW(_attrname, current_value)
>>>>> +
>>>>> +/* Attribute property show only */
>>>>> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _dev_id, _feat_id, _prop_type) \
>>>>> +   static ssize_t _attrname##_##_prop##_show(                             \
>>>>> +           struct kobject *kobj, struct kobj_attribute *attr, char *buf)  \
>>>>> +   {                                                                      \
>>>>> +           return attr_capdata_01_show(kobj, attr, buf, _dev_id,          \
>>>>> +                                       _feat_id, _prop_type);             \
>>>>> +   }                                                                      \
>>>>> +   static struct kobj_attribute attr_##_attrname##_##_prop =              \
>>>>> +           __LL_ATTR_RO(_attrname, _prop)
>>>>> +
>>>>> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dev_id, _feat_id,    \
>>>>> +                               _dispname)                                \
>>>>> +   __LL_TUNABLE_RW_CAP01(_attrname, _dev_id, _feat_id);                  \
>>>>> +   __LL_TUNABLE_RO_CAP01(default_value, _attrname, _dev_id, _feat_id,    \
>>>>> +                         DEFAULT_VAL);                                   \
>>>>> +   __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);          \
>>>>> +   __LL_TUNABLE_RO_CAP01(max_value, _attrname, _dev_id, _feat_id,        \
>>>>> +                         MAX_VAL);                                       \
>>>>> +   __LL_TUNABLE_RO_CAP01(min_value, _attrname, _dev_id, _feat_id,        \
>>>>> +                         MIN_VAL);                                       \
>>>>> +   __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, _dev_id, _feat_id, \
>>>>> +                         STEP_VAL);                                      \
>>>>> +   static struct kobj_attribute attr_##_attrname##_type =                \
>>>>> +           __LL_ATTR_RO_AS(type, int_type_show);                         \
>>>>> +   static struct attribute *_attrname##_attrs[] = {                      \
>>>>> +           &attr_##_attrname##_current_value.attr,                       \
>>>>> +           &attr_##_attrname##_default_value.attr,                       \
>>>>> +           &attr_##_attrname##_display_name.attr,                        \
>>>>> +           &attr_##_attrname##_max_value.attr,                           \
>>>>> +           &attr_##_attrname##_min_value.attr,                           \
>>>>> +           &attr_##_attrname##_scalar_increment.attr,                    \
>>>>> +           &attr_##_attrname##_type.attr,                                \
>>>>> +           NULL,                                                         \
>>>>> +   };                                                                    \
>>>>> +   static const struct attribute_group _attrname##_attr_group = {        \
>>>>> +           .name = _fsname, .attrs = _attrname##_attrs                   \
>>>>> +   }
>>>>> +
>>>>> +#endif /* !_LENOVO_LEGION_WMI_H_ */

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-17 23:06 ` [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers Derek J. Clark
  2024-12-18  2:39   ` Mario Limonciello
@ 2024-12-25  5:25   ` Cody T.-H. Chiu
  2024-12-25  8:34     ` Derek J. Clark
  2024-12-27 18:48   ` [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers Antheas Kapenekakis
  2 siblings, 1 reply; 29+ messages in thread
From: Cody T.-H. Chiu @ 2024-12-25  5:25 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, ike.pan
  Cc: Ilpo Järvinen, Jonathan Corbet, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	platform-driver-x86, linux-doc, linux-kernel


On 12/17/2024 17:06, Derek J. Clark wrote:
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> ...
> +config LEGION_OTHER_WMI
> +	tristate "Lenovo Legion Other Method WMI Driver"
> +	depends on LEGION_GAMEZONE_WMI
> +	depends on LEGION_DATA_01_WMI
> +	select FW_ATTR_CLASS
> +	help
> +	  Say Y here if you have a WMI aware Lenovo Legion device and would 
like to use the
> +	  firmware_attributes API to control various tunable settings 
typically exposed by
> +	  Lenovo software in Windows.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called lenovo_legion_wmi_other.
> +
>   config IDEAPAD_LAPTOP
>   	tristate "Lenovo IdeaPad Laptop Extras"
>   	depends on ACPI

Hi Derek,

Thank you for the initiative, love to see we'll finally get a driver 
developed with the help of official specs.

Perhaps it's common knowledge to the crowd here but I'd like to call out 
right now significant portion of the support on Legion ACPI / WMI came 
from ideapad-laptop which explicitly detects it:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108

Per my observation majority of users have no idea this is the case 
because of the misnomer, adding another set of drivers with Legion in 
the name explicitly, that don't support those features would double the 
dissonance.

I wonder if reconciling this is in your planned scope? If not IMO at 
least this should be called out in documentation / Kconfig.

PS: I'm a developer myself but at lower level kernel domain I'm just a 
user so hopefully I'm not just adding noise here.

- Cody

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-25  5:25   ` Cody T.-H. Chiu
@ 2024-12-25  8:34     ` Derek J. Clark
  2024-12-25 21:11       ` Armin Wolf
  2024-12-30  7:51       ` Cody T.-H. Chiu
  0 siblings, 2 replies; 29+ messages in thread
From: Derek J. Clark @ 2024-12-25  8:34 UTC (permalink / raw)
  To: Cody T.-H. Chiu, Hans de Goede, ike.pan
  Cc: Ilpo Järvinen, Jonathan Corbet, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	platform-driver-x86, linux-doc, linux-kernel



On December 24, 2024 9:25:19 PM PST, "Cody T.-H. Chiu" <codyit@gmail.com> wrote:
>
>On 12/17/2024 17:06, Derek J. Clark wrote:
>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>> ...
>> +config LEGION_OTHER_WMI
>> +	tristate "Lenovo Legion Other Method WMI Driver"
>> +	depends on LEGION_GAMEZONE_WMI
>> +	depends on LEGION_DATA_01_WMI
>> +	select FW_ATTR_CLASS
>> +	help
>> +	  Say Y here if you have a WMI aware Lenovo Legion device and would 
>like to use the
>> +	  firmware_attributes API to control various tunable settings 
>typically exposed by
>> +	  Lenovo software in Windows.
>> +
>> +	  To compile this driver as a module, choose M here: the module will
>> +	  be called lenovo_legion_wmi_other.
>> +
>>   config IDEAPAD_LAPTOP
>>   	tristate "Lenovo IdeaPad Laptop Extras"
>>   	depends on ACPI
>
>Hi Derek,
>
>Thank you for the initiative, love to see we'll finally get a driver developed with the help of official specs.
>
>Perhaps it's common knowledge to the crowd here but I'd like to call out right now significant portion of the support on Legion ACPI / WMI came from ideapad-laptop which explicitly detects it:
>
>https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108


Hi Cody,

Doing a quick search of that GUID on the Lenovo Legion WMI spec there are no matches. Perhaps someone at Lenovo can shed some light here, but the IdeaPad driver grabbing that GUID shouldn't interfere with the GUID's we're working on here.

>Per my observation majority of users have no idea this is the case because of the misnomer, adding another set of drivers with Legion in the name explicitly, that don't support those features would double the dissonance.

It appears the feature sets are quite different. This seems to enable use of special function/media keys on some (one?) Legion laptops, and it also tries to register an ACPI based platform profile. While the driver does load on my legion go, only the amd_pmf and lenovo-legion-wmi-gamezone drivers have platform profiles registered under the new class at /sys/class/platform-profile/ so that isn't a conflict. I think that the ACPI method may only work on the yoga laptops that are supported by this driver? Again, maybe one of the Lenovo reps can comment on that, but it appears to predate the Custom and Other mode WMI GUID's.

>I wonder if reconciling this is in your planned scope? If not IMO at least this should be called out in documentation / Kconfig.

Reconciliation wouldn't be in-line with the WMI driver requirements outlined in the kernel docs as each unique GUID needs to have its own driver in the current spec. It is possible we might need to add a quirk table in the future if we want to add function keys support for the Custom Method or Other Method function keys in the future. Since the Go has no keyboard I can't confirm if the IdeaPad driver is functional on more legion laptops, but considering the DMI quirks that are used in conjunction I would assume support needs to be added explicitly.

If someone wants to add documentation on the IdeaPad driver and what it provides that would be good. I'm not familiar enough with it to really do it myself.

>PS: I'm a developer myself but at lower level kernel domain I'm just a user so hopefully I'm not just adding noise here.
>
>- Cody

- Derek

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-25  8:34     ` Derek J. Clark
@ 2024-12-25 21:11       ` Armin Wolf
  2024-12-26  7:17         ` Derek J. Clark
  2024-12-30  7:51       ` Cody T.-H. Chiu
  1 sibling, 1 reply; 29+ messages in thread
From: Armin Wolf @ 2024-12-25 21:11 UTC (permalink / raw)
  To: Derek J. Clark, Cody T.-H. Chiu, Hans de Goede, ike.pan
  Cc: Ilpo Järvinen, Jonathan Corbet, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	platform-driver-x86, linux-doc, linux-kernel

Am 25.12.24 um 09:34 schrieb Derek J. Clark:

>
> On December 24, 2024 9:25:19 PM PST, "Cody T.-H. Chiu" <codyit@gmail.com> wrote:
>> On 12/17/2024 17:06, Derek J. Clark wrote:
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> ...
>>> +config LEGION_OTHER_WMI
>>> +	tristate "Lenovo Legion Other Method WMI Driver"
>>> +	depends on LEGION_GAMEZONE_WMI
>>> +	depends on LEGION_DATA_01_WMI
>>> +	select FW_ATTR_CLASS
>>> +	help
>>> +	  Say Y here if you have a WMI aware Lenovo Legion device and would
>> like to use the
>>> +	  firmware_attributes API to control various tunable settings
>> typically exposed by
>>> +	  Lenovo software in Windows.
>>> +
>>> +	  To compile this driver as a module, choose M here: the module will
>>> +	  be called lenovo_legion_wmi_other.
>>> +
>>>    config IDEAPAD_LAPTOP
>>>    	tristate "Lenovo IdeaPad Laptop Extras"
>>>    	depends on ACPI
>> Hi Derek,
>>
>> Thank you for the initiative, love to see we'll finally get a driver developed with the help of official specs.
>>
>> Perhaps it's common knowledge to the crowd here but I'd like to call out right now significant portion of the support on Legion ACPI / WMI came from ideapad-laptop which explicitly detects it:
>>
>> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108
>
> Hi Cody,
>
> Doing a quick search of that GUID on the Lenovo Legion WMI spec there are no matches. Perhaps someone at Lenovo can shed some light here, but the IdeaPad driver grabbing that GUID shouldn't interfere with the GUID's we're working on here.
>
>> Per my observation majority of users have no idea this is the case because of the misnomer, adding another set of drivers with Legion in the name explicitly, that don't support those features would double the dissonance.
> It appears the feature sets are quite different. This seems to enable use of special function/media keys on some (one?) Legion laptops, and it also tries to register an ACPI based platform profile. While the driver does load on my legion go, only the amd_pmf and lenovo-legion-wmi-gamezone drivers have platform profiles registered under the new class at /sys/class/platform-profile/ so that isn't a conflict. I think that the ACPI method may only work on the yoga laptops that are supported by this driver? Again, maybe one of the Lenovo reps can comment on that, but it appears to predate the Custom and Other mode WMI GUID's.

Maybe we can remove the "legion" part from the driver name since this WMI device seems to be used on other machines too. Maybe you can instead use "lenovo" when naming the drivers?

Thanks,
Armin Wolf

>
>> I wonder if reconciling this is in your planned scope? If not IMO at least this should be called out in documentation / Kconfig.
> Reconciliation wouldn't be in-line with the WMI driver requirements outlined in the kernel docs as each unique GUID needs to have its own driver in the current spec. It is possible we might need to add a quirk table in the future if we want to add function keys support for the Custom Method or Other Method function keys in the future. Since the Go has no keyboard I can't confirm if the IdeaPad driver is functional on more legion laptops, but considering the DMI quirks that are used in conjunction I would assume support needs to be added explicitly.
>
> If someone wants to add documentation on the IdeaPad driver and what it provides that would be good. I'm not familiar enough with it to really do it myself.

As long as both drivers use different GUIDs we can assume that they do not conflict which each another. Anything else can be added later.

Thanks,
Armin Wolf

>
>> PS: I'm a developer myself but at lower level kernel domain I'm just a user so hopefully I'm not just adding noise here.
>>
>> - Cody
> - Derek
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-22 23:05   ` Armin Wolf
@ 2024-12-26  0:18     ` John Martens
  2024-12-26 23:19       ` Armin Wolf
  2024-12-27 23:18       ` Derek John Clark
  0 siblings, 2 replies; 29+ messages in thread
From: John Martens @ 2024-12-26  0:18 UTC (permalink / raw)
  To: w_armin
  Cc: corbet, derekjohn.clark, hdegoede, ilpo.jarvinen, johnfanv2,
	linux-doc, linux-kernel, luke, mpearson-lenovo, nijs1, pgriffais,
	platform-driver-x86, shaohz1, superm1


I guess the most important task is to get following points right because
they are hard to fix later.

1. Should there be a unifrom sysfs interface for different access methods?
Depending on the model different methods must be used to control the 
same feature, e.g. the powermode, fan table, dust-cleaning-mode. 
The access methods could be a different WMI method (newer model), 
direct ACPI without WMI, or port mapped IO (outb/inb). I suggest that 
regardless of the access methods it should be produce the same sysfs entry. 

Example: there is a fan-fullspeed-methods/dustcleaning-mode that 
sets the fans to the maximal possible speed. I suggest that regardless of 
the used access method there should be the one file:

/sys/class/firmware-attributes/*/attributes/fanfullspeed/current_value

Alternatively, one could use the less elegant approach:

/sys/class/firmware-attributes/*/attributes/wmi-other-fanfullspeed/current_value
/sys/class/firmware-attributes/*/attributes/wmi-gamezone-fanfullspeed/current_value
/sys/class/firmware-attributes/*/attributes/acpi1-fanfullspeed/current_value

2. Naming and file structure: As mentioned above, there different methods - 
including non-WMI methods - are used. Hence, it might not be optimal name
the driver "legion-wmi". One idea would be to name the folder/driver "legion"
and then seperate into multiple files by access methods (WMI by GUID, ACPI, 
port mapped IO). 

3. Driver Structure, selection of access method and probing: The right access
method (WMI, ACPI, ...) has to be chosen for each model. Some of them can
be automatically probed, some of them have to be hard coded (c.f. also Window
tools) by the letter-only prefix of the BIOS version. 

Depending on the driver structure there are multiple ideas how to manage this i
nformation:

a: global-access-into-driver-decide-by-enum: initially the driver can store
the method of access (WMI, ACPI, ...) for e.g. modifying fanfullspeed as
an enum/bitfield/... globally. The value can be decided on by probing and
some hard coded rules. There is one "glovbal" c-file that acts as an 
entrypoint into the driver and adds all the show/store functions. When the 
show-function is called it is decided e.g. by a switch statement which 
function in one of the different files (WMI, ACPI, ...) is called. 
The upside of this method is that if there are not warnings in the code, 
then every case is totally covered. The downside is a lot of boilerplate 
code.

b: global-access-into-driver-decide-by-function-pointer: Same as above
in case a, but direclty use function pointers instead of enum/bits. There
is one function pointers for each attribute in a "global" struct. When
the driver is loaded initially, it sets each function pointers to modify 
an attribute the right function for the model. The upside is 
less boilerplate. The downside is that it might get a little
less safe working with the function pointers.

c: independent-access-in-independent-driver-parts: the driver is split
into totally independent parts for each method (WMI, ACPI, ...) and GUID.
Each driver part is responsible for creating the sysfs entries. To
prevent conflicts each part has to use a unique name (see 1)
for the attribute. Alternatively, the choice of access has to be propagated 
down to each part to prevent creating the same sysfs attribute multiple
times. The upside is the elegance and easy extension. The downside
is the weird sysfs user-interface and the weird coupling between
the different driver parts.

d: totally independent drivers: make a totally independent driver
(module) for each access method.


Some more remarks: 
- I would never make one attribute depend on another
attribute, e.g. when changing some power parameters of GPU/CPU it 
should not change the power mode (e.g. going to custom mode). Initially,
I did the same but it turned out to be not a good idea. However,
if one changes some power settings and is not custom-powermode some
sometimes some weird things happen. Sometimes also all changes are 
ignored by the firmware as seen in the ACPI dissassembly. I guess
it is best to just manage this in user space.
- When trying to find out what access method to choose one cannot rely
on the ACPI/WMI interface. From disassembling the ACPI, one can see
that sometimes/often even if the function is not implemented it
will return without error. Moreover, there are some WMI methods
with name "*IsSupported" (or similar) but they often do not tell
the truth.
- Using just one WMI interface is simple — my grandmother could do it. 
However, when juggling and organizing the various access methods, your 
guidance is needed to set the driver on the right path from the beginning.
So I defenitely, appreciate your input on the different options.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-25 21:11       ` Armin Wolf
@ 2024-12-26  7:17         ` Derek J. Clark
  2024-12-27 17:21           ` Mark Pearson
  0 siblings, 1 reply; 29+ messages in thread
From: Derek J. Clark @ 2024-12-26  7:17 UTC (permalink / raw)
  To: Armin Wolf, Cody T.-H. Chiu, Hans de Goede, ike.pan
  Cc: Ilpo Järvinen, Jonathan Corbet, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	platform-driver-x86, linux-doc, linux-kernel



On December 25, 2024 1:11:32 PM PST, Armin Wolf <W_Armin@gmx.de> wrote:
>Am 25.12.24 um 09:34 schrieb Derek J. Clark:
>
>> 
>> On December 24, 2024 9:25:19 PM PST, "Cody T.-H. Chiu" <codyit@gmail.com> wrote:
>>> On 12/17/2024 17:06, Derek J. Clark wrote:
>>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>>> ...
>>>> +config LEGION_OTHER_WMI
>>>> +	tristate "Lenovo Legion Other Method WMI Driver"
>>>> +	depends on LEGION_GAMEZONE_WMI
>>>> +	depends on LEGION_DATA_01_WMI
>>>> +	select FW_ATTR_CLASS
>>>> +	help
>>>> +	  Say Y here if you have a WMI aware Lenovo Legion device and would
>>> like to use the
>>>> +	  firmware_attributes API to control various tunable settings
>>> typically exposed by
>>>> +	  Lenovo software in Windows.
>>>> +
>>>> +	  To compile this driver as a module, choose M here: the module will
>>>> +	  be called lenovo_legion_wmi_other.
>>>> +
>>>>    config IDEAPAD_LAPTOP
>>>>    	tristate "Lenovo IdeaPad Laptop Extras"
>>>>    	depends on ACPI
>>> Hi Derek,
>>> 
>>> Thank you for the initiative, love to see we'll finally get a driver developed with the help of official specs.
>>> 
>>> Perhaps it's common knowledge to the crowd here but I'd like to call out right now significant portion of the support on Legion ACPI / WMI came from ideapad-laptop which explicitly detects it:
>>> 
>>> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108
>> 
>> Hi Cody,
>> 
>> Doing a quick search of that GUID on the Lenovo Legion WMI spec there are no matches. Perhaps someone at Lenovo can shed some light here, but the IdeaPad driver grabbing that GUID shouldn't interfere with the GUID's we're working on here.
>> 
>>> Per my observation majority of users have no idea this is the case because of the misnomer, adding another set of drivers with Legion in the name explicitly, that don't support those features would double the dissonance.
>> It appears the feature sets are quite different. This seems to enable use of special function/media keys on some (one?) Legion laptops, and it also tries to register an ACPI based platform profile. While the driver does load on my legion go, only the amd_pmf and lenovo-legion-wmi-gamezone drivers have platform profiles registered under the new class at /sys/class/platform-profile/ so that isn't a conflict. I think that the ACPI method may only work on the yoga laptops that are supported by this driver? Again, maybe one of the Lenovo reps can comment on that, but it appears to predate the Custom and Other mode WMI GUID's.
>
>Maybe we can remove the "legion" part from the driver name since this WMI device seems to be used on other machines too. Maybe you can instead use "lenovo" when naming the drivers?
>
>Thanks,
>Armin Wolf

I think you have it backwards. Per the spec only legion laptops will use the GUID's for gamezone, custom/other method, lighting, or capability data. Removing legion from the driver name would probably cause more confusion. The GUID in the IdeaPad driver is what seems out of place. I took a closer look at it and the functionality provided by the GUID he mentioned is a notify for when Fn keys are pressed and none of the other GUIDs have that flag implemented. There is a comment about it being for a legion 5 laptop but it may be the case that some IdeaPad laptops use it as well, I'll ask some of the Lenovo folks directly and see if I can get a positive answer if it is a generic Lenovo interface. IMO if we want to reduce confusion it might make more sense to move that GUID into its own driver at a later date, naming it lenovo-wmi-something without a specific product line, or if it's only used on legion laptops then calling it lenovo-legion-wmi-something might be preferred. Alternatively renaming the IdeaPad driver lenovo-laptop could work to disambiguate it. TBS i don't think it's a high priority right now. I'm going to focus on the gamezone and other method drivers for now.

Regards,
Derek

>> 
>>> I wonder if reconciling this is in your planned scope? If not IMO at least this should be called out in documentation / Kconfig.
>> Reconciliation wouldn't be in-line with the WMI driver requirements outlined in the kernel docs as each unique GUID needs to have its own driver in the current spec. It is possible we might need to add a quirk table in the future if we want to add function keys support for the Custom Method or Other Method function keys in the future. Since the Go has no keyboard I can't confirm if the IdeaPad driver is functional on more legion laptops, but considering the DMI quirks that are used in conjunction I would assume support needs to be added explicitly.
>> 
>> If someone wants to add documentation on the IdeaPad driver and what it provides that would be good. I'm not familiar enough with it to really do it myself.
>
>As long as both drivers use different GUIDs we can assume that they do not conflict which each another. Anything else can be added later.
>
>Thanks,
>Armin Wolf
>
>> 
>>> PS: I'm a developer myself but at lower level kernel domain I'm just a user so hopefully I'm not just adding noise here.
>>> 
>>> - Cody
>> - Derek
>> 

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-26  0:18     ` John Martens
@ 2024-12-26 23:19       ` Armin Wolf
  2024-12-27 17:06         ` Mark Pearson
  2024-12-27 23:18       ` Derek John Clark
  1 sibling, 1 reply; 29+ messages in thread
From: Armin Wolf @ 2024-12-26 23:19 UTC (permalink / raw)
  To: John Martens
  Cc: corbet, derekjohn.clark, hdegoede, ilpo.jarvinen, linux-doc,
	linux-kernel, luke, mpearson-lenovo, nijs1, pgriffais,
	platform-driver-x86, shaohz1, superm1

Am 26.12.24 um 01:18 schrieb John Martens:

> I guess the most important task is to get following points right because
> they are hard to fix later.
>
> 1. Should there be a unifrom sysfs interface for different access methods?
> Depending on the model different methods must be used to control the
> same feature, e.g. the powermode, fan table, dust-cleaning-mode.
> The access methods could be a different WMI method (newer model),
> direct ACPI without WMI, or port mapped IO (outb/inb). I suggest that
> regardless of the access methods it should be produce the same sysfs entry.
>
> Example: there is a fan-fullspeed-methods/dustcleaning-mode that
> sets the fans to the maximal possible speed. I suggest that regardless of
> the used access method there should be the one file:
>
> /sys/class/firmware-attributes/*/attributes/fanfullspeed/current_value
>
> Alternatively, one could use the less elegant approach:
>
> /sys/class/firmware-attributes/*/attributes/wmi-other-fanfullspeed/current_value
> /sys/class/firmware-attributes/*/attributes/wmi-gamezone-fanfullspeed/current_value
> /sys/class/firmware-attributes/*/attributes/acpi1-fanfullspeed/current_value

The firmware-attribute class interface is only intended for attributes which are persistent
and cannot be exposed over other subsystem interfaces.

The "current_value" attribute can be exposed using the hwmon subsystem. Also i assume that
the setting is not persistent across reboots (correct me if i am wrong).

Also depending on the driver the path will be:

/sys/class/firmware-attributes/<name>/attributes/<attr name>/current_value

Because of this i think having a separate interface for each driver is better. We can of course harmonize
the naming of the attributes where it makes sense.

>
> 2. Naming and file structure: As mentioned above, there different methods -
> including non-WMI methods - are used. Hence, it might not be optimal name
> the driver "legion-wmi". One idea would be to name the folder/driver "legion"
> and then seperate into multiple files by access methods (WMI by GUID, ACPI,
> port mapped IO).
>
> 3. Driver Structure, selection of access method and probing: The right access
> method (WMI, ACPI, ...) has to be chosen for each model. Some of them can
> be automatically probed, some of them have to be hard coded (c.f. also Window
> tools) by the letter-only prefix of the BIOS version.
>
> Depending on the driver structure there are multiple ideas how to manage this i
> nformation:
>
> a: global-access-into-driver-decide-by-enum: initially the driver can store
> the method of access (WMI, ACPI, ...) for e.g. modifying fanfullspeed as
> an enum/bitfield/... globally. The value can be decided on by probing and
> some hard coded rules. There is one "glovbal" c-file that acts as an
> entrypoint into the driver and adds all the show/store functions. When the
> show-function is called it is decided e.g. by a switch statement which
> function in one of the different files (WMI, ACPI, ...) is called.
> The upside of this method is that if there are not warnings in the code,
> then every case is totally covered. The downside is a lot of boilerplate
> code.
>
> b: global-access-into-driver-decide-by-function-pointer: Same as above
> in case a, but direclty use function pointers instead of enum/bits. There
> is one function pointers for each attribute in a "global" struct. When
> the driver is loaded initially, it sets each function pointers to modify
> an attribute the right function for the model. The upside is
> less boilerplate. The downside is that it might get a little
> less safe working with the function pointers.
>
> c: independent-access-in-independent-driver-parts: the driver is split
> into totally independent parts for each method (WMI, ACPI, ...) and GUID.
> Each driver part is responsible for creating the sysfs entries. To
> prevent conflicts each part has to use a unique name (see 1)
> for the attribute. Alternatively, the choice of access has to be propagated
> down to each part to prevent creating the same sysfs attribute multiple
> times. The upside is the elegance and easy extension. The downside
> is the weird sysfs user-interface and the weird coupling between
> the different driver parts.
>
> d: totally independent drivers: make a totally independent driver
> (module) for each access method.

I would prefer approach d. Ideally each driver would use standard subsystem interfaces (hwmon, backlight, firmware-attributes, platform-profile, etc)
so that userspace software can be written in a driver-agnostic way.

>
>
> Some more remarks:
> - I would never make one attribute depend on another
> attribute, e.g. when changing some power parameters of GPU/CPU it
> should not change the power mode (e.g. going to custom mode). Initially,
> I did the same but it turned out to be not a good idea. However,
> if one changes some power settings and is not custom-powermode some
> sometimes some weird things happen. Sometimes also all changes are
> ignored by the firmware as seen in the ACPI dissassembly. I guess
> it is best to just manage this in user space.

I agree.

> - When trying to find out what access method to choose one cannot rely
> on the ACPI/WMI interface. From disassembling the ACPI, one can see
> that sometimes/often even if the function is not implemented it
> will return without error. Moreover, there are some WMI methods
> with name "*IsSupported" (or similar) but they often do not tell
> the truth.

Oh no.

I hope that we just misinterpret the result of those methods. Otherwise this would indeed be
very frustrating. Maybe some help from Lenovo can solve this issue.

Thanks,
Armin Wolf

> - Using just one WMI interface is simple — my grandmother could do it.
> However, when juggling and organizing the various access methods, your
> guidance is needed to set the driver on the right path from the beginning.
> So I defenitely, appreciate your input on the different options.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-26 23:19       ` Armin Wolf
@ 2024-12-27 17:06         ` Mark Pearson
  0 siblings, 0 replies; 29+ messages in thread
From: Mark Pearson @ 2024-12-27 17:06 UTC (permalink / raw)
  To: Armin Wolf, John Martens
  Cc: Jonathan Corbet, Derek J . Clark, Hans de Goede,
	Ilpo Järvinen, linux-doc, linux-kernel, Luke D . Jones,
	nijs1, pgriffais, platform-driver-x86@vger.kernel.org, shaohz1,
	Mario Limonciello

On Thu, Dec 26, 2024, at 6:19 PM, Armin Wolf wrote:
> Am 26.12.24 um 01:18 schrieb John Martens:
>
<snip>
>
>> - When trying to find out what access method to choose one cannot rely
>> on the ACPI/WMI interface. From disassembling the ACPI, one can see
>> that sometimes/often even if the function is not implemented it
>> will return without error. Moreover, there are some WMI methods
>> with name "*IsSupported" (or similar) but they often do not tell
>> the truth.
>
> Oh no.
>
> I hope that we just misinterpret the result of those methods. Otherwise 
> this would indeed be
> very frustrating. Maybe some help from Lenovo can solve this issue.
>

Can someone (John?) send me the details off email list and I'll see if the Legion team can help.
(this comes with a caveat that the Legion platforms are not part of the official Linux program yet, so no promises or guarantees)

>
>> - Using just one WMI interface is simple — my grandmother could do it.

You have an awesome grandmother :D 

Mark

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-26  7:17         ` Derek J. Clark
@ 2024-12-27 17:21           ` Mark Pearson
  0 siblings, 0 replies; 29+ messages in thread
From: Mark Pearson @ 2024-12-27 17:21 UTC (permalink / raw)
  To: Derek J . Clark, Armin Wolf, Cody T.-H. Chiu, Hans de Goede,
	ike.pan
  Cc: Ilpo Järvinen, Jonathan Corbet, Luke D . Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Pierre-Loup A . Griffais,
	platform-driver-x86@vger.kernel.org, linux-doc, linux-kernel


On Thu, Dec 26, 2024, at 2:17 AM, Derek J. Clark wrote:
> On December 25, 2024 1:11:32 PM PST, Armin Wolf <W_Armin@gmx.de> wrote:
>>Am 25.12.24 um 09:34 schrieb Derek J. Clark:
>>
>>> 
>>> On December 24, 2024 9:25:19 PM PST, "Cody T.-H. Chiu" <codyit@gmail.com> wrote:
>>>> On 12/17/2024 17:06, Derek J. Clark wrote:
>>>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>>>> ...
>>>>> +config LEGION_OTHER_WMI
>>>>> +	tristate "Lenovo Legion Other Method WMI Driver"
>>>>> +	depends on LEGION_GAMEZONE_WMI
>>>>> +	depends on LEGION_DATA_01_WMI
>>>>> +	select FW_ATTR_CLASS
>>>>> +	help
>>>>> +	  Say Y here if you have a WMI aware Lenovo Legion device and would
>>>> like to use the
>>>>> +	  firmware_attributes API to control various tunable settings
>>>> typically exposed by
>>>>> +	  Lenovo software in Windows.
>>>>> +
>>>>> +	  To compile this driver as a module, choose M here: the module will
>>>>> +	  be called lenovo_legion_wmi_other.
>>>>> +
>>>>>    config IDEAPAD_LAPTOP
>>>>>    	tristate "Lenovo IdeaPad Laptop Extras"
>>>>>    	depends on ACPI
>>>> Hi Derek,
>>>> 
>>>> Thank you for the initiative, love to see we'll finally get a driver developed with the help of official specs.
>>>> 
>>>> Perhaps it's common knowledge to the crowd here but I'd like to call out right now significant portion of the support on Legion ACPI / WMI came from ideapad-laptop which explicitly detects it:
>>>> 
>>>> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108
>>> 
>>> Hi Cody,
>>> 
>>> Doing a quick search of that GUID on the Lenovo Legion WMI spec there are no matches. Perhaps someone at Lenovo can shed some light here, but the IdeaPad driver grabbing that GUID shouldn't interfere with the GUID's we're working on here.
>>> 
>>>> Per my observation majority of users have no idea this is the case because of the misnomer, adding another set of drivers with Legion in the name explicitly, that don't support those features would double the dissonance.
>>> It appears the feature sets are quite different. This seems to enable use of special function/media keys on some (one?) Legion laptops, and it also tries to register an ACPI based platform profile. While the driver does load on my legion go, only the amd_pmf and lenovo-legion-wmi-gamezone drivers have platform profiles registered under the new class at /sys/class/platform-profile/ so that isn't a conflict. I think that the ACPI method may only work on the yoga laptops that are supported by this driver? Again, maybe one of the Lenovo reps can comment on that, but it appears to predate the Custom and Other mode WMI GUID's.
>>
>>Maybe we can remove the "legion" part from the driver name since this WMI device seems to be used on other machines too. Maybe you can instead use "lenovo" when naming the drivers?
>>
>>Thanks,
>>Armin Wolf
>
> I think you have it backwards. Per the spec only legion laptops will 
> use the GUID's for gamezone, custom/other method, lighting, or 
> capability data. Removing legion from the driver name would probably 
> cause more confusion. The GUID in the IdeaPad driver is what seems out 
> of place. I took a closer look at it and the functionality provided by 
> the GUID he mentioned is a notify for when Fn keys are pressed and none 
> of the other GUIDs have that flag implemented. There is a comment about 
> it being for a legion 5 laptop but it may be the case that some IdeaPad 
> laptops use it as well, I'll ask some of the Lenovo folks directly and 
> see if I can get a positive answer if it is a generic Lenovo interface. 
> IMO if we want to reduce confusion it might make more sense to move 
> that GUID into its own driver at a later date, naming it 
> lenovo-wmi-something without a specific product line, or if it's only 
> used on legion laptops then calling it lenovo-legion-wmi-something 
> might be preferred. Alternatively renaming the IdeaPad driver 
> lenovo-laptop could work to disambiguate it. TBS i don't think it's a 
> high priority right now. I'm going to focus on the gamezone and other 
> method drivers for now.
>

It's quite rare to get a spec that is common across all business units sadly (or at least that is my experience). 
I also think it is Legion specific, but we'll have to confirm with that team.
Suggestion: call it lenovo-gamezone-wmi?

Side note - I wouldn't rename ideapad as lenovo-laptop. There are way too many other laptops (thinkpad etc) that don't use that driver (and there is no official Lenovo involvement with that driver to my knowledge).

Mark



^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-17 23:06 ` [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers Derek J. Clark
  2024-12-18  2:39   ` Mario Limonciello
  2024-12-25  5:25   ` Cody T.-H. Chiu
@ 2024-12-27 18:48   ` Antheas Kapenekakis
  2024-12-27 23:22     ` Derek John Clark
  2024-12-29 20:28     ` Armin Wolf
  2 siblings, 2 replies; 29+ messages in thread
From: Antheas Kapenekakis @ 2024-12-27 18:48 UTC (permalink / raw)
  To: derekjohn.clark
  Cc: corbet, hdegoede, ilpo.jarvinen, linux-doc, linux-kernel, luke,
	mpearson-lenovo, nijs1, pgriffais, platform-driver-x86, shaohz1,
	superm1, zhangzx36, johnfanv2, codyit, W_Armin

Hi Derek,

Good job on the driver. I spent a bit of time reviewing it today, and I
have a few thoughts. Hopefully you can go through them fast, and we can
have a solid base of understanding moving forward so that I can e.g.,
merge your driver on Bazzite ahead of the kernel, so you can get some
valuable testing.

// Firmware Attributes
Right now, I have one major concern I'd like to be addressed before
moving into the details of the driver. This is mirrored in the Asus
driver you referenced [1] as well and I do not think I have seen solid
argumentation for it yet.

Essentially, you are using the firmware attributes interface for the
ephemeral attributes SPL, SPPT, and FPPT which is also a concern mirrored
in the thread with Armin (+cc) and John (+cc) [2]. The question here
becomes, if exposing fan curve attributes via the firmware interface is
fickle, why is it correct for SPL, SPPT, and FPPT?

For context, when it comes to Asus (see [1]), these tunables reset during
reboots, after sleep, and when changing the TDP mode. Specifically, TDP
mode, SPL, SPPT, and FPPT, and the fan curve are not preserved between
reboots, with only TDP mode being preserved between sleep. Following, 
setting TDP mode resets SPL, SPP, and FPPT, and setting any one of those
resets the fan curve.

For Lenovo and the Legion Go (1st Gen) it actually depends on the BIOS
version. In early BIOSes, the custom tunings used to be preserved until
you reset the BIOS. However, I suspect that this caused used confusion, as
if you uninstalled Legion Space, the tunings to custom mode would still be
applied which would be unintuitive. In new BIOS versions, the tunings reset
when changing TDP modes, which can be done with the combination Legion
L + Y. I am unsure if they are still preserved between reboots if you
remain in custom mode. Fan curve resets as well. But the point is that they
are not persisted, at least to the extent expected by the firmware
attributes class and writing to them in other modes is undefined.

Lastly, if another driver were to be developed e.g., for AMD out-of-tree
to control devices without a vendor interface, we'd have the same
issue. As this driver or [1] merging would set a precedent for using
firmware attributes for these tunings, I think it is important to reach
agreement before moving forward. For AMD w/o vendor, more details can
be found in [3]. In [3], Mario makes an RFC for an alt interface for SPL,
SPPT, and FPPT, through amd_pmf which does not have this peculiar issue.

There are settings where it does make sense however, such as VRAM, boot
sound (Asus), and the suspend light/barrel plug RGB (Legion). These
settings are typically persisted in BIOS and there is no ambiguity for
fitness in those.

// Driver Details
Ok, as for the rest of the driver, I have (i) some stylistic comments and
(ii) will mirror similar concerns to John in unnecessary accesses.

(i) Stylistic Comments
I would personally be a fan of adding WMI support to the kernel, such that
userspace can access WMI attributes without the use of a driver, as it is
done in Windows. However, as I have discussed with Mario, such a thing is
not an easy task, as it would require adding a BMOF parser to the kernel
which is a monumental effort. In any case, the current kernel requires
us custom write drivers for the WMI interface.

The reason for this preface is that I think that your driver style is a bit
"too close" to what such a thing would look. E.g., in the driver I can see
snippets such as `LENOVO_CAPABILITY_DATA_01 WMI data block driver.` and
`MODULE_IMPORT_NS("CAPDATA_WMI");`. Since you are going through the effort
of writing a custom driver, I would be more opinionated in how I'd design
the driver, so that it's more intuitive from a user's perspective. I think
John's driver (which has its own issues) and the asus-wmi drivers strike a
bit better balance, where they "translate" the WMI calls into an ABI that
can be documented and then parsed by a developer.

Such a design process would then also allow to claim the name legion, as
you could make sure the legion driver only loads on Legion laptops, where
now it would randomly load in other laptops as well. Sidenote here is
that this is something I also found confusing, as the Legion Go does not
have a keyboard so that driver should definitely not load.
E.g., you could only load when you detect the Gamezone interface and then
access the other two as well. The Gamezone interface is only used in
Legion laptops AFAIK. I think Lenovo also kind of implements all three
as stubs even if some are not used, to allow extending them with BIOS
updates, so blindly loading drivers based on the interface might not be
the best idea in any case. When the Legion Go released, I think only one
of the three interfaces was used, for example, but all three were present.

(ii) Unnecessary Accesses
Even though you went hands off on your design of the driver, at the same
time you held back when it comes to userspace accesses by globally forcing
the Other function hints as limits. This creates two issues.

The first one is that those limits might be wrong for certain devices or
certain users might want to go a bit above them, which would mean that if
you enforce them you'd need to provide a way to quirk them and a module
parameter override (I know they are correct for the Legion Go as I have
looked through them as well). But if it is not necessary in Windows,
why would we add additional roadblocks in the kernel? If Lenovo feels the
need to enforce them, they can do so in their firmware and extend that
protection to Windows as well. Asus does when it comes to their fan curve
firmware, for example.

The second is that you are making a lot of necessary calls, which may harm
performance or potentially cause instability. My workflow for setting TDP
on the Go is that I first respect the TDP mode the user has set using
Legion L + Y. If the user wants to change that, then I do the following:
set TDP mode to custom, set the TDP, set the SPL, SPPT, and FPPT, and then
set the fan curve. I also add a small delay in-between each of these calls
as a further precaution. In your current driver, each of these calls would
make two additional calls to the WMI interface (one for the limits and
one for the TDP mode), which would more than double the number of calls
made in a typical scenario (from 5 to 12), where each triplet is made
back to back.

There is also the issue mentioned by John, where you do cross-interface
interactions etc. To fix this, you'd have to retrieve the limits from
firmware on probing and then cache them in the driver after quirking. It
would be much easier just to skip all of this for now and just use them to
prepopulate _min and _max values as hints instead, which you already do.

Those were my comments. You mentioned that you are travelling and might not
have access to your PC so take your time with the replies.

Happy holidays,
Antheas

[1] https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/
[2] https://lore.kernel.org/lkml/eb165321-6a52-4464-bb58-11d8f9ff2008@gmx.de/
[3] https://lore.kernel.org/lkml/20240926025955.1728766-1-superm1@kernel.org/

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-26  0:18     ` John Martens
  2024-12-26 23:19       ` Armin Wolf
@ 2024-12-27 23:18       ` Derek John Clark
  1 sibling, 0 replies; 29+ messages in thread
From: Derek John Clark @ 2024-12-27 23:18 UTC (permalink / raw)
  To: John Martens
  Cc: w_armin, corbet, hdegoede, ilpo.jarvinen, linux-doc, linux-kernel,
	luke, mpearson-lenovo, nijs1, pgriffais, platform-driver-x86,
	shaohz1, superm1

On Wed, Dec 25, 2024 at 4:18 PM John Martens <johnfanv2@gmail.com> wrote:

>I guess the most important task is to get following points right because
>they are hard to fix later.
>
>1. Should there be a unifrom sysfs interface for different access methods?
>Depending on the model different methods must be used to control the
>same feature, e.g. the powermode, fan table, dust-cleaning-mode.
>The access methods could be a different WMI method (newer model),
>direct ACPI without WMI, or port mapped IO (outb/inb). I suggest that
>regardless of the access methods it should be produce the same sysfs entry.
>

>Example: there is a fan-fullspeed-methods/dustcleaning-mode that
>sets the fans to the maximal possible speed. I suggest that regardless of
>the used access method there should be the one file:
>
>/sys/class/firmware-attributes/*/attributes/fanfullspeed/current_value
>
>Alternatively, one could use the less elegant approach:
>
>/sys/class/firmware-attributes/*/attributes/wmi-other-fanfullspeed/current_value
>/sys/class/firmware-attributes/*/attributes/wmi-gamezone-fanfullspeed/current_value
>/sys/class/firmware-attributes/*/attributes/acpi1-fanfullspeed/current_value

I agree generally, though there are some limitations. As Armin mentioned, the
firmware-attributes interface will handle this with the pattern
/sys/class/firmware-attributes/<name>. <name> is derived when the class is
initialized. In v1 I used lenovo-legion-wmi but based on the discussion so far
I'll likely drop legion from all patterns and call everything based on its GUID
plain text name like Mark suggested, lenovo-wmi-other for Other Method,
lenovo-wmi-custom for Custom Method, etc. That will ensure two things:
(1) drivers won't conflict in namespace if there is an unused but present in
the bios older interface on a newer device.
(2) Userspace apps can determine the preferred interface for a given device if
more than one happens to be present. I don't think this will be very common,
but it can be handled if it does happen.

The goal of this driver, jointly with asus-armoury, is to standardize the
features that aren't already in the kernel while utilizing those that are. I.E.
AMD PPT doesn't yet have a standard interface, platform-profile does. The names
of the attributes that provide specific functionality will remain the same
between drivers (ppt_pl1_spl for example). As firmware-attributes is a class,
it is easy to use udev in userspace apps to avoid hard coding paths so that any
interface that provides these attributes will be interchangeable or include a
hierarchy table. In the event more than one "competing" driver loads the
userspace app can prioritize a specific interface and, if a genuine conflict
arises between two drivers on a specific device, it will be easy enough to
quirk that device to not load one or the other. The fanfullspeed attribute
should be able to follow this same pattern when implemented.

>2. Naming and file structure: As mentioned above, there different methods -
>including non-WMI methods - are used. Hence, it might not be optimal name
>the driver "legion-wmi". One idea would be to name the folder/driver "legion"
>and then seperate into multiple files by access methods (WMI by GUID, ACPI,
>port mapped IO).

Since each driver will be independent I don't see this being a problem.
lenovo-acpi-tunables or lenovo-i2c-tunables (as example) could coexist.
As above, each firmware-attributes path will be different but the attributes
within them would be standardized.

>3. Driver Structure, selection of access method and probing: The right access
>method (WMI, ACPI, ...) has to be chosen for each model. Some of them can
>be automatically probed, some of them have to be hard coded (c.f. also Window
>tools) by the letter-only prefix of the BIOS version.
>
>Depending on the driver structure there are multiple ideas how to manage this i
>nformation:
>
>a: global-access-into-driver-decide-by-enum: initially the driver can store
>the method of access (WMI, ACPI, ...) for e.g. modifying fanfullspeed as
>an enum/bitfield/... globally. The value can be decided on by probing and
>some hard coded rules. There is one "glovbal" c-file that acts as an
>entrypoint into the driver and adds all the show/store functions. When the
>show-function is called it is decided e.g. by a switch statement which
>function in one of the different files (WMI, ACPI, ...) is called.
>The upside of this method is that if there are not warnings in the code,
>then every case is totally covered. The downside is a lot of boilerplate
>code.
>
>b: global-access-into-driver-decide-by-function-pointer: Same as above
>in case a, but direclty use function pointers instead of enum/bits. There
>is one function pointers for each attribute in a "global" struct. When
>the driver is loaded initially, it sets each function pointers to modify
>an attribute the right function for the model. The upside is
>less boilerplate. The downside is that it might get a little
>less safe working with the function pointers.
>
>c: independent-access-in-independent-driver-parts: the driver is split
>into totally independent parts for each method (WMI, ACPI, ...) and GUID.
>Each driver part is responsible for creating the sysfs entries. To
>prevent conflicts each part has to use a unique name (see 1)
>for the attribute. Alternatively, the choice of access has to be propagated
>down to each part to prevent creating the same sysfs attribute multiple
>times. The upside is the elegance and easy extension. The downside
>is the weird sysfs user-interface and the weird coupling between
>the different driver parts.
>
>d: totally independent drivers: make a totally independent driver
>(module) for each access method.
>
>Some more remarks:
>- I would never make one attribute depend on another
>attribute, e.g. when changing some power parameters of GPU/CPU it
>should not change the power mode (e.g. going to custom mode). Initially,
>I did the same but it turned out to be not a good idea. However,
>if one changes some power settings and is not custom-powermode some
>sometimes some weird things happen. Sometimes also all changes are
>ignored by the firmware as seen in the ACPI dissassembly. I guess
>it is best to just manage this in user space.

I think that is the consensus going forward. In v2 each interface will be
independent of the others except when it relies on a WMI data block as part of
its data validation.

>- When trying to find out what access method to choose one cannot rely
>on the ACPI/WMI interface. From disassembling the ACPI, one can see
>that sometimes/often even if the function is not implemented it
>will return without error. Moreover, there are some WMI methods
>with name "*IsSupported" (or similar) but they often do not tell
>the truth.

Generally I prefer to use the path of least intervention and automatic
discovery with explicit deny lists, then explicit allow lists if needed. That
will limit the amount of work needed to support new hardware that implements
the same functionality. WMI interfaces for example should load automatically,
unless there is a restrictive DMI quirk for a specific model. If BIOS revision
is of concern then that driver will quirk an alternative method or deny the
driver overall. If automatic discovery isn't an option, such as EC writes with
inb/outb, then we would explicitly load it using a DMI table in that driver.
From my experience this seems to be the standard for kernel drivers. We will
need to rely on your historical data and Lenovo to get this right.

>- Using just one WMI interface is simple — my grandmother could do it.
>However, when juggling and organizing the various access methods, your
>guidance is needed to set the driver on the right path from the beginning.
>So I defenitely, appreciate your input on the different options.

Thanks John,
Derek

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-27 18:48   ` [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers Antheas Kapenekakis
@ 2024-12-27 23:22     ` Derek John Clark
  2024-12-28  1:09       ` Antheas Kapenekakis
  2024-12-29 20:28     ` Armin Wolf
  1 sibling, 1 reply; 29+ messages in thread
From: Derek John Clark @ 2024-12-27 23:22 UTC (permalink / raw)
  To: Antheas Kapenekakis
  Cc: corbet, hdegoede, ilpo.jarvinen, linux-doc, linux-kernel, luke,
	mpearson-lenovo, nijs1, pgriffais, platform-driver-x86, shaohz1,
	superm1, zhangzx36, johnfanv2, codyit, W_Armin

On Fri, Dec 27, 2024 at 10:48 AM Antheas Kapenekakis <lkml@antheas.dev> wrote:
Subject: Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers

>Hi Derek,

Seasons Greetings, Antheas.

>Good job on the driver. I spent a bit of time reviewing it today, and I
>have a few thoughts. Hopefully you can go through them fast, and we can
>have a solid base of understanding moving forward so that I can e.g.,
>merge your driver on Bazzite ahead of the kernel, so you can get some
>valuable testing.
>
>// Firmware Attributes
>Right now, I have one major concern I'd like to be addressed before
>moving into the details of the driver. This is mirrored in the Asus
>driver you referenced [1] as well and I do not think I have seen solid
>argumentation for it yet.
>
>Essentially, you are using the firmware attributes interface for the
>ephemeral attributes SPL, SPPT, and FPPT which is also a concern mirrored
>in the thread with Armin (+cc) and John (+cc) [2]. The question here
>becomes, if exposing fan curve attributes via the firmware interface is
>fickle, why is it correct for SPL, SPPT, and FPPT?

See [1] and [2] below. Hans de Goede and Ilpo Järvinen signed off on using it
this way for the asus-armoury driver, as proposed by Mario. This class is the
"most" correct one currently present in the kernel. If you think an alternative
solution would make more sense then I would encourage you to create that
interface and submit it. In the meantime I am going to move forward with what
has already been discussed and, if you manage to get in in before this series
is approved, then I will adjust. Otherwise, please CC me on the patch that
changes this driver to include it.

>For context, when it comes to Asus (see [1]), these tunables reset during
>reboots, after sleep, and when changing the TDP mode. Specifically, TDP
>mode, SPL, SPPT, and FPPT, and the fan curve are not preserved between
>reboots, with only TDP mode being preserved between sleep. Following,
>setting TDP mode resets SPL, SPP, and FPPT, and setting any one of those
>resets the fan curve.
>
>For Lenovo and the Legion Go (1st Gen) it actually depends on the BIOS
>version. In early BIOSes, the custom tunings used to be preserved until
>you reset the BIOS. However, I suspect that this caused used confusion, as
>if you uninstalled Legion Space, the tunings to custom mode would still be
>applied which would be unintuitive. In new BIOS versions, the tunings reset
>when changing TDP modes, which can be done with the combination Legion
>L + Y. I am unsure if they are still preserved between reboots if you
>remain in custom mode. Fan curve resets as well. But the point is that they
>are not persisted, at least to the extent expected by the firmware
>attributes class and writing to them in other modes is undefined.
>
>Lastly, if another driver were to be developed e.g., for AMD out-of-tree
>to control devices without a vendor interface, we'd have the same
>issue. As this driver or [1] merging would set a precedent for using
>firmware attributes for these tunings, I think it is important to reach
>agreement before moving forward. For AMD w/o vendor, more details can
>be found in [3]. In [3], Mario makes an RFC for an alt interface for SPL,
>SPPT, and FPPT, through amd_pmf which does not have this peculiar issue.
>
>There are settings where it does make sense however, such as VRAM, boot
>sound (Asus), and the suspend light/barrel plug RGB (Legion). These
>settings are typically persisted in BIOS and there is no ambiguity for
>fitness in those.
>

>// Driver Details
>Ok, as for the rest of the driver, I have (i) some stylistic comments and
>(ii) will mirror similar concerns to John in unnecessary accesses.
>
>(i) Stylistic Comments
>I would personally be a fan of adding WMI support to the kernel, such that
>userspace can access WMI attributes without the use of a driver, as it is
>done in Windows. However, as I have discussed with Mario, such a thing is
>not an easy task, as it would require adding a BMOF parser to the kernel
>which is a monumental effort. In any case, the current kernel requires
>us custom write drivers for the WMI interface.
>
>The reason for this preface is that I think that your driver style is a bit
>"too close" to what such a thing would look. E.g., in the driver I can see
>snippets such as `LENOVO_CAPABILITY_DATA_01 WMI data block driver.` and
>`MODULE_IMPORT_NS("CAPDATA_WMI");`. Since you are going through the effort
>of writing a custom driver, I would be more opinionated in how I'd design
>the driver, so that it's more intuitive from a user's perspective. I think
>John's driver (which has its own issues) and the asus-wmi drivers strike a
>bit better balance, where they "translate" the WMI calls into an ABI that
>can be documented and then parsed by a developer.

I'm not sure what you are talking about exactly. Can you provide some sort of
example? The scope is quite specific as it is, and will be even more specific
once I include the requested changes. There is no ambiguity for the user on
what /sys/firmware/acpi/platform_profile or /sys/class/firmware-attributes/
lenovo-wmi-gamezone/attributes/ppt_pl1_spl are. As Armin suggested and I ack'd,
capdata01 and Other Method drivers will be linked as components in v2.

>Such a design process would then also allow to claim the name legion, as
>you could make sure the legion driver only loads on Legion laptops, where
>now it would randomly load in other laptops as well. Sidenote here is
>that this is something I also found confusing, as the Legion Go does not
>have a keyboard so that driver should definitely not load.

This is a consequence of the WMI design in the kernel as is. WMI GUID tables
only load on devices that implement them. If a device erroneously reports an
unimplemented GUID that is a problem with the BIOS and it needs (preferred) a
BIOS update and/or a DMI quirk to block it if loading it creates an issue. This
will be device specific.

>E.g., you could only load when you detect the Gamezone interface and then
>access the other two as well. The Gamezone interface is only used in
>Legion laptops AFAIK. I think Lenovo also kind of implements all three
>as stubs even if some are not used, to allow extending them with BIOS
>updates, so blindly loading drivers based on the interface might not be
>the best idea in any case. When the Legion Go released, I think only one
>of the three interfaces was used, for example, but all three were present.

If we run into a situation where that check passes on a device because the
interface produces a valid ACPI object that says it is supported even though
the interface isn't fully implemented then we can quirk it. Also, this is
contradictory to your point (ii) where you insist these calls are unnecessary.

>(ii) Unnecessary Accesses
>Even though you went hands off on your design of the driver, at the same
>time you held back when it comes to userspace accesses by globally forcing
>the Other function hints as limits. This creates two issues.
>
>The first one is that those limits might be wrong for certain devices or
>certain users might want to go a bit above them, which would mean that if
>you enforce them you'd need to provide a way to quirk them and a module
>parameter override (I know they are correct for the Legion Go as I have
>looked through them as well). But if it is not necessary in Windows,
>why would we add additional roadblocks in the kernel? If Lenovo feels the
>need to enforce them, they can do so in their firmware and extend that
>protection to Windows as well. Asus does when it comes to their fan curve
>firmware, for example.

Unless you have some concrete examples of this actually occuring I'm inclined
to assume that Lenovo knows how to implement their interface. If we do find a
device that has a broken CAPDATA interface, again we can quirk it.
Notwithstanding that, the data exists and it ensures that the interface isn't
abused to the point that hardware is pushed beyond its design limitations.
Mario or Xino can correct me here, but I expect AMD and Lenovo have an interest
in ensuring the kernel doesn't permit settings that can lead to additional
RMAs. For users who wish to disregard that there are solutions to modify this
data which void the warranty, as you are well aware. I'll also point out that
Lenovo controls the userspace software in Windows and never intended to support
Linux originally, so it is reasonable to assume this was just an oversight in
the design and they didn't expect anyone else would implement another WMI driver
or software when it was designed. In any case, My primary concern is that the
interface is safe and reliable.

>The second is that you are making a lot of necessary calls, which may harm
>performance or potentially cause instability. My workflow for setting TDP
>on the Go is that I first respect the TDP mode the user has set using
>Legion L + Y. If the user wants to change that, then I do the following:
>set TDP mode to custom, set the TDP, set the SPL, SPPT, and FPPT, and then
>set the fan curve. I also add a small delay in-between each of these calls
>as a further precaution. In your current driver, each of these calls would
>make two additional calls to the WMI interface (one for the limits and
>one for the TDP mode), which would more than double the number of calls
>made in a typical scenario (from 5 to 12), where each triplet is made
>back to back.
>
>There is also the issue mentioned by John, where you do cross-interface
>interactions etc. To fix this, you'd have to retrieve the limits from
>firmware on probing and then cache them in the driver after quirking. It
>would be much easier just to skip all of this for now and just use them to
>prepopulate _min and _max values as hints instead, which you already do.

I had already planned on making this change for other reasons, but I want to
dispel some misinformation you have presented here. First, I highly doubt
anyone is making enough calls to this interface that the extra 4ns are going
to even be noticed. I would say they are certainly using the interface
incorrectly if they do. Second, LENOVO_CAPABILITY_DATA_01 is a separate
interface, and it is (currently) only called once per attribute change. For
some background, this was originally added when I was permitting the current
fan profile to determine which capability data page was presented and set. In
the submitted version I hard code the page to custom as testing under quiet,
balanced, and performance showed no change. Technically it is an assumption
that this will be universally true for all Legion hardware, but the change was
made as the final approved version of Mario's patches which added the necessary
custom profile to platform_profile explicitly states in the documentation that
"custom" mode is not user selectable. As Armin pointed out, this only applies to
the legacy interface in /sys/firmware. In any case, you need not worry about
this as v2 will not need this call more than once during probe, and gamezone
will not be called by Other Method ever.

>Those were my comments. You mentioned that you are travelling and might not
>have access to your PC so take your time with the replies.
>
>Happy holidays,
>Antheas


[1] https://lore.kernel.org/all/e9f4fb37-5277-a7f0-2bec-8a6909b4e674@linux.intel.com/
[2] https://lore.kernel.org/all/07fe1bf8-f470-4e40-86ba-0f8d2e61a3e6@amd.com/

Cheers,
Derek

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-27 23:22     ` Derek John Clark
@ 2024-12-28  1:09       ` Antheas Kapenekakis
  2024-12-28  3:30         ` Derek John Clark
  0 siblings, 1 reply; 29+ messages in thread
From: Antheas Kapenekakis @ 2024-12-28  1:09 UTC (permalink / raw)
  To: Derek John Clark
  Cc: corbet, hdegoede, ilpo.jarvinen, linux-doc, linux-kernel, luke,
	mpearson-lenovo, nijs1, pgriffais, platform-driver-x86, shaohz1,
	superm1, zhangzx36, johnfanv2, codyit, W_Armin

On Sat, 28 Dec 2024 at 00:22, Derek John Clark
<derekjohn.clark@gmail.com> wrote:
>
> On Fri, Dec 27, 2024 at 10:48 AM Antheas Kapenekakis <lkml@antheas.dev> wrote:
> Subject: Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
>
> >Hi Derek,
>
> Seasons Greetings, Antheas.
>
> >Good job on the driver. I spent a bit of time reviewing it today, and I
> >have a few thoughts. Hopefully you can go through them fast, and we can
> >have a solid base of understanding moving forward so that I can e.g.,
> >merge your driver on Bazzite ahead of the kernel, so you can get some
> >valuable testing.
> >
> >// Firmware Attributes
> >Right now, I have one major concern I'd like to be addressed before
> >moving into the details of the driver. This is mirrored in the Asus
> >driver you referenced [1] as well and I do not think I have seen solid
> >argumentation for it yet.
> >
> >Essentially, you are using the firmware attributes interface for the
> >ephemeral attributes SPL, SPPT, and FPPT which is also a concern mirrored
> >in the thread with Armin (+cc) and John (+cc) [2]. The question here
> >becomes, if exposing fan curve attributes via the firmware interface is
> >fickle, why is it correct for SPL, SPPT, and FPPT?
>
> See [1] and [2] below. Hans de Goede and Ilpo Järvinen signed off on using it
> this way for the asus-armoury driver, as proposed by Mario. This class is the
> "most" correct one currently present in the kernel. If you think an alternative
> solution would make more sense then I would encourage you to create that
> interface and submit it. In the meantime I am going to move forward with what
> has already been discussed and, if you manage to get in in before this series
> is approved, then I will adjust. Otherwise, please CC me on the patch that
> changes this driver to include it.

They need to re-agree given the context below. It is one thing for
them to agree on it theoretically for settings that they might imagine
are persistent and another thing when in reality they are not.

You did not address this in your comment here.

The problem I am raising is that SPL, SPPT, and FPPT specifically are
not persistent enough to meet the guidelines of that interface.

[1] and [2] do not address this either.

I do not have an alternative planned, just noting that I'd like
everyone to be on the same page before we go ahead with this ABI.

> >For context, when it comes to Asus (see [1]), these tunables reset during
> >reboots, after sleep, and when changing the TDP mode. Specifically, TDP
> >mode, SPL, SPPT, and FPPT, and the fan curve are not preserved between
> >reboots, with only TDP mode being preserved between sleep. Following,
> >setting TDP mode resets SPL, SPP, and FPPT, and setting any one of those
> >resets the fan curve.
> >
> >For Lenovo and the Legion Go (1st Gen) it actually depends on the BIOS
> >version. In early BIOSes, the custom tunings used to be preserved until
> >you reset the BIOS. However, I suspect that this caused used confusion, as
> >if you uninstalled Legion Space, the tunings to custom mode would still be
> >applied which would be unintuitive. In new BIOS versions, the tunings reset
> >when changing TDP modes, which can be done with the combination Legion
> >L + Y. I am unsure if they are still preserved between reboots if you
> >remain in custom mode. Fan curve resets as well. But the point is that they
> >are not persisted, at least to the extent expected by the firmware
> >attributes class and writing to them in other modes is undefined.
> >
> >Lastly, if another driver were to be developed e.g., for AMD out-of-tree
> >to control devices without a vendor interface, we'd have the same
> >issue. As this driver or [1] merging would set a precedent for using
> >firmware attributes for these tunings, I think it is important to reach
> >agreement before moving forward. For AMD w/o vendor, more details can
> >be found in [3]. In [3], Mario makes an RFC for an alt interface for SPL,
> >SPPT, and FPPT, through amd_pmf which does not have this peculiar issue.
> >
> >There are settings where it does make sense however, such as VRAM, boot
> >sound (Asus), and the suspend light/barrel plug RGB (Legion). These
> >settings are typically persisted in BIOS and there is no ambiguity for
> >fitness in those.
> >
>
> >// Driver Details
> >Ok, as for the rest of the driver, I have (i) some stylistic comments and
> >(ii) will mirror similar concerns to John in unnecessary accesses.
> >
> >(i) Stylistic Comments
> >I would personally be a fan of adding WMI support to the kernel, such that
> >userspace can access WMI attributes without the use of a driver, as it is
> >done in Windows. However, as I have discussed with Mario, such a thing is
> >not an easy task, as it would require adding a BMOF parser to the kernel
> >which is a monumental effort. In any case, the current kernel requires
> >us custom write drivers for the WMI interface.
> >
> >The reason for this preface is that I think that your driver style is a bit
> >"too close" to what such a thing would look. E.g., in the driver I can see
> >snippets such as `LENOVO_CAPABILITY_DATA_01 WMI data block driver.` and
> >`MODULE_IMPORT_NS("CAPDATA_WMI");`. Since you are going through the effort
> >of writing a custom driver, I would be more opinionated in how I'd design
> >the driver, so that it's more intuitive from a user's perspective. I think
> >John's driver (which has its own issues) and the asus-wmi drivers strike a
> >bit better balance, where they "translate" the WMI calls into an ABI that
> >can be documented and then parsed by a developer.
>
> I'm not sure what you are talking about exactly. Can you provide some sort of
> example? The scope is quite specific as it is, and will be even more specific
> once I include the requested changes. There is no ambiguity for the user on
> what /sys/firmware/acpi/platform_profile or /sys/class/firmware-attributes/
> lenovo-wmi-gamezone/attributes/ppt_pl1_spl are. As Armin suggested and I ack'd,
> capdata01 and Other Method drivers will be linked as components in v2.

To rephrase, your ABI style is not intuitive, because it contains
implementation details such as "gamezone", "capdata01", and "Other
Method", in addition to the ABI being hardcoded to the WMI structure
lenovo uses. The documentation uses those keywords as well.

I just brought up that you could have taken different design choices
to have a cleaner API, and at the same time avoided comments such as
the IdeaPad one or using the Legion name.

If I understand correctly your last sentence, Armin suggested much of
the same (ie combine and merge).

> >Such a design process would then also allow to claim the name legion, as
> >you could make sure the legion driver only loads on Legion laptops, where
> >now it would randomly load in other laptops as well. Sidenote here is
> >that this is something I also found confusing, as the Legion Go does not
> >have a keyboard so that driver should definitely not load.
>
> This is a consequence of the WMI design in the kernel as is. WMI GUID tables
> only load on devices that implement them. If a device erroneously reports an
> unimplemented GUID that is a problem with the BIOS and it needs (preferred) a
> BIOS update and/or a DMI quirk to block it if loading it creates an issue. This
> will be device specific.

An industry practice cannot be a BIOS bug. If Lenovo has a practice of
using those interfaces in all of their BIOSes to allow for future
extension you'd need to take account of that.

Since there are Lenovo reps here they are more equipped to comment on
it than I am.

GUID tables loading != drivers loading also, I would not pin that on
the kernel. You could bail on a stub implementation or require
multiple interfaces be present.

> >E.g., you could only load when you detect the Gamezone interface and then
> >access the other two as well. The Gamezone interface is only used in
> >Legion laptops AFAIK. I think Lenovo also kind of implements all three
> >as stubs even if some are not used, to allow extending them with BIOS
> >updates, so blindly loading drivers based on the interface might not be
> >the best idea in any case. When the Legion Go released, I think only one
> >of the three interfaces was used, for example, but all three were present.
>
> If we run into a situation where that check passes on a device because the
> interface produces a valid ACPI object that says it is supported even though
> the interface isn't fully implemented then we can quirk it. Also, this is
> contradictory to your point (ii) where you insist these calls are unnecessary.

For the first part, see above. As for (ii) I only said they are
unnecessary during writing, not during probe or deriving _min, _max
which would be done once by userspace software.

> >(ii) Unnecessary Accesses
> >Even though you went hands off on your design of the driver, at the same
> >time you held back when it comes to userspace accesses by globally forcing
> >the Other function hints as limits. This creates two issues.
> >
> >The first one is that those limits might be wrong for certain devices or
> >certain users might want to go a bit above them, which would mean that if
> >you enforce them you'd need to provide a way to quirk them and a module
> >parameter override (I know they are correct for the Legion Go as I have
> >looked through them as well). But if it is not necessary in Windows,
> >why would we add additional roadblocks in the kernel? If Lenovo feels the
> >need to enforce them, they can do so in their firmware and extend that
> >protection to Windows as well. Asus does when it comes to their fan curve
> >firmware, for example.
>
> Unless you have some concrete examples of this actually occuring I'm inclined
> to assume that Lenovo knows how to implement their interface. If we do find a
> device that has a broken CAPDATA interface, again we can quirk it.
> Notwithstanding that, the data exists and it ensures that the interface isn't
> abused to the point that hardware is pushed beyond its design limitations.
> Mario or Xino can correct me here, but I expect AMD and Lenovo have an interest
> in ensuring the kernel doesn't permit settings that can lead to additional
> RMAs. For users who wish to disregard that there are solutions to modify this
> data which void the warranty, as you are well aware. I'll also point out that
> Lenovo controls the userspace software in Windows and never intended to support
> Linux originally, so it is reasonable to assume this was just an oversight in
> the design and they didn't expect anyone else would implement another WMI driver
> or software when it was designed. In any case, My primary concern is that the
> interface is safe and reliable.

WMI functions are accessible to all software in WIndows without any
limits and there are multiple WIndows software for Legion and Asus
devices that use those interfaces without limits.

So if it is a problem it is in the interest of Lenovo to fix this in
their BIOS, so that they remove that liability from both OSs. Here I
have to say that the max TDP of the device is limited by AMD CBS and
Lenovo has implemented overheating protection so it is actually quite
safe already.

So it is your choice to take on that overhead. If you do take it on,
make sure you dont do extra WMI calls and introduce a quirk system.

> >The second is that you are making a lot of necessary calls, which may harm
> >performance or potentially cause instability. My workflow for setting TDP
> >on the Go is that I first respect the TDP mode the user has set using
> >Legion L + Y. If the user wants to change that, then I do the following:
> >set TDP mode to custom, set the TDP, set the SPL, SPPT, and FPPT, and then
> >set the fan curve. I also add a small delay in-between each of these calls
> >as a further precaution. In your current driver, each of these calls would
> >make two additional calls to the WMI interface (one for the limits and
> >one for the TDP mode), which would more than double the number of calls
> >made in a typical scenario (from 5 to 12), where each triplet is made
> >back to back.
> >
> >There is also the issue mentioned by John, where you do cross-interface
> >interactions etc. To fix this, you'd have to retrieve the limits from
> >firmware on probing and then cache them in the driver after quirking. It
> >would be much easier just to skip all of this for now and just use them to
> >prepopulate _min and _max values as hints instead, which you already do.
>
> I had already planned on making this change for other reasons, but I want to
> dispel some misinformation you have presented here. First, I highly doubt
> anyone is making enough calls to this interface that the extra 4ns are going
> to even be noticed. I would say they are certainly using the interface

That's hardly an argument for making unnecessary calls unfortunately.

> incorrectly if they do. Second, LENOVO_CAPABILITY_DATA_01 is a separate
> interface, and it is (currently) only called once per attribute change. For
> some background, this was originally added when I was permitting the current
> fan profile to determine which capability data page was presented and set. In
> the submitted version I hard code the page to custom as testing under quiet,
> balanced, and performance showed no change.

I do not understand what "I hard code the page to custom" means.

If you mean the capability data does not change you are right, they
are hardcoded in the decompiled ACPI I am pretty sure (it has been
close to a year now so I might be forgetting).

> Technically it is an assumption
> that this will be universally true for all Legion hardware, but the change was
> made as the final approved version of Mario's patches which added the necessary
> custom profile to platform_profile explicitly states in the documentation that
> "custom" mode is not user selectable. As Armin pointed out, this only applies to
> the legacy interface in /sys/firmware. In any case, you need not worry about
> this as v2 will not need this call more than once during probe, and gamezone
> will not be called by Other Method ever.

When reviewing your code, I saw that it does two checks: one for
checking whether in custom mode and bailing, and one for checking caps
and then bailing. Which equals 3 calls.

It's good that you will be fixing that/I hope you will be fixing that.
It is not clear from your comment. Please try to skip the capability
call too.

If I missed anything lmk

Antheas

> >Those were my comments. You mentioned that you are travelling and might not
> >have access to your PC so take your time with the replies.
> >
> >Happy holidays,
> >Antheas
>
>
> [1] https://lore.kernel.org/all/e9f4fb37-5277-a7f0-2bec-8a6909b4e674@linux.intel.com/
> [2] https://lore.kernel.org/all/07fe1bf8-f470-4e40-86ba-0f8d2e61a3e6@amd.com/
>
> Cheers,
> Derek

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-28  1:09       ` Antheas Kapenekakis
@ 2024-12-28  3:30         ` Derek John Clark
  2024-12-28 11:50           ` Antheas Kapenekakis
  0 siblings, 1 reply; 29+ messages in thread
From: Derek John Clark @ 2024-12-28  3:30 UTC (permalink / raw)
  To: Antheas Kapenekakis
  Cc: corbet, hdegoede, ilpo.jarvinen, linux-doc, linux-kernel, luke,
	mpearson-lenovo, nijs1, pgriffais, platform-driver-x86, shaohz1,
	superm1, zhangzx36, johnfanv2, codyit, W_Armin

On Fri, Dec 27, 2024 at 5:10 PM Antheas Kapenekakis <lkml@antheas.dev> wrote:

> They need to re-agree given the context below. It is one thing for
> them to agree on it theoretically for settings that they might imagine
> are persistent and another thing when in reality they are not.
>
> You did not address this in your comment here.
>
> The problem I am raising is that SPL, SPPT, and FPPT specifically are
> not persistent enough to meet the guidelines of that interface.
>
> [1] and [2] do not address this either.
>
> I do not have an alternative planned, just noting that I'd like
> everyone to be on the same page before we go ahead with this ABI.

I'll let them weigh in on this again if they want to, but I think it
was clear from those threads that this is a new way to use the class.
Armin's comment was related to the fan curve setting John was
discussing, which is specifically covered by the hwmon subsystem.
Hwmon does not cover platform profiles or PPT.

> To rephrase, your ABI style is not intuitive, because it contains
> implementation details such as "gamezone", "capdata01", and "Other
> Method", in addition to the ABI being hardcoded to the WMI structure
> lenovo uses. The documentation uses those keywords as well.

Yeah, it's a driver for those interfaces... If you want an agnostic
BMOF driver then make one. This isn't that.

> If I understand correctly your last sentence, Armin suggested much of
> the same (ie combine and merge).

You don't seem to, no. The suggestion was to use the component  driver
API to aggregate the Other Method driver with the capability data
driver so that the firmware-attributes class is only loaded when both
are present. That is decidedly different from breaking the kernel's
WMI driver requirements and merging two GUID's into one driver.

> GUID tables loading != drivers loading also, I would not pin that on
> the kernel.

What exactly do you think the following does?

 +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table);

> I do not understand what "I hard code the page to custom" means.
> If you mean the capability data does not change you are right, they
> are hardcoded in the decompiled ACPI I am pretty sure (it has been
> close to a year now so I might be forgetting).

The capability data interface has a data block instance for every
attribute in every fan mode. SPL has one for quiet, balanced,
performance, and custom. The method for getting that data block (page)
is the same as calling get/set in Other Method (0x01030100 -
0x0103FF00). Every page produces different values for each attribute,
but I am only ever retrieving the instance for custom (0x0103FF00) as
that's the only one where setting that method ID in Other Method
changes the values on the Legion Go. It is the only relevant data for
userspace. Other Gaming Series laptops might treat this differently,
where every fan mode has an applicable range. I'll need to do more
testing on other hardware to confirm that. In any case, this isn't
relevant as I'm dropping the gamezone check (as I've stated multiple
times in this discussion) and always setting/getting the custom method
ID for a given attribute.

> It's good that you will be fixing that/I hope you will be fixing that.
> It is not clear from your comment. Please try to skip the capability
> call too.

I was pretty clear...

>> v2 will not need this call more than once during probe, and gamezone
> > will not be called by Other Method ever.

Derek

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-28  3:30         ` Derek John Clark
@ 2024-12-28 11:50           ` Antheas Kapenekakis
  2024-12-29 20:45             ` Armin Wolf
  0 siblings, 1 reply; 29+ messages in thread
From: Antheas Kapenekakis @ 2024-12-28 11:50 UTC (permalink / raw)
  To: Derek John Clark
  Cc: corbet, hdegoede, ilpo.jarvinen, linux-doc, linux-kernel, luke,
	mpearson-lenovo, nijs1, pgriffais, platform-driver-x86, shaohz1,
	superm1, zhangzx36, johnfanv2, codyit, W_Armin

> I'll let them weigh in on this again if they want to, but I think it
> was clear from those threads that this is a new way to use the class.
> Armin's comment was related to the fan curve setting John was
> discussing, which is specifically covered by the hwmon subsystem.
> Hwmon does not cover platform profiles or PPT.

I quote the following from Armin:

> The firmware-attribute class interface is only intended for attributes which are persistent
> and cannot be exposed over other subsystem interfaces.

The former part is not met here.

> > To rephrase, your ABI style is not intuitive, because it contains
> > implementation details such as "gamezone", "capdata01", and "Other
> > Method", in addition to the ABI being hardcoded to the WMI structure
> > lenovo uses. The documentation uses those keywords as well.
>
> Yeah, it's a driver for those interfaces... If you want an agnostic
> BMOF driver then make one. This isn't that.

It's a driver for Legion Go and Legion laptops. _Not_ those
interfaces. Which only exist in gen 7+ if I recall from John's driver.
That was my comment.

Establishing an ABI that works with older laptops and laptops that
supersede those interfaces would be beneficial I'd say.

> > If I understand correctly your last sentence, Armin suggested much of
> > the same (ie combine and merge).
>
> You don't seem to, no. The suggestion was to use the component  driver
> API to aggregate the Other Method driver with the capability data
> driver so that the firmware-attributes class is only loaded when both
> are present. That is decidedly different from breaking the kernel's
> WMI driver requirements and merging two GUID's into one driver.
>
> > GUID tables loading != drivers loading also, I would not pin that on
> > the kernel.
>
> What exactly do you think the following does?
>
>  +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table);

Call the probe function that can -ENODEV

> > I do not understand what "I hard code the page to custom" means.
> > If you mean the capability data does not change you are right, they
> > are hardcoded in the decompiled ACPI I am pretty sure (it has been
> > close to a year now so I might be forgetting).
>
> The capability data interface has a data block instance for every
> attribute in every fan mode. SPL has one for quiet, balanced,
> performance, and custom. The method for getting that data block (page)
> is the same as calling get/set in Other Method (0x01030100 -
> 0x0103FF00). Every page produces different values for each attribute,
> but I am only ever retrieving the instance for custom (0x0103FF00) as
> that's the only one where setting that method ID in Other Method
> changes the values on the Legion Go. It is the only relevant data for
> userspace. Other Gaming Series laptops might treat this differently,
> where every fan mode has an applicable range. I'll need to do more
> testing on other hardware to confirm that. In any case, this isn't
> relevant as I'm dropping the gamezone check (as I've stated multiple
> times in this discussion) and always setting/getting the custom method
> ID for a given attribute.

Hm, for some reason I missed the capability block when doing my RE
[1]. Feel free to reference when making the driver.

You should also provision for the fact some legion laptops have an
extreme mode which is stubbed on the Legion Go

Ok,
let's wrap up this discussion and put a bow on it.

I currently have two issues that block me from committing to your
driver: novel use of kernel APIs/design and performance
degradation/instability from unnecessary calls and checks, as those
are (i) slower (ii) could error out (iii) could have incorrect data.

The former can leave me with tech debt if your proposed ABI is vetoed
and the latter would result in a degraded experience for my users; I
would be putting in work to go backwards. I do not mention missing
features, as that is something I could have also worked on if I
committed to your driver.

Therefore, I'm left in a situation where I have to wait for buy-in
from kernel maintainers and for your V2, hoping it fixes the latter
issue which you said it will only do partly.

Best,
Antheas

[1] https://github.com/hhd-dev/hwinfo/tree/master/devices/legion_go#get-feature-command

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-27 18:48   ` [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers Antheas Kapenekakis
  2024-12-27 23:22     ` Derek John Clark
@ 2024-12-29 20:28     ` Armin Wolf
  1 sibling, 0 replies; 29+ messages in thread
From: Armin Wolf @ 2024-12-29 20:28 UTC (permalink / raw)
  To: Antheas Kapenekakis, derekjohn.clark
  Cc: corbet, hdegoede, ilpo.jarvinen, linux-doc, linux-kernel, luke,
	mpearson-lenovo, nijs1, pgriffais, platform-driver-x86, shaohz1,
	superm1, zhangzx36, johnfanv2, codyit

Am 27.12.24 um 19:48 schrieb Antheas Kapenekakis:

> Hi Derek,
>
> Good job on the driver. I spent a bit of time reviewing it today, and I
> have a few thoughts. Hopefully you can go through them fast, and we can
> have a solid base of understanding moving forward so that I can e.g.,
> merge your driver on Bazzite ahead of the kernel, so you can get some
> valuable testing.
>
> // Firmware Attributes
> Right now, I have one major concern I'd like to be addressed before
> moving into the details of the driver. This is mirrored in the Asus
> driver you referenced [1] as well and I do not think I have seen solid
> argumentation for it yet.
>
> Essentially, you are using the firmware attributes interface for the
> ephemeral attributes SPL, SPPT, and FPPT which is also a concern mirrored
> in the thread with Armin (+cc) and John (+cc) [2]. The question here
> becomes, if exposing fan curve attributes via the firmware interface is
> fickle, why is it correct for SPL, SPPT, and FPPT?
>
> For context, when it comes to Asus (see [1]), these tunables reset during
> reboots, after sleep, and when changing the TDP mode. Specifically, TDP
> mode, SPL, SPPT, and FPPT, and the fan curve are not preserved between
> reboots, with only TDP mode being preserved between sleep. Following,
> setting TDP mode resets SPL, SPP, and FPPT, and setting any one of those
> resets the fan curve.
>
> For Lenovo and the Legion Go (1st Gen) it actually depends on the BIOS
> version. In early BIOSes, the custom tunings used to be preserved until
> you reset the BIOS. However, I suspect that this caused used confusion, as
> if you uninstalled Legion Space, the tunings to custom mode would still be
> applied which would be unintuitive. In new BIOS versions, the tunings reset
> when changing TDP modes, which can be done with the combination Legion
> L + Y. I am unsure if they are still preserved between reboots if you
> remain in custom mode. Fan curve resets as well. But the point is that they
> are not persisted, at least to the extent expected by the firmware
> attributes class and writing to them in other modes is undefined.
>
> Lastly, if another driver were to be developed e.g., for AMD out-of-tree
> to control devices without a vendor interface, we'd have the same
> issue. As this driver or [1] merging would set a precedent for using
> firmware attributes for these tunings, I think it is important to reach
> agreement before moving forward. For AMD w/o vendor, more details can
> be found in [3]. In [3], Mario makes an RFC for an alt interface for SPL,
> SPPT, and FPPT, through amd_pmf which does not have this peculiar issue.
>
> There are settings where it does make sense however, such as VRAM, boot
> sound (Asus), and the suspend light/barrel plug RGB (Legion). These
> settings are typically persisted in BIOS and there is no ambiguity for
> fitness in those.
>
> // Driver Details
> Ok, as for the rest of the driver, I have (i) some stylistic comments and
> (ii) will mirror similar concerns to John in unnecessary accesses.
>
> (i) Stylistic Comments
> I would personally be a fan of adding WMI support to the kernel, such that
> userspace can access WMI attributes without the use of a driver, as it is
> done in Windows. However, as I have discussed with Mario, such a thing is
> not an easy task, as it would require adding a BMOF parser to the kernel
> which is a monumental effort. In any case, the current kernel requires
> us custom write drivers for the WMI interface.

Actually i am currently working on decoding the BMOF format based on the bmfdec utility
developed by Pali.

The issue with exposing those WMI methods to userspace like how it is done under Windows
would create a massive security issue since most WMI methods are security sensitive and
quite fragile when presented with invalid/unusual input data (i fully blame the BIOS developers
here, they simply do not care about this).

That is why we have to stick with WMI drivers.

>
> The reason for this preface is that I think that your driver style is a bit
> "too close" to what such a thing would look. E.g., in the driver I can see
> snippets such as `LENOVO_CAPABILITY_DATA_01 WMI data block driver.` and
> `MODULE_IMPORT_NS("CAPDATA_WMI");`. Since you are going through the effort
> of writing a custom driver, I would be more opinionated in how I'd design
> the driver, so that it's more intuitive from a user's perspective. I think
> John's driver (which has its own issues) and the asus-wmi drivers strike a
> bit better balance, where they "translate" the WMI calls into an ABI that
> can be documented and then parsed by a developer.
>
> Such a design process would then also allow to claim the name legion, as
> you could make sure the legion driver only loads on Legion laptops, where
> now it would randomly load in other laptops as well. Sidenote here is
> that this is something I also found confusing, as the Legion Go does not
> have a keyboard so that driver should definitely not load.
> E.g., you could only load when you detect the Gamezone interface and then
> access the other two as well. The Gamezone interface is only used in
> Legion laptops AFAIK. I think Lenovo also kind of implements all three
> as stubs even if some are not used, to allow extending them with BIOS
> updates, so blindly loading drivers based on the interface might not be
> the best idea in any case. When the Legion Go released, I think only one
> of the three interfaces was used, for example, but all three were present.
> (ii) Unnecessary Accesses
> Even though you went hands off on your design of the driver, at the same
> time you held back when it comes to userspace accesses by globally forcing
> the Other function hints as limits. This creates two issues.
>
> The first one is that those limits might be wrong for certain devices or
> certain users might want to go a bit above them, which would mean that if
> you enforce them you'd need to provide a way to quirk them and a module
> parameter override (I know they are correct for the Legion Go as I have
> looked through them as well). But if it is not necessary in Windows,
> why would we add additional roadblocks in the kernel? If Lenovo feels the
> need to enforce them, they can do so in their firmware and extend that
> protection to Windows as well. Asus does when it comes to their fan curve
> firmware, for example.

IMHO if the firmware provides min and max limits for settings then the kernel should
enforce them. The reason for this is that the firmware might only be tested with the
official Lenovo OEM software and might therefor rely on the userspace software to not
exceed those limits.
If we allow a userspace application to exceed those limits then the firmware could break
and/or behave erratically.

Of course we could add a module param to tell the driver to ignore those limits. But such
a thing is totally optional and can be added later when the need arises.

> The second is that you are making a lot of necessary calls, which may harm
> performance or potentially cause instability. My workflow for setting TDP
> on the Go is that I first respect the TDP mode the user has set using
> Legion L + Y. If the user wants to change that, then I do the following:
> set TDP mode to custom, set the TDP, set the SPL, SPPT, and FPPT, and then
> set the fan curve. I also add a small delay in-between each of these calls
> as a further precaution. In your current driver, each of these calls would
> make two additional calls to the WMI interface (one for the limits and
> one for the TDP mode), which would more than double the number of calls
> made in a typical scenario (from 5 to 12), where each triplet is made
> back to back.
>
> There is also the issue mentioned by John, where you do cross-interface
> interactions etc. To fix this, you'd have to retrieve the limits from
> firmware on probing and then cache them in the driver after quirking. It
> would be much easier just to skip all of this for now and just use them to
> prepopulate _min and _max values as hints instead, which you already do.

Your point has some merit, as WMI calls are indeed quite slow. Caching the values would indeed increase performance.
However i still suggest that the driver actually enforces those limits, as the firmware might rely on this.

Thanks,
Armin Wolf

> Those were my comments. You mentioned that you are travelling and might not
> have access to your PC so take your time with the replies.
>
> Happy holidays,
> Antheas
>
> [1] https://lore.kernel.org/all/20240930000046.51388-1-luke@ljones.dev/
> [2] https://lore.kernel.org/lkml/eb165321-6a52-4464-bb58-11d8f9ff2008@gmx.de/
> [3] https://lore.kernel.org/lkml/20240926025955.1728766-1-superm1@kernel.org/

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-28 11:50           ` Antheas Kapenekakis
@ 2024-12-29 20:45             ` Armin Wolf
  2024-12-29 22:41               ` Antheas Kapenekakis
  0 siblings, 1 reply; 29+ messages in thread
From: Armin Wolf @ 2024-12-29 20:45 UTC (permalink / raw)
  To: Antheas Kapenekakis, Derek John Clark
  Cc: corbet, hdegoede, ilpo.jarvinen, linux-doc, linux-kernel, luke,
	mpearson-lenovo, nijs1, pgriffais, platform-driver-x86, shaohz1,
	superm1, zhangzx36, johnfanv2, codyit

Am 28.12.24 um 12:50 schrieb Antheas Kapenekakis:

>> I'll let them weigh in on this again if they want to, but I think it
>> was clear from those threads that this is a new way to use the class.
>> Armin's comment was related to the fan curve setting John was
>> discussing, which is specifically covered by the hwmon subsystem.
>> Hwmon does not cover platform profiles or PPT.
> I quote the following from Armin:
>
>> The firmware-attribute class interface is only intended for attributes which are persistent
>> and cannot be exposed over other subsystem interfaces.
> The former part is not met here.

If Ilpo and Hans agree to extend the scope of the firmware-attribute class to also cover non-persistent
firmware settings then i will not resist.

>
>>> To rephrase, your ABI style is not intuitive, because it contains
>>> implementation details such as "gamezone", "capdata01", and "Other
>>> Method", in addition to the ABI being hardcoded to the WMI structure
>>> lenovo uses. The documentation uses those keywords as well.
>> Yeah, it's a driver for those interfaces... If you want an agnostic
>> BMOF driver then make one. This isn't that.
> It's a driver for Legion Go and Legion laptops. _Not_ those
> interfaces. Which only exist in gen 7+ if I recall from John's driver.
> That was my comment.
>
> Establishing an ABI that works with older laptops and laptops that
> supersede those interfaces would be beneficial I'd say.

Excuse me for asking a stupid question here, but what WMI interfaces exactly are we currently arguing about?

>>> If I understand correctly your last sentence, Armin suggested much of
>>> the same (ie combine and merge).
>> You don't seem to, no. The suggestion was to use the component  driver
>> API to aggregate the Other Method driver with the capability data
>> driver so that the firmware-attributes class is only loaded when both
>> are present. That is decidedly different from breaking the kernel's
>> WMI driver requirements and merging two GUID's into one driver.
>>
>>> GUID tables loading != drivers loading also, I would not pin that on
>>> the kernel.
>> What exactly do you think the following does?
>>
>>   +MODULE_DEVICE_TABLE(wmi, gamezone_wmi_id_table);
> Call the probe function that can -ENODEV
>
>>> I do not understand what "I hard code the page to custom" means.
>>> If you mean the capability data does not change you are right, they
>>> are hardcoded in the decompiled ACPI I am pretty sure (it has been
>>> close to a year now so I might be forgetting).
>> The capability data interface has a data block instance for every
>> attribute in every fan mode. SPL has one for quiet, balanced,
>> performance, and custom. The method for getting that data block (page)
>> is the same as calling get/set in Other Method (0x01030100 -
>> 0x0103FF00). Every page produces different values for each attribute,
>> but I am only ever retrieving the instance for custom (0x0103FF00) as
>> that's the only one where setting that method ID in Other Method
>> changes the values on the Legion Go. It is the only relevant data for
>> userspace. Other Gaming Series laptops might treat this differently,
>> where every fan mode has an applicable range. I'll need to do more
>> testing on other hardware to confirm that. In any case, this isn't
>> relevant as I'm dropping the gamezone check (as I've stated multiple
>> times in this discussion) and always setting/getting the custom method
>> ID for a given attribute.
> Hm, for some reason I missed the capability block when doing my RE
> [1]. Feel free to reference when making the driver.
>
> You should also provision for the fact some legion laptops have an
> extreme mode which is stubbed on the Legion Go
>
> Ok,
> let's wrap up this discussion and put a bow on it.
>
> I currently have two issues that block me from committing to your
> driver: novel use of kernel APIs/design and performance
> degradation/instability from unnecessary calls and checks, as those
> are (i) slower (ii) could error out (iii) could have incorrect data.
>
> The former can leave me with tech debt if your proposed ABI is vetoed
> and the latter would result in a degraded experience for my users; I
> would be putting in work to go backwards. I do not mention missing
> features, as that is something I could have also worked on if I
> committed to your driver.
>
> Therefore, I'm left in a situation where I have to wait for buy-in
> from kernel maintainers and for your V2, hoping it fixes the latter
> issue which you said it will only do partly.

Regarding the firmware-attributes: if Ilpo and Hans give their OK, then i see no problems in using the firmware-attribute class for
non-persistent firmware settings. I for my part would be OK with such a change.

Regarding the enforcement of firmware limits: i believe that caching those limits during probing would solve the performance problem.
If users want to override those limits when we can add a module param (marked as unsafe to taint the kernel if used) later which tells
the driver to ignore those limits when writing firmware settings.

Any important points which i missed?

Thanks,
Armin Wolf

> Best,
> Antheas
>
> [1] https://github.com/hhd-dev/hwinfo/tree/master/devices/legion_go#get-feature-command

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-29 20:45             ` Armin Wolf
@ 2024-12-29 22:41               ` Antheas Kapenekakis
  2024-12-30 13:41                 ` Antheas Kapenekakis
  0 siblings, 1 reply; 29+ messages in thread
From: Antheas Kapenekakis @ 2024-12-29 22:41 UTC (permalink / raw)
  To: Armin Wolf
  Cc: Derek John Clark, corbet, hdegoede, ilpo.jarvinen, linux-doc,
	linux-kernel, luke, mpearson-lenovo, nijs1, pgriffais,
	platform-driver-x86, shaohz1, superm1, zhangzx36, johnfanv2,
	codyit

Hi Armin,
indeed you covered everything.

I am a bit hesitant about binding sppt, fppt, and spl into those
interfaces as they need to be set in a very specific ordering and
rules. E.g., spl < sppt < fppt after setting tdp and before the fan
curve and after sleep maybe depending on device, after reboot maybe
after keybind (Legion L + Y) as well. Which is not what's expected by
the userspace programs consuming this interface. In addition, this
would expose them to perusing users where they might be confused. I
also know that its difficult by looking at a patch series to
understand the nature of these values. However, given my previous
email, you now have the full context you need to make a decision.
If you think it is appropriate, it is fine by me.

I'd personally stick them next to platform_profile with a /name
discoverability mechanism similar to hwmon, where tuning
software can find them (something similar to Mario's RFC
that I linked above). Other settings such as the bios light that
interface is perfectly good for.

As for the hardware limits. You are absolutely right, the ACPI eforces
none, incl. for Lenovo. And the quality is as you expect. For the
Legion Go, they are quite creative. They added a battery 80%
capacity limit by re-using the key value for booting from AC [1-2].
They also used a weird ABI for the lighting interface to turn off
the suspend light for a good half of the BIOSes, then they fixed it
when they allowed to turn off the suspend light during sleep as well,
which caused that option to break in Legion Space for I want to say
two months. Nevertheless, nobody has broken a Legion Go yet
messing with those settings by e.g., overclocking. It also brings
into view that while the Legion Go uses a derived Legion bios it
has started diverging a bit as it has its own vendor software.

So I would say that it is good that the other function has a discovery
mechanism and that gamezone has some bitmasks for that purpose as
well. It means that if we tap on them during probe, at least for
Legion laptops from the last 3 years, we can get pretty good support
from the get go. Before that, it is a mix of EC + WMI (see [3]).

In regards to firmware limits, it is something I would not include in
the first patch series as it will just make this harder to merge, esp.
if there are laptops with wrong limits. Then there are issues with
overrides etc. I would advertise the limits through _min, _max so we
can figure this out later and I would not do a runtime WMI check, as
we have to run the check during probe anyway to populate sysfs, where
it is natural to cache the limits.

FInally, if indeed the gamezone function is Legion specific, and the
key-value pairs of the Other function are legion specific, from a
stylistic perspective I would tend towards making the ABI of the
driver Legion specific and abstract away its WMI details. E.g., I'd
use the name legion-wmi for a combined driver instead of
lenovo-gamezone-wmi which would then not be useful if lenovo moves
past gamezone. And I'd make sure it only loads on legion laptops. I'm
not up to date on my WMI driver conventions, so this is just a
suggestion.

Best,
Antheas

[1] https://github.com/BartoszCichecki/LenovoLegionToolkit/blob/21c0e8ca8b98181a2dedbec1e436d695932a4b0f/LenovoLegionToolkit.Lib/Enums.cs#L72
[2] https://github.com/hhd-dev/adjustor/blob/188ef6c3e4d7020f2110dd29df6d78847026d41e/src/adjustor/core/lenovo.py#L241
[3] https://github.com/johnfanv2/LenovoLegionLinux

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers
  2024-12-25  8:34     ` Derek J. Clark
  2024-12-25 21:11       ` Armin Wolf
@ 2024-12-30  7:51       ` Cody T.-H. Chiu
  1 sibling, 0 replies; 29+ messages in thread
From: Cody T.-H. Chiu @ 2024-12-30  7:51 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, ike.pan
  Cc: Ilpo Järvinen, Jonathan Corbet, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	platform-driver-x86, linux-doc, linux-kernel, Armin Wolf,
	John Martens


On 12/25/24 9:34 PM, Derek J. Clark wrote:
> On December 24, 2024 9:25:19 PM PST, "Cody T.-H. Chiu"<codyit@gmail.com> wrote:
>> On 12/17/2024 17:06, Derek J. Clark wrote:
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> ...
>>> +config LEGION_OTHER_WMI
>>> +	tristate "Lenovo Legion Other Method WMI Driver"
>>> +	depends on LEGION_GAMEZONE_WMI
>>> +	depends on LEGION_DATA_01_WMI
>>> +	select FW_ATTR_CLASS
>>> +	help
>>> +	  Say Y here if you have a WMI aware Lenovo Legion device and would
>> like to use the
>>> +	  firmware_attributes API to control various tunable settings
>> typically exposed by
>>> +	  Lenovo software in Windows.
>>> +
>>> +	  To compile this driver as a module, choose M here: the module will
>>> +	  be called lenovo_legion_wmi_other.
>>> +
>>>    config IDEAPAD_LAPTOP
>>>    	tristate "Lenovo IdeaPad Laptop Extras"
>>>    	depends on ACPI
>> Hi Derek,
>>
>> Thank you for the initiative, love to see we'll finally get a driver developed with the help of official specs.
>>
>> Perhaps it's common knowledge to the crowd here but I'd like to call out right now significant portion of the support on Legion ACPI / WMI came from ideapad-laptop which explicitly detects it:
>>
>> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/x86/ideapad-laptop.c?h=v6.13-rc4#n2108
> Hi Cody,
>
> Doing a quick search of that GUID on the Lenovo Legion WMI spec there are no matches. Perhaps someone at Lenovo can shed some light here, but the IdeaPad driver grabbing that GUID shouldn't interfere with the GUID's we're working on here.
>
>> Per my observation majority of users have no idea this is the case because of the misnomer, adding another set of drivers with Legion in the name explicitly, that don't support those features would double the dissonance.
> It appears the feature sets are quite different. This seems to enable use of special function/media keys on some (one?) Legion laptops,

I refrained from responding since John or Legion team have more 
canonical answers, but seeing it's holiday season you might not get one 
soon, here's more info in case you're still working on V2 during this time.

I only have two concrete datapoints, the original commit's (3ae86d2d4704 
) Legion 5 R700P and my Legion Slim 5 16AHP9, a 2024 model I recently 
bought. It's running solely on ideapad-laptop since no LLL support yet. 
Which I do see:

/sys/bus/wmi/devices/8FC0DE0C-B4E4-43FD-B0F3-8871711C1294

Seeing how LLL doesn't implement any function key support, I think a 
better educated guess is that it is universal to all the stated 
supported models there (happy users) and likely all Legion laptops.

Relatedly perhaps all Legions are technically under ideapad family?

SKU Number: LENOVO_MT_83DH_BU_idea_FM_Legion Slim 5 16AHP9

> and it also tries to register an ACPI based platform profile. While the driver does load on my legion go, only the amd_pmf and lenovo-legion-wmi-gamezone drivers have platform profiles registered under the new class at /sys/class/platform-profile/ so that isn't a conflict. I think that the ACPI method may only work on the yoga laptops that are supported by this driver? Again, maybe one of the Lenovo reps can comment on that, but it appears to predate the Custom and Other mode WMI GUID's.

Not only yoga laptops. It's a bit nuanced, I'm not sure if it's a bug 
but it's a potential point of conflict.  On my dmesg it shows the info 
from ideapad-laptop.c:2182:

[   14.348395] ideapad_acpi VPC2004:00: DYTC interface is not available

And:

$ find /sys -iname platform-profile -o -iname platform_profile
./kernel/btf/platform_profile
./kernel/debug/printk/index/platform_profile
./module/platform_profile

However when I press Fn+Q my power led cycles through Blue (low power), 
White (balanced), Red (performance) - with noticeable fan noise 
difference (so I haven't looked into actual changes).

Looks like ideapad-laptop.c:1382 platform_profile_cycle() does get 
called and succeed regardless?

Now I don't have enough domain expertise to say for sure but using two 
interfaces (EC / WMI) to modify the same underlying attribute smells 
like it could introduce inconsistent state or race condition?

The ideapad-laptop.c:2305 "VPC2004" EC (sub?) device ID for the "Virtual 
Power Controller" also appears to be universal across ideapad and 
friends which seems to mean all Legion laptops (same reason as above, 
happy users). My 2024 model also supports a large subset of the of the 
ACPI methods there so it's nearly fully functional.

$ cat /sys/kernel/debug/ideapad/cfg
_CFG: 0xfc050010

Capabilities: bluetooth wifi
OSD support: num-lock caps-lock mic-mute touchpad camera
Graphics:

$ cat /sys/kernel/debug/ideapad/status
Backlight max:  11
Backlight now:  0
BL power value: on (1)
=====================
Radio status: on (1)
Wifi status:  on (1)
BT status:    off (0)
3G status:    off (0)
=====================
Touchpad status: on (1)
Camera status:   off (0)
=====================
GBMD: 0x00820822
HALS: 0x0000c2c0

>> I wonder if reconciling this is in your planned scope? If not IMO at least this should be called out in documentation / Kconfig.
> Reconciliation wouldn't be in-line with the WMI driver requirements outlined in the kernel docs as each unique GUID needs to have its own driver in the current spec. It is possible we might need to add a quirk table in the future if we want to add function keys support for the Custom Method or Other Method function keys in the future. Since the Go has no keyboard I can't confirm if the IdeaPad driver is functional on more legion laptops, but considering the DMI quirks that are used in conjunction I would assume support needs to be added explicitly.
>
> If someone wants to add documentation on the IdeaPad driver and what it provides that would be good. I'm not familiar enough with it to really do it myself.

We have very different definition on the term reconciliation if you 
think it's not in-line with any requirements. I was referring to driver 
structures / namespace more accurately reflect actual hardware topology 
as an established desirable state, I never suggested all of them going 
into one driver.

Initially I was only pointing it out working backwards from the 
potential steady state from a user's perspective. Suppose you stop 
development after only this set of drivers (which is more than 
reasonable since none of us have infinite bandwidth). Users then build 
their kernel or troubleshoot, they see some modules named *legion* and 
the ideapad-laptop which even kernel driver dev have no idea is related 
to their hardware, that would cause confusion. A potential mitigation is 
to have ideapad-laptop stating Legions belong to this family, plus 
Legion driver stating it's incomplete without the others. Anyway I'm 
glad to see you'd rename it after discussions later in this thread.

While this set of drivers are now tighter scoped I hope it's still 
beneficial to have an more accurate holistic view. When I develop in 
other domains it does help me better name / structure things and design 
interfaces that could be time consuming to change later.


Happy holidays

- Cody

>> PS: I'm a developer myself but at lower level kernel domain I'm just a user so hopefully I'm not just adding noise here.
>>
>> - Cody
>>        products
> - Derek

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers
  2024-12-29 22:41               ` Antheas Kapenekakis
@ 2024-12-30 13:41                 ` Antheas Kapenekakis
  0 siblings, 0 replies; 29+ messages in thread
From: Antheas Kapenekakis @ 2024-12-30 13:41 UTC (permalink / raw)
  To: Armin Wolf
  Cc: Derek John Clark, corbet, hdegoede, ilpo.jarvinen, linux-doc,
	linux-kernel, luke, mpearson-lenovo, nijs1, pgriffais,
	platform-driver-x86, shaohz1, superm1, zhangzx36, johnfanv2,
	codyit

I guess I am late on the party on [1], just reviewed the series. Quite
a nice series

Given there is a class device for this now, it would make sense to me
that "tunings" for each platform driver would go there

Antheas

[1] https://lore.kernel.org/all/20241206031918.1537-11-mario.limonciello@amd.com/

On Sun, 29 Dec 2024 at 23:41, Antheas Kapenekakis <lkml@antheas.dev> wrote:
>
> Hi Armin,
> indeed you covered everything.
>
> I am a bit hesitant about binding sppt, fppt, and spl into those
> interfaces as they need to be set in a very specific ordering and
> rules. E.g., spl < sppt < fppt after setting tdp and before the fan
> curve and after sleep maybe depending on device, after reboot maybe
> after keybind (Legion L + Y) as well. Which is not what's expected by
> the userspace programs consuming this interface. In addition, this
> would expose them to perusing users where they might be confused. I
> also know that its difficult by looking at a patch series to
> understand the nature of these values. However, given my previous
> email, you now have the full context you need to make a decision.
> If you think it is appropriate, it is fine by me.
>
> I'd personally stick them next to platform_profile with a /name
> discoverability mechanism similar to hwmon, where tuning
> software can find them (something similar to Mario's RFC
> that I linked above). Other settings such as the bios light that
> interface is perfectly good for.
>
> As for the hardware limits. You are absolutely right, the ACPI eforces
> none, incl. for Lenovo. And the quality is as you expect. For the
> Legion Go, they are quite creative. They added a battery 80%
> capacity limit by re-using the key value for booting from AC [1-2].
> They also used a weird ABI for the lighting interface to turn off
> the suspend light for a good half of the BIOSes, then they fixed it
> when they allowed to turn off the suspend light during sleep as well,
> which caused that option to break in Legion Space for I want to say
> two months. Nevertheless, nobody has broken a Legion Go yet
> messing with those settings by e.g., overclocking. It also brings
> into view that while the Legion Go uses a derived Legion bios it
> has started diverging a bit as it has its own vendor software.
>
> So I would say that it is good that the other function has a discovery
> mechanism and that gamezone has some bitmasks for that purpose as
> well. It means that if we tap on them during probe, at least for
> Legion laptops from the last 3 years, we can get pretty good support
> from the get go. Before that, it is a mix of EC + WMI (see [3]).
>
> In regards to firmware limits, it is something I would not include in
> the first patch series as it will just make this harder to merge, esp.
> if there are laptops with wrong limits. Then there are issues with
> overrides etc. I would advertise the limits through _min, _max so we
> can figure this out later and I would not do a runtime WMI check, as
> we have to run the check during probe anyway to populate sysfs, where
> it is natural to cache the limits.
>
> FInally, if indeed the gamezone function is Legion specific, and the
> key-value pairs of the Other function are legion specific, from a
> stylistic perspective I would tend towards making the ABI of the
> driver Legion specific and abstract away its WMI details. E.g., I'd
> use the name legion-wmi for a combined driver instead of
> lenovo-gamezone-wmi which would then not be useful if lenovo moves
> past gamezone. And I'd make sure it only loads on legion laptops. I'm
> not up to date on my WMI driver conventions, so this is just a
> suggestion.
>
> Best,
> Antheas
>
> [1] https://github.com/BartoszCichecki/LenovoLegionToolkit/blob/21c0e8ca8b98181a2dedbec1e436d695932a4b0f/LenovoLegionToolkit.Lib/Enums.cs#L72
> [2] https://github.com/hhd-dev/adjustor/blob/188ef6c3e4d7020f2110dd29df6d78847026d41e/src/adjustor/core/lenovo.py#L241
> [3] https://github.com/johnfanv2/LenovoLegionLinux

^ permalink raw reply	[flat|nested] 29+ messages in thread

end of thread, other threads:[~2024-12-30 13:41 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-17 23:06 [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers Derek J. Clark
2024-12-17 23:06 ` [PATCH 1/1] platform/x86: Add lenovo-legion-wmi drivers Derek J. Clark
2024-12-18  2:39   ` Mario Limonciello
2024-12-18  3:36     ` Derek J. Clark
2024-12-22 22:55       ` Armin Wolf
2024-12-23 15:47         ` Derek John Clark
2024-12-23 16:50           ` Armin Wolf
2024-12-25  5:25   ` Cody T.-H. Chiu
2024-12-25  8:34     ` Derek J. Clark
2024-12-25 21:11       ` Armin Wolf
2024-12-26  7:17         ` Derek J. Clark
2024-12-27 17:21           ` Mark Pearson
2024-12-30  7:51       ` Cody T.-H. Chiu
2024-12-27 18:48   ` [PATCH 0/1] platform/x86: Add Lenovo Legion WMI Drivers Antheas Kapenekakis
2024-12-27 23:22     ` Derek John Clark
2024-12-28  1:09       ` Antheas Kapenekakis
2024-12-28  3:30         ` Derek John Clark
2024-12-28 11:50           ` Antheas Kapenekakis
2024-12-29 20:45             ` Armin Wolf
2024-12-29 22:41               ` Antheas Kapenekakis
2024-12-30 13:41                 ` Antheas Kapenekakis
2024-12-29 20:28     ` Armin Wolf
2024-12-22  8:42 ` John Martens
     [not found]   ` <CAFqHKT==PiLb_VuFPHJzgfrprukGGig+iUzcih+3TJ1VYt94Dw@mail.gmail.com>
2024-12-22 20:34     ` Derek John Clark
2024-12-22 23:05   ` Armin Wolf
2024-12-26  0:18     ` John Martens
2024-12-26 23:19       ` Armin Wolf
2024-12-27 17:06         ` Mark Pearson
2024-12-27 23:18       ` Derek John Clark

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).