linux-doc.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers
@ 2025-03-17 14:43 Derek J. Clark
  2025-03-17 14:43 ` [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation Derek J. Clark
                   ` (6 more replies)
  0 siblings, 7 replies; 49+ messages in thread
From: Derek J. Clark @ 2025-03-17 14:43 UTC (permalink / raw)
  To: Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	Derek J . Clark, platform-driver-x86, linux-doc, linux-kernel

Adds support for the Lenovo "Gaming Series" of laptop hardware that 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
Mode", and "LENOVO_CAPABILITY_DATA_01" interfaces are implemented, but
I attempted to structure the driver so that adding the "Custom Mode",
"Lighting", and other data block interfaces would be trivial in a later
patches.

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/platform-driver-x86/20250316230724.100165-1-luke@ljones.dev/

The drivers have been tested by me on the Lenovo Legion Go and Legion Go
S.

Suggested-by: Mario Limonciello <superm1@kernel.org>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
--- 
v4:
 - Added lenovo-wmi-events driver
 - Added headers for every driver
 - Fixes requested from v3 review
v3:
https://lore.kernel.org/platform-driver-x86/20250225220037.16073-1-derekjohn.clark@gmail.com/
v2:
https://lore.kernel.org/platform-driver-x86/20250102004854.14874-1-derekjohn.clark@gmail.com/
v1:
https://lore.kernel.org/platform-driver-x86/20241217230645.15027-1-derekjohn.clark@gmail.com/

Derek J. Clark (6):
  platform/x86: Add lenovo-wmi-* driver Documentation
  platform/x86: Add lenovo-wmi-helpers
  platform/x86: Add Lenovo WMI Events Driver
  platform/x86: Add Lenovo Capability Data 01 WMI Driver
  platform/x86: Add Lenovo Other Mode WMI Driver
  platform/x86: Add Lenovo Gamezone WMI Driver

 .../wmi/devices/lenovo-wmi-gamezone.rst       | 203 ++++++
 .../wmi/devices/lenovo-wmi-other-method.rst   | 108 +++
 MAINTAINERS                                   |  17 +
 drivers/platform/x86/Kconfig                  |  40 ++
 drivers/platform/x86/Makefile                 |   5 +
 drivers/platform/x86/lenovo-wmi-capdata01.c   | 136 ++++
 drivers/platform/x86/lenovo-wmi-capdata01.h   |  29 +
 drivers/platform/x86/lenovo-wmi-events.c      | 132 ++++
 drivers/platform/x86/lenovo-wmi-events.h      |  21 +
 drivers/platform/x86/lenovo-wmi-gamezone.c    | 380 +++++++++++
 drivers/platform/x86/lenovo-wmi-gamezone.h    |  18 +
 drivers/platform/x86/lenovo-wmi-helpers.c     |  64 ++
 drivers/platform/x86/lenovo-wmi-helpers.h     |  24 +
 drivers/platform/x86/lenovo-wmi-other.c       | 626 ++++++++++++++++++
 drivers/platform/x86/lenovo-wmi-other.h       |  19 +
 15 files changed, 1822 insertions(+)
 create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
 create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst
 create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
 create mode 100644 drivers/platform/x86/lenovo-wmi-events.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-events.h
 create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h
 create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h
 create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-other.h

-- 
2.49.0


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

* [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation
  2025-03-17 14:43 [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
@ 2025-03-17 14:43 ` Derek J. Clark
  2025-03-18  4:24   ` Mario Limonciello
                     ` (3 more replies)
  2025-03-17 14:43 ` [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers Derek J. Clark
                   ` (5 subsequent siblings)
  6 siblings, 4 replies; 49+ messages in thread
From: Derek J. Clark @ 2025-03-17 14:43 UTC (permalink / raw)
  To: Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	Derek J . Clark, platform-driver-x86, linux-doc, linux-kernel

Adds documentation for new lenovo-wmi drivers.

Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
 - Fixed MOF formatting issues.
 - Fixed spelling mistakes.
 - Updated description of balanced-performance profile for Gamezone.
 - Updated description of thermal mode event GUID for Gamezone.
v3:
- Split documentation into multiple files, one for each parent
  driver for the Gamezone and Other Mode WMI interfaces.
- Add MOF data for all parent and child interfaces.
- Remove lenovo-wmi-camera.c driver from v2 documentation.
v2:
- Update description of Custom Profile to include the need to manually
  set it.
- Remove all references to Legion hardware.
- Add section for lenovo-wmi-camera.c driver as it follows the same
  naming convention.
---
 .../wmi/devices/lenovo-wmi-gamezone.rst       | 203 ++++++++++++++++++
 .../wmi/devices/lenovo-wmi-other-method.rst   | 108 ++++++++++
 MAINTAINERS                                   |   7 +
 3 files changed, 318 insertions(+)
 create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
 create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst

diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
new file mode 100644
index 000000000000..bde63dde285d
--- /dev/null
+++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
@@ -0,0 +1,203 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+==========================================================
+Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
+==========================================================
+
+Introduction
+============
+The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
+The priamry "Gamezone" GUID provides advanced features such as fan
+profiles and overclocking. It is paired with multiple event GUIDs
+and data block GUIDs that provide context for the various methods.
+
+Gamezone Data
+-------------
+
+WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Gamezone Data WMI interface provides platform-profile and fan curve
+settings for devices that fall under the "Gaming Series" of Lenovo devices.
+It uses a notifier chain to inform other Lenovo WMI interface drivers of the
+current platform profile when it changes.
+
+The following platform profiles are supported:
+ - low-power
+ - balanced
+ - balanced-performance*
+ - performance
+ - custom
+
+Balanced-Performance
+~~~~~~~~~~~~~~~~~~~~
+Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
+enabled in their BIOS. For these devices, the performance platform profile
+will correspond to the BIOS Extreme Mode, while the balanced-performance
+platform profile will correspond to the BIOS Performance mode. For legacy
+devices, the performance platform profile will correspond with the BIOS
+Performance mode. For some newer devices the "Extreme Mode" profile is
+incomplete in the BIOS and setting it will cause undefined behavior. A
+BIOS bug quirk table is provided to ensure these devices cannot set
+"Extreme Mode" from the driver.
+
+Custom Profile
+~~~~~~~~~~~~~~
+The custom profile represents a hardware mode on Lenovo devices that enables
+user modifications to Package Power Tracking (PPT) and fan curve settings.
+When an attribute exposed by the Other Mode WMI interface is to be modified,
+the Gamezone driver must first be switched to the "custom" profile manually,
+or the setting will have no effect. If another profile is set from the list
+of supported profiles, the BIOS will override any user PPT settings when
+switching to that profile.
+
+Gamezone Thermal Mode Event
+---------------------------
+
+WMI GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Gamezone Thermal Mode Event interface notifies the system when the platform
+profile has changed, either through the hardware event (Fn+Q for laptops or
+Legion + Y for Go Series), or through the Gamezone WMI interface. This event is
+implemented in the Lenovo WMI Events driver (lenovo-wmi-events).
+
+
+WMI interface description
+=========================
+
+The WMI interface description can be decoded from the embedded binary MOF (bmof)
+data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
+
+::
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_GAMEZONE_DATA class"), guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
+  class LENOVO_GAMEZONE_DATA {
+  	[key, read] string InstanceName;
+  	[read] boolean Active;
+
+    [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")] void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
+    [WmiMethodId(11), Implemented, Description("Get AslCode Version")] void GetVersion ([out, Description("AslCode version")] UINT32 Data);
+    [WmiMethodId(12), Implemented, Description("Fan cooling capability")] void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
+    [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")] void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
+    [WmiMethodId(14), Implemented, Description("cpu oc capability")] void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
+    [WmiMethodId(15), Implemented, Description("bios has overclock capability")] void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
+    [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")] void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
+    [WmiMethodId(18), Implemented, Description("Get CPU temperature")] void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
+    [WmiMethodId(19), Implemented, Description("Get GPU temperature")] void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
+    [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")] void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
+    [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")] void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
+    [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")] void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
+    [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")] void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
+    [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")] void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
+    [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")] void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
+    [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")] void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
+    [WmiMethodId(30), Implemented, Description("Get Keyboard feature list")] void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
+    [WmiMethodId(31), Implemented, Description("Get Memory OC Information")] void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
+    [WmiMethodId(32), Implemented, Description("Water Cooling feature capability")] void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
+    [WmiMethodId(33), Implemented, Description("Set Water Cooling status")] void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
+    [WmiMethodId(34), Implemented, Description("Get Water Cooling status")] void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
+    [WmiMethodId(35), Implemented, Description("Lighting feature capability")] void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
+    [WmiMethodId(36), Implemented, Description("Set keyboard light off or on to max")] void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
+    [WmiMethodId(37), Implemented, Description("Get keyboard light on/off status")] void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
+    [WmiMethodId(38), Implemented, Description("Get Macrokey scan code")] void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
+    [WmiMethodId(39), Implemented, Description("Get Macrokey count")] void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
+    [WmiMethodId(40), Implemented, Description("Support G-Sync feature")] void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
+    [WmiMethodId(41), Implemented, Description("Get G-Sync Status")] void GetGSyncStatus ([out, Description("Get G-Sync Status")] UINT32 Data);
+    [WmiMethodId(42), Implemented, Description("Set G-Sync Status")] void SetGSyncStatus ([in, Description("Set G-Sync Status")] UINT32 Data);
+    [WmiMethodId(43), Implemented, Description("Support Smart Fan feature")] void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
+    [WmiMethodId(44), Implemented, Description("Set Smart Fan Mode")] void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
+    [WmiMethodId(45), Implemented, Description("Get Smart Fan Mode")] void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
+    [WmiMethodId(46), Implemented, Description("Get Smart Fan Setting Mode")] void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
+    [WmiMethodId(47), Implemented, Description("Get Power Charge Mode")] void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
+    [WmiMethodId(48), Implemented, Description("Get Gaming Product Info")] void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
+    [WmiMethodId(49), Implemented, Description("Over Drive feature capability")] void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
+    [WmiMethodId(50), Implemented, Description("Get Over Drive status")] void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
+    [WmiMethodId(51), Implemented, Description("Set Over Drive status")] void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
+    [WmiMethodId(52), Implemented, Description("Set Light Control Owner")] void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
+    [WmiMethodId(53), Implemented, Description("Set DDS Control Owner")] void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
+    [WmiMethodId(54), Implemented, Description("Get the flag of restore OC value")] void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
+    [WmiMethodId(55), Implemented, Description("Get Real Thremal Mode")] void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
+    [WmiMethodId(56), Implemented, Description("Get the OC switch status in BIOS")] void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
+    [WmiMethodId(59), Implemented, Description("Get hardware info support version")] void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
+    [WmiMethodId(60), Implemented, Description("Get Cpu core 0 max frequency")] void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
+    [WmiMethodId(62), Implemented, Description("Check the Adapter type fit for OC")] void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
+    [WmiMethodId(63), Implemented, Description("Is support IGPU mode")] void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
+    [WmiMethodId(64), Implemented, Description("Get IGPU Mode Status")] void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
+    [WmiMethodId(65), Implemented, Description("Set IGPU Mode")] void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
+    [WmiMethodId(66), Implemented, Description("Notify DGPU Status")] void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
+    [WmiMethodId(67), Implemented, Description("Is changed Y log")] void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
+    [WmiMethodId(68), Implemented, Description("Get DGPU Hardwawre ID")] void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of CPU OC parameter list"), guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
+  class LENOVO_GAMEZONE_CPU_OC_DATA {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("OC tune id.")] uint32 Tuneid;
+    [WmiDataId(2), read, Description("Default value.")] uint32 DefaultValue;
+    [WmiDataId(3), read, Description("OC Value.")] uint32 OCValue;
+    [WmiDataId(4), read, Description("Min Value.")] uint32 MinValue;
+    [WmiDataId(5), read, Description("Max Value.")] uint32 MaxValue;
+    [WmiDataId(6), read, Description("Scale Value.")] uint32 ScaleValue;
+    [WmiDataId(7), read, Description("OC Order id.")] uint32 OCOrderid;
+    [WmiDataId(8), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
+    [WmiDataId(9), read, Description("Delay time in ms.")] uint32 Interval;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of GPU OC parameter list"), guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
+  class LENOVO_GAMEZONE_GPU_OC_DATA {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("P-State ID.")] uint32 PStateID;
+    [WmiDataId(2), read, Description("CLOCK ID.")] uint32 ClockID;
+    [WmiDataId(3), read, Description("Default value.")] uint32 defaultvalue;
+    [WmiDataId(4), read, Description("OC Offset freqency.")] uint32 OCOffsetFreq;
+    [WmiDataId(5), read, Description("OC Min offset value.")] uint32 OCMinOffset;
+    [WmiDataId(6), read, Description("OC Max offset value.")] uint32 OCMaxOffset;
+    [WmiDataId(7), read, Description("OC Offset Scale.")] uint32 OCOffsetScale;
+    [WmiDataId(8), read, Description("OC Order id.")] uint32 OCOrderid;
+    [WmiDataId(9), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Fancooling finish event"), guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
+  class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("Fancooling clean finish event")] uint32 EventId;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan mode change event"), guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
+  class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("Smart Fan Mode change event")] uint32 mode;
+    [WmiDataId(2), read, Description("version of FN+Q")] uint32 version;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan setting mode change event"), guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
+  class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("Smart Fan Setting mode change event")] uint32 mode;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("POWER CHARGE MODE Change EVENT"), guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
+  class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")] uint32 mode;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Thermal Mode Real Mode change event"), guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
+  class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("Thermal Mode Real Mode")] uint32 mode;
+  };
diff --git a/Documentation/wmi/devices/lenovo-wmi-other-method.rst b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
new file mode 100644
index 000000000000..b48832726311
--- /dev/null
+++ b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
@@ -0,0 +1,108 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+===========================================================
+Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
+===========================================================
+
+Introduction
+============
+Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
+The primary Other Mode interface provides advanced power tuning features
+such as Package Power Tracking (PPT). It is paired with multiple data block
+GUIDs that provide context for the various methods.
+
+
+Other Mode
+----------
+
+WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Other Mode WMI interface uses the firmware_attributes class to expose
+various WMI attributes 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 devices. Each
+attribute exposed by the Other Mode interface has corresponding
+capability data blocks which allow the driver to probe details about the
+attribute. Each attribute has multiple pages, one for each of the platform
+profiles managed by the Gamezone interface. Attributes are exposed in sysfs
+under the following path:
+
+::
+/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
+
+LENOVO_CAPABILITY_DATA_01
+-------------------------
+
+WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The LENOVO_CAPABILITY_DATA_01 interface provides information on various
+power limits of integrated CPU and GPU components.
+
+Each attribute has the following properties:
+ - current_value
+ - default_value
+ - display_name
+ - max_value
+ - min_value
+ - scalar_increment
+ - type
+
+The following attributes are implemented:
+ - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
+ - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
+ - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
+
+
+WMI interface description
+=========================
+
+The WMI interface description can be decoded from the embedded binary MOF (bmof)
+data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
+
+::
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_OTHER_METHOD class"), guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
+  class LENOVO_OTHER_METHOD {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiMethodId(17), Implemented, Description("Get Feature Value ")] void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
+    [WmiMethodId(18), Implemented, Description("Set Feature Value ")] void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
+    [WmiMethodId(19), Implemented, Description("Get Data By Command ")] void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
+    [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")] void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 00"), guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
+  class LENOVO_CAPABILITY_DATA_00 {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
+    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
+    [WmiDataId(3), read, Description("Capability Default Value.")] uint32 DefaultValue;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 01"), guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
+  class LENOVO_CAPABILITY_DATA_01 {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
+    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
+    [WmiDataId(3), read, Description("Default Value.")] uint32 DefaultValue;
+    [WmiDataId(4), read, Description("Step.")] uint32 Step;
+    [WmiDataId(5), read, Description("Minimum Value.")] uint32 MinValue;
+    [WmiDataId(6), read, Description("Maximum Value.")] uint32 MaxValue;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 02"), guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
+  class LENOVO_CAPABILITY_DATA_02 {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
+    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
+    [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize;
+    [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[];
+  };
diff --git a/MAINTAINERS b/MAINTAINERS
index 1afd30d00aec..675f4b26426d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13158,6 +13158,13 @@ S:	Maintained
 W:	http://legousb.sourceforge.net/
 F:	drivers/usb/misc/legousbtower.c
 
+LENOVO WMI DRIVERS
+M:	Derek J. Clark <derekjohn.clark@gmail.com>
+L:	platform-driver-x86@vger.kernel.org
+S:	Maintained
+F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
+F:	Documentation/wmi/devices/lenovo-wmi-other.rst
+
 LENOVO WMI HOTKEY UTILITIES DRIVER
 M:	Jackie Dong <xy-jackie@139.com>
 L:	platform-driver-x86@vger.kernel.org
-- 
2.49.0


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

* [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers
  2025-03-17 14:43 [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
  2025-03-17 14:43 ` [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation Derek J. Clark
@ 2025-03-17 14:43 ` Derek J. Clark
  2025-03-18  4:27   ` Mario Limonciello
                     ` (3 more replies)
  2025-03-17 14:43 ` [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver Derek J. Clark
                   ` (4 subsequent siblings)
  6 siblings, 4 replies; 49+ messages in thread
From: Derek J. Clark @ 2025-03-17 14:43 UTC (permalink / raw)
  To: Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	Derek J . Clark, platform-driver-x86, linux-doc, linux-kernel

Adds documentation for all new lenovo-wmi drivers.

Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
 - Changed namespace to LENOVO_WMI_HELPERS from LENOVO_WMI.
 - Changed filenames to lenovo-wmi-helpers from lenovo-wmi.
 - Removed structs and functions implemented by other drivers.
---
 MAINTAINERS                               |  2 +
 drivers/platform/x86/Kconfig              |  4 ++
 drivers/platform/x86/Makefile             |  1 +
 drivers/platform/x86/lenovo-wmi-helpers.c | 64 +++++++++++++++++++++++
 drivers/platform/x86/lenovo-wmi-helpers.h | 24 +++++++++
 5 files changed, 95 insertions(+)
 create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 675f4b26426d..3a370a18b806 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
 S:	Maintained
 F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
 F:	Documentation/wmi/devices/lenovo-wmi-other.rst
+F:	drivers/platform/x86/lenovo-wmi-helpers.c
+F:	drivers/platform/x86/lenovo-wmi-helpers.h
 
 LENOVO WMI HOTKEY UTILITIES DRIVER
 M:	Jackie Dong <xy-jackie@139.com>
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 43407e76476b..bece1ba61417 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -459,6 +459,10 @@ config IBM_RTL
 	 state = 0 (BIOS SMIs on)
 	 state = 1 (BIOS SMIs off)
 
+config LENOVO_WMI_HELPERS
+	tristate
+	depends on ACPI_WMI
+
 config IDEAPAD_LAPTOP
 	tristate "Lenovo IdeaPad Laptop Extras"
 	depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 650dfbebb6c8..5a9f4e94f78b 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -69,6 +69,7 @@ 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_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
 
 # Intel
 obj-y				+= intel/
diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x86/lenovo-wmi-helpers.c
new file mode 100644
index 000000000000..36d553502223
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-helpers.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Legion WMI helpers 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 Method" 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 Method" 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 drivers
+ * uses a common procedure to get data fro the WMI interface, enumerated here.
+ *
+ * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ *
+ */
+
+#include <linux/wmi.h>
+#include "lenovo-wmi-helpers.h"
+
+/*
+ * lwmi_dev_evaluate_method() - Helper function to call wmidev_evaluate_method
+ * for Lenovo WMI device method calls that return an ACPI integer.
+ * @wdev: Pointer to the WMI device to be called.
+ * @instance: Instance of the called method.
+ * @method_id: WMI Method ID for the method to be called.
+ * @buf: Buffer of all arguments for the given method_id.
+ * @size: Length of the buffer.
+ * @retval: Pointer for the return value to be assigned.
+ *
+ * Returns: 0, or an error.
+ */
+int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
+			     u32 method_id, unsigned char *buf, size_t size,
+			     u32 *retval)
+{
+	struct acpi_buffer input = { size, buf };
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *ret_obj __free(kfree) = NULL;
+	acpi_status status;
+
+	status = wmidev_evaluate_method(wdev, instance, method_id, &input,
+					&output);
+
+	if (ACPI_FAILURE(status))
+		return -EIO;
+
+	if (retval) {
+		ret_obj = output.pointer;
+		if (!ret_obj)
+			return -ENODATA;
+
+		if (ret_obj->type != ACPI_TYPE_INTEGER)
+			return -ENXIO;
+
+		*retval = (u32)ret_obj->integer.value;
+	}
+	return 0;
+};
+EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_method, "LENOVO_WMI_HELPERS");
+
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-wmi-helpers.h b/drivers/platform/x86/lenovo-wmi-helpers.h
new file mode 100644
index 000000000000..7e0d7870790e
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-helpers.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ *
+ */
+#include <linux/notifier.h>
+#include <linux/platform_profile.h>
+
+#ifndef _LENOVO_WMI_HELPERS_H_
+#define _LENOVO_WMI_HELPERS_H_
+
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+struct wmi_method_args_32 {
+	u32 arg0;
+	u32 arg1;
+};
+
+int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
+			     u32 method_id, unsigned char *buf, size_t size,
+			     u32 *retval);
+
+#endif /* !_LENOVO_WMI_HELPERS_H_ */
-- 
2.49.0


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

* [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver
  2025-03-17 14:43 [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
  2025-03-17 14:43 ` [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation Derek J. Clark
  2025-03-17 14:43 ` [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers Derek J. Clark
@ 2025-03-17 14:43 ` Derek J. Clark
  2025-03-18  4:30   ` Mario Limonciello
                     ` (3 more replies)
  2025-03-17 14:43 ` [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver Derek J. Clark
                   ` (3 subsequent siblings)
  6 siblings, 4 replies; 49+ messages in thread
From: Derek J. Clark @ 2025-03-17 14:43 UTC (permalink / raw)
  To: Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	Derek J . Clark, platform-driver-x86, linux-doc, linux-kernel

Adds lenovo-wmi-events driver. The events driver is designed as a
general entrypoint for all Lenovo WMI Events. It acts as a notification
chain head that will process event data and pass it on to registered
drivers so they can react to the events.

Currently only the Gamezone interface Thermal Mode Event GUID is
implemented in this driver. It is documented in the Gamezone
documentation.

Suggested-by: Armin Wolf <W_Armin@gmx.de>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
 - Remove the Thermal Mode Event GUID from Gamezone and add this driver.
---
 MAINTAINERS                              |   2 +
 drivers/platform/x86/Kconfig             |   4 +
 drivers/platform/x86/Makefile            |   1 +
 drivers/platform/x86/lenovo-wmi-events.c | 132 +++++++++++++++++++++++
 drivers/platform/x86/lenovo-wmi-events.h |  21 ++++
 5 files changed, 160 insertions(+)
 create mode 100644 drivers/platform/x86/lenovo-wmi-events.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-events.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 3a370a18b806..6dde75922aaf 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
 S:	Maintained
 F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
 F:	Documentation/wmi/devices/lenovo-wmi-other.rst
+F:	drivers/platform/x86/lenovo-wmi-events.c
+F:	drivers/platform/x86/lenovo-wmi-events.h
 F:	drivers/platform/x86/lenovo-wmi-helpers.c
 F:	drivers/platform/x86/lenovo-wmi-helpers.h
 
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index bece1ba61417..13b8f4ac5dc5 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -459,6 +459,10 @@ config IBM_RTL
 	 state = 0 (BIOS SMIs on)
 	 state = 1 (BIOS SMIs off)
 
+config LENOVO_WMI_EVENTS
+	tristate
+	depends on ACPI_WMI
+
 config LENOVO_WMI_HELPERS
 	tristate
 	depends on ACPI_WMI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 5a9f4e94f78b..fc039839286a 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -69,6 +69,7 @@ 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_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
 obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
 
 # Intel
diff --git a/drivers/platform/x86/lenovo-wmi-events.c b/drivers/platform/x86/lenovo-wmi-events.c
new file mode 100644
index 000000000000..3ea0face3c0d
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-events.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo WMI Events driver. Lenovo WMI interfaces provide various
+ * hardware triggered events that many drivers need to have propagated.
+ * This driver provides a uniform entrypoint for these events so that
+ * any driver that needs to respond to these events can subscribe to a
+ * notifier chain.
+ *
+ * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/list.h>
+#include <linux/notifier.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+#include "lenovo-wmi-events.h"
+
+/* Interface GUIDs */
+#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
+
+#define LENOVO_WMI_EVENT_DEVICE(guid, type)                        \
+	.guid_string = (guid), .context = &(enum lwmi_events_type) \
+	{                                                          \
+		type                                               \
+	}
+
+static BLOCKING_NOTIFIER_HEAD(events_chain_head);
+
+struct lwmi_events_priv {
+	struct wmi_device *wdev;
+	enum lwmi_events_type type;
+};
+
+/* Notifier Methods */
+int lwmi_events_register_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_register(&events_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
+
+int lwmi_events_unregister_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_unregister(&events_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS");
+
+static void devm_lwmi_events_unregister_notifier(void *data)
+{
+	struct notifier_block *nb = data;
+
+	lwmi_events_unregister_notifier(nb);
+}
+
+int devm_lwmi_events_register_notifier(struct device *dev,
+				       struct notifier_block *nb)
+{
+	int ret;
+
+	ret = lwmi_events_register_notifier(nb);
+	if (ret < 0)
+		return ret;
+
+	return devm_add_action_or_reset(dev,
+				devm_lwmi_events_unregister_notifier, nb);
+}
+EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
+
+/* Driver Methods */
+static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj)
+{
+	struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev);
+	int sel_prof;
+	int ret;
+
+	switch (priv->type) {
+	case THERMAL_MODE_EVENT:
+		if (obj->type != ACPI_TYPE_INTEGER)
+			return;
+
+		sel_prof = obj->integer.value;
+		ret = blocking_notifier_call_chain(&events_chain_head,
+						   THERMAL_MODE_EVENT, &sel_prof);
+		if (ret == NOTIFY_BAD)
+			dev_err(&wdev->dev,
+				"Failed to send notification to call chain for WMI Events\n");
+		break;
+	default:
+		return;
+	}
+}
+
+static int lwmi_events_probe(struct wmi_device *wdev, const void *context)
+{
+	struct lwmi_events_priv *priv;
+
+	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	if (!context)
+		return -EINVAL;
+
+	priv->wdev = wdev;
+	priv->type = *(enum lwmi_events_type *)context;
+
+	dev_set_drvdata(&wdev->dev, priv);
+	return 0;
+}
+
+static const struct wmi_device_id lwmi_events_id_table[] = {
+	{ LENOVO_WMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID,
+				  THERMAL_MODE_EVENT) },
+	{}
+};
+
+static struct wmi_driver lwmi_events_driver = {
+	.driver = {
+		.name = "lenovo_wmi_events",
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.id_table = lwmi_events_id_table,
+	.probe = lwmi_events_probe,
+	.notify = lwmi_events_notify,
+	.no_singleton = true,
+};
+
+module_wmi_driver(lwmi_events_driver);
+
+MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo WMI Events Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-wmi-events.h b/drivers/platform/x86/lenovo-wmi-events.h
new file mode 100644
index 000000000000..a3fa934eaa10
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-events.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/notifier.h>
+#include <linux/types.h>
+
+#ifndef _LENOVO_WMI_EVENTS_H_
+#define _LENOVO_WMI_EVENTS_H_
+
+enum lwmi_events_type {
+	THERMAL_MODE_EVENT = 1,
+};
+
+int lwmi_events_register_notifier(struct notifier_block *nb);
+int lwmi_events_unregister_notifier(struct notifier_block *nb);
+int devm_lwmi_events_register_notifier(struct device *dev,
+				       struct notifier_block *nb);
+
+#endif /* !_LENOVO_WMI_EVENTS_H_ */
-- 
2.49.0


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

* [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver
  2025-03-17 14:43 [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
                   ` (2 preceding siblings ...)
  2025-03-17 14:43 ` [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver Derek J. Clark
@ 2025-03-17 14:43 ` Derek J. Clark
  2025-03-26 19:47   ` Matthew Schwartz
                     ` (2 more replies)
  2025-03-17 14:43 ` [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode " Derek J. Clark
                   ` (2 subsequent siblings)
  6 siblings, 3 replies; 49+ messages in thread
From: Derek J. Clark @ 2025-03-17 14:43 UTC (permalink / raw)
  To: Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	Derek J . Clark, platform-driver-x86, linux-doc, linux-kernel

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

Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
 - Make driver data a private struct, remove references from Other Mode
   driver.
 - Don't cache data at device initialization. Instead, on component bind,
   cache the data on a member variable of the Other Mode driver data
   passed as a void pointer.
 - Add header file for capdata01 structs.
 - Add new struct to pass capdata01 array data and array length to Other
   Mode.
v3:
- Add as component to lenovo-wmi-other driver.
v2:
- Use devm_kmalloc to ensure driver can be instanced, remove global
  reference.
- Ensure reverse Christmas tree for all variable declarations.
- Remove extra whitespace.
- Use guard(mutex) in all mutex instances, global mutex.
- Use pr_fmt instead of adding the driver name to each pr_err.
- Remove noisy pr_info usage.
- Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
- Use list to get the lenovo_wmi_cd01_priv instance in
  lenovo_wmi_capdata01_get as none of the data provided by the macros
  that will use it can pass a member of the struct for use in
  container_of.
---
 MAINTAINERS                                 |   2 +
 drivers/platform/x86/Kconfig                |   4 +
 drivers/platform/x86/Makefile               |   1 +
 drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++
 drivers/platform/x86/lenovo-wmi-capdata01.h |  29 +++++
 5 files changed, 172 insertions(+)
 create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 6dde75922aaf..56ead241a053 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
 S:	Maintained
 F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
 F:	Documentation/wmi/devices/lenovo-wmi-other.rst
+F:	drivers/platform/x86/lenovo-wmi-capdata01.c
+F:	drivers/platform/x86/lenovo-wmi-capdata01.h
 F:	drivers/platform/x86/lenovo-wmi-events.c
 F:	drivers/platform/x86/lenovo-wmi-events.h
 F:	drivers/platform/x86/lenovo-wmi-helpers.c
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 13b8f4ac5dc5..64663667f0cb 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
 	tristate
 	depends on ACPI_WMI
 
+config LENOVO_WMI_DATA01
+	tristate
+	depends on ACPI_WMI
+
 config IDEAPAD_LAPTOP
 	tristate "Lenovo IdeaPad Laptop Extras"
 	depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index fc039839286a..7a35c77221b7 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -69,6 +69,7 @@ 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_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
 obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
 obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
 
diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
new file mode 100644
index 000000000000..b6876611ffd9
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
@@ -0,0 +1,136 @@
+// 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 Mode" WMI interface,
+ * including if it is supported by the hardware, the default_value, max_value,
+ * min_value, and step increment.
+ *
+ * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/gfp_types.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+#include "lenovo-wmi-capdata01.h"
+
+/* Interface GUIDs */
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+
+struct lwmi_cd01_priv {
+	struct wmi_device *wdev;
+};
+
+/*
+ * lenovo_cd01_component_bind() - On master bind, caches all capability data on
+ * the master device.
+ * @cd01_dev: Pointer to the capability data 01 parent device.
+ * @om_dev: Pointer to the other mode parent device.
+ * @data: capdata01_list object pointer to return the capability data with.
+ *
+ * Returns: 0, or an error.
+ */
+static int lenovo_cd01_component_bind(struct device *cd01_dev,
+				      struct device *om_dev, void *data)
+{
+	struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
+	int count, idx;
+
+	if (!priv)
+		return -ENODEV;
+
+	count = wmidev_instance_count(priv->wdev);
+
+	if (count == 0)
+		return -EINVAL;
+
+	((struct cd01_list *)data)->count = count;
+	((struct cd01_list *)data)->data = devm_kmalloc_array(om_dev, count,
+							      sizeof(struct capdata01 *),
+							      GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	for (idx = 0; idx < count; idx++) {
+		union acpi_object *ret_obj __free(kfree) = NULL;
+
+		ret_obj = wmidev_block_query(priv->wdev, idx);
+		if (!ret_obj) {
+			((struct cd01_list *)data)->data[idx] = NULL;
+			continue;
+		}
+		if (ret_obj->type != ACPI_TYPE_BUFFER) {
+			((struct cd01_list *)data)->data[idx] = NULL;
+			continue;
+		}
+
+		if (ret_obj->buffer.length != sizeof(struct capdata01)) {
+			((struct cd01_list *)data)->data[idx] = NULL;
+			continue;
+		}
+
+		((struct cd01_list *)data)->data[idx] =
+			devm_kmemdup(om_dev, ret_obj->buffer.pointer,
+				     ret_obj->buffer.length, GFP_KERNEL);
+	}
+	return 0;
+}
+
+static const struct component_ops lenovo_cd01_component_ops = {
+	.bind = lenovo_cd01_component_bind,
+};
+
+static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
+
+{
+	struct lwmi_cd01_priv *priv;
+	int ret;
+
+	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->wdev = wdev;
+	dev_set_drvdata(&wdev->dev, priv);
+
+	ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
+
+	return ret;
+}
+
+static void lwmi_cd01_remove(struct wmi_device *wdev)
+{
+	component_del(&wdev->dev, &lenovo_cd01_component_ops);
+}
+
+static const struct wmi_device_id lwmi_cd01_id_table[] = {
+	{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
+	{}
+};
+
+static struct wmi_driver lwmi_cd01_driver = {
+	.driver = {
+		.name = "lenovo_wmi_cd01",
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.id_table = lwmi_cd01_id_table,
+	.probe = lwmi_cd01_probe,
+	.remove = lwmi_cd01_remove,
+	.no_singleton = true,
+};
+
+int lwmi_cd01_match(struct device *dev, void *data)
+{
+	return dev->driver == &lwmi_cd01_driver.driver;
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
+
+module_wmi_driver(lwmi_cd01_driver);
+
+MODULE_DEVICE_TABLE(wmi, lwmi_cd01_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-wmi-capdata01.h b/drivers/platform/x86/lenovo-wmi-capdata01.h
new file mode 100644
index 000000000000..c7067a8d0398
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-capdata01.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ *
+ */
+
+#ifndef _LENOVO_WMI_CAPDATA01_H_
+#define _LENOVO_WMI_CAPDATA01_H_
+
+#include <linux/device.h>
+#include <linux/types.h>
+
+struct capdata01 {
+	u32 id;
+	u32 supported;
+	u32 default_value;
+	u32 step;
+	u32 min_value;
+	u32 max_value;
+};
+
+struct cd01_list {
+	struct capdata01 **data;
+	int count;
+};
+
+int lwmi_cd01_match(struct device *dev, void *data);
+
+#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
-- 
2.49.0


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

* [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode WMI Driver
  2025-03-17 14:43 [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
                   ` (3 preceding siblings ...)
  2025-03-17 14:43 ` [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver Derek J. Clark
@ 2025-03-17 14:43 ` Derek J. Clark
  2025-03-26 19:48   ` Matthew Schwartz
                     ` (2 more replies)
  2025-03-17 14:43 ` [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone " Derek J. Clark
  2025-03-27  3:52 ` [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers Armin Wolf
  6 siblings, 3 replies; 49+ messages in thread
From: Derek J. Clark @ 2025-03-17 14:43 UTC (permalink / raw)
  To: Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	Derek J . Clark, platform-driver-x86, linux-doc, linux-kernel

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

Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Treat Other Mode as a notifier chain head, use the notifier chain to
  get the current mode from Gamezone.
- Add header file for Other Mode specific structs and finctions.
- Use component master bind to cache the capdata01 array locally.
- Drop all reference to external driver private data structs.
- Various fixes from review.
v3:
- Add notifier block and store result for getting the Gamezone interface
  profile changes.
- Add driver as master component of capdata01 driver.
- Use FIELD_PREP where appropriate.
- Move macros and associated functions out of lemovo-wmi.h that are only
  used by this driver.
v2:
- Use devm_kmalloc to ensure driver can be instanced, remove global
  reference.
- Ensure reverse Christmas tree for all variable declarations.
- Remove extra whitespace.
- Use guard(mutex) in all mutex instances, global mutex.
- Use pr_fmt instead of adding the driver name to each pr_err.
- Remove noisy pr_info usage.
- Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
- Use list to get the lenovo_wmi_om_priv instance in some macro
  called functions as the data provided by the macros that use it
  doesn't pass a member of the struct for use in container_of.
- Do not rely on GameZone interface to grab the current fan mode.
---
 MAINTAINERS                             |   2 +
 drivers/platform/x86/Kconfig            |  15 +
 drivers/platform/x86/Makefile           |   1 +
 drivers/platform/x86/lenovo-wmi-other.c | 626 ++++++++++++++++++++++++
 drivers/platform/x86/lenovo-wmi-other.h |  19 +
 5 files changed, 663 insertions(+)
 create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-other.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 56ead241a053..87daee6075ad 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13170,6 +13170,8 @@ F:	drivers/platform/x86/lenovo-wmi-events.c
 F:	drivers/platform/x86/lenovo-wmi-events.h
 F:	drivers/platform/x86/lenovo-wmi-helpers.c
 F:	drivers/platform/x86/lenovo-wmi-helpers.h
+F:	drivers/platform/x86/lenovo-wmi-other.c
+F:	drivers/platform/x86/lenovo-wmi-other.h
 
 LENOVO WMI HOTKEY UTILITIES DRIVER
 M:	Jackie Dong <xy-jackie@139.com>
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 64663667f0cb..fc47604e37f7 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -471,6 +471,21 @@ config LENOVO_WMI_DATA01
 	tristate
 	depends on ACPI_WMI
 
+config LENOVO_WMI_TUNING
+	tristate "Lenovo Other Mode WMI Driver"
+	depends on ACPI_WMI
+	select FW_ATTR_CLASS
+	select LENOVO_WMI_DATA01
+	select LENOVO_WMI_EVENTS
+	select LENOVO_WMI_HELPERS
+	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-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 7a35c77221b7..c6ce3c8594b1 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
 obj-$(CONFIG_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
 obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
 obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
+obj-$(CONFIG_LENOVO_WMI_TUNING)	+= lenovo-wmi-other.o
 
 # Intel
 obj-y				+= intel/
diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
new file mode 100644
index 000000000000..b517e45338e0
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-other.c
@@ -0,0 +1,626 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
+ * class to expose the various WMI functions provided by the "Other Mode" 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 laptop
+ * devices. Each attribute exposed by the "Other Mode"" 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.
+ *
+ * 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/gfp_types.h>
+#include <linux/idr.h>
+#include <linux/kobject.h>
+#include <linux/notifier.h>
+#include <linux/platform_profile.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "lenovo-wmi-capdata01.h"
+#include "lenovo-wmi-events.h"
+#include "lenovo-wmi-gamezone.h"
+#include "lenovo-wmi-helpers.h"
+#include "lenovo-wmi-other.h"
+#include "firmware_attributes_class.h"
+
+/* Interface GUIDs */
+#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_FPPT 0x03 /* Long Term Power Limit */
+#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
+
+/* Type IDs*/
+#define WMI_TYPE_ID_NONE 0x00
+
+/* Method IDs */
+#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
+#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
+
+/* Attribute ID bitmasks */
+#define ATTR_DEV_ID_MASK GENMASK(31, 24)
+#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
+#define ATTR_MODE_ID_MASK GENMASK(15, 8)
+#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
+
+static BLOCKING_NOTIFIER_HEAD(om_chain_head);
+
+enum attribute_property {
+	DEFAULT_VAL,
+	MAX_VAL,
+	MIN_VAL,
+	STEP_VAL,
+	SUPPORTED,
+};
+
+struct lwmi_om_priv {
+	struct blocking_notifier_head nhead;
+	struct component_master_ops *ops;
+	struct cd01_list cd01_list;
+	struct device *fw_attr_dev;
+	struct kset *fw_attr_kset;
+	struct notifier_block nb;
+	struct wmi_device *wdev;
+	struct ida ida;
+	int ida_id;
+};
+
+/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
+struct tunable_attr_01 {
+	u32 type_id;
+	u32 device_id;
+	u32 feature_id;
+	u32 store_value;
+	struct device *dev;
+	struct capdata01 *capdata;
+};
+
+/* Tunable Attributes */
+struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
+				       .feature_id = WMI_FEATURE_ID_CPU_SPL,
+				       .type_id = WMI_TYPE_ID_NONE };
+struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
+					.feature_id = WMI_FEATURE_ID_CPU_SPPT,
+					.type_id = WMI_TYPE_ID_NONE };
+struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
+					.feature_id = WMI_FEATURE_ID_CPU_FPPT,
+					.type_id = WMI_TYPE_ID_NONE };
+
+struct capdata01_attr_group {
+	const struct attribute_group *attr_group;
+	struct tunable_attr_01 *tunable_attr;
+};
+
+#define FW_ATTR_FOLDER "lenovo-wmi-other"
+
+/* Notifier Methods */
+int lwmi_om_register_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_register(&om_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
+
+int lwmi_om_unregister_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_unregister(&om_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
+
+static void devm_lwmi_om_unregister_notifier(void *data)
+{
+	struct notifier_block *nb = data;
+
+	lwmi_om_unregister_notifier(nb);
+}
+
+int devm_lwmi_om_register_notifier(struct device *dev,
+				   struct notifier_block *nb)
+{
+	int ret;
+
+	ret = lwmi_om_register_notifier(nb);
+	if (ret < 0)
+		return ret;
+
+	return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier,
+					nb);
+}
+EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
+
+static int lwmi_om_notifier_call(enum thermal_mode *mode)
+{
+	int ret;
+
+	ret = blocking_notifier_call_chain(&om_chain_head, THERMAL_MODE_EVENT,
+					   mode);
+
+	if (ret != NOTIFY_OK)
+		return -EINVAL;
+
+	if (*mode < SMARTFAN_MODE_QUIET || *mode > SMARTFAN_MODE_CUSTOM)
+		return -EINVAL;
+
+	return 0;
+}
+
+/* Attribute Methods */
+/*
+ * int_type_show() - Emit the data type for an integer attribute
+ * @kobj: Pointer to the driver object.
+ * @kobj_attribute: Pointer to the attribute calling this function.
+ * @buf: The buffer to write to.
+ *
+ * Returns: Number of characters written to buf.
+ */
+static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
+			     char *buf)
+{
+	return sysfs_emit(buf, "integer\n");
+}
+
+/*
+ * attr_capdata01_get - Get the data of the specified attribute
+ * from lwmi_om->cd01.
+ * @tunable_attr: The attribute to be populated.
+ *
+ * Returns: Either a pointer to capability data, or NULL.
+ */
+static struct capdata01 *
+attr_capdata01_get_data(struct lwmi_om_priv *priv,
+			struct tunable_attr_01 *tunable_attr,
+			enum thermal_mode mode)
+{
+	u32 attribute_id =
+		FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+		FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+		FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
+		FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+	int idx;
+
+	for (idx = 0; idx < priv->cd01_list.count; idx++) {
+		if (!priv->cd01_list.data[idx])
+			continue;
+
+		if (priv->cd01_list.data[idx]->id != attribute_id)
+			continue;
+		return priv->cd01_list.data[idx];
+	}
+	return NULL;
+}
+
+/**
+ * attr_capdata01_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.
+ * @tunable_attr: The attribute to be read.
+ * @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 number of characters written to buf, or an error.
+ */
+static ssize_t attr_capdata01_show(struct kobject *kobj,
+				   struct kobj_attribute *kattr, char *buf,
+				   struct tunable_attr_01 *tunable_attr,
+				   enum attribute_property prop)
+{
+	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
+	struct capdata01 *capdata;
+	int value;
+
+	if (!priv)
+		return -ENODEV;
+
+	capdata = attr_capdata01_get_data(priv, tunable_attr,
+					  SMARTFAN_MODE_CUSTOM);
+
+	if (!capdata)
+		return -ENODEV;
+
+	switch (prop) {
+	case DEFAULT_VAL:
+		value = capdata->default_value;
+		break;
+	case MAX_VAL:
+		value = capdata->max_value;
+		break;
+	case MIN_VAL:
+		value = capdata->min_value;
+		break;
+	case STEP_VAL:
+		value = capdata->step;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return sysfs_emit(buf, "%d\n", value);
+}
+
+/* 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.
+ * @tunable_attr: The attribute to be stored.
+ *
+ * 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.
+ */
+static ssize_t attr_current_value_store(struct kobject *kobj,
+					struct kobj_attribute *kattr,
+					const char *buf, size_t count,
+					struct tunable_attr_01 *tunable_attr)
+{
+	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
+	struct wmi_method_args_32 args;
+	struct capdata01 *capdata;
+	enum thermal_mode mode;
+	u32 attribute_id;
+	u32 value;
+	int err;
+
+	if (!priv)
+		return -ENODEV;
+
+	err = lwmi_om_notifier_call(&mode);
+	if (err)
+		return err;
+
+	if (mode != SMARTFAN_MODE_CUSTOM)
+		return -EINVAL;
+
+	capdata = attr_capdata01_get_data(priv, tunable_attr, mode);
+
+	if (!capdata)
+		return -ENODEV;
+
+	attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+		       FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+		       FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
+		       FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+	err = kstrtouint(buf, 10, &value);
+	if (err)
+		return err;
+
+	if (value < capdata->min_value || value > capdata->max_value)
+		return -EINVAL;
+
+	args.arg0 = attribute_id;
+	args.arg1 = value;
+
+	err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_SET,
+				       (unsigned char *)&args, sizeof(args),
+				       NULL);
+
+	if (err)
+		return err;
+
+	tunable_attr->store_value = value;
+	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.
+ * @tunable_attr: The 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 number of characters written to buf, or an error.
+ */
+static ssize_t attr_current_value_show(struct kobject *kobj,
+				       struct kobj_attribute *kattr, char *buf,
+				       struct tunable_attr_01 *tunable_attr)
+{
+	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
+	struct wmi_method_args_32 args;
+	enum thermal_mode mode;
+	u32 attribute_id;
+	int retval;
+	int err;
+
+	if (!priv)
+		return -ENODEV;
+
+	err = lwmi_om_notifier_call(&mode);
+	if (err)
+		return err;
+
+	attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+		       FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+		       FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
+		       FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+	args.arg0 = attribute_id;
+
+	err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_GET,
+				       (unsigned char *)&args, sizeof(args),
+				       &retval);
+
+	if (err)
+		return err;
+
+	return sysfs_emit(buf, "%d\n", retval);
+}
+
+/* Attribute macros */
+#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 *kattr, char *buf) \
+	{                                                                      \
+		return sysfs_emit(buf, _fmt, _val);                            \
+	}                                                                      \
+	static struct kobj_attribute attr_##_attrname##_##_prop =              \
+		__LL_ATTR_RO(_attrname, _prop)
+
+/* Attribute current value read/write */
+#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname)                            \
+	static ssize_t _attrname##_current_value_store(                        \
+		struct kobject *kobj, struct kobj_attribute *kattr,            \
+		const char *buf, size_t count)                                 \
+	{                                                                      \
+		return attr_current_value_store(kobj, kattr, buf, count,       \
+						&_attrname);                   \
+	}                                                                      \
+	static ssize_t _attrname##_current_value_show(                         \
+		struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
+	{                                                                      \
+		return attr_current_value_show(kobj, kattr, buf, &_attrname);  \
+	}                                                                      \
+	static struct kobj_attribute attr_##_attrname##_current_value =        \
+		__LL_ATTR_RW(_attrname, current_value)
+
+/* Attribute property read only */
+#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type)                    \
+	static ssize_t _attrname##_##_prop##_show(                             \
+		struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
+	{                                                                      \
+		return attr_capdata01_show(kobj, kattr, buf, &_attrname,       \
+					   _prop_type);                        \
+	}                                                                      \
+	static struct kobj_attribute attr_##_attrname##_##_prop =              \
+		__LL_ATTR_RO(_attrname, _prop)
+
+#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname)     \
+	__LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname);                   \
+	__LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL);  \
+	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);   \
+	__LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL);          \
+	__LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL);          \
+	__LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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            \
+	}
+
+ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
+			    "Set the CPU sustained power limit");
+ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
+			    "Set the CPU slow package power tracking limit");
+ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
+			    "Set the CPU fast package power tracking limit");
+
+static struct capdata01_attr_group capdata01_attr_groups[] = {
+	{ &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
+	{ &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
+	{ &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
+	{},
+};
+
+/*
+ * lwmi_om_fw_attr_add() - Registers all capdata01_attr_groups[] attributes as
+ * firmware_attributes_class members.
+ * @priv: The Other Mode driver data.
+ *
+ * Returns: Either 0, or an error.
+ */
+static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
+{
+	int err, i;
+
+	ida_init(&priv->ida);
+	priv->ida_id = ida_alloc(&priv->ida, GFP_KERNEL);
+	if (priv->ida_id < 0)
+		return priv->ida_id;
+
+	priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
+					  MKDEV(0, 0), NULL, "%s",
+					  FW_ATTR_FOLDER);
+	if (IS_ERR(priv->fw_attr_dev)) {
+		err = PTR_ERR(priv->fw_attr_dev);
+		return err;
+	}
+
+	priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
+						 &priv->fw_attr_dev->kobj);
+	if (!priv->fw_attr_kset) {
+		err = -ENOMEM;
+		goto err_destroy_classdev;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
+		err = sysfs_create_group(&priv->fw_attr_kset->kobj,
+					 capdata01_attr_groups[i].attr_group);
+		if (err) {
+			pr_debug("Failed to create sysfs-group for %s: %d\n",
+				 capdata01_attr_groups[i].attr_group->name,
+				 err);
+			goto err_remove_groups;
+		}
+		capdata01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
+	}
+	return 0;
+
+err_remove_groups:
+	ida_free(&priv->ida, priv->ida_id);
+	while (i-- >= 0) {
+		sysfs_remove_group(&priv->fw_attr_kset->kobj,
+				   capdata01_attr_groups[i].attr_group);
+	}
+	kset_unregister(priv->fw_attr_kset);
+
+err_destroy_classdev:
+	device_unregister(priv->fw_attr_dev);
+	return err;
+}
+
+/*
+ * lwmi_om_fw_attr_remove() - Unregisters all capdata01_attr_groups[] attributes as
+ * firmware_attributes_class members.
+ * @priv: The Other Mode driver data.
+ *
+ */
+static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
+{
+	int size = ARRAY_SIZE(capdata01_attr_groups);
+
+	while (--size >= 0) {
+		sysfs_remove_group(&priv->fw_attr_kset->kobj,
+				   capdata01_attr_groups[size].attr_group);
+	}
+	kset_unregister(priv->fw_attr_kset);
+	device_unregister(priv->fw_attr_dev);
+}
+
+static int lwmi_om_master_bind(struct device *dev)
+{
+	struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+	int ret;
+
+	ret = component_bind_all(dev, &priv->cd01_list);
+	if (ret)
+		return ret;
+
+	return lwmi_om_fw_attr_add(priv);
+}
+
+static void lwmi_om_master_unbind(struct device *dev)
+{
+	component_unbind_all(dev, NULL);
+}
+
+static const struct component_master_ops lwmi_om_master_ops = {
+	.bind = lwmi_om_master_bind,
+	.unbind = lwmi_om_master_unbind,
+};
+
+static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
+{
+	struct component_match *master_match = NULL;
+	struct lwmi_om_priv *priv;
+
+	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->wdev = wdev;
+	dev_set_drvdata(&wdev->dev, priv);
+
+	component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
+	if (IS_ERR(master_match))
+		return PTR_ERR(master_match);
+
+	return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
+					       master_match);
+}
+
+static void lwmi_other_remove(struct wmi_device *wdev)
+{
+	struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
+
+	component_master_del(&wdev->dev, &lwmi_om_master_ops);
+	lwmi_om_fw_attr_remove(priv);
+	ida_free(&priv->ida, priv->ida_id);
+}
+
+static const struct wmi_device_id lwmi_other_id_table[] = {
+	{ LENOVO_OTHER_METHOD_GUID, NULL },
+	{}
+};
+
+static struct wmi_driver lwmi_other_driver = {
+	.driver = {
+		.name = "lenovo_wmi_other",
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.id_table = lwmi_other_id_table,
+	.probe = lwmi_other_probe,
+	.remove = lwmi_other_remove,
+	.no_singleton = true,
+};
+
+module_wmi_driver(lwmi_other_driver);
+
+MODULE_IMPORT_NS("LENOVO_WMI_CD01");
+MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
+MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-wmi-other.h b/drivers/platform/x86/lenovo-wmi-other.h
new file mode 100644
index 000000000000..9fba35ef1137
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-other.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ *
+ */
+
+#ifndef _LENOVO_WMI_OTHER_H_
+#define _LENOVO_WMI_OTHER_H_
+
+#include <linux/device.h>
+#include <linux/notifier.h>
+#include <linux/types.h>
+
+int lwmi_om_register_notifier(struct notifier_block *nb);
+int lwmi_om_unregister_notifier(struct notifier_block *nb);
+int devm_lwmi_om_register_notifier(struct device *dev,
+				   struct notifier_block *nb);
+
+#endif /* !_LENOVO_WMI_H_ */
-- 
2.49.0


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

* [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone WMI Driver
  2025-03-17 14:43 [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
                   ` (4 preceding siblings ...)
  2025-03-17 14:43 ` [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode " Derek J. Clark
@ 2025-03-17 14:43 ` Derek J. Clark
  2025-03-26 19:48   ` Matthew Schwartz
                     ` (2 more replies)
  2025-03-27  3:52 ` [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers Armin Wolf
  6 siblings, 3 replies; 49+ messages in thread
From: Derek J. Clark @ 2025-03-17 14:43 UTC (permalink / raw)
  To: Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	Derek J . Clark, platform-driver-x86, linux-doc, linux-kernel

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

Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Add notifier blocks for the Events and Other Mode drivers.
- Remove notifier block chain head and all reference to Thermal Mode
  Event GUID.
- Add header for Gamezone specific structs and functions.
- Various fixes from review.
v3:
- Use notifier chain to report platform profile changes to any
  subscribed drivers.
- Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
  chain.
- Adds support for Extreme Mode profile on supported hardware, as well
  as a DMI quirk table for some devices that report extreme mode version
  support but so not have it fully implemented.
- Update to include recent changes to platform-profile.
v2:
- Use devm_kmalloc to ensure driver can be instanced, remove global
  reference.
- Ensure reverse Christmas tree for all variable declarations.
- Remove extra whitespace.
- Use guard(mutex) in all mutex instances, global mutex.
- Use pr_fmt instead of adding the driver name to each pr_err.
- Remove noisy pr_info usage.
- Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
- Remove GZ_WMI symbol exporting.
---
 MAINTAINERS                                |   2 +
 drivers/platform/x86/Kconfig               |  13 +
 drivers/platform/x86/Makefile              |   1 +
 drivers/platform/x86/lenovo-wmi-gamezone.c | 380 +++++++++++++++++++++
 drivers/platform/x86/lenovo-wmi-gamezone.h |  18 +
 5 files changed, 414 insertions(+)
 create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 87daee6075ad..0416afd997a0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13168,6 +13168,8 @@ F:	drivers/platform/x86/lenovo-wmi-capdata01.c
 F:	drivers/platform/x86/lenovo-wmi-capdata01.h
 F:	drivers/platform/x86/lenovo-wmi-events.c
 F:	drivers/platform/x86/lenovo-wmi-events.h
+F:	drivers/platform/x86/lenovo-wmi-gamezone.c
+F:	drivers/platform/x86/lenovo-wmi-gamezone.h
 F:	drivers/platform/x86/lenovo-wmi-helpers.c
 F:	drivers/platform/x86/lenovo-wmi-helpers.h
 F:	drivers/platform/x86/lenovo-wmi-other.c
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index fc47604e37f7..ecf3246c8fda 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -467,6 +467,19 @@ config LENOVO_WMI_HELPERS
 	tristate
 	depends on ACPI_WMI
 
+config LENOVO_WMI_GAMEZONE
+	tristate "Lenovo GameZone WMI Driver"
+	depends on ACPI_WMI
+	select ACPI_PLATFORM_PROFILE
+	select LENOVO_WMI_EVENTS
+	select LENOVO_WMI_HELPERS
+	help
+	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
+	  platform-profile firmware interface to manage power usage.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called lenovo-wmi-gamezone.
+
 config LENOVO_WMI_DATA01
 	tristate
 	depends on ACPI_WMI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index c6ce3c8594b1..f3e64926a96b 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_YT2_1380)		+= lenovo-yoga-tab2-pro-1380-fastcharger.o
 obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
 obj-$(CONFIG_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
 obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
+obj-$(CONFIG_LENOVO_WMI_GAMEZONE)	+= lenovo-wmi-gamezone.o
 obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
 obj-$(CONFIG_LENOVO_WMI_TUNING)	+= lenovo-wmi-other.o
 
diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
new file mode 100644
index 000000000000..9d453a836227
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
@@ -0,0 +1,380 @@
+// 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/dmi.h>
+#include <linux/list.h>
+#include <linux/notifier.h>
+#include <linux/platform_profile.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "lenovo-wmi-events.h"
+#include "lenovo-wmi-gamezone.h"
+#include "lenovo-wmi-helpers.h"
+#include "lenovo-wmi-other.h"
+
+/* Interface GUIDs */
+#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 BLOCKING_NOTIFIER_HEAD(gz_chain_head);
+
+struct lwmi_event_priv {
+	enum thermal_mode current_mode;
+	struct wmi_device *wdev;
+	bool extreme_supported;
+	struct device *ppdev; /*platform profile device */
+	struct notifier_block event_nb;
+	struct notifier_block mode_nb;
+};
+
+struct quirk_entry {
+	bool extreme_supported;
+};
+
+static struct quirk_entry quirk_no_extreme_bug = {
+	.extreme_supported = false,
+};
+
+/* Notifier Methods */
+/*
+ * lwmi_gz_mode_call() - Call method for lenovo-wmi-other notifier
+ * block call chain. For THERMAL_MODE_EVENT, returns current_mode
+ *
+ * @nb: The notifier_block registered to lenovo-wmi-other
+ * @cmd: The event triggered by lenovo-wmi-other
+ * @data: The data to be returned by the event.
+ *
+ * Returns: notifier_block status.
+ */
+static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
+			     void *data)
+{
+	struct lwmi_event_priv *priv;
+
+	priv = container_of(nb, struct lwmi_event_priv, mode_nb);
+	if (!priv)
+		return NOTIFY_BAD;
+
+	switch (cmd) {
+	case THERMAL_MODE_EVENT:
+		*(enum thermal_mode *)data = priv->current_mode;
+		break;
+	default:
+		return NOTIFY_DONE;
+	}
+
+	return NOTIFY_OK;
+}
+
+/*
+ * lwmi_gz_event_call() - Call method for lenovo-wmi-events notifier
+ * block call chain. For THERMAL_MODE_EVENT, sets current_mode and
+ * notifies platform_profile of a change.
+ *
+ * @nb: The notifier_block registered to lenovo-wmi-events
+ * @cmd: The event triggered by lenovo-wmi-events
+ * @data: The data to be updated by the event.
+ *
+ * Returns: notifier_block status.
+ */
+static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
+			      void *data)
+{
+	struct lwmi_event_priv *priv;
+
+	priv = container_of(nb, struct lwmi_event_priv, event_nb);
+	if (!priv)
+		return NOTIFY_BAD;
+
+	switch (cmd) {
+	case THERMAL_MODE_EVENT:
+		priv->current_mode = *((enum thermal_mode *)data);
+		platform_profile_notify(&priv->wdev->dev);
+		break;
+	default:
+		return NOTIFY_DONE;
+	}
+
+	return NOTIFY_OK;
+}
+
+/* Platform Profile Methods & Setup */
+/*
+ * lwmi_gz_platform_profile_supported() - Gets the version of the WMI
+ * interface to determine the support level.
+ *
+ * @wdev: The Gamezone WMI device.
+ * @supported: Pointer to return the support level with.
+ *
+ * Returns: 0, or an error.
+ */
+static int lwmi_gz_platform_profile_supported(struct wmi_device *wdev,
+					      int *supported)
+{
+	return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP,
+					0, 0, supported);
+}
+
+/*
+ * lwmi_gz_thermal_mode_get() - Gets the currently set thermal mode from
+ * the Gamezone WMI interface.
+ *
+ * @wdev: The Gamezone WMI device.
+ * @mode: Pointer to return the thermal mode with.
+ *
+ * Returns: 0, or an error.
+ */
+static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
+				    enum thermal_mode *mode)
+{
+	return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET,
+					0, 0, mode);
+}
+
+static int lwmi_gz_profile_get(struct device *dev,
+			       enum platform_profile_option *profile)
+{
+	struct lwmi_event_priv *priv = dev_get_drvdata(dev);
+	enum thermal_mode mode;
+	int ret;
+
+	ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode);
+	if (ret)
+		return ret;
+
+	switch (mode) {
+	case SMARTFAN_MODE_QUIET:
+		*profile = PLATFORM_PROFILE_LOW_POWER;
+		break;
+	case SMARTFAN_MODE_BALANCED:
+		*profile = PLATFORM_PROFILE_BALANCED;
+		break;
+	case SMARTFAN_MODE_PERFORMANCE:
+		if (priv->extreme_supported) {
+			*profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+			break;
+		}
+		*profile = PLATFORM_PROFILE_PERFORMANCE;
+		break;
+	case SMARTFAN_MODE_EXTREME:
+		*profile = PLATFORM_PROFILE_PERFORMANCE;
+		break;
+	case SMARTFAN_MODE_CUSTOM:
+		*profile = PLATFORM_PROFILE_CUSTOM;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	priv->current_mode = mode;
+
+	return 0;
+}
+
+static int lwmi_gz_profile_set(struct device *dev,
+			       enum platform_profile_option profile)
+{
+	struct lwmi_event_priv *priv = dev_get_drvdata(dev);
+	struct wmi_method_args_32 args;
+	enum thermal_mode mode;
+	int ret;
+
+	switch (profile) {
+	case PLATFORM_PROFILE_LOW_POWER:
+		mode = SMARTFAN_MODE_QUIET;
+		break;
+	case PLATFORM_PROFILE_BALANCED:
+		mode = SMARTFAN_MODE_BALANCED;
+		break;
+	case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
+		mode = SMARTFAN_MODE_PERFORMANCE;
+		break;
+	case PLATFORM_PROFILE_PERFORMANCE:
+		if (priv->extreme_supported) {
+			mode = SMARTFAN_MODE_EXTREME;
+			break;
+		}
+		mode = SMARTFAN_MODE_PERFORMANCE;
+		break;
+	case PLATFORM_PROFILE_CUSTOM:
+		mode = SMARTFAN_MODE_CUSTOM;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	args.arg0 = mode;
+
+	ret = lwmi_dev_evaluate_method(priv->wdev, 0x0,
+				       WMI_METHOD_ID_SMARTFAN_SET,
+				       (unsigned char *)&args, sizeof(args),
+				       NULL);
+	if (ret)
+		return ret;
+
+	priv->current_mode = mode;
+
+	return 0;
+}
+
+static const struct dmi_system_id fwbug_list[] = {
+	{
+		.ident = "Legion Go 8APU1",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
+		},
+		.driver_data = &quirk_no_extreme_bug,
+	},
+	{
+		.ident = "Legion Go S 8ARP1",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
+		},
+		.driver_data = &quirk_no_extreme_bug,
+	},
+	{
+		.ident = "Legion Go S 8APU1",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
+		},
+		.driver_data = &quirk_no_extreme_bug,
+	},
+	{},
+
+};
+
+/*
+ * extreme_supported() - Evaluate if a device supports extreme thermal mode.
+ * For devices that have a profile_support_ver of 6 or greater a DMI check
+ * is done. Some devices report a version that supports extreme mode but
+ * have an incomplete entry in the BIOS. To ensure this cannot be set, they
+ * are quirked to prevent assignment.
+ *
+ * @profile_support_ver: Version of WMI interface provided by
+ * lwmi_gz_platform_profile_supported.
+ *
+ * Returns: bool
+ */
+static bool extreme_supported(int profile_support_ver)
+{
+	const struct dmi_system_id *dmi_id;
+	struct quirk_entry *quirks;
+
+	if (profile_support_ver < 6)
+		return false;
+
+	dmi_id = dmi_first_match(fwbug_list);
+	if (!dmi_id)
+		return true;
+
+	quirks = dmi_id->driver_data;
+	return quirks->extreme_supported;
+}
+
+static int lwmi_platform_profile_probe(void *drvdata, unsigned long *choices)
+{
+	struct lwmi_event_priv *priv = drvdata;
+	int profile_support_ver;
+	int ret;
+
+	ret = lwmi_gz_platform_profile_supported(priv->wdev,
+						 &profile_support_ver);
+	if (ret)
+		return ret;
+
+	if (profile_support_ver < 1)
+		return -ENODEV;
+
+	priv->extreme_supported = extreme_supported(profile_support_ver);
+
+	set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
+	set_bit(PLATFORM_PROFILE_BALANCED, choices);
+	set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+	set_bit(PLATFORM_PROFILE_CUSTOM, choices);
+
+	if (priv->extreme_supported)
+		set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
+
+	return 0;
+}
+
+static const struct platform_profile_ops lwmi_gz_platform_profile_ops = {
+	.probe = lwmi_platform_profile_probe,
+	.profile_get = lwmi_gz_profile_get,
+	.profile_set = lwmi_gz_profile_set,
+};
+
+/* Driver Methods */
+static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
+{
+	struct lwmi_event_priv *priv;
+	int ret;
+
+	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->event_nb.notifier_call = lwmi_gz_event_call;
+	ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
+	if (ret)
+		return ret;
+
+	priv->mode_nb.notifier_call = lwmi_gz_mode_call;
+	ret = devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb);
+	if (ret)
+		return ret;
+
+	priv->wdev = wdev;
+	dev_set_drvdata(&wdev->dev, priv);
+
+	priv->ppdev = platform_profile_register(&wdev->dev,
+						"lenovo-wmi-gamezone", priv,
+						&lwmi_gz_platform_profile_ops);
+
+	if (IS_ERR(priv->ppdev))
+		return -ENODEV;
+
+	ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct wmi_device_id lwmi_gz_id_table[] = { { LENOVO_GAMEZONE_GUID,
+							   NULL },
+							 {} };
+
+static struct wmi_driver lwmi_gz_driver = {
+	.driver = {
+		.name = "lenovo_wmi_gamezone",
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.id_table = lwmi_gz_id_table,
+	.probe = lwmi_gz_probe,
+	.no_singleton = true,
+};
+
+module_wmi_driver(lwmi_gz_driver);
+
+MODULE_IMPORT_NS("LENOVO_WMI_EVENTS");
+MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
+MODULE_IMPORT_NS("LENOVO_WMI_OTHER");
+MODULE_DEVICE_TABLE(wmi, lwmi_gz_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-wmi-gamezone.h b/drivers/platform/x86/lenovo-wmi-gamezone.h
new file mode 100644
index 000000000000..ac536803160b
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-gamezone.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ *
+ */
+
+#ifndef _LENOVO_WMI_GAMEZONE_H_
+#define _LENOVO_WMI_GAMEZONE_H_
+
+enum thermal_mode {
+	SMARTFAN_MODE_QUIET = 0x01,
+	SMARTFAN_MODE_BALANCED = 0x02,
+	SMARTFAN_MODE_PERFORMANCE = 0x03,
+	SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
+	SMARTFAN_MODE_CUSTOM = 0xFF,
+};
+
+#endif /* !_LENOVO_WMI_GAMEZONE_H_ */
-- 
2.49.0


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

* Re: [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation
  2025-03-17 14:43 ` [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation Derek J. Clark
@ 2025-03-18  4:24   ` Mario Limonciello
  2025-03-19  2:48   ` Derek J. Clark
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 49+ messages in thread
From: Mario Limonciello @ 2025-03-18  4:24 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang,
	Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel



On 3/17/25 09:43, Derek J. Clark wrote:
> Adds documentation for new lenovo-wmi drivers.
> 
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>

Reviewed-by: Mario Limonciello <mario.limonciello@amd.com>

> ---
> v4:
>   - Fixed MOF formatting issues.
>   - Fixed spelling mistakes.
>   - Updated description of balanced-performance profile for Gamezone.
>   - Updated description of thermal mode event GUID for Gamezone.
> v3:
> - Split documentation into multiple files, one for each parent
>    driver for the Gamezone and Other Mode WMI interfaces.
> - Add MOF data for all parent and child interfaces.
> - Remove lenovo-wmi-camera.c driver from v2 documentation.
> v2:
> - Update description of Custom Profile to include the need to manually
>    set it.
> - Remove all references to Legion hardware.
> - Add section for lenovo-wmi-camera.c driver as it follows the same
>    naming convention.
> ---
>   .../wmi/devices/lenovo-wmi-gamezone.rst       | 203 ++++++++++++++++++
>   .../wmi/devices/lenovo-wmi-other-method.rst   | 108 ++++++++++
>   MAINTAINERS                                   |   7 +
>   3 files changed, 318 insertions(+)
>   create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>   create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst
> 
> diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> new file mode 100644
> index 000000000000..bde63dde285d
> --- /dev/null
> +++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> @@ -0,0 +1,203 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +==========================================================
> +Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
> +==========================================================
> +
> +Introduction
> +============
> +The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
> +The priamry "Gamezone" GUID provides advanced features such as fan
> +profiles and overclocking. It is paired with multiple event GUIDs
> +and data block GUIDs that provide context for the various methods.
> +
> +Gamezone Data
> +-------------
> +
> +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The Gamezone Data WMI interface provides platform-profile and fan curve
> +settings for devices that fall under the "Gaming Series" of Lenovo devices.
> +It uses a notifier chain to inform other Lenovo WMI interface drivers of the
> +current platform profile when it changes.
> +
> +The following platform profiles are supported:
> + - low-power
> + - balanced
> + - balanced-performance*
> + - performance
> + - custom
> +
> +Balanced-Performance
> +~~~~~~~~~~~~~~~~~~~~
> +Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
> +enabled in their BIOS. For these devices, the performance platform profile
> +will correspond to the BIOS Extreme Mode, while the balanced-performance
> +platform profile will correspond to the BIOS Performance mode. For legacy
> +devices, the performance platform profile will correspond with the BIOS
> +Performance mode. For some newer devices the "Extreme Mode" profile is
> +incomplete in the BIOS and setting it will cause undefined behavior. A
> +BIOS bug quirk table is provided to ensure these devices cannot set
> +"Extreme Mode" from the driver.
> +
> +Custom Profile
> +~~~~~~~~~~~~~~
> +The custom profile represents a hardware mode on Lenovo devices that enables
> +user modifications to Package Power Tracking (PPT) and fan curve settings.
> +When an attribute exposed by the Other Mode WMI interface is to be modified,
> +the Gamezone driver must first be switched to the "custom" profile manually,
> +or the setting will have no effect. If another profile is set from the list
> +of supported profiles, the BIOS will override any user PPT settings when
> +switching to that profile.
> +
> +Gamezone Thermal Mode Event
> +---------------------------
> +
> +WMI GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The Gamezone Thermal Mode Event interface notifies the system when the platform
> +profile has changed, either through the hardware event (Fn+Q for laptops or
> +Legion + Y for Go Series), or through the Gamezone WMI interface. This event is
> +implemented in the Lenovo WMI Events driver (lenovo-wmi-events).
> +
> +
> +WMI interface description
> +=========================
> +
> +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> +
> +::
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_GAMEZONE_DATA class"), guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
> +  class LENOVO_GAMEZONE_DATA {
> +  	[key, read] string InstanceName;
> +  	[read] boolean Active;
> +
> +    [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")] void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
> +    [WmiMethodId(11), Implemented, Description("Get AslCode Version")] void GetVersion ([out, Description("AslCode version")] UINT32 Data);
> +    [WmiMethodId(12), Implemented, Description("Fan cooling capability")] void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
> +    [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")] void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
> +    [WmiMethodId(14), Implemented, Description("cpu oc capability")] void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
> +    [WmiMethodId(15), Implemented, Description("bios has overclock capability")] void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
> +    [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")] void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
> +    [WmiMethodId(18), Implemented, Description("Get CPU temperature")] void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
> +    [WmiMethodId(19), Implemented, Description("Get GPU temperature")] void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
> +    [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")] void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
> +    [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")] void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
> +    [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")] void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
> +    [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")] void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
> +    [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")] void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
> +    [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")] void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
> +    [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")] void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
> +    [WmiMethodId(30), Implemented, Description("Get Keyboard feature list")] void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
> +    [WmiMethodId(31), Implemented, Description("Get Memory OC Information")] void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
> +    [WmiMethodId(32), Implemented, Description("Water Cooling feature capability")] void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
> +    [WmiMethodId(33), Implemented, Description("Set Water Cooling status")] void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
> +    [WmiMethodId(34), Implemented, Description("Get Water Cooling status")] void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
> +    [WmiMethodId(35), Implemented, Description("Lighting feature capability")] void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
> +    [WmiMethodId(36), Implemented, Description("Set keyboard light off or on to max")] void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
> +    [WmiMethodId(37), Implemented, Description("Get keyboard light on/off status")] void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
> +    [WmiMethodId(38), Implemented, Description("Get Macrokey scan code")] void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
> +    [WmiMethodId(39), Implemented, Description("Get Macrokey count")] void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
> +    [WmiMethodId(40), Implemented, Description("Support G-Sync feature")] void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
> +    [WmiMethodId(41), Implemented, Description("Get G-Sync Status")] void GetGSyncStatus ([out, Description("Get G-Sync Status")] UINT32 Data);
> +    [WmiMethodId(42), Implemented, Description("Set G-Sync Status")] void SetGSyncStatus ([in, Description("Set G-Sync Status")] UINT32 Data);
> +    [WmiMethodId(43), Implemented, Description("Support Smart Fan feature")] void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
> +    [WmiMethodId(44), Implemented, Description("Set Smart Fan Mode")] void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
> +    [WmiMethodId(45), Implemented, Description("Get Smart Fan Mode")] void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
> +    [WmiMethodId(46), Implemented, Description("Get Smart Fan Setting Mode")] void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
> +    [WmiMethodId(47), Implemented, Description("Get Power Charge Mode")] void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
> +    [WmiMethodId(48), Implemented, Description("Get Gaming Product Info")] void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
> +    [WmiMethodId(49), Implemented, Description("Over Drive feature capability")] void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
> +    [WmiMethodId(50), Implemented, Description("Get Over Drive status")] void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
> +    [WmiMethodId(51), Implemented, Description("Set Over Drive status")] void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
> +    [WmiMethodId(52), Implemented, Description("Set Light Control Owner")] void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
> +    [WmiMethodId(53), Implemented, Description("Set DDS Control Owner")] void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
> +    [WmiMethodId(54), Implemented, Description("Get the flag of restore OC value")] void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
> +    [WmiMethodId(55), Implemented, Description("Get Real Thremal Mode")] void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
> +    [WmiMethodId(56), Implemented, Description("Get the OC switch status in BIOS")] void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
> +    [WmiMethodId(59), Implemented, Description("Get hardware info support version")] void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
> +    [WmiMethodId(60), Implemented, Description("Get Cpu core 0 max frequency")] void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
> +    [WmiMethodId(62), Implemented, Description("Check the Adapter type fit for OC")] void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
> +    [WmiMethodId(63), Implemented, Description("Is support IGPU mode")] void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
> +    [WmiMethodId(64), Implemented, Description("Get IGPU Mode Status")] void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
> +    [WmiMethodId(65), Implemented, Description("Set IGPU Mode")] void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
> +    [WmiMethodId(66), Implemented, Description("Notify DGPU Status")] void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
> +    [WmiMethodId(67), Implemented, Description("Is changed Y log")] void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
> +    [WmiMethodId(68), Implemented, Description("Get DGPU Hardwawre ID")] void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of CPU OC parameter list"), guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
> +  class LENOVO_GAMEZONE_CPU_OC_DATA {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("OC tune id.")] uint32 Tuneid;
> +    [WmiDataId(2), read, Description("Default value.")] uint32 DefaultValue;
> +    [WmiDataId(3), read, Description("OC Value.")] uint32 OCValue;
> +    [WmiDataId(4), read, Description("Min Value.")] uint32 MinValue;
> +    [WmiDataId(5), read, Description("Max Value.")] uint32 MaxValue;
> +    [WmiDataId(6), read, Description("Scale Value.")] uint32 ScaleValue;
> +    [WmiDataId(7), read, Description("OC Order id.")] uint32 OCOrderid;
> +    [WmiDataId(8), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
> +    [WmiDataId(9), read, Description("Delay time in ms.")] uint32 Interval;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of GPU OC parameter list"), guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
> +  class LENOVO_GAMEZONE_GPU_OC_DATA {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("P-State ID.")] uint32 PStateID;
> +    [WmiDataId(2), read, Description("CLOCK ID.")] uint32 ClockID;
> +    [WmiDataId(3), read, Description("Default value.")] uint32 defaultvalue;
> +    [WmiDataId(4), read, Description("OC Offset freqency.")] uint32 OCOffsetFreq;
> +    [WmiDataId(5), read, Description("OC Min offset value.")] uint32 OCMinOffset;
> +    [WmiDataId(6), read, Description("OC Max offset value.")] uint32 OCMaxOffset;
> +    [WmiDataId(7), read, Description("OC Offset Scale.")] uint32 OCOffsetScale;
> +    [WmiDataId(8), read, Description("OC Order id.")] uint32 OCOrderid;
> +    [WmiDataId(9), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Fancooling finish event"), guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
> +  class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("Fancooling clean finish event")] uint32 EventId;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan mode change event"), guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
> +  class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("Smart Fan Mode change event")] uint32 mode;
> +    [WmiDataId(2), read, Description("version of FN+Q")] uint32 version;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan setting mode change event"), guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
> +  class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("Smart Fan Setting mode change event")] uint32 mode;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("POWER CHARGE MODE Change EVENT"), guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
> +  class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")] uint32 mode;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Thermal Mode Real Mode change event"), guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
> +  class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("Thermal Mode Real Mode")] uint32 mode;
> +  };
> diff --git a/Documentation/wmi/devices/lenovo-wmi-other-method.rst b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> new file mode 100644
> index 000000000000..b48832726311
> --- /dev/null
> +++ b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> @@ -0,0 +1,108 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +===========================================================
> +Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
> +===========================================================
> +
> +Introduction
> +============
> +Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
> +The primary Other Mode interface provides advanced power tuning features
> +such as Package Power Tracking (PPT). It is paired with multiple data block
> +GUIDs that provide context for the various methods.
> +
> +
> +Other Mode
> +----------
> +
> +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The Other Mode WMI interface uses the firmware_attributes class to expose
> +various WMI attributes 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 devices. Each
> +attribute exposed by the Other Mode interface has corresponding
> +capability data blocks which allow the driver to probe details about the
> +attribute. Each attribute has multiple pages, one for each of the platform
> +profiles managed by the Gamezone interface. Attributes are exposed in sysfs
> +under the following path:
> +
> +::
> +/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
> +
> +LENOVO_CAPABILITY_DATA_01
> +-------------------------
> +
> +WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The LENOVO_CAPABILITY_DATA_01 interface provides information on various
> +power limits of integrated CPU and GPU components.
> +
> +Each attribute has the following properties:
> + - current_value
> + - default_value
> + - display_name
> + - max_value
> + - min_value
> + - scalar_increment
> + - type
> +
> +The following attributes are implemented:
> + - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
> + - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
> + - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
> +
> +
> +WMI interface description
> +=========================
> +
> +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> +
> +::
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_OTHER_METHOD class"), guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
> +  class LENOVO_OTHER_METHOD {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiMethodId(17), Implemented, Description("Get Feature Value ")] void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
> +    [WmiMethodId(18), Implemented, Description("Set Feature Value ")] void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
> +    [WmiMethodId(19), Implemented, Description("Get Data By Command ")] void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
> +    [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")] void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 00"), guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
> +  class LENOVO_CAPABILITY_DATA_00 {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
> +    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
> +    [WmiDataId(3), read, Description("Capability Default Value.")] uint32 DefaultValue;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 01"), guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
> +  class LENOVO_CAPABILITY_DATA_01 {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
> +    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
> +    [WmiDataId(3), read, Description("Default Value.")] uint32 DefaultValue;
> +    [WmiDataId(4), read, Description("Step.")] uint32 Step;
> +    [WmiDataId(5), read, Description("Minimum Value.")] uint32 MinValue;
> +    [WmiDataId(6), read, Description("Maximum Value.")] uint32 MaxValue;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 02"), guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
> +  class LENOVO_CAPABILITY_DATA_02 {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
> +    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
> +    [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize;
> +    [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[];
> +  };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1afd30d00aec..675f4b26426d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13158,6 +13158,13 @@ S:	Maintained
>   W:	http://legousb.sourceforge.net/
>   F:	drivers/usb/misc/legousbtower.c
>   
> +LENOVO WMI DRIVERS
> +M:	Derek J. Clark <derekjohn.clark@gmail.com>
> +L:	platform-driver-x86@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> +F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +
>   LENOVO WMI HOTKEY UTILITIES DRIVER
>   M:	Jackie Dong <xy-jackie@139.com>
>   L:	platform-driver-x86@vger.kernel.org


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

* Re: [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers
  2025-03-17 14:43 ` [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers Derek J. Clark
@ 2025-03-18  4:27   ` Mario Limonciello
  2025-03-19  2:50     ` Derek J. Clark
  2025-03-26 19:45   ` Matthew Schwartz
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 49+ messages in thread
From: Mario Limonciello @ 2025-03-18  4:27 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang,
	Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel

On 3/17/25 09:43, Derek J. Clark wrote:
> Adds documentation for all new lenovo-wmi drivers.

You seem to have accidentally lost the commit message for this patch 
from earlier versions and got the exact same commit message as the first 
patch.

With that fixed the rest of the patch looks fine.  You can add for next 
version.

Reviewed-by: Mario Limonciello <mario.limonciello@amd.com>

> 
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
>   - Changed namespace to LENOVO_WMI_HELPERS from LENOVO_WMI.
>   - Changed filenames to lenovo-wmi-helpers from lenovo-wmi.
>   - Removed structs and functions implemented by other drivers.
> ---
>   MAINTAINERS                               |  2 +
>   drivers/platform/x86/Kconfig              |  4 ++
>   drivers/platform/x86/Makefile             |  1 +
>   drivers/platform/x86/lenovo-wmi-helpers.c | 64 +++++++++++++++++++++++
>   drivers/platform/x86/lenovo-wmi-helpers.h | 24 +++++++++
>   5 files changed, 95 insertions(+)
>   create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 675f4b26426d..3a370a18b806 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>   S:	Maintained
>   F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>   F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +F:	drivers/platform/x86/lenovo-wmi-helpers.c
> +F:	drivers/platform/x86/lenovo-wmi-helpers.h
>   
>   LENOVO WMI HOTKEY UTILITIES DRIVER
>   M:	Jackie Dong <xy-jackie@139.com>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 43407e76476b..bece1ba61417 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,10 @@ config IBM_RTL
>   	 state = 0 (BIOS SMIs on)
>   	 state = 1 (BIOS SMIs off)
>   
> +config LENOVO_WMI_HELPERS
> +	tristate
> +	depends on ACPI_WMI
> +
>   config IDEAPAD_LAPTOP
>   	tristate "Lenovo IdeaPad Laptop Extras"
>   	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 650dfbebb6c8..5a9f4e94f78b 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>   
>   # Intel
>   obj-y				+= intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x86/lenovo-wmi-helpers.c
> new file mode 100644
> index 000000000000..36d553502223
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-helpers.c
> @@ -0,0 +1,64 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Legion WMI helpers 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 Method" 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 Method" 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 drivers
> + * uses a common procedure to get data fro the WMI interface, enumerated here.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#include <linux/wmi.h>
> +#include "lenovo-wmi-helpers.h"
> +
> +/*
> + * lwmi_dev_evaluate_method() - Helper function to call wmidev_evaluate_method
> + * for Lenovo WMI device method calls that return an ACPI integer.
> + * @wdev: Pointer to the WMI device to be called.
> + * @instance: Instance of the called method.
> + * @method_id: WMI Method ID for the method to be called.
> + * @buf: Buffer of all arguments for the given method_id.
> + * @size: Length of the buffer.
> + * @retval: Pointer for the return value to be assigned.
> + *
> + * Returns: 0, or an error.
> + */
> +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> +			     u32 method_id, unsigned char *buf, size_t size,
> +			     u32 *retval)
> +{
> +	struct acpi_buffer input = { size, buf };
> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> +	union acpi_object *ret_obj __free(kfree) = NULL;
> +	acpi_status status;
> +
> +	status = wmidev_evaluate_method(wdev, instance, method_id, &input,
> +					&output);
> +
> +	if (ACPI_FAILURE(status))
> +		return -EIO;
> +
> +	if (retval) {
> +		ret_obj = output.pointer;
> +		if (!ret_obj)
> +			return -ENODATA;
> +
> +		if (ret_obj->type != ACPI_TYPE_INTEGER)
> +			return -ENXIO;
> +
> +		*retval = (u32)ret_obj->integer.value;
> +	}
> +	return 0;
> +};
> +EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_method, "LENOVO_WMI_HELPERS");
> +
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.h b/drivers/platform/x86/lenovo-wmi-helpers.h
> new file mode 100644
> index 000000000000..7e0d7870790e
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-helpers.h
> @@ -0,0 +1,24 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +
> +#ifndef _LENOVO_WMI_HELPERS_H_
> +#define _LENOVO_WMI_HELPERS_H_
> +
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +struct wmi_method_args_32 {
> +	u32 arg0;
> +	u32 arg1;
> +};
> +
> +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> +			     u32 method_id, unsigned char *buf, size_t size,
> +			     u32 *retval);
> +
> +#endif /* !_LENOVO_WMI_HELPERS_H_ */


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

* Re: [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver
  2025-03-17 14:43 ` [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver Derek J. Clark
@ 2025-03-18  4:30   ` Mario Limonciello
  2025-03-26 19:47   ` Matthew Schwartz
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 49+ messages in thread
From: Mario Limonciello @ 2025-03-18  4:30 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang,
	Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel



On 3/17/25 09:43, Derek J. Clark wrote:
> Adds lenovo-wmi-events driver. The events driver is designed as a
> general entrypoint for all Lenovo WMI Events. It acts as a notification
> chain head that will process event data and pass it on to registered
> drivers so they can react to the events.
> 
> Currently only the Gamezone interface Thermal Mode Event GUID is
> implemented in this driver. It is documented in the Gamezone
> documentation.
> 
> Suggested-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>

Reviewed-by: Mario Limonciello <mario.limonciello@amd.com>

> ---
> v4:
>   - Remove the Thermal Mode Event GUID from Gamezone and add this driver.
> ---
>   MAINTAINERS                              |   2 +
>   drivers/platform/x86/Kconfig             |   4 +
>   drivers/platform/x86/Makefile            |   1 +
>   drivers/platform/x86/lenovo-wmi-events.c | 132 +++++++++++++++++++++++
>   drivers/platform/x86/lenovo-wmi-events.h |  21 ++++
>   5 files changed, 160 insertions(+)
>   create mode 100644 drivers/platform/x86/lenovo-wmi-events.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-events.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3a370a18b806..6dde75922aaf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>   S:	Maintained
>   F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>   F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +F:	drivers/platform/x86/lenovo-wmi-events.c
> +F:	drivers/platform/x86/lenovo-wmi-events.h
>   F:	drivers/platform/x86/lenovo-wmi-helpers.c
>   F:	drivers/platform/x86/lenovo-wmi-helpers.h
>   
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index bece1ba61417..13b8f4ac5dc5 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,10 @@ config IBM_RTL
>   	 state = 0 (BIOS SMIs on)
>   	 state = 1 (BIOS SMIs off)
>   
> +config LENOVO_WMI_EVENTS
> +	tristate
> +	depends on ACPI_WMI
> +
>   config LENOVO_WMI_HELPERS
>   	tristate
>   	depends on ACPI_WMI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 5a9f4e94f78b..fc039839286a 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
>   obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>   
>   # Intel
> diff --git a/drivers/platform/x86/lenovo-wmi-events.c b/drivers/platform/x86/lenovo-wmi-events.c
> new file mode 100644
> index 000000000000..3ea0face3c0d
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-events.c
> @@ -0,0 +1,132 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo WMI Events driver. Lenovo WMI interfaces provide various
> + * hardware triggered events that many drivers need to have propagated.
> + * This driver provides a uniform entrypoint for these events so that
> + * any driver that needs to respond to these events can subscribe to a
> + * notifier chain.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/list.h>
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +#include "lenovo-wmi-events.h"
> +
> +/* Interface GUIDs */
> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> +
> +#define LENOVO_WMI_EVENT_DEVICE(guid, type)                        \
> +	.guid_string = (guid), .context = &(enum lwmi_events_type) \
> +	{                                                          \
> +		type                                               \
> +	}
> +
> +static BLOCKING_NOTIFIER_HEAD(events_chain_head);
> +
> +struct lwmi_events_priv {
> +	struct wmi_device *wdev;
> +	enum lwmi_events_type type;
> +};
> +
> +/* Notifier Methods */
> +int lwmi_events_register_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_register(&events_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
> +
> +int lwmi_events_unregister_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_unregister(&events_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS");
> +
> +static void devm_lwmi_events_unregister_notifier(void *data)
> +{
> +	struct notifier_block *nb = data;
> +
> +	lwmi_events_unregister_notifier(nb);
> +}
> +
> +int devm_lwmi_events_register_notifier(struct device *dev,
> +				       struct notifier_block *nb)
> +{
> +	int ret;
> +
> +	ret = lwmi_events_register_notifier(nb);
> +	if (ret < 0)
> +		return ret;
> +
> +	return devm_add_action_or_reset(dev,
> +				devm_lwmi_events_unregister_notifier, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
> +
> +/* Driver Methods */
> +static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj)
> +{
> +	struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev);
> +	int sel_prof;
> +	int ret;
> +
> +	switch (priv->type) {
> +	case THERMAL_MODE_EVENT:
> +		if (obj->type != ACPI_TYPE_INTEGER)
> +			return;
> +
> +		sel_prof = obj->integer.value;
> +		ret = blocking_notifier_call_chain(&events_chain_head,
> +						   THERMAL_MODE_EVENT, &sel_prof);
> +		if (ret == NOTIFY_BAD)
> +			dev_err(&wdev->dev,
> +				"Failed to send notification to call chain for WMI Events\n");
> +		break;
> +	default:
> +		return;
> +	}
> +}
> +
> +static int lwmi_events_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct lwmi_events_priv *priv;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	if (!context)
> +		return -EINVAL;
> +
> +	priv->wdev = wdev;
> +	priv->type = *(enum lwmi_events_type *)context;
> +
> +	dev_set_drvdata(&wdev->dev, priv);
> +	return 0;
> +}
> +
> +static const struct wmi_device_id lwmi_events_id_table[] = {
> +	{ LENOVO_WMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID,
> +				  THERMAL_MODE_EVENT) },
> +	{}
> +};
> +
> +static struct wmi_driver lwmi_events_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_events",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_events_id_table,
> +	.probe = lwmi_events_probe,
> +	.notify = lwmi_events_notify,
> +	.no_singleton = true,
> +};
> +
> +module_wmi_driver(lwmi_events_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo WMI Events Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi-events.h b/drivers/platform/x86/lenovo-wmi-events.h
> new file mode 100644
> index 000000000000..a3fa934eaa10
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-events.h
> @@ -0,0 +1,21 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +
> +#ifndef _LENOVO_WMI_EVENTS_H_
> +#define _LENOVO_WMI_EVENTS_H_
> +
> +enum lwmi_events_type {
> +	THERMAL_MODE_EVENT = 1,
> +};
> +
> +int lwmi_events_register_notifier(struct notifier_block *nb);
> +int lwmi_events_unregister_notifier(struct notifier_block *nb);
> +int devm_lwmi_events_register_notifier(struct device *dev,
> +				       struct notifier_block *nb);
> +
> +#endif /* !_LENOVO_WMI_EVENTS_H_ */


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

* Re: [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation
  2025-03-17 14:43 ` [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation Derek J. Clark
  2025-03-18  4:24   ` Mario Limonciello
@ 2025-03-19  2:48   ` Derek J. Clark
  2025-03-19  4:41   ` Bagas Sanjaya
  2025-03-27  0:26   ` Armin Wolf
  3 siblings, 0 replies; 49+ messages in thread
From: Derek J. Clark @ 2025-03-19  2:48 UTC (permalink / raw)
  To: Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, linux-kernel



On March 17, 2025 4:43:21 AM HST, "Derek J. Clark" <derekjohn.clark@gmail.com> wrote:
>Adds documentation for new lenovo-wmi drivers.
>
>Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>---
>v4:
> - Fixed MOF formatting issues.
> - Fixed spelling mistakes.
> - Updated description of balanced-performance profile for Gamezone.
> - Updated description of thermal mode event GUID for Gamezone.
>v3:
>- Split documentation into multiple files, one for each parent
>  driver for the Gamezone and Other Mode WMI interfaces.
>- Add MOF data for all parent and child interfaces.
>- Remove lenovo-wmi-camera.c driver from v2 documentation.
>v2:
>- Update description of Custom Profile to include the need to manually
>  set it.
>- Remove all references to Legion hardware.
>- Add section for lenovo-wmi-camera.c driver as it follows the same
>  naming convention.
>---
> .../wmi/devices/lenovo-wmi-gamezone.rst       | 203 ++++++++++++++++++
> .../wmi/devices/lenovo-wmi-other-method.rst   | 108 ++++++++++
> MAINTAINERS                                   |   7 +
> 3 files changed, 318 insertions(+)
> create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst
>
>diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>new file mode 100644
>index 000000000000..bde63dde285d
>--- /dev/null
>+++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>@@ -0,0 +1,203 @@
>+.. SPDX-License-Identifier: GPL-2.0-or-later
>+==========================================================
>+Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
>+==========================================================
>+
>+Introduction
>+============
>+The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
>+The priamry "Gamezone" GUID provides advanced features such as fan
>+profiles and overclocking. It is paired with multiple event GUIDs
>+and data block GUIDs that provide context for the various methods.
>+
>+Gamezone Data
>+-------------
>+
>+WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>+
>+The Gamezone Data WMI interface provides platform-profile and fan curve
>+settings for devices that fall under the "Gaming Series" of Lenovo devices.
>+It uses a notifier chain to inform other Lenovo WMI interface drivers of the
>+current platform profile when it changes.
>+
>+The following platform profiles are supported:
>+ - low-power
>+ - balanced
>+ - balanced-performance*
>+ - performance
>+ - custom
>+
>+Balanced-Performance
>+~~~~~~~~~~~~~~~~~~~~
>+Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
>+enabled in their BIOS. For these devices, the performance platform profile
>+will correspond to the BIOS Extreme Mode, while the balanced-performance
>+platform profile will correspond to the BIOS Performance mode. For legacy
>+devices, the performance platform profile will correspond with the BIOS
>+Performance mode. For some newer devices the "Extreme Mode" profile is
>+incomplete in the BIOS and setting it will cause undefined behavior. A
>+BIOS bug quirk table is provided to ensure these devices cannot set
>+"Extreme Mode" from the driver.
>+
>+Custom Profile
>+~~~~~~~~~~~~~~
>+The custom profile represents a hardware mode on Lenovo devices that enables
>+user modifications to Package Power Tracking (PPT) and fan curve settings.
>+When an attribute exposed by the Other Mode WMI interface is to be modified,
>+the Gamezone driver must first be switched to the "custom" profile manually,
>+or the setting will have no effect. If another profile is set from the list
>+of supported profiles, the BIOS will override any user PPT settings when
>+switching to that profile.
>+
>+Gamezone Thermal Mode Event
>+---------------------------
>+
>+WMI GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
>+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>+
>+The Gamezone Thermal Mode Event interface notifies the system when the platform
>+profile has changed, either through the hardware event (Fn+Q for laptops or
>+Legion + Y for Go Series), or through the Gamezone WMI interface. This event is
>+implemented in the Lenovo WMI Events driver (lenovo-wmi-events).
>+
>+
>+WMI interface description
>+=========================
>+
>+The WMI interface description can be decoded from the embedded binary MOF (bmof)
>+data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
>+
>+::
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_GAMEZONE_DATA class"), guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
>+  class LENOVO_GAMEZONE_DATA {
>+  	[key, read] string InstanceName;
>+  	[read] boolean Active;

When applying as a backport with git am I found I missed two spaces before the above lines. Will fix for next version.

- Derek

>+    [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")] void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
>+    [WmiMethodId(11), Implemented, Description("Get AslCode Version")] void GetVersion ([out, Description("AslCode version")] UINT32 Data);
>+    [WmiMethodId(12), Implemented, Description("Fan cooling capability")] void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
>+    [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")] void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
>+    [WmiMethodId(14), Implemented, Description("cpu oc capability")] void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
>+    [WmiMethodId(15), Implemented, Description("bios has overclock capability")] void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
>+    [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")] void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
>+    [WmiMethodId(18), Implemented, Description("Get CPU temperature")] void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
>+    [WmiMethodId(19), Implemented, Description("Get GPU temperature")] void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
>+    [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")] void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
>+    [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")] void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
>+    [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")] void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
>+    [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")] void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
>+    [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")] void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
>+    [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")] void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
>+    [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")] void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
>+    [WmiMethodId(30), Implemented, Description("Get Keyboard feature list")] void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
>+    [WmiMethodId(31), Implemented, Description("Get Memory OC Information")] void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
>+    [WmiMethodId(32), Implemented, Description("Water Cooling feature capability")] void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
>+    [WmiMethodId(33), Implemented, Description("Set Water Cooling status")] void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
>+    [WmiMethodId(34), Implemented, Description("Get Water Cooling status")] void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
>+    [WmiMethodId(35), Implemented, Description("Lighting feature capability")] void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
>+    [WmiMethodId(36), Implemented, Description("Set keyboard light off or on to max")] void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
>+    [WmiMethodId(37), Implemented, Description("Get keyboard light on/off status")] void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
>+    [WmiMethodId(38), Implemented, Description("Get Macrokey scan code")] void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
>+    [WmiMethodId(39), Implemented, Description("Get Macrokey count")] void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
>+    [WmiMethodId(40), Implemented, Description("Support G-Sync feature")] void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
>+    [WmiMethodId(41), Implemented, Description("Get G-Sync Status")] void GetGSyncStatus ([out, Description("Get G-Sync Status")] UINT32 Data);
>+    [WmiMethodId(42), Implemented, Description("Set G-Sync Status")] void SetGSyncStatus ([in, Description("Set G-Sync Status")] UINT32 Data);
>+    [WmiMethodId(43), Implemented, Description("Support Smart Fan feature")] void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
>+    [WmiMethodId(44), Implemented, Description("Set Smart Fan Mode")] void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
>+    [WmiMethodId(45), Implemented, Description("Get Smart Fan Mode")] void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
>+    [WmiMethodId(46), Implemented, Description("Get Smart Fan Setting Mode")] void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
>+    [WmiMethodId(47), Implemented, Description("Get Power Charge Mode")] void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
>+    [WmiMethodId(48), Implemented, Description("Get Gaming Product Info")] void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
>+    [WmiMethodId(49), Implemented, Description("Over Drive feature capability")] void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
>+    [WmiMethodId(50), Implemented, Description("Get Over Drive status")] void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
>+    [WmiMethodId(51), Implemented, Description("Set Over Drive status")] void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
>+    [WmiMethodId(52), Implemented, Description("Set Light Control Owner")] void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
>+    [WmiMethodId(53), Implemented, Description("Set DDS Control Owner")] void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
>+    [WmiMethodId(54), Implemented, Description("Get the flag of restore OC value")] void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
>+    [WmiMethodId(55), Implemented, Description("Get Real Thremal Mode")] void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
>+    [WmiMethodId(56), Implemented, Description("Get the OC switch status in BIOS")] void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
>+    [WmiMethodId(59), Implemented, Description("Get hardware info support version")] void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
>+    [WmiMethodId(60), Implemented, Description("Get Cpu core 0 max frequency")] void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
>+    [WmiMethodId(62), Implemented, Description("Check the Adapter type fit for OC")] void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
>+    [WmiMethodId(63), Implemented, Description("Is support IGPU mode")] void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
>+    [WmiMethodId(64), Implemented, Description("Get IGPU Mode Status")] void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
>+    [WmiMethodId(65), Implemented, Description("Set IGPU Mode")] void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
>+    [WmiMethodId(66), Implemented, Description("Notify DGPU Status")] void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
>+    [WmiMethodId(67), Implemented, Description("Is changed Y log")] void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
>+    [WmiMethodId(68), Implemented, Description("Get DGPU Hardwawre ID")] void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
>+  };
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of CPU OC parameter list"), guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
>+  class LENOVO_GAMEZONE_CPU_OC_DATA {
>+    [key, read] string InstanceName;
>+    [read] boolean Active;
>+
>+    [WmiDataId(1), read, Description("OC tune id.")] uint32 Tuneid;
>+    [WmiDataId(2), read, Description("Default value.")] uint32 DefaultValue;
>+    [WmiDataId(3), read, Description("OC Value.")] uint32 OCValue;
>+    [WmiDataId(4), read, Description("Min Value.")] uint32 MinValue;
>+    [WmiDataId(5), read, Description("Max Value.")] uint32 MaxValue;
>+    [WmiDataId(6), read, Description("Scale Value.")] uint32 ScaleValue;
>+    [WmiDataId(7), read, Description("OC Order id.")] uint32 OCOrderid;
>+    [WmiDataId(8), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
>+    [WmiDataId(9), read, Description("Delay time in ms.")] uint32 Interval;
>+  };
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of GPU OC parameter list"), guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
>+  class LENOVO_GAMEZONE_GPU_OC_DATA {
>+    [key, read] string InstanceName;
>+    [read] boolean Active;
>+
>+    [WmiDataId(1), read, Description("P-State ID.")] uint32 PStateID;
>+    [WmiDataId(2), read, Description("CLOCK ID.")] uint32 ClockID;
>+    [WmiDataId(3), read, Description("Default value.")] uint32 defaultvalue;
>+    [WmiDataId(4), read, Description("OC Offset freqency.")] uint32 OCOffsetFreq;
>+    [WmiDataId(5), read, Description("OC Min offset value.")] uint32 OCMinOffset;
>+    [WmiDataId(6), read, Description("OC Max offset value.")] uint32 OCMaxOffset;
>+    [WmiDataId(7), read, Description("OC Offset Scale.")] uint32 OCOffsetScale;
>+    [WmiDataId(8), read, Description("OC Order id.")] uint32 OCOrderid;
>+    [WmiDataId(9), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
>+  };
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Fancooling finish event"), guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
>+  class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
>+    [key, read] string InstanceName;
>+    [read] boolean Active;
>+
>+    [WmiDataId(1), read, Description("Fancooling clean finish event")] uint32 EventId;
>+  };
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan mode change event"), guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
>+  class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
>+    [key, read] string InstanceName;
>+    [read] boolean Active;
>+
>+    [WmiDataId(1), read, Description("Smart Fan Mode change event")] uint32 mode;
>+    [WmiDataId(2), read, Description("version of FN+Q")] uint32 version;
>+  };
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan setting mode change event"), guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
>+  class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
>+    [key, read] string InstanceName;
>+    [read] boolean Active;
>+
>+    [WmiDataId(1), read, Description("Smart Fan Setting mode change event")] uint32 mode;
>+  };
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("POWER CHARGE MODE Change EVENT"), guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
>+  class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
>+    [key, read] string InstanceName;
>+    [read] boolean Active;
>+
>+    [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")] uint32 mode;
>+  };
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Thermal Mode Real Mode change event"), guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
>+  class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
>+    [key, read] string InstanceName;
>+    [read] boolean Active;
>+
>+    [WmiDataId(1), read, Description("Thermal Mode Real Mode")] uint32 mode;
>+  };
>diff --git a/Documentation/wmi/devices/lenovo-wmi-other-method.rst b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
>new file mode 100644
>index 000000000000..b48832726311
>--- /dev/null
>+++ b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
>@@ -0,0 +1,108 @@
>+.. SPDX-License-Identifier: GPL-2.0-or-later
>+===========================================================
>+Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
>+===========================================================
>+
>+Introduction
>+============
>+Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
>+The primary Other Mode interface provides advanced power tuning features
>+such as Package Power Tracking (PPT). It is paired with multiple data block
>+GUIDs that provide context for the various methods.
>+
>+
>+Other Mode
>+----------
>+
>+WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
>+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>+
>+The Other Mode WMI interface uses the firmware_attributes class to expose
>+various WMI attributes 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 devices. Each
>+attribute exposed by the Other Mode interface has corresponding
>+capability data blocks which allow the driver to probe details about the
>+attribute. Each attribute has multiple pages, one for each of the platform
>+profiles managed by the Gamezone interface. Attributes are exposed in sysfs
>+under the following path:
>+
>+::
>+/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
>+
>+LENOVO_CAPABILITY_DATA_01
>+-------------------------
>+
>+WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>+
>+The LENOVO_CAPABILITY_DATA_01 interface provides information on various
>+power limits of integrated CPU and GPU components.
>+
>+Each attribute has the following properties:
>+ - current_value
>+ - default_value
>+ - display_name
>+ - max_value
>+ - min_value
>+ - scalar_increment
>+ - type
>+
>+The following attributes are implemented:
>+ - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
>+ - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
>+ - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
>+
>+
>+WMI interface description
>+=========================
>+
>+The WMI interface description can be decoded from the embedded binary MOF (bmof)
>+data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
>+
>+::
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_OTHER_METHOD class"), guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
>+  class LENOVO_OTHER_METHOD {
>+    [key, read] string InstanceName;
>+    [read] boolean Active;
>+
>+    [WmiMethodId(17), Implemented, Description("Get Feature Value ")] void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
>+    [WmiMethodId(18), Implemented, Description("Set Feature Value ")] void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
>+    [WmiMethodId(19), Implemented, Description("Get Data By Command ")] void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
>+    [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")] void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
>+  };
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 00"), guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
>+  class LENOVO_CAPABILITY_DATA_00 {
>+    [key, read] string InstanceName;
>+    [read] boolean Active;
>+
>+    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
>+    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
>+    [WmiDataId(3), read, Description("Capability Default Value.")] uint32 DefaultValue;
>+  };
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 01"), guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
>+  class LENOVO_CAPABILITY_DATA_01 {
>+    [key, read] string InstanceName;
>+    [read] boolean Active;
>+
>+    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
>+    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
>+    [WmiDataId(3), read, Description("Default Value.")] uint32 DefaultValue;
>+    [WmiDataId(4), read, Description("Step.")] uint32 Step;
>+    [WmiDataId(5), read, Description("Minimum Value.")] uint32 MinValue;
>+    [WmiDataId(6), read, Description("Maximum Value.")] uint32 MaxValue;
>+  };
>+
>+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 02"), guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
>+  class LENOVO_CAPABILITY_DATA_02 {
>+    [key, read] string InstanceName;
>+    [read] boolean Active;
>+
>+    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
>+    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
>+    [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize;
>+    [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[];
>+  };
>diff --git a/MAINTAINERS b/MAINTAINERS
>index 1afd30d00aec..675f4b26426d 100644
>--- a/MAINTAINERS
>+++ b/MAINTAINERS
>@@ -13158,6 +13158,13 @@ S:	Maintained
> W:	http://legousb.sourceforge.net/
> F:	drivers/usb/misc/legousbtower.c
> 
>+LENOVO WMI DRIVERS
>+M:	Derek J. Clark <derekjohn.clark@gmail.com>
>+L:	platform-driver-x86@vger.kernel.org
>+S:	Maintained
>+F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>+F:	Documentation/wmi/devices/lenovo-wmi-other.rst
>+
> LENOVO WMI HOTKEY UTILITIES DRIVER
> M:	Jackie Dong <xy-jackie@139.com>
> L:	platform-driver-x86@vger.kernel.org

- Derek

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

* Re: [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers
  2025-03-18  4:27   ` Mario Limonciello
@ 2025-03-19  2:50     ` Derek J. Clark
  0 siblings, 0 replies; 49+ messages in thread
From: Derek J. Clark @ 2025-03-19  2:50 UTC (permalink / raw)
  To: Mario Limonciello, Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Luke Jones, Xino Ni, Zhixin Zhang,
	Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel



On March 17, 2025 6:27:34 PM HST, Mario Limonciello <superm1@kernel.org> wrote:
>On 3/17/25 09:43, Derek J. Clark wrote:
>> Adds documentation for all new lenovo-wmi drivers.
>
>You seem to have accidentally lost the commit message for this patch from earlier versions and got the exact same commit message as the first patch.
>
>With that fixed the rest of the patch looks fine.  You can add for next version.
>
>Reviewed-by: Mario Limonciello <mario.limonciello@amd.com>
>

Hmm, not sure how that happened but thanks for letting me know. I'll get that fixed for the next one.
- Derek

>> 
>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>> ---
>> v4:
>>   - Changed namespace to LENOVO_WMI_HELPERS from LENOVO_WMI.
>>   - Changed filenames to lenovo-wmi-helpers from lenovo-wmi.
>>   - Removed structs and functions implemented by other drivers.
>> ---
>>   MAINTAINERS                               |  2 +
>>   drivers/platform/x86/Kconfig              |  4 ++
>>   drivers/platform/x86/Makefile             |  1 +
>>   drivers/platform/x86/lenovo-wmi-helpers.c | 64 +++++++++++++++++++++++
>>   drivers/platform/x86/lenovo-wmi-helpers.h | 24 +++++++++
>>   5 files changed, 95 insertions(+)
>>   create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c
>>   create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h
>> 
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 675f4b26426d..3a370a18b806 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>>   S:	Maintained
>>   F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>>   F:	Documentation/wmi/devices/lenovo-wmi-other.rst
>> +F:	drivers/platform/x86/lenovo-wmi-helpers.c
>> +F:	drivers/platform/x86/lenovo-wmi-helpers.h
>>     LENOVO WMI HOTKEY UTILITIES DRIVER
>>   M:	Jackie Dong <xy-jackie@139.com>
>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>> index 43407e76476b..bece1ba61417 100644
>> --- a/drivers/platform/x86/Kconfig
>> +++ b/drivers/platform/x86/Kconfig
>> @@ -459,6 +459,10 @@ config IBM_RTL
>>   	 state = 0 (BIOS SMIs on)
>>   	 state = 1 (BIOS SMIs off)
>>   +config LENOVO_WMI_HELPERS
>> +	tristate
>> +	depends on ACPI_WMI
>> +
>>   config IDEAPAD_LAPTOP
>>   	tristate "Lenovo IdeaPad Laptop Extras"
>>   	depends on ACPI
>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>> index 650dfbebb6c8..5a9f4e94f78b 100644
>> --- a/drivers/platform/x86/Makefile
>> +++ b/drivers/platform/x86/Makefile
>> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>>     # Intel
>>   obj-y				+= intel/
>> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x86/lenovo-wmi-helpers.c
>> new file mode 100644
>> index 000000000000..36d553502223
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-wmi-helpers.c
>> @@ -0,0 +1,64 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Lenovo Legion WMI helpers 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 Method" 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 Method" 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 drivers
>> + * uses a common procedure to get data fro the WMI interface, enumerated here.
>> + *
>> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>> + *
>> + */
>> +
>> +#include <linux/wmi.h>
>> +#include "lenovo-wmi-helpers.h"
>> +
>> +/*
>> + * lwmi_dev_evaluate_method() - Helper function to call wmidev_evaluate_method
>> + * for Lenovo WMI device method calls that return an ACPI integer.
>> + * @wdev: Pointer to the WMI device to be called.
>> + * @instance: Instance of the called method.
>> + * @method_id: WMI Method ID for the method to be called.
>> + * @buf: Buffer of all arguments for the given method_id.
>> + * @size: Length of the buffer.
>> + * @retval: Pointer for the return value to be assigned.
>> + *
>> + * Returns: 0, or an error.
>> + */
>> +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
>> +			     u32 method_id, unsigned char *buf, size_t size,
>> +			     u32 *retval)
>> +{
>> +	struct acpi_buffer input = { size, buf };
>> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>> +	union acpi_object *ret_obj __free(kfree) = NULL;
>> +	acpi_status status;
>> +
>> +	status = wmidev_evaluate_method(wdev, instance, method_id, &input,
>> +					&output);
>> +
>> +	if (ACPI_FAILURE(status))
>> +		return -EIO;
>> +
>> +	if (retval) {
>> +		ret_obj = output.pointer;
>> +		if (!ret_obj)
>> +			return -ENODATA;
>> +
>> +		if (ret_obj->type != ACPI_TYPE_INTEGER)
>> +			return -ENXIO;
>> +
>> +		*retval = (u32)ret_obj->integer.value;
>> +	}
>> +	return 0;
>> +};
>> +EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_method, "LENOVO_WMI_HELPERS");
>> +
>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>> +MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.h b/drivers/platform/x86/lenovo-wmi-helpers.h
>> new file mode 100644
>> index 000000000000..7e0d7870790e
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-wmi-helpers.h
>> @@ -0,0 +1,24 @@
>> +/* SPDX-License-Identifier: GPL-2.0-or-later
>> + *
>> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>> + *
>> + */
>> +#include <linux/notifier.h>
>> +#include <linux/platform_profile.h>
>> +
>> +#ifndef _LENOVO_WMI_HELPERS_H_
>> +#define _LENOVO_WMI_HELPERS_H_
>> +
>> +#include <linux/types.h>
>> +#include <linux/wmi.h>
>> +
>> +struct wmi_method_args_32 {
>> +	u32 arg0;
>> +	u32 arg1;
>> +};
>> +
>> +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
>> +			     u32 method_id, unsigned char *buf, size_t size,
>> +			     u32 *retval);
>> +
>> +#endif /* !_LENOVO_WMI_HELPERS_H_ */
>

- Derek

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

* Re: [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation
  2025-03-17 14:43 ` [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation Derek J. Clark
  2025-03-18  4:24   ` Mario Limonciello
  2025-03-19  2:48   ` Derek J. Clark
@ 2025-03-19  4:41   ` Bagas Sanjaya
  2025-03-19  4:50     ` Derek J. Clark
  2025-03-27  0:26   ` Armin Wolf
  3 siblings, 1 reply; 49+ messages in thread
From: Bagas Sanjaya @ 2025-03-19  4:41 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1828 bytes --]

On Mon, Mar 17, 2025 at 07:43:21AM -0700, Derek J. Clark wrote:
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +==========================================================
> +Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
> +==========================================================
><snipped>...
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +===========================================================
> +Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
> +===========================================================

I get htmldocs warnings due to SPDX line not being separated with title
heading:

Documentation/wmi/devices/lenovo-wmi-gamezone.rst:2: WARNING: Explicit markup ends without a blank line; unexpected unindent. [docutils]
Documentation/wmi/devices/lenovo-wmi-other-method.rst:2: WARNING: Explicit markup ends without a blank line; unexpected unindent. [docutils]

> +The Other Mode WMI interface uses the firmware_attributes class to expose
> +various WMI attributes 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 devices. Each
> +attribute exposed by the Other Mode interface has corresponding
> +capability data blocks which allow the driver to probe details about the
> +attribute. Each attribute has multiple pages, one for each of the platform
> +profiles managed by the Gamezone interface. Attributes are exposed in sysfs
> +under the following path:
> +
> +::
> +/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/

sysfs path above isn't outputted as literal code block as it lacks indentation
in the block text.

Thanks.

-- 
An old man doll... just what I always wanted! - Clara

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

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

* Re: [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation
  2025-03-19  4:41   ` Bagas Sanjaya
@ 2025-03-19  4:50     ` Derek J. Clark
  0 siblings, 0 replies; 49+ messages in thread
From: Derek J. Clark @ 2025-03-19  4:50 UTC (permalink / raw)
  To: Bagas Sanjaya, Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, linux-kernel



On March 18, 2025 6:41:57 PM HST, Bagas Sanjaya <bagasdotme@gmail.com> wrote:
>On Mon, Mar 17, 2025 at 07:43:21AM -0700, Derek J. Clark wrote:
>> +.. SPDX-License-Identifier: GPL-2.0-or-later
>> +==========================================================
>> +Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
>> +==========================================================
>><snipped>...
>> +.. SPDX-License-Identifier: GPL-2.0-or-later
>> +===========================================================
>> +Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
>> +===========================================================
>
>I get htmldocs warnings due to SPDX line not being separated with title
>heading:
>
>Documentation/wmi/devices/lenovo-wmi-gamezone.rst:2: WARNING: Explicit markup ends without a blank line; unexpected unindent. [docutils]
>Documentation/wmi/devices/lenovo-wmi-other-method.rst:2: WARNING: Explicit markup ends without a blank line; unexpected unindent. [docutils]
>

Acked.

>> +The Other Mode WMI interface uses the firmware_attributes class to expose
>> +various WMI attributes 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 devices. Each
>> +attribute exposed by the Other Mode interface has corresponding
>> +capability data blocks which allow the driver to probe details about the
>> +attribute. Each attribute has multiple pages, one for each of the platform
>> +profiles managed by the Gamezone interface. Attributes are exposed in sysfs
>> +under the following path:
>> +
>> +::
>> +/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
>
>sysfs path above isn't outputted as literal code block as it lacks indentation
>in the block text.
>
>Thanks.
>

Acked as well.

Thanks,
Derek

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

* Re: [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers
  2025-03-17 14:43 ` [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers Derek J. Clark
  2025-03-18  4:27   ` Mario Limonciello
@ 2025-03-26 19:45   ` Matthew Schwartz
  2025-03-27  0:40   ` Armin Wolf
  2025-03-27 12:43   ` Ilpo Järvinen
  3 siblings, 0 replies; 49+ messages in thread
From: Matthew Schwartz @ 2025-03-26 19:45 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, linux-kernel

On 3/17/25 7:43 AM, Derek J. Clark wrote:
> Adds documentation for all new lenovo-wmi drivers.
> 
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>

The helpers are working with the other drivers on my Legion Go
and Legion Go S. I left one minor comment for a typo in-line.

Tested-by: Matthew Schwartz <matthew.schwartz@linux.dev>

> ---
> v4:
>  - Changed namespace to LENOVO_WMI_HELPERS from LENOVO_WMI.
>  - Changed filenames to lenovo-wmi-helpers from lenovo-wmi.
>  - Removed structs and functions implemented by other drivers.
> ---
>  MAINTAINERS                               |  2 +
>  drivers/platform/x86/Kconfig              |  4 ++
>  drivers/platform/x86/Makefile             |  1 +
>  drivers/platform/x86/lenovo-wmi-helpers.c | 64 +++++++++++++++++++++++
>  drivers/platform/x86/lenovo-wmi-helpers.h | 24 +++++++++
>  5 files changed, 95 insertions(+)
>  create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c
>  create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 675f4b26426d..3a370a18b806 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>  F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +F:	drivers/platform/x86/lenovo-wmi-helpers.c
> +F:	drivers/platform/x86/lenovo-wmi-helpers.h
>  
>  LENOVO WMI HOTKEY UTILITIES DRIVER
>  M:	Jackie Dong <xy-jackie@139.com>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 43407e76476b..bece1ba61417 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,10 @@ config IBM_RTL
>  	 state = 0 (BIOS SMIs on)
>  	 state = 1 (BIOS SMIs off)
>  
> +config LENOVO_WMI_HELPERS
> +	tristate
> +	depends on ACPI_WMI
> +
>  config IDEAPAD_LAPTOP
>  	tristate "Lenovo IdeaPad Laptop Extras"
>  	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 650dfbebb6c8..5a9f4e94f78b 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>  
>  # Intel
>  obj-y				+= intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x86/lenovo-wmi-helpers.c
> new file mode 100644
> index 000000000000..36d553502223
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-helpers.c
> @@ -0,0 +1,64 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Legion WMI helpers 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 Method" 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 Method" 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 drivers
> + * uses a common procedure to get data fro the WMI interface, enumerated here.
"fro" should be "from" right?

> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#include <linux/wmi.h>
> +#include "lenovo-wmi-helpers.h"
> +
> +/*
> + * lwmi_dev_evaluate_method() - Helper function to call wmidev_evaluate_method
> + * for Lenovo WMI device method calls that return an ACPI integer.
> + * @wdev: Pointer to the WMI device to be called.
> + * @instance: Instance of the called method.
> + * @method_id: WMI Method ID for the method to be called.
> + * @buf: Buffer of all arguments for the given method_id.
> + * @size: Length of the buffer.
> + * @retval: Pointer for the return value to be assigned.
> + *
> + * Returns: 0, or an error.
> + */
> +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> +			     u32 method_id, unsigned char *buf, size_t size,
> +			     u32 *retval)
> +{
> +	struct acpi_buffer input = { size, buf };
> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> +	union acpi_object *ret_obj __free(kfree) = NULL;
> +	acpi_status status;
> +
> +	status = wmidev_evaluate_method(wdev, instance, method_id, &input,
> +					&output);
> +
> +	if (ACPI_FAILURE(status))
> +		return -EIO;
> +
> +	if (retval) {
> +		ret_obj = output.pointer;
> +		if (!ret_obj)
> +			return -ENODATA;
> +
> +		if (ret_obj->type != ACPI_TYPE_INTEGER)
> +			return -ENXIO;
> +
> +		*retval = (u32)ret_obj->integer.value;
> +	}
> +	return 0;
> +};
> +EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_method, "LENOVO_WMI_HELPERS");
> +
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.h b/drivers/platform/x86/lenovo-wmi-helpers.h
> new file mode 100644
> index 000000000000..7e0d7870790e
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-helpers.h
> @@ -0,0 +1,24 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +
> +#ifndef _LENOVO_WMI_HELPERS_H_
> +#define _LENOVO_WMI_HELPERS_H_
> +
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +struct wmi_method_args_32 {
> +	u32 arg0;
> +	u32 arg1;
> +};
> +
> +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> +			     u32 method_id, unsigned char *buf, size_t size,
> +			     u32 *retval);
> +
> +#endif /* !_LENOVO_WMI_HELPERS_H_ */


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

* Re: [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver
  2025-03-17 14:43 ` [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver Derek J. Clark
  2025-03-18  4:30   ` Mario Limonciello
@ 2025-03-26 19:47   ` Matthew Schwartz
  2025-03-27  1:03   ` Armin Wolf
  2025-03-27 12:47   ` Ilpo Järvinen
  3 siblings, 0 replies; 49+ messages in thread
From: Matthew Schwartz @ 2025-03-26 19:47 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, linux-kernel

On 3/17/25 7:43 AM, Derek J. Clark wrote:
> Adds lenovo-wmi-events driver. The events driver is designed as a
> general entrypoint for all Lenovo WMI Events. It acts as a notification
> chain head that will process event data and pass it on to registered
> drivers so they can react to the events.
> 
> Currently only the Gamezone interface Thermal Mode Event GUID is
> implemented in this driver. It is documented in the Gamezone
> documentation.
> 
> Suggested-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>

WMI events are working on my Legion Go and Legion Go S.

Tested-by: Matthew Schwartz <matthew.schwartz@linux.dev>

> ---
> v4:
>  - Remove the Thermal Mode Event GUID from Gamezone and add this driver.
> ---
>  MAINTAINERS                              |   2 +
>  drivers/platform/x86/Kconfig             |   4 +
>  drivers/platform/x86/Makefile            |   1 +
>  drivers/platform/x86/lenovo-wmi-events.c | 132 +++++++++++++++++++++++
>  drivers/platform/x86/lenovo-wmi-events.h |  21 ++++
>  5 files changed, 160 insertions(+)
>  create mode 100644 drivers/platform/x86/lenovo-wmi-events.c
>  create mode 100644 drivers/platform/x86/lenovo-wmi-events.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3a370a18b806..6dde75922aaf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>  F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +F:	drivers/platform/x86/lenovo-wmi-events.c
> +F:	drivers/platform/x86/lenovo-wmi-events.h
>  F:	drivers/platform/x86/lenovo-wmi-helpers.c
>  F:	drivers/platform/x86/lenovo-wmi-helpers.h
>  
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index bece1ba61417..13b8f4ac5dc5 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,10 @@ config IBM_RTL
>  	 state = 0 (BIOS SMIs on)
>  	 state = 1 (BIOS SMIs off)
>  
> +config LENOVO_WMI_EVENTS
> +	tristate
> +	depends on ACPI_WMI
> +
>  config LENOVO_WMI_HELPERS
>  	tristate
>  	depends on ACPI_WMI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 5a9f4e94f78b..fc039839286a 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
>  obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>  
>  # Intel
> diff --git a/drivers/platform/x86/lenovo-wmi-events.c b/drivers/platform/x86/lenovo-wmi-events.c
> new file mode 100644
> index 000000000000..3ea0face3c0d
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-events.c
> @@ -0,0 +1,132 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo WMI Events driver. Lenovo WMI interfaces provide various
> + * hardware triggered events that many drivers need to have propagated.
> + * This driver provides a uniform entrypoint for these events so that
> + * any driver that needs to respond to these events can subscribe to a
> + * notifier chain.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/list.h>
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +#include "lenovo-wmi-events.h"
> +
> +/* Interface GUIDs */
> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> +
> +#define LENOVO_WMI_EVENT_DEVICE(guid, type)                        \
> +	.guid_string = (guid), .context = &(enum lwmi_events_type) \
> +	{                                                          \
> +		type                                               \
> +	}
> +
> +static BLOCKING_NOTIFIER_HEAD(events_chain_head);
> +
> +struct lwmi_events_priv {
> +	struct wmi_device *wdev;
> +	enum lwmi_events_type type;
> +};
> +
> +/* Notifier Methods */
> +int lwmi_events_register_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_register(&events_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
> +
> +int lwmi_events_unregister_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_unregister(&events_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS");
> +
> +static void devm_lwmi_events_unregister_notifier(void *data)
> +{
> +	struct notifier_block *nb = data;
> +
> +	lwmi_events_unregister_notifier(nb);
> +}
> +
> +int devm_lwmi_events_register_notifier(struct device *dev,
> +				       struct notifier_block *nb)
> +{
> +	int ret;
> +
> +	ret = lwmi_events_register_notifier(nb);
> +	if (ret < 0)
> +		return ret;
> +
> +	return devm_add_action_or_reset(dev,
> +				devm_lwmi_events_unregister_notifier, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
> +
> +/* Driver Methods */
> +static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj)
> +{
> +	struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev);
> +	int sel_prof;
> +	int ret;
> +
> +	switch (priv->type) {
> +	case THERMAL_MODE_EVENT:
> +		if (obj->type != ACPI_TYPE_INTEGER)
> +			return;
> +
> +		sel_prof = obj->integer.value;
> +		ret = blocking_notifier_call_chain(&events_chain_head,
> +						   THERMAL_MODE_EVENT, &sel_prof);
> +		if (ret == NOTIFY_BAD)
> +			dev_err(&wdev->dev,
> +				"Failed to send notification to call chain for WMI Events\n");
> +		break;
> +	default:
> +		return;
> +	}
> +}
> +
> +static int lwmi_events_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct lwmi_events_priv *priv;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	if (!context)
> +		return -EINVAL;
> +
> +	priv->wdev = wdev;
> +	priv->type = *(enum lwmi_events_type *)context;
> +
> +	dev_set_drvdata(&wdev->dev, priv);
> +	return 0;
> +}
> +
> +static const struct wmi_device_id lwmi_events_id_table[] = {
> +	{ LENOVO_WMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID,
> +				  THERMAL_MODE_EVENT) },
> +	{}
> +};
> +
> +static struct wmi_driver lwmi_events_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_events",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_events_id_table,
> +	.probe = lwmi_events_probe,
> +	.notify = lwmi_events_notify,
> +	.no_singleton = true,
> +};
> +
> +module_wmi_driver(lwmi_events_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo WMI Events Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi-events.h b/drivers/platform/x86/lenovo-wmi-events.h
> new file mode 100644
> index 000000000000..a3fa934eaa10
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-events.h
> @@ -0,0 +1,21 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +
> +#ifndef _LENOVO_WMI_EVENTS_H_
> +#define _LENOVO_WMI_EVENTS_H_
> +
> +enum lwmi_events_type {
> +	THERMAL_MODE_EVENT = 1,
> +};
> +
> +int lwmi_events_register_notifier(struct notifier_block *nb);
> +int lwmi_events_unregister_notifier(struct notifier_block *nb);
> +int devm_lwmi_events_register_notifier(struct device *dev,
> +				       struct notifier_block *nb);
> +
> +#endif /* !_LENOVO_WMI_EVENTS_H_ */

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

* Re: [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver
  2025-03-17 14:43 ` [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver Derek J. Clark
@ 2025-03-26 19:47   ` Matthew Schwartz
  2025-03-27  1:29   ` Armin Wolf
  2025-03-27 12:56   ` Ilpo Järvinen
  2 siblings, 0 replies; 49+ messages in thread
From: Matthew Schwartz @ 2025-03-26 19:47 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, linux-kernel

On 3/17/25 7:43 AM, Derek J. Clark wrote:
> Adds lenovo-wmi-capdata01 driver which provides the
> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode"
> enabled hardware. Provides an interface for querying if a given
> attribute is supported by the hardware, as well as its default_value,
> max_value, min_value, and step increment.
> 
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>

All exposed attributes are passed to "Other Mode" on my Legion Go and Legion Go S.

Tested-by: Matthew Schwartz <matthew.schwartz@linux.dev>

> ---
> v4:
>  - Make driver data a private struct, remove references from Other Mode
>    driver.
>  - Don't cache data at device initialization. Instead, on component bind,
>    cache the data on a member variable of the Other Mode driver data
>    passed as a void pointer.
>  - Add header file for capdata01 structs.
>  - Add new struct to pass capdata01 array data and array length to Other
>    Mode.
> v3:
> - Add as component to lenovo-wmi-other driver.
> v2:
> - Use devm_kmalloc to ensure driver can be instanced, remove global
>   reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
> - Use list to get the lenovo_wmi_cd01_priv instance in
>   lenovo_wmi_capdata01_get as none of the data provided by the macros
>   that will use it can pass a member of the struct for use in
>   container_of.
> ---
>  MAINTAINERS                                 |   2 +
>  drivers/platform/x86/Kconfig                |   4 +
>  drivers/platform/x86/Makefile               |   1 +
>  drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++
>  drivers/platform/x86/lenovo-wmi-capdata01.h |  29 +++++
>  5 files changed, 172 insertions(+)
>  create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>  create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6dde75922aaf..56ead241a053 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>  F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +F:	drivers/platform/x86/lenovo-wmi-capdata01.c
> +F:	drivers/platform/x86/lenovo-wmi-capdata01.h
>  F:	drivers/platform/x86/lenovo-wmi-events.c
>  F:	drivers/platform/x86/lenovo-wmi-events.h
>  F:	drivers/platform/x86/lenovo-wmi-helpers.c
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 13b8f4ac5dc5..64663667f0cb 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
>  	tristate
>  	depends on ACPI_WMI
>  
> +config LENOVO_WMI_DATA01
> +	tristate
> +	depends on ACPI_WMI
> +
>  config IDEAPAD_LAPTOP
>  	tristate "Lenovo IdeaPad Laptop Extras"
>  	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index fc039839286a..7a35c77221b7 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
>  obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
>  obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>  
> diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
> new file mode 100644
> index 000000000000..b6876611ffd9
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> @@ -0,0 +1,136 @@
> +// 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 Mode" WMI interface,
> + * including if it is supported by the hardware, the default_value, max_value,
> + * min_value, and step increment.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/component.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/gfp_types.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +#include "lenovo-wmi-capdata01.h"
> +
> +/* Interface GUIDs */
> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +
> +struct lwmi_cd01_priv {
> +	struct wmi_device *wdev;
> +};
> +
> +/*
> + * lenovo_cd01_component_bind() - On master bind, caches all capability data on
> + * the master device.
> + * @cd01_dev: Pointer to the capability data 01 parent device.
> + * @om_dev: Pointer to the other mode parent device.
> + * @data: capdata01_list object pointer to return the capability data with.
> + *
> + * Returns: 0, or an error.
> + */
> +static int lenovo_cd01_component_bind(struct device *cd01_dev,
> +				      struct device *om_dev, void *data)
> +{
> +	struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
> +	int count, idx;
> +
> +	if (!priv)
> +		return -ENODEV;
> +
> +	count = wmidev_instance_count(priv->wdev);
> +
> +	if (count == 0)
> +		return -EINVAL;
> +
> +	((struct cd01_list *)data)->count = count;
> +	((struct cd01_list *)data)->data = devm_kmalloc_array(om_dev, count,
> +							      sizeof(struct capdata01 *),
> +							      GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	for (idx = 0; idx < count; idx++) {
> +		union acpi_object *ret_obj __free(kfree) = NULL;
> +
> +		ret_obj = wmidev_block_query(priv->wdev, idx);
> +		if (!ret_obj) {
> +			((struct cd01_list *)data)->data[idx] = NULL;
> +			continue;
> +		}
> +		if (ret_obj->type != ACPI_TYPE_BUFFER) {
> +			((struct cd01_list *)data)->data[idx] = NULL;
> +			continue;
> +		}
> +
> +		if (ret_obj->buffer.length != sizeof(struct capdata01)) {
> +			((struct cd01_list *)data)->data[idx] = NULL;
> +			continue;
> +		}
> +
> +		((struct cd01_list *)data)->data[idx] =
> +			devm_kmemdup(om_dev, ret_obj->buffer.pointer,
> +				     ret_obj->buffer.length, GFP_KERNEL);
> +	}
> +	return 0;
> +}
> +
> +static const struct component_ops lenovo_cd01_component_ops = {
> +	.bind = lenovo_cd01_component_bind,
> +};
> +
> +static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
> +
> +{
> +	struct lwmi_cd01_priv *priv;
> +	int ret;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->wdev = wdev;
> +	dev_set_drvdata(&wdev->dev, priv);
> +
> +	ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
> +
> +	return ret;
> +}
> +
> +static void lwmi_cd01_remove(struct wmi_device *wdev)
> +{
> +	component_del(&wdev->dev, &lenovo_cd01_component_ops);
> +}
> +
> +static const struct wmi_device_id lwmi_cd01_id_table[] = {
> +	{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> +	{}
> +};
> +
> +static struct wmi_driver lwmi_cd01_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_cd01",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_cd01_id_table,
> +	.probe = lwmi_cd01_probe,
> +	.remove = lwmi_cd01_remove,
> +	.no_singleton = true,
> +};
> +
> +int lwmi_cd01_match(struct device *dev, void *data)
> +{
> +	return dev->driver == &lwmi_cd01_driver.driver;
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
> +
> +module_wmi_driver(lwmi_cd01_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lwmi_cd01_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-wmi-capdata01.h b/drivers/platform/x86/lenovo-wmi-capdata01.h
> new file mode 100644
> index 000000000000..c7067a8d0398
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#ifndef _LENOVO_WMI_CAPDATA01_H_
> +#define _LENOVO_WMI_CAPDATA01_H_
> +
> +#include <linux/device.h>
> +#include <linux/types.h>
> +
> +struct capdata01 {
> +	u32 id;
> +	u32 supported;
> +	u32 default_value;
> +	u32 step;
> +	u32 min_value;
> +	u32 max_value;
> +};
> +
> +struct cd01_list {
> +	struct capdata01 **data;
> +	int count;
> +};
> +
> +int lwmi_cd01_match(struct device *dev, void *data);
> +
> +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */


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

* Re: [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode WMI Driver
  2025-03-17 14:43 ` [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode " Derek J. Clark
@ 2025-03-26 19:48   ` Matthew Schwartz
  2025-03-27  3:28   ` Armin Wolf
  2025-03-27 13:49   ` Ilpo Järvinen
  2 siblings, 0 replies; 49+ messages in thread
From: Matthew Schwartz @ 2025-03-26 19:48 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, linux-kernel

On 3/17/25 7:43 AM, Derek J. Clark wrote:
> Adds lenovo-wmi-other driver which provides the Lenovo "Other Mode" WMI
> interface that comes on some Lenovo "Gaming Series" hardware. Provides a
> firmware-attributes class which enables the use of tunable knobs for SPL,
> SPPT, and FPPT.
> 
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>

Tuning all of the attributes works on my Legion Go and Legion Go S.

Tested-by: Matthew Schwartz <matthew.schwartz@linux.dev>

> ---
> v4:
> - Treat Other Mode as a notifier chain head, use the notifier chain to
>   get the current mode from Gamezone.
> - Add header file for Other Mode specific structs and finctions.
> - Use component master bind to cache the capdata01 array locally.
> - Drop all reference to external driver private data structs.
> - Various fixes from review.
> v3:
> - Add notifier block and store result for getting the Gamezone interface
>   profile changes.
> - Add driver as master component of capdata01 driver.
> - Use FIELD_PREP where appropriate.
> - Move macros and associated functions out of lemovo-wmi.h that are only
>   used by this driver.
> v2:
> - Use devm_kmalloc to ensure driver can be instanced, remove global
>   reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> - Use list to get the lenovo_wmi_om_priv instance in some macro
>   called functions as the data provided by the macros that use it
>   doesn't pass a member of the struct for use in container_of.
> - Do not rely on GameZone interface to grab the current fan mode.
> ---
>  MAINTAINERS                             |   2 +
>  drivers/platform/x86/Kconfig            |  15 +
>  drivers/platform/x86/Makefile           |   1 +
>  drivers/platform/x86/lenovo-wmi-other.c | 626 ++++++++++++++++++++++++
>  drivers/platform/x86/lenovo-wmi-other.h |  19 +
>  5 files changed, 663 insertions(+)
>  create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>  create mode 100644 drivers/platform/x86/lenovo-wmi-other.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 56ead241a053..87daee6075ad 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13170,6 +13170,8 @@ F:	drivers/platform/x86/lenovo-wmi-events.c
>  F:	drivers/platform/x86/lenovo-wmi-events.h
>  F:	drivers/platform/x86/lenovo-wmi-helpers.c
>  F:	drivers/platform/x86/lenovo-wmi-helpers.h
> +F:	drivers/platform/x86/lenovo-wmi-other.c
> +F:	drivers/platform/x86/lenovo-wmi-other.h
>  
>  LENOVO WMI HOTKEY UTILITIES DRIVER
>  M:	Jackie Dong <xy-jackie@139.com>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 64663667f0cb..fc47604e37f7 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -471,6 +471,21 @@ config LENOVO_WMI_DATA01
>  	tristate
>  	depends on ACPI_WMI
>  
> +config LENOVO_WMI_TUNING
> +	tristate "Lenovo Other Mode WMI Driver"
> +	depends on ACPI_WMI
> +	select FW_ATTR_CLASS
> +	select LENOVO_WMI_DATA01
> +	select LENOVO_WMI_EVENTS
> +	select LENOVO_WMI_HELPERS
> +	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-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 7a35c77221b7..c6ce3c8594b1 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -72,6 +72,7 @@ obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
>  obj-$(CONFIG_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
>  obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
>  obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
> +obj-$(CONFIG_LENOVO_WMI_TUNING)	+= lenovo-wmi-other.o
>  
>  # Intel
>  obj-y				+= intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> new file mode 100644
> index 000000000000..b517e45338e0
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.c
> @@ -0,0 +1,626 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
> + * class to expose the various WMI functions provided by the "Other Mode" 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 laptop
> + * devices. Each attribute exposed by the "Other Mode"" 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.
> + *
> + * 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/bitfield.h>
> +#include <linux/cleanup.h>
> +#include <linux/component.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/gfp_types.h>
> +#include <linux/idr.h>
> +#include <linux/kobject.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +#include "lenovo-wmi-capdata01.h"
> +#include "lenovo-wmi-events.h"
> +#include "lenovo-wmi-gamezone.h"
> +#include "lenovo-wmi-helpers.h"
> +#include "lenovo-wmi-other.h"
> +#include "firmware_attributes_class.h"
> +
> +/* Interface GUIDs */
> +#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_FPPT 0x03 /* Long Term Power Limit */
> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> +
> +/* Type IDs*/
> +#define WMI_TYPE_ID_NONE 0x00
> +
> +/* Method IDs */
> +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
> +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
> +
> +/* Attribute ID bitmasks */
> +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
> +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
> +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
> +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
> +
> +static BLOCKING_NOTIFIER_HEAD(om_chain_head);
> +
> +enum attribute_property {
> +	DEFAULT_VAL,
> +	MAX_VAL,
> +	MIN_VAL,
> +	STEP_VAL,
> +	SUPPORTED,
> +};
> +
> +struct lwmi_om_priv {
> +	struct blocking_notifier_head nhead;
> +	struct component_master_ops *ops;
> +	struct cd01_list cd01_list;
> +	struct device *fw_attr_dev;
> +	struct kset *fw_attr_kset;
> +	struct notifier_block nb;
> +	struct wmi_device *wdev;
> +	struct ida ida;
> +	int ida_id;
> +};
> +
> +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> +struct tunable_attr_01 {
> +	u32 type_id;
> +	u32 device_id;
> +	u32 feature_id;
> +	u32 store_value;
> +	struct device *dev;
> +	struct capdata01 *capdata;
> +};
> +
> +/* Tunable Attributes */
> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> +				       .feature_id = WMI_FEATURE_ID_CPU_SPL,
> +				       .type_id = WMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> +					.feature_id = WMI_FEATURE_ID_CPU_SPPT,
> +					.type_id = WMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> +					.feature_id = WMI_FEATURE_ID_CPU_FPPT,
> +					.type_id = WMI_TYPE_ID_NONE };
> +
> +struct capdata01_attr_group {
> +	const struct attribute_group *attr_group;
> +	struct tunable_attr_01 *tunable_attr;
> +};
> +
> +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> +
> +/* Notifier Methods */
> +int lwmi_om_register_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_register(&om_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
> +
> +int lwmi_om_unregister_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_unregister(&om_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
> +
> +static void devm_lwmi_om_unregister_notifier(void *data)
> +{
> +	struct notifier_block *nb = data;
> +
> +	lwmi_om_unregister_notifier(nb);
> +}
> +
> +int devm_lwmi_om_register_notifier(struct device *dev,
> +				   struct notifier_block *nb)
> +{
> +	int ret;
> +
> +	ret = lwmi_om_register_notifier(nb);
> +	if (ret < 0)
> +		return ret;
> +
> +	return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier,
> +					nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
> +
> +static int lwmi_om_notifier_call(enum thermal_mode *mode)
> +{
> +	int ret;
> +
> +	ret = blocking_notifier_call_chain(&om_chain_head, THERMAL_MODE_EVENT,
> +					   mode);
> +
> +	if (ret != NOTIFY_OK)
> +		return -EINVAL;
> +
> +	if (*mode < SMARTFAN_MODE_QUIET || *mode > SMARTFAN_MODE_CUSTOM)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +/* Attribute Methods */
> +/*
> + * int_type_show() - Emit the data type for an integer attribute
> + * @kobj: Pointer to the driver object.
> + * @kobj_attribute: Pointer to the attribute calling this function.
> + * @buf: The buffer to write to.
> + *
> + * Returns: Number of characters written to buf.
> + */
> +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
> +			     char *buf)
> +{
> +	return sysfs_emit(buf, "integer\n");
> +}
> +
> +/*
> + * attr_capdata01_get - Get the data of the specified attribute
> + * from lwmi_om->cd01.
> + * @tunable_attr: The attribute to be populated.
> + *
> + * Returns: Either a pointer to capability data, or NULL.
> + */
> +static struct capdata01 *
> +attr_capdata01_get_data(struct lwmi_om_priv *priv,
> +			struct tunable_attr_01 *tunable_attr,
> +			enum thermal_mode mode)
> +{
> +	u32 attribute_id =
> +		FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> +		FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> +		FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> +		FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +	int idx;
> +
> +	for (idx = 0; idx < priv->cd01_list.count; idx++) {
> +		if (!priv->cd01_list.data[idx])
> +			continue;
> +
> +		if (priv->cd01_list.data[idx]->id != attribute_id)
> +			continue;
> +		return priv->cd01_list.data[idx];
> +	}
> +	return NULL;
> +}
> +
> +/**
> + * attr_capdata01_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.
> + * @tunable_attr: The attribute to be read.
> + * @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 number of characters written to buf, or an error.
> + */
> +static ssize_t attr_capdata01_show(struct kobject *kobj,
> +				   struct kobj_attribute *kattr, char *buf,
> +				   struct tunable_attr_01 *tunable_attr,
> +				   enum attribute_property prop)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> +	struct capdata01 *capdata;
> +	int value;
> +
> +	if (!priv)
> +		return -ENODEV;
> +
> +	capdata = attr_capdata01_get_data(priv, tunable_attr,
> +					  SMARTFAN_MODE_CUSTOM);
> +
> +	if (!capdata)
> +		return -ENODEV;
> +
> +	switch (prop) {
> +	case DEFAULT_VAL:
> +		value = capdata->default_value;
> +		break;
> +	case MAX_VAL:
> +		value = capdata->max_value;
> +		break;
> +	case MIN_VAL:
> +		value = capdata->min_value;
> +		break;
> +	case STEP_VAL:
> +		value = capdata->step;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return sysfs_emit(buf, "%d\n", value);
> +}
> +
> +/* 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.
> + * @tunable_attr: The attribute to be stored.
> + *
> + * 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.
> + */
> +static ssize_t attr_current_value_store(struct kobject *kobj,
> +					struct kobj_attribute *kattr,
> +					const char *buf, size_t count,
> +					struct tunable_attr_01 *tunable_attr)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> +	struct wmi_method_args_32 args;
> +	struct capdata01 *capdata;
> +	enum thermal_mode mode;
> +	u32 attribute_id;
> +	u32 value;
> +	int err;
> +
> +	if (!priv)
> +		return -ENODEV;
> +
> +	err = lwmi_om_notifier_call(&mode);
> +	if (err)
> +		return err;
> +
> +	if (mode != SMARTFAN_MODE_CUSTOM)
> +		return -EINVAL;
> +
> +	capdata = attr_capdata01_get_data(priv, tunable_attr, mode);
> +
> +	if (!capdata)
> +		return -ENODEV;
> +
> +	attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> +		       FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> +		       FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> +		       FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> +	err = kstrtouint(buf, 10, &value);
> +	if (err)
> +		return err;
> +
> +	if (value < capdata->min_value || value > capdata->max_value)
> +		return -EINVAL;
> +
> +	args.arg0 = attribute_id;
> +	args.arg1 = value;
> +
> +	err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_SET,
> +				       (unsigned char *)&args, sizeof(args),
> +				       NULL);
> +
> +	if (err)
> +		return err;
> +
> +	tunable_attr->store_value = value;
> +	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.
> + * @tunable_attr: The 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 number of characters written to buf, or an error.
> + */
> +static ssize_t attr_current_value_show(struct kobject *kobj,
> +				       struct kobj_attribute *kattr, char *buf,
> +				       struct tunable_attr_01 *tunable_attr)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> +	struct wmi_method_args_32 args;
> +	enum thermal_mode mode;
> +	u32 attribute_id;
> +	int retval;
> +	int err;
> +
> +	if (!priv)
> +		return -ENODEV;
> +
> +	err = lwmi_om_notifier_call(&mode);
> +	if (err)
> +		return err;
> +
> +	attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> +		       FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> +		       FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> +		       FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> +	args.arg0 = attribute_id;
> +
> +	err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_GET,
> +				       (unsigned char *)&args, sizeof(args),
> +				       &retval);
> +
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buf, "%d\n", retval);
> +}
> +
> +/* Attribute macros */
> +#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 *kattr, char *buf) \
> +	{                                                                      \
> +		return sysfs_emit(buf, _fmt, _val);                            \
> +	}                                                                      \
> +	static struct kobj_attribute attr_##_attrname##_##_prop =              \
> +		__LL_ATTR_RO(_attrname, _prop)
> +
> +/* Attribute current value read/write */
> +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname)                            \
> +	static ssize_t _attrname##_current_value_store(                        \
> +		struct kobject *kobj, struct kobj_attribute *kattr,            \
> +		const char *buf, size_t count)                                 \
> +	{                                                                      \
> +		return attr_current_value_store(kobj, kattr, buf, count,       \
> +						&_attrname);                   \
> +	}                                                                      \
> +	static ssize_t _attrname##_current_value_show(                         \
> +		struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> +	{                                                                      \
> +		return attr_current_value_show(kobj, kattr, buf, &_attrname);  \
> +	}                                                                      \
> +	static struct kobj_attribute attr_##_attrname##_current_value =        \
> +		__LL_ATTR_RW(_attrname, current_value)
> +
> +/* Attribute property read only */
> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type)                    \
> +	static ssize_t _attrname##_##_prop##_show(                             \
> +		struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> +	{                                                                      \
> +		return attr_capdata01_show(kobj, kattr, buf, &_attrname,       \
> +					   _prop_type);                        \
> +	}                                                                      \
> +	static struct kobj_attribute attr_##_attrname##_##_prop =              \
> +		__LL_ATTR_RO(_attrname, _prop)
> +
> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname)     \
> +	__LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname);                   \
> +	__LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL);  \
> +	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);   \
> +	__LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL);          \
> +	__LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL);          \
> +	__LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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            \
> +	}
> +
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> +			    "Set the CPU sustained power limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> +			    "Set the CPU slow package power tracking limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> +			    "Set the CPU fast package power tracking limit");
> +
> +static struct capdata01_attr_group capdata01_attr_groups[] = {
> +	{ &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> +	{ &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> +	{ &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> +	{},
> +};
> +
> +/*
> + * lwmi_om_fw_attr_add() - Registers all capdata01_attr_groups[] attributes as
> + * firmware_attributes_class members.
> + * @priv: The Other Mode driver data.
> + *
> + * Returns: Either 0, or an error.
> + */
> +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
> +{
> +	int err, i;
> +
> +	ida_init(&priv->ida);
> +	priv->ida_id = ida_alloc(&priv->ida, GFP_KERNEL);
> +	if (priv->ida_id < 0)
> +		return priv->ida_id;
> +
> +	priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
> +					  MKDEV(0, 0), NULL, "%s",
> +					  FW_ATTR_FOLDER);
> +	if (IS_ERR(priv->fw_attr_dev)) {
> +		err = PTR_ERR(priv->fw_attr_dev);
> +		return err;
> +	}
> +
> +	priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> +						 &priv->fw_attr_dev->kobj);
> +	if (!priv->fw_attr_kset) {
> +		err = -ENOMEM;
> +		goto err_destroy_classdev;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> +		err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> +					 capdata01_attr_groups[i].attr_group);
> +		if (err) {
> +			pr_debug("Failed to create sysfs-group for %s: %d\n",
> +				 capdata01_attr_groups[i].attr_group->name,
> +				 err);
> +			goto err_remove_groups;
> +		}
> +		capdata01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
> +	}
> +	return 0;
> +
> +err_remove_groups:
> +	ida_free(&priv->ida, priv->ida_id);
> +	while (i-- >= 0) {
> +		sysfs_remove_group(&priv->fw_attr_kset->kobj,
> +				   capdata01_attr_groups[i].attr_group);
> +	}
> +	kset_unregister(priv->fw_attr_kset);
> +
> +err_destroy_classdev:
> +	device_unregister(priv->fw_attr_dev);
> +	return err;
> +}
> +
> +/*
> + * lwmi_om_fw_attr_remove() - Unregisters all capdata01_attr_groups[] attributes as
> + * firmware_attributes_class members.
> + * @priv: The Other Mode driver data.
> + *
> + */
> +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
> +{
> +	int size = ARRAY_SIZE(capdata01_attr_groups);
> +
> +	while (--size >= 0) {
> +		sysfs_remove_group(&priv->fw_attr_kset->kobj,
> +				   capdata01_attr_groups[size].attr_group);
> +	}
> +	kset_unregister(priv->fw_attr_kset);
> +	device_unregister(priv->fw_attr_dev);
> +}
> +
> +static int lwmi_om_master_bind(struct device *dev)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = component_bind_all(dev, &priv->cd01_list);
> +	if (ret)
> +		return ret;
> +
> +	return lwmi_om_fw_attr_add(priv);
> +}
> +
> +static void lwmi_om_master_unbind(struct device *dev)
> +{
> +	component_unbind_all(dev, NULL);
> +}
> +
> +static const struct component_master_ops lwmi_om_master_ops = {
> +	.bind = lwmi_om_master_bind,
> +	.unbind = lwmi_om_master_unbind,
> +};
> +
> +static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct component_match *master_match = NULL;
> +	struct lwmi_om_priv *priv;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->wdev = wdev;
> +	dev_set_drvdata(&wdev->dev, priv);
> +
> +	component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
> +	if (IS_ERR(master_match))
> +		return PTR_ERR(master_match);
> +
> +	return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
> +					       master_match);
> +}
> +
> +static void lwmi_other_remove(struct wmi_device *wdev)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> +	component_master_del(&wdev->dev, &lwmi_om_master_ops);
> +	lwmi_om_fw_attr_remove(priv);
> +	ida_free(&priv->ida, priv->ida_id);
> +}
> +
> +static const struct wmi_device_id lwmi_other_id_table[] = {
> +	{ LENOVO_OTHER_METHOD_GUID, NULL },
> +	{}
> +};
> +
> +static struct wmi_driver lwmi_other_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_other",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_other_id_table,
> +	.probe = lwmi_other_probe,
> +	.remove = lwmi_other_remove,
> +	.no_singleton = true,
> +};
> +
> +module_wmi_driver(lwmi_other_driver);
> +
> +MODULE_IMPORT_NS("LENOVO_WMI_CD01");
> +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> +MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi-other.h b/drivers/platform/x86/lenovo-wmi-other.h
> new file mode 100644
> index 000000000000..9fba35ef1137
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#ifndef _LENOVO_WMI_OTHER_H_
> +#define _LENOVO_WMI_OTHER_H_
> +
> +#include <linux/device.h>
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +
> +int lwmi_om_register_notifier(struct notifier_block *nb);
> +int lwmi_om_unregister_notifier(struct notifier_block *nb);
> +int devm_lwmi_om_register_notifier(struct device *dev,
> +				   struct notifier_block *nb);
> +
> +#endif /* !_LENOVO_WMI_H_ */


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

* Re: [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone WMI Driver
  2025-03-17 14:43 ` [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone " Derek J. Clark
@ 2025-03-26 19:48   ` Matthew Schwartz
  2025-03-27  3:49   ` Armin Wolf
  2025-03-27 13:56   ` Ilpo Järvinen
  2 siblings, 0 replies; 49+ messages in thread
From: Matthew Schwartz @ 2025-03-26 19:48 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Armin Wolf, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, linux-kernel

On 3/17/25 7:43 AM, Derek J. Clark wrote:
> Adds lenovo-wmi-gamezone driver which provides the Lenovo Gamezone WMI
> interface that comes on Lenovo "Gaming Series" hardware. Provides ACPI
> platform profiles over WMI.
> 
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>

Platform profiles are working on my Legion Go and Legion Go S. 

Tested-by: Matthew Schwartz <matthew.schwartz@linux.dev>

> ---
> v4:
> - Add notifier blocks for the Events and Other Mode drivers.
> - Remove notifier block chain head and all reference to Thermal Mode
>   Event GUID.
> - Add header for Gamezone specific structs and functions.
> - Various fixes from review.
> v3:
> - Use notifier chain to report platform profile changes to any
>   subscribed drivers.
> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
>   chain.
> - Adds support for Extreme Mode profile on supported hardware, as well
>   as a DMI quirk table for some devices that report extreme mode version
>   support but so not have it fully implemented.
> - Update to include recent changes to platform-profile.
> v2:
> - Use devm_kmalloc to ensure driver can be instanced, remove global
>   reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> - Remove GZ_WMI symbol exporting.
> ---
>  MAINTAINERS                                |   2 +
>  drivers/platform/x86/Kconfig               |  13 +
>  drivers/platform/x86/Makefile              |   1 +
>  drivers/platform/x86/lenovo-wmi-gamezone.c | 380 +++++++++++++++++++++
>  drivers/platform/x86/lenovo-wmi-gamezone.h |  18 +
>  5 files changed, 414 insertions(+)
>  create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>  create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 87daee6075ad..0416afd997a0 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13168,6 +13168,8 @@ F:	drivers/platform/x86/lenovo-wmi-capdata01.c
>  F:	drivers/platform/x86/lenovo-wmi-capdata01.h
>  F:	drivers/platform/x86/lenovo-wmi-events.c
>  F:	drivers/platform/x86/lenovo-wmi-events.h
> +F:	drivers/platform/x86/lenovo-wmi-gamezone.c
> +F:	drivers/platform/x86/lenovo-wmi-gamezone.h
>  F:	drivers/platform/x86/lenovo-wmi-helpers.c
>  F:	drivers/platform/x86/lenovo-wmi-helpers.h
>  F:	drivers/platform/x86/lenovo-wmi-other.c
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index fc47604e37f7..ecf3246c8fda 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -467,6 +467,19 @@ config LENOVO_WMI_HELPERS
>  	tristate
>  	depends on ACPI_WMI
>  
> +config LENOVO_WMI_GAMEZONE
> +	tristate "Lenovo GameZone WMI Driver"
> +	depends on ACPI_WMI
> +	select ACPI_PLATFORM_PROFILE
> +	select LENOVO_WMI_EVENTS
> +	select LENOVO_WMI_HELPERS
> +	help
> +	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> +	  platform-profile firmware interface to manage power usage.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called lenovo-wmi-gamezone.
> +
>  config LENOVO_WMI_DATA01
>  	tristate
>  	depends on ACPI_WMI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index c6ce3c8594b1..f3e64926a96b 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -71,6 +71,7 @@ obj-$(CONFIG_YT2_1380)		+= lenovo-yoga-tab2-pro-1380-fastcharger.o
>  obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
>  obj-$(CONFIG_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
>  obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
> +obj-$(CONFIG_LENOVO_WMI_GAMEZONE)	+= lenovo-wmi-gamezone.o
>  obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>  obj-$(CONFIG_LENOVO_WMI_TUNING)	+= lenovo-wmi-other.o
>  
> diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> new file mode 100644
> index 000000000000..9d453a836227
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> @@ -0,0 +1,380 @@
> +// 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/dmi.h>
> +#include <linux/list.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +#include "lenovo-wmi-events.h"
> +#include "lenovo-wmi-gamezone.h"
> +#include "lenovo-wmi-helpers.h"
> +#include "lenovo-wmi-other.h"
> +
> +/* Interface GUIDs */
> +#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 BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> +
> +struct lwmi_event_priv {
> +	enum thermal_mode current_mode;
> +	struct wmi_device *wdev;
> +	bool extreme_supported;
> +	struct device *ppdev; /*platform profile device */
> +	struct notifier_block event_nb;
> +	struct notifier_block mode_nb;
> +};
> +
> +struct quirk_entry {
> +	bool extreme_supported;
> +};
> +
> +static struct quirk_entry quirk_no_extreme_bug = {
> +	.extreme_supported = false,
> +};
> +
> +/* Notifier Methods */
> +/*
> + * lwmi_gz_mode_call() - Call method for lenovo-wmi-other notifier
> + * block call chain. For THERMAL_MODE_EVENT, returns current_mode
> + *
> + * @nb: The notifier_block registered to lenovo-wmi-other
> + * @cmd: The event triggered by lenovo-wmi-other
> + * @data: The data to be returned by the event.
> + *
> + * Returns: notifier_block status.
> + */
> +static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
> +			     void *data)
> +{
> +	struct lwmi_event_priv *priv;
> +
> +	priv = container_of(nb, struct lwmi_event_priv, mode_nb);
> +	if (!priv)
> +		return NOTIFY_BAD;
> +
> +	switch (cmd) {
> +	case THERMAL_MODE_EVENT:
> +		*(enum thermal_mode *)data = priv->current_mode;
> +		break;
> +	default:
> +		return NOTIFY_DONE;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +/*
> + * lwmi_gz_event_call() - Call method for lenovo-wmi-events notifier
> + * block call chain. For THERMAL_MODE_EVENT, sets current_mode and
> + * notifies platform_profile of a change.
> + *
> + * @nb: The notifier_block registered to lenovo-wmi-events
> + * @cmd: The event triggered by lenovo-wmi-events
> + * @data: The data to be updated by the event.
> + *
> + * Returns: notifier_block status.
> + */
> +static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
> +			      void *data)
> +{
> +	struct lwmi_event_priv *priv;
> +
> +	priv = container_of(nb, struct lwmi_event_priv, event_nb);
> +	if (!priv)
> +		return NOTIFY_BAD;
> +
> +	switch (cmd) {
> +	case THERMAL_MODE_EVENT:
> +		priv->current_mode = *((enum thermal_mode *)data);
> +		platform_profile_notify(&priv->wdev->dev);
> +		break;
> +	default:
> +		return NOTIFY_DONE;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +/* Platform Profile Methods & Setup */
> +/*
> + * lwmi_gz_platform_profile_supported() - Gets the version of the WMI
> + * interface to determine the support level.
> + *
> + * @wdev: The Gamezone WMI device.
> + * @supported: Pointer to return the support level with.
> + *
> + * Returns: 0, or an error.
> + */
> +static int lwmi_gz_platform_profile_supported(struct wmi_device *wdev,
> +					      int *supported)
> +{
> +	return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP,
> +					0, 0, supported);
> +}
> +
> +/*
> + * lwmi_gz_thermal_mode_get() - Gets the currently set thermal mode from
> + * the Gamezone WMI interface.
> + *
> + * @wdev: The Gamezone WMI device.
> + * @mode: Pointer to return the thermal mode with.
> + *
> + * Returns: 0, or an error.
> + */
> +static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
> +				    enum thermal_mode *mode)
> +{
> +	return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET,
> +					0, 0, mode);
> +}
> +
> +static int lwmi_gz_profile_get(struct device *dev,
> +			       enum platform_profile_option *profile)
> +{
> +	struct lwmi_event_priv *priv = dev_get_drvdata(dev);
> +	enum thermal_mode mode;
> +	int ret;
> +
> +	ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode);
> +	if (ret)
> +		return ret;
> +
> +	switch (mode) {
> +	case SMARTFAN_MODE_QUIET:
> +		*profile = PLATFORM_PROFILE_LOW_POWER;
> +		break;
> +	case SMARTFAN_MODE_BALANCED:
> +		*profile = PLATFORM_PROFILE_BALANCED;
> +		break;
> +	case SMARTFAN_MODE_PERFORMANCE:
> +		if (priv->extreme_supported) {
> +			*profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> +			break;
> +		}
> +		*profile = PLATFORM_PROFILE_PERFORMANCE;
> +		break;
> +	case SMARTFAN_MODE_EXTREME:
> +		*profile = PLATFORM_PROFILE_PERFORMANCE;
> +		break;
> +	case SMARTFAN_MODE_CUSTOM:
> +		*profile = PLATFORM_PROFILE_CUSTOM;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	priv->current_mode = mode;
> +
> +	return 0;
> +}
> +
> +static int lwmi_gz_profile_set(struct device *dev,
> +			       enum platform_profile_option profile)
> +{
> +	struct lwmi_event_priv *priv = dev_get_drvdata(dev);
> +	struct wmi_method_args_32 args;
> +	enum thermal_mode mode;
> +	int ret;
> +
> +	switch (profile) {
> +	case PLATFORM_PROFILE_LOW_POWER:
> +		mode = SMARTFAN_MODE_QUIET;
> +		break;
> +	case PLATFORM_PROFILE_BALANCED:
> +		mode = SMARTFAN_MODE_BALANCED;
> +		break;
> +	case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> +		mode = SMARTFAN_MODE_PERFORMANCE;
> +		break;
> +	case PLATFORM_PROFILE_PERFORMANCE:
> +		if (priv->extreme_supported) {
> +			mode = SMARTFAN_MODE_EXTREME;
> +			break;
> +		}
> +		mode = SMARTFAN_MODE_PERFORMANCE;
> +		break;
> +	case PLATFORM_PROFILE_CUSTOM:
> +		mode = SMARTFAN_MODE_CUSTOM;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	args.arg0 = mode;
> +
> +	ret = lwmi_dev_evaluate_method(priv->wdev, 0x0,
> +				       WMI_METHOD_ID_SMARTFAN_SET,
> +				       (unsigned char *)&args, sizeof(args),
> +				       NULL);
> +	if (ret)
> +		return ret;
> +
> +	priv->current_mode = mode;
> +
> +	return 0;
> +}
> +
> +static const struct dmi_system_id fwbug_list[] = {
> +	{
> +		.ident = "Legion Go 8APU1",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> +		},
> +		.driver_data = &quirk_no_extreme_bug,
> +	},
> +	{
> +		.ident = "Legion Go S 8ARP1",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> +		},
> +		.driver_data = &quirk_no_extreme_bug,
> +	},
> +	{
> +		.ident = "Legion Go S 8APU1",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> +		},
> +		.driver_data = &quirk_no_extreme_bug,
> +	},
> +	{},
> +
> +};
> +
> +/*
> + * extreme_supported() - Evaluate if a device supports extreme thermal mode.
> + * For devices that have a profile_support_ver of 6 or greater a DMI check
> + * is done. Some devices report a version that supports extreme mode but
> + * have an incomplete entry in the BIOS. To ensure this cannot be set, they
> + * are quirked to prevent assignment.
> + *
> + * @profile_support_ver: Version of WMI interface provided by
> + * lwmi_gz_platform_profile_supported.
> + *
> + * Returns: bool
> + */
> +static bool extreme_supported(int profile_support_ver)
> +{
> +	const struct dmi_system_id *dmi_id;
> +	struct quirk_entry *quirks;
> +
> +	if (profile_support_ver < 6)
> +		return false;
> +
> +	dmi_id = dmi_first_match(fwbug_list);
> +	if (!dmi_id)
> +		return true;
> +
> +	quirks = dmi_id->driver_data;
> +	return quirks->extreme_supported;
> +}
> +
> +static int lwmi_platform_profile_probe(void *drvdata, unsigned long *choices)
> +{
> +	struct lwmi_event_priv *priv = drvdata;
> +	int profile_support_ver;
> +	int ret;
> +
> +	ret = lwmi_gz_platform_profile_supported(priv->wdev,
> +						 &profile_support_ver);
> +	if (ret)
> +		return ret;
> +
> +	if (profile_support_ver < 1)
> +		return -ENODEV;
> +
> +	priv->extreme_supported = extreme_supported(profile_support_ver);
> +
> +	set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> +	set_bit(PLATFORM_PROFILE_BALANCED, choices);
> +	set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> +	set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> +
> +	if (priv->extreme_supported)
> +		set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> +
> +	return 0;
> +}
> +
> +static const struct platform_profile_ops lwmi_gz_platform_profile_ops = {
> +	.probe = lwmi_platform_profile_probe,
> +	.profile_get = lwmi_gz_profile_get,
> +	.profile_set = lwmi_gz_profile_set,
> +};
> +
> +/* Driver Methods */
> +static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct lwmi_event_priv *priv;
> +	int ret;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->event_nb.notifier_call = lwmi_gz_event_call;
> +	ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
> +	if (ret)
> +		return ret;
> +
> +	priv->mode_nb.notifier_call = lwmi_gz_mode_call;
> +	ret = devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb);
> +	if (ret)
> +		return ret;
> +
> +	priv->wdev = wdev;
> +	dev_set_drvdata(&wdev->dev, priv);
> +
> +	priv->ppdev = platform_profile_register(&wdev->dev,
> +						"lenovo-wmi-gamezone", priv,
> +						&lwmi_gz_platform_profile_ops);
> +
> +	if (IS_ERR(priv->ppdev))
> +		return -ENODEV;
> +
> +	ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static const struct wmi_device_id lwmi_gz_id_table[] = { { LENOVO_GAMEZONE_GUID,
> +							   NULL },
> +							 {} };
> +
> +static struct wmi_driver lwmi_gz_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_gamezone",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_gz_id_table,
> +	.probe = lwmi_gz_probe,
> +	.no_singleton = true,
> +};
> +
> +module_wmi_driver(lwmi_gz_driver);
> +
> +MODULE_IMPORT_NS("LENOVO_WMI_EVENTS");
> +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> +MODULE_IMPORT_NS("LENOVO_WMI_OTHER");
> +MODULE_DEVICE_TABLE(wmi, lwmi_gz_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-wmi-gamezone.h b/drivers/platform/x86/lenovo-wmi-gamezone.h
> new file mode 100644
> index 000000000000..ac536803160b
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#ifndef _LENOVO_WMI_GAMEZONE_H_
> +#define _LENOVO_WMI_GAMEZONE_H_
> +
> +enum thermal_mode {
> +	SMARTFAN_MODE_QUIET = 0x01,
> +	SMARTFAN_MODE_BALANCED = 0x02,
> +	SMARTFAN_MODE_PERFORMANCE = 0x03,
> +	SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> +	SMARTFAN_MODE_CUSTOM = 0xFF,
> +};
> +
> +#endif /* !_LENOVO_WMI_GAMEZONE_H_ */


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

* Re: [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation
  2025-03-17 14:43 ` [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation Derek J. Clark
                     ` (2 preceding siblings ...)
  2025-03-19  4:41   ` Bagas Sanjaya
@ 2025-03-27  0:26   ` Armin Wolf
  2025-03-30  4:49     ` Derek John Clark
  3 siblings, 1 reply; 49+ messages in thread
From: Armin Wolf @ 2025-03-27  0:26 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel

Am 17.03.25 um 15:43 schrieb Derek J. Clark:

> Adds documentation for new lenovo-wmi drivers.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
>   - Fixed MOF formatting issues.
>   - Fixed spelling mistakes.
>   - Updated description of balanced-performance profile for Gamezone.
>   - Updated description of thermal mode event GUID for Gamezone.
> v3:
> - Split documentation into multiple files, one for each parent
>    driver for the Gamezone and Other Mode WMI interfaces.
> - Add MOF data for all parent and child interfaces.
> - Remove lenovo-wmi-camera.c driver from v2 documentation.
> v2:
> - Update description of Custom Profile to include the need to manually
>    set it.
> - Remove all references to Legion hardware.
> - Add section for lenovo-wmi-camera.c driver as it follows the same
>    naming convention.
> ---
>   .../wmi/devices/lenovo-wmi-gamezone.rst       | 203 ++++++++++++++++++
>   .../wmi/devices/lenovo-wmi-other-method.rst   | 108 ++++++++++
>   MAINTAINERS                                   |   7 +
>   3 files changed, 318 insertions(+)
>   create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>   create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst
>
> diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> new file mode 100644
> index 000000000000..bde63dde285d
> --- /dev/null
> +++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> @@ -0,0 +1,203 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +==========================================================
> +Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
> +==========================================================
> +
> +Introduction
> +============
> +The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
> +The priamry "Gamezone" GUID provides advanced features such as fan
> +profiles and overclocking. It is paired with multiple event GUIDs
> +and data block GUIDs that provide context for the various methods.
> +
> +Gamezone Data
> +-------------
> +
> +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The Gamezone Data WMI interface provides platform-profile and fan curve
> +settings for devices that fall under the "Gaming Series" of Lenovo devices.
> +It uses a notifier chain to inform other Lenovo WMI interface drivers of the
> +current platform profile when it changes.
> +
> +The following platform profiles are supported:
> + - low-power
> + - balanced
> + - balanced-performance*
> + - performance
> + - custom
> +
> +Balanced-Performance
> +~~~~~~~~~~~~~~~~~~~~
> +Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
> +enabled in their BIOS. For these devices, the performance platform profile
> +will correspond to the BIOS Extreme Mode, while the balanced-performance
> +platform profile will correspond to the BIOS Performance mode. For legacy
> +devices, the performance platform profile will correspond with the BIOS
> +Performance mode. For some newer devices the "Extreme Mode" profile is
> +incomplete in the BIOS and setting it will cause undefined behavior. A
> +BIOS bug quirk table is provided to ensure these devices cannot set
> +"Extreme Mode" from the driver.
> +
> +Custom Profile
> +~~~~~~~~~~~~~~
> +The custom profile represents a hardware mode on Lenovo devices that enables
> +user modifications to Package Power Tracking (PPT) and fan curve settings.
> +When an attribute exposed by the Other Mode WMI interface is to be modified,
> +the Gamezone driver must first be switched to the "custom" profile manually,
> +or the setting will have no effect. If another profile is set from the list
> +of supported profiles, the BIOS will override any user PPT settings when
> +switching to that profile.
> +
> +Gamezone Thermal Mode Event
> +---------------------------
> +
> +WMI GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"

Hi,

if i remember correctly we agreed that the GUIDs could use some special markup (monospace, ...)
instead of using "".

> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The Gamezone Thermal Mode Event interface notifies the system when the platform
> +profile has changed, either through the hardware event (Fn+Q for laptops or
> +Legion + Y for Go Series), or through the Gamezone WMI interface. This event is
> +implemented in the Lenovo WMI Events driver (lenovo-wmi-events).
> +
> +
> +WMI interface description
> +=========================
> +
> +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> +
> +::
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_GAMEZONE_DATA class"), guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
> +  class LENOVO_GAMEZONE_DATA {
> +  	[key, read] string InstanceName;
> +  	[read] boolean Active;

When building the html documentation the following errors appear:

/home/wolf/Dokumente/Kernel/platform-drivers-x86/Documentation/wmi/devices/lenovo-wmi-gamezone.rst:2: WARNING: Explicit markup ends without a blank line; unexpected unindent.
/home/wolf/Dokumente/Kernel/platform-drivers-x86/Documentation/wmi/devices/lenovo-wmi-other-method.rst:2: WARNING: Explicit markup ends without a blank line; unexpected unindent.

Please fix those errors.

> +
> +    [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")] void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
> +    [WmiMethodId(11), Implemented, Description("Get AslCode Version")] void GetVersion ([out, Description("AslCode version")] UINT32 Data);
> +    [WmiMethodId(12), Implemented, Description("Fan cooling capability")] void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
> +    [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")] void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
> +    [WmiMethodId(14), Implemented, Description("cpu oc capability")] void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
> +    [WmiMethodId(15), Implemented, Description("bios has overclock capability")] void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
> +    [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")] void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
> +    [WmiMethodId(18), Implemented, Description("Get CPU temperature")] void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
> +    [WmiMethodId(19), Implemented, Description("Get GPU temperature")] void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
> +    [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")] void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
> +    [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")] void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
> +    [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")] void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
> +    [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")] void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
> +    [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")] void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
> +    [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")] void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
> +    [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")] void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
> +    [WmiMethodId(30), Implemented, Description("Get Keyboard feature list")] void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
> +    [WmiMethodId(31), Implemented, Description("Get Memory OC Information")] void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
> +    [WmiMethodId(32), Implemented, Description("Water Cooling feature capability")] void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
> +    [WmiMethodId(33), Implemented, Description("Set Water Cooling status")] void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
> +    [WmiMethodId(34), Implemented, Description("Get Water Cooling status")] void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
> +    [WmiMethodId(35), Implemented, Description("Lighting feature capability")] void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
> +    [WmiMethodId(36), Implemented, Description("Set keyboard light off or on to max")] void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
> +    [WmiMethodId(37), Implemented, Description("Get keyboard light on/off status")] void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
> +    [WmiMethodId(38), Implemented, Description("Get Macrokey scan code")] void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
> +    [WmiMethodId(39), Implemented, Description("Get Macrokey count")] void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
> +    [WmiMethodId(40), Implemented, Description("Support G-Sync feature")] void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
> +    [WmiMethodId(41), Implemented, Description("Get G-Sync Status")] void GetGSyncStatus ([out, Description("Get G-Sync Status")] UINT32 Data);
> +    [WmiMethodId(42), Implemented, Description("Set G-Sync Status")] void SetGSyncStatus ([in, Description("Set G-Sync Status")] UINT32 Data);
> +    [WmiMethodId(43), Implemented, Description("Support Smart Fan feature")] void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
> +    [WmiMethodId(44), Implemented, Description("Set Smart Fan Mode")] void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
> +    [WmiMethodId(45), Implemented, Description("Get Smart Fan Mode")] void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
> +    [WmiMethodId(46), Implemented, Description("Get Smart Fan Setting Mode")] void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
> +    [WmiMethodId(47), Implemented, Description("Get Power Charge Mode")] void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
> +    [WmiMethodId(48), Implemented, Description("Get Gaming Product Info")] void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
> +    [WmiMethodId(49), Implemented, Description("Over Drive feature capability")] void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
> +    [WmiMethodId(50), Implemented, Description("Get Over Drive status")] void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
> +    [WmiMethodId(51), Implemented, Description("Set Over Drive status")] void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
> +    [WmiMethodId(52), Implemented, Description("Set Light Control Owner")] void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
> +    [WmiMethodId(53), Implemented, Description("Set DDS Control Owner")] void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
> +    [WmiMethodId(54), Implemented, Description("Get the flag of restore OC value")] void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
> +    [WmiMethodId(55), Implemented, Description("Get Real Thremal Mode")] void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
> +    [WmiMethodId(56), Implemented, Description("Get the OC switch status in BIOS")] void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
> +    [WmiMethodId(59), Implemented, Description("Get hardware info support version")] void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
> +    [WmiMethodId(60), Implemented, Description("Get Cpu core 0 max frequency")] void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
> +    [WmiMethodId(62), Implemented, Description("Check the Adapter type fit for OC")] void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
> +    [WmiMethodId(63), Implemented, Description("Is support IGPU mode")] void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
> +    [WmiMethodId(64), Implemented, Description("Get IGPU Mode Status")] void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
> +    [WmiMethodId(65), Implemented, Description("Set IGPU Mode")] void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
> +    [WmiMethodId(66), Implemented, Description("Notify DGPU Status")] void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
> +    [WmiMethodId(67), Implemented, Description("Is changed Y log")] void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
> +    [WmiMethodId(68), Implemented, Description("Get DGPU Hardwawre ID")] void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of CPU OC parameter list"), guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
> +  class LENOVO_GAMEZONE_CPU_OC_DATA {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("OC tune id.")] uint32 Tuneid;
> +    [WmiDataId(2), read, Description("Default value.")] uint32 DefaultValue;
> +    [WmiDataId(3), read, Description("OC Value.")] uint32 OCValue;
> +    [WmiDataId(4), read, Description("Min Value.")] uint32 MinValue;
> +    [WmiDataId(5), read, Description("Max Value.")] uint32 MaxValue;
> +    [WmiDataId(6), read, Description("Scale Value.")] uint32 ScaleValue;
> +    [WmiDataId(7), read, Description("OC Order id.")] uint32 OCOrderid;
> +    [WmiDataId(8), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
> +    [WmiDataId(9), read, Description("Delay time in ms.")] uint32 Interval;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of GPU OC parameter list"), guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
> +  class LENOVO_GAMEZONE_GPU_OC_DATA {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("P-State ID.")] uint32 PStateID;
> +    [WmiDataId(2), read, Description("CLOCK ID.")] uint32 ClockID;
> +    [WmiDataId(3), read, Description("Default value.")] uint32 defaultvalue;
> +    [WmiDataId(4), read, Description("OC Offset freqency.")] uint32 OCOffsetFreq;
> +    [WmiDataId(5), read, Description("OC Min offset value.")] uint32 OCMinOffset;
> +    [WmiDataId(6), read, Description("OC Max offset value.")] uint32 OCMaxOffset;
> +    [WmiDataId(7), read, Description("OC Offset Scale.")] uint32 OCOffsetScale;
> +    [WmiDataId(8), read, Description("OC Order id.")] uint32 OCOrderid;
> +    [WmiDataId(9), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Fancooling finish event"), guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
> +  class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("Fancooling clean finish event")] uint32 EventId;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan mode change event"), guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
> +  class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("Smart Fan Mode change event")] uint32 mode;
> +    [WmiDataId(2), read, Description("version of FN+Q")] uint32 version;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan setting mode change event"), guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
> +  class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("Smart Fan Setting mode change event")] uint32 mode;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("POWER CHARGE MODE Change EVENT"), guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
> +  class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")] uint32 mode;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Thermal Mode Real Mode change event"), guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
> +  class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description("Thermal Mode Real Mode")] uint32 mode;
> +  };
> diff --git a/Documentation/wmi/devices/lenovo-wmi-other-method.rst b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> new file mode 100644
> index 000000000000..b48832726311
> --- /dev/null
> +++ b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> @@ -0,0 +1,108 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +===========================================================
> +Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
> +===========================================================
> +
> +Introduction
> +============
> +Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
> +The primary Other Mode interface provides advanced power tuning features
> +such as Package Power Tracking (PPT). It is paired with multiple data block
> +GUIDs that provide context for the various methods.
> +
> +
> +Other Mode
> +----------
> +
> +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The Other Mode WMI interface uses the firmware_attributes class to expose
> +various WMI attributes 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 devices. Each
> +attribute exposed by the Other Mode interface has corresponding
> +capability data blocks which allow the driver to probe details about the
> +attribute. Each attribute has multiple pages, one for each of the platform
> +profiles managed by the Gamezone interface. Attributes are exposed in sysfs
> +under the following path:
> +
> +::
> +/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/

Something is wrong with the formatting, the text is being rendered as-is.
Please indent the path with two spaces and add a blank line between the "::"
and the path.

Thanks,
Armin Wolf

> +
> +LENOVO_CAPABILITY_DATA_01
> +-------------------------
> +
> +WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The LENOVO_CAPABILITY_DATA_01 interface provides information on various
> +power limits of integrated CPU and GPU components.
> +
> +Each attribute has the following properties:
> + - current_value
> + - default_value
> + - display_name
> + - max_value
> + - min_value
> + - scalar_increment
> + - type
> +
> +The following attributes are implemented:
> + - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
> + - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
> + - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
> +
> +
> +WMI interface description
> +=========================
> +
> +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> +
> +::
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_OTHER_METHOD class"), guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
> +  class LENOVO_OTHER_METHOD {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiMethodId(17), Implemented, Description("Get Feature Value ")] void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
> +    [WmiMethodId(18), Implemented, Description("Set Feature Value ")] void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
> +    [WmiMethodId(19), Implemented, Description("Get Data By Command ")] void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
> +    [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")] void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 00"), guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
> +  class LENOVO_CAPABILITY_DATA_00 {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
> +    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
> +    [WmiDataId(3), read, Description("Capability Default Value.")] uint32 DefaultValue;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 01"), guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
> +  class LENOVO_CAPABILITY_DATA_01 {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
> +    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
> +    [WmiDataId(3), read, Description("Default Value.")] uint32 DefaultValue;
> +    [WmiDataId(4), read, Description("Step.")] uint32 Step;
> +    [WmiDataId(5), read, Description("Minimum Value.")] uint32 MinValue;
> +    [WmiDataId(6), read, Description("Maximum Value.")] uint32 MaxValue;
> +  };
> +
> +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 02"), guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
> +  class LENOVO_CAPABILITY_DATA_02 {
> +    [key, read] string InstanceName;
> +    [read] boolean Active;
> +
> +    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
> +    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
> +    [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize;
> +    [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[];
> +  };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1afd30d00aec..675f4b26426d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13158,6 +13158,13 @@ S:	Maintained
>   W:	http://legousb.sourceforge.net/
>   F:	drivers/usb/misc/legousbtower.c
>
> +LENOVO WMI DRIVERS
> +M:	Derek J. Clark <derekjohn.clark@gmail.com>
> +L:	platform-driver-x86@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> +F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +
>   LENOVO WMI HOTKEY UTILITIES DRIVER
>   M:	Jackie Dong <xy-jackie@139.com>
>   L:	platform-driver-x86@vger.kernel.org

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

* Re: [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers
  2025-03-17 14:43 ` [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers Derek J. Clark
  2025-03-18  4:27   ` Mario Limonciello
  2025-03-26 19:45   ` Matthew Schwartz
@ 2025-03-27  0:40   ` Armin Wolf
  2025-03-30  4:55     ` Derek John Clark
  2025-03-27 12:43   ` Ilpo Järvinen
  3 siblings, 1 reply; 49+ messages in thread
From: Armin Wolf @ 2025-03-27  0:40 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel

Am 17.03.25 um 15:43 schrieb Derek J. Clark:

> Adds documentation for all new lenovo-wmi drivers.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
>   - Changed namespace to LENOVO_WMI_HELPERS from LENOVO_WMI.
>   - Changed filenames to lenovo-wmi-helpers from lenovo-wmi.
>   - Removed structs and functions implemented by other drivers.
> ---
>   MAINTAINERS                               |  2 +
>   drivers/platform/x86/Kconfig              |  4 ++
>   drivers/platform/x86/Makefile             |  1 +
>   drivers/platform/x86/lenovo-wmi-helpers.c | 64 +++++++++++++++++++++++
>   drivers/platform/x86/lenovo-wmi-helpers.h | 24 +++++++++
>   5 files changed, 95 insertions(+)
>   create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 675f4b26426d..3a370a18b806 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>   S:	Maintained
>   F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>   F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +F:	drivers/platform/x86/lenovo-wmi-helpers.c
> +F:	drivers/platform/x86/lenovo-wmi-helpers.h
>
>   LENOVO WMI HOTKEY UTILITIES DRIVER
>   M:	Jackie Dong <xy-jackie@139.com>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 43407e76476b..bece1ba61417 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,10 @@ config IBM_RTL
>   	 state = 0 (BIOS SMIs on)
>   	 state = 1 (BIOS SMIs off)
>
> +config LENOVO_WMI_HELPERS
> +	tristate
> +	depends on ACPI_WMI
> +
>   config IDEAPAD_LAPTOP
>   	tristate "Lenovo IdeaPad Laptop Extras"
>   	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 650dfbebb6c8..5a9f4e94f78b 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>
>   # Intel
>   obj-y				+= intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x86/lenovo-wmi-helpers.c
> new file mode 100644
> index 000000000000..36d553502223
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-helpers.c
> @@ -0,0 +1,64 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Legion WMI helpers 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 Method" 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 Method" 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 drivers
> + * uses a common procedure to get data fro the WMI interface, enumerated here.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#include <linux/wmi.h>

Hi,

please also include linux/acpi.h, linux/cleanup.h, linux/errno.h, linux/module.h and linux/export.h.

> +#include "lenovo-wmi-helpers.h"
> +
> +/*

Please make sure that the doc comments are valid kernel doc comments, see https://docs.kernel.org/doc-guide/kernel-doc.html
for details. For example please make sure that the comments start with a "/**".

It would also be nice if you actually describe the purpose of each function.

> + * lwmi_dev_evaluate_method() - Helper function to call wmidev_evaluate_method
> + * for Lenovo WMI device method calls that return an ACPI integer.
> + * @wdev: Pointer to the WMI device to be called.
> + * @instance: Instance of the called method.
> + * @method_id: WMI Method ID for the method to be called.
> + * @buf: Buffer of all arguments for the given method_id.
> + * @size: Length of the buffer.
> + * @retval: Pointer for the return value to be assigned.
> + *
> + * Returns: 0, or an error.
> + */
> +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> +			     u32 method_id, unsigned char *buf, size_t size,
> +			     u32 *retval)
> +{
> +	struct acpi_buffer input = { size, buf };

Reverse X-mas tree declaration order please.

Thanks,
Armin Wolf

> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> +	union acpi_object *ret_obj __free(kfree) = NULL;
> +	acpi_status status;
> +
> +	status = wmidev_evaluate_method(wdev, instance, method_id, &input,
> +					&output);
> +
> +	if (ACPI_FAILURE(status))
> +		return -EIO;
> +
> +	if (retval) {
> +		ret_obj = output.pointer;
> +		if (!ret_obj)
> +			return -ENODATA;
> +
> +		if (ret_obj->type != ACPI_TYPE_INTEGER)
> +			return -ENXIO;
> +
> +		*retval = (u32)ret_obj->integer.value;
> +	}
> +	return 0;
> +};
> +EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_method, "LENOVO_WMI_HELPERS");
> +
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.h b/drivers/platform/x86/lenovo-wmi-helpers.h
> new file mode 100644
> index 000000000000..7e0d7870790e
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-helpers.h
> @@ -0,0 +1,24 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +
> +#ifndef _LENOVO_WMI_HELPERS_H_
> +#define _LENOVO_WMI_HELPERS_H_
> +
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +struct wmi_method_args_32 {
> +	u32 arg0;
> +	u32 arg1;
> +};
> +
> +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> +			     u32 method_id, unsigned char *buf, size_t size,
> +			     u32 *retval);
> +
> +#endif /* !_LENOVO_WMI_HELPERS_H_ */

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

* Re: [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver
  2025-03-17 14:43 ` [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver Derek J. Clark
  2025-03-18  4:30   ` Mario Limonciello
  2025-03-26 19:47   ` Matthew Schwartz
@ 2025-03-27  1:03   ` Armin Wolf
  2025-03-30  4:55     ` Derek John Clark
  2025-03-27 12:47   ` Ilpo Järvinen
  3 siblings, 1 reply; 49+ messages in thread
From: Armin Wolf @ 2025-03-27  1:03 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel

Am 17.03.25 um 15:43 schrieb Derek J. Clark:

> Adds lenovo-wmi-events driver. The events driver is designed as a
> general entrypoint for all Lenovo WMI Events. It acts as a notification
> chain head that will process event data and pass it on to registered
> drivers so they can react to the events.
>
> Currently only the Gamezone interface Thermal Mode Event GUID is
> implemented in this driver. It is documented in the Gamezone
> documentation.
>
> Suggested-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
>   - Remove the Thermal Mode Event GUID from Gamezone and add this driver.
> ---
>   MAINTAINERS                              |   2 +
>   drivers/platform/x86/Kconfig             |   4 +
>   drivers/platform/x86/Makefile            |   1 +
>   drivers/platform/x86/lenovo-wmi-events.c | 132 +++++++++++++++++++++++
>   drivers/platform/x86/lenovo-wmi-events.h |  21 ++++
>   5 files changed, 160 insertions(+)
>   create mode 100644 drivers/platform/x86/lenovo-wmi-events.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-events.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3a370a18b806..6dde75922aaf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>   S:	Maintained
>   F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>   F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +F:	drivers/platform/x86/lenovo-wmi-events.c
> +F:	drivers/platform/x86/lenovo-wmi-events.h
>   F:	drivers/platform/x86/lenovo-wmi-helpers.c
>   F:	drivers/platform/x86/lenovo-wmi-helpers.h
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index bece1ba61417..13b8f4ac5dc5 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,10 @@ config IBM_RTL
>   	 state = 0 (BIOS SMIs on)
>   	 state = 1 (BIOS SMIs off)
>
> +config LENOVO_WMI_EVENTS
> +	tristate
> +	depends on ACPI_WMI
> +
>   config LENOVO_WMI_HELPERS
>   	tristate
>   	depends on ACPI_WMI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 5a9f4e94f78b..fc039839286a 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
>   obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>
>   # Intel
> diff --git a/drivers/platform/x86/lenovo-wmi-events.c b/drivers/platform/x86/lenovo-wmi-events.c
> new file mode 100644
> index 000000000000..3ea0face3c0d
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-events.c
> @@ -0,0 +1,132 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo WMI Events driver. Lenovo WMI interfaces provide various
> + * hardware triggered events that many drivers need to have propagated.
> + * This driver provides a uniform entrypoint for these events so that
> + * any driver that needs to respond to these events can subscribe to a
> + * notifier chain.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/list.h>
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +#include "lenovo-wmi-events.h"

Hi,

please also include linux/acpi.h, linux/export.h and linux/module.h. Also why do you import
linux/list.h?

> +
> +/* Interface GUIDs */
> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> +
> +#define LENOVO_WMI_EVENT_DEVICE(guid, type)                        \
> +	.guid_string = (guid), .context = &(enum lwmi_events_type) \
> +	{                                                          \
> +		type                                               \
> +	}
> +
> +static BLOCKING_NOTIFIER_HEAD(events_chain_head);
> +
> +struct lwmi_events_priv {
> +	struct wmi_device *wdev;
> +	enum lwmi_events_type type;
> +};
> +
> +/* Notifier Methods */
> +int lwmi_events_register_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_register(&events_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
> +
> +int lwmi_events_unregister_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_unregister(&events_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS");
> +
> +static void devm_lwmi_events_unregister_notifier(void *data)
> +{
> +	struct notifier_block *nb = data;
> +
> +	lwmi_events_unregister_notifier(nb);
> +}
> +
> +int devm_lwmi_events_register_notifier(struct device *dev,
> +				       struct notifier_block *nb)
> +{
> +	int ret;
> +
> +	ret = lwmi_events_register_notifier(nb);
> +	if (ret < 0)
> +		return ret;
> +
> +	return devm_add_action_or_reset(dev,
> +				devm_lwmi_events_unregister_notifier, nb);

Please remove this line break here.

> +}
> +EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
> +
> +/* Driver Methods */
> +static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj)
> +{
> +	struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev);
> +	int sel_prof;
> +	int ret;
> +
> +	switch (priv->type) {
> +	case THERMAL_MODE_EVENT:
> +		if (obj->type != ACPI_TYPE_INTEGER)
> +			return;
> +
> +		sel_prof = obj->integer.value;
> +		ret = blocking_notifier_call_chain(&events_chain_head,
> +						   THERMAL_MODE_EVENT, &sel_prof);
> +		if (ret == NOTIFY_BAD)
> +			dev_err(&wdev->dev,
> +				"Failed to send notification to call chain for WMI Events\n");
> +		break;
> +	default:
> +		return;
> +	}
> +}
> +
> +static int lwmi_events_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct lwmi_events_priv *priv;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	if (!context)
> +		return -EINVAL;
> +
> +	priv->wdev = wdev;
> +	priv->type = *(enum lwmi_events_type *)context;
> +
> +	dev_set_drvdata(&wdev->dev, priv);
> +	return 0;
> +}
> +
> +static const struct wmi_device_id lwmi_events_id_table[] = {
> +	{ LENOVO_WMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID,
> +				  THERMAL_MODE_EVENT) },
> +	{}
> +};
> +
> +static struct wmi_driver lwmi_events_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_events",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_events_id_table,
> +	.probe = lwmi_events_probe,
> +	.notify = lwmi_events_notify,
> +	.no_singleton = true,
> +};
> +
> +module_wmi_driver(lwmi_events_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo WMI Events Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi-events.h b/drivers/platform/x86/lenovo-wmi-events.h
> new file mode 100644
> index 000000000000..a3fa934eaa10
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-events.h
> @@ -0,0 +1,21 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later

The SPDX license identifier needs to be a separate comment.

With those minor issues being fixed:
Reviewed-by: Armin Wolf <W_Armin@gmx.de>

> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +
> +#ifndef _LENOVO_WMI_EVENTS_H_
> +#define _LENOVO_WMI_EVENTS_H_
> +
> +enum lwmi_events_type {
> +	THERMAL_MODE_EVENT = 1,
> +};
> +
> +int lwmi_events_register_notifier(struct notifier_block *nb);
> +int lwmi_events_unregister_notifier(struct notifier_block *nb);
> +int devm_lwmi_events_register_notifier(struct device *dev,
> +				       struct notifier_block *nb);
> +
> +#endif /* !_LENOVO_WMI_EVENTS_H_ */

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

* Re: [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver
  2025-03-17 14:43 ` [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver Derek J. Clark
  2025-03-26 19:47   ` Matthew Schwartz
@ 2025-03-27  1:29   ` Armin Wolf
  2025-04-02 20:47     ` Derek John Clark
  2025-03-27 12:56   ` Ilpo Järvinen
  2 siblings, 1 reply; 49+ messages in thread
From: Armin Wolf @ 2025-03-27  1:29 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel

Am 17.03.25 um 15:43 schrieb Derek J. Clark:

> Adds lenovo-wmi-capdata01 driver which provides the
> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode"
> enabled hardware. Provides an interface for querying if a given
> attribute is supported by the hardware, as well as its default_value,
> max_value, min_value, and step increment.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
>   - Make driver data a private struct, remove references from Other Mode
>     driver.
>   - Don't cache data at device initialization. Instead, on component bind,
>     cache the data on a member variable of the Other Mode driver data
>     passed as a void pointer.
>   - Add header file for capdata01 structs.
>   - Add new struct to pass capdata01 array data and array length to Other
>     Mode.
> v3:
> - Add as component to lenovo-wmi-other driver.
> v2:
> - Use devm_kmalloc to ensure driver can be instanced, remove global
>    reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
> - Use list to get the lenovo_wmi_cd01_priv instance in
>    lenovo_wmi_capdata01_get as none of the data provided by the macros
>    that will use it can pass a member of the struct for use in
>    container_of.
> ---
>   MAINTAINERS                                 |   2 +
>   drivers/platform/x86/Kconfig                |   4 +
>   drivers/platform/x86/Makefile               |   1 +
>   drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++
>   drivers/platform/x86/lenovo-wmi-capdata01.h |  29 +++++
>   5 files changed, 172 insertions(+)
>   create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6dde75922aaf..56ead241a053 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>   S:	Maintained
>   F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>   F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +F:	drivers/platform/x86/lenovo-wmi-capdata01.c
> +F:	drivers/platform/x86/lenovo-wmi-capdata01.h
>   F:	drivers/platform/x86/lenovo-wmi-events.c
>   F:	drivers/platform/x86/lenovo-wmi-events.h
>   F:	drivers/platform/x86/lenovo-wmi-helpers.c
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 13b8f4ac5dc5..64663667f0cb 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
>   	tristate
>   	depends on ACPI_WMI
>
> +config LENOVO_WMI_DATA01
> +	tristate
> +	depends on ACPI_WMI
> +
>   config IDEAPAD_LAPTOP
>   	tristate "Lenovo IdeaPad Laptop Extras"
>   	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index fc039839286a..7a35c77221b7 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
>   obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
>   obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>
> diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
> new file mode 100644
> index 000000000000..b6876611ffd9
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> @@ -0,0 +1,136 @@
> +// 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 Mode" WMI interface,
> + * including if it is supported by the hardware, the default_value, max_value,
> + * min_value, and step increment.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/component.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/gfp_types.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +#include "lenovo-wmi-capdata01.h"

Hi,

please also include linux/acpi.h, linux/export.h and linux/module.h.

> +
> +/* Interface GUIDs */
> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +
> +struct lwmi_cd01_priv {
> +	struct wmi_device *wdev;
> +};
> +
> +/*

/* -> /**

> + * lenovo_cd01_component_bind() - On master bind, caches all capability data on
> + * the master device.
> + * @cd01_dev: Pointer to the capability data 01 parent device.
> + * @om_dev: Pointer to the other mode parent device.
> + * @data: capdata01_list object pointer to return the capability data with.
> + *
> + * Returns: 0, or an error.
> + */
> +static int lenovo_cd01_component_bind(struct device *cd01_dev,
> +				      struct device *om_dev, void *data)
> +{
> +	struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
> +	int count, idx;
> +
> +	if (!priv)
> +		return -ENODEV;

This check is unnecessary, please drop.

> +
> +	count = wmidev_instance_count(priv->wdev);
> +
> +	if (count == 0)
> +		return -EINVAL;

The WMI driver core already ensures that WMI devices with 0 instances are
rejected. Please drop this check.

> +
> +	((struct cd01_list *)data)->count = count;
> +	((struct cd01_list *)data)->data = devm_kmalloc_array(om_dev, count,
> +							      sizeof(struct capdata01 *),
> +							      GFP_KERNEL);

Two things:

  - using a local variable with a type of struct cd01_list * results in cleaner source code here

  - using devres is not possible inside the component callbacks, since the lifetime of the component
    device is not necessarily tied to the lifetime of the underlying device.

I suggest you move the whole WMI data querying into lwmi_cd01_probe(), because then you can keep using
devres.

> +	if (!data)
> +		return -ENOMEM;
> +
> +	for (idx = 0; idx < count; idx++) {
> +		union acpi_object *ret_obj __free(kfree) = NULL;
> +
> +		ret_obj = wmidev_block_query(priv->wdev, idx);
> +		if (!ret_obj) {
> +			((struct cd01_list *)data)->data[idx] = NULL;
> +			continue;
> +		}
> +		if (ret_obj->type != ACPI_TYPE_BUFFER) {
> +			((struct cd01_list *)data)->data[idx] = NULL;
> +			continue;
> +		}
> +
> +		if (ret_obj->buffer.length != sizeof(struct capdata01)) {
> +			((struct cd01_list *)data)->data[idx] = NULL;
> +			continue;
> +		}
> +
> +		((struct cd01_list *)data)->data[idx] =
> +			devm_kmemdup(om_dev, ret_obj->buffer.pointer,
> +				     ret_obj->buffer.length, GFP_KERNEL);
> +	}
> +	return 0;
> +}
> +
> +static const struct component_ops lenovo_cd01_component_ops = {
> +	.bind = lenovo_cd01_component_bind,
> +};
> +
> +static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
> +
> +{
> +	struct lwmi_cd01_priv *priv;
> +	int ret;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->wdev = wdev;
> +	dev_set_drvdata(&wdev->dev, priv);
> +
> +	ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
> +
> +	return ret;

Please just omit the local variable here and return the result of component_add() directly here.

> +}
> +
> +static void lwmi_cd01_remove(struct wmi_device *wdev)
> +{
> +	component_del(&wdev->dev, &lenovo_cd01_component_ops);
> +}
> +
> +static const struct wmi_device_id lwmi_cd01_id_table[] = {
> +	{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> +	{}
> +};
> +
> +static struct wmi_driver lwmi_cd01_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_cd01",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_cd01_id_table,
> +	.probe = lwmi_cd01_probe,
> +	.remove = lwmi_cd01_remove,
> +	.no_singleton = true,
> +};
> +
> +int lwmi_cd01_match(struct device *dev, void *data)
> +{
> +	return dev->driver == &lwmi_cd01_driver.driver;
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
> +
> +module_wmi_driver(lwmi_cd01_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lwmi_cd01_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-wmi-capdata01.h b/drivers/platform/x86/lenovo-wmi-capdata01.h
> new file mode 100644
> index 000000000000..c7067a8d0398
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later

SPDX license identifiers need to be a separate comment.

> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#ifndef _LENOVO_WMI_CAPDATA01_H_
> +#define _LENOVO_WMI_CAPDATA01_H_
> +
> +#include <linux/device.h>
> +#include <linux/types.h>
> +
> +struct capdata01 {
> +	u32 id;
> +	u32 supported;
> +	u32 default_value;
> +	u32 step;
> +	u32 min_value;
> +	u32 max_value;
> +};
> +
> +struct cd01_list {
> +	struct capdata01 **data;
> +	int count;
> +};

In order to save memory you could try something like this:

struct cd01_list {
	size_t count;
	struct capdata01 data[];
};

This way you

1. Avoid the memory fragmentation resulting from multiple memory allocations.

2. Omit two pointers when accessing the data.

You can use struct_size() from linux/overflow.h to calculate the size of such
an array with a trailing flexible array.

Thanks,
Armin Wolf

> +
> +int lwmi_cd01_match(struct device *dev, void *data);
> +
> +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */

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

* Re: [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode WMI Driver
  2025-03-17 14:43 ` [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode " Derek J. Clark
  2025-03-26 19:48   ` Matthew Schwartz
@ 2025-03-27  3:28   ` Armin Wolf
  2025-04-02 22:24     ` Derek John Clark
  2025-03-27 13:49   ` Ilpo Järvinen
  2 siblings, 1 reply; 49+ messages in thread
From: Armin Wolf @ 2025-03-27  3:28 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel

Am 17.03.25 um 15:43 schrieb Derek J. Clark:

> Adds lenovo-wmi-other driver which provides the Lenovo "Other Mode" WMI
> interface that comes on some Lenovo "Gaming Series" hardware. Provides a
> firmware-attributes class which enables the use of tunable knobs for SPL,
> SPPT, and FPPT.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
> - Treat Other Mode as a notifier chain head, use the notifier chain to
>    get the current mode from Gamezone.
> - Add header file for Other Mode specific structs and finctions.
> - Use component master bind to cache the capdata01 array locally.
> - Drop all reference to external driver private data structs.
> - Various fixes from review.
> v3:
> - Add notifier block and store result for getting the Gamezone interface
>    profile changes.
> - Add driver as master component of capdata01 driver.
> - Use FIELD_PREP where appropriate.
> - Move macros and associated functions out of lemovo-wmi.h that are only
>    used by this driver.
> v2:
> - Use devm_kmalloc to ensure driver can be instanced, remove global
>    reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> - Use list to get the lenovo_wmi_om_priv instance in some macro
>    called functions as the data provided by the macros that use it
>    doesn't pass a member of the struct for use in container_of.
> - Do not rely on GameZone interface to grab the current fan mode.
> ---
>   MAINTAINERS                             |   2 +
>   drivers/platform/x86/Kconfig            |  15 +
>   drivers/platform/x86/Makefile           |   1 +
>   drivers/platform/x86/lenovo-wmi-other.c | 626 ++++++++++++++++++++++++
>   drivers/platform/x86/lenovo-wmi-other.h |  19 +
>   5 files changed, 663 insertions(+)
>   create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-other.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 56ead241a053..87daee6075ad 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13170,6 +13170,8 @@ F:	drivers/platform/x86/lenovo-wmi-events.c
>   F:	drivers/platform/x86/lenovo-wmi-events.h
>   F:	drivers/platform/x86/lenovo-wmi-helpers.c
>   F:	drivers/platform/x86/lenovo-wmi-helpers.h
> +F:	drivers/platform/x86/lenovo-wmi-other.c
> +F:	drivers/platform/x86/lenovo-wmi-other.h
>
>   LENOVO WMI HOTKEY UTILITIES DRIVER
>   M:	Jackie Dong <xy-jackie@139.com>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 64663667f0cb..fc47604e37f7 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -471,6 +471,21 @@ config LENOVO_WMI_DATA01
>   	tristate
>   	depends on ACPI_WMI
>
> +config LENOVO_WMI_TUNING
> +	tristate "Lenovo Other Mode WMI Driver"
> +	depends on ACPI_WMI
> +	select FW_ATTR_CLASS
> +	select LENOVO_WMI_DATA01
> +	select LENOVO_WMI_EVENTS
> +	select LENOVO_WMI_HELPERS
> +	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-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 7a35c77221b7..c6ce3c8594b1 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -72,6 +72,7 @@ obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
>   obj-$(CONFIG_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
>   obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
>   obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
> +obj-$(CONFIG_LENOVO_WMI_TUNING)	+= lenovo-wmi-other.o
>
>   # Intel
>   obj-y				+= intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> new file mode 100644
> index 000000000000..b517e45338e0
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.c
> @@ -0,0 +1,626 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
> + * class to expose the various WMI functions provided by the "Other Mode" 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 laptop
> + * devices. Each attribute exposed by the "Other Mode"" 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.
> + *
> + * 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/bitfield.h>
> +#include <linux/cleanup.h>
> +#include <linux/component.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/gfp_types.h>
> +#include <linux/idr.h>
> +#include <linux/kobject.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>

Hi,

please also include linux/acpi.h, linux/export.h and linux/module.h.

> +
> +#include "lenovo-wmi-capdata01.h"
> +#include "lenovo-wmi-events.h"
> +#include "lenovo-wmi-gamezone.h"
> +#include "lenovo-wmi-helpers.h"
> +#include "lenovo-wmi-other.h"
> +#include "firmware_attributes_class.h"
> +
> +/* Interface GUIDs */
> +#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_FPPT 0x03 /* Long Term Power Limit */
> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> +
> +/* Type IDs*/
> +#define WMI_TYPE_ID_NONE 0x00
> +
> +/* Method IDs */
> +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
> +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
> +
> +/* Attribute ID bitmasks */
> +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
> +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
> +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
> +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
> +
> +static BLOCKING_NOTIFIER_HEAD(om_chain_head);
> +
> +enum attribute_property {
> +	DEFAULT_VAL,
> +	MAX_VAL,
> +	MIN_VAL,
> +	STEP_VAL,
> +	SUPPORTED,
> +};
> +
> +struct lwmi_om_priv {
> +	struct blocking_notifier_head nhead;

Is nhead actually used somewhere?

> +	struct component_master_ops *ops;
> +	struct cd01_list cd01_list;
> +	struct device *fw_attr_dev;
> +	struct kset *fw_attr_kset;
> +	struct notifier_block nb;
> +	struct wmi_device *wdev;
> +	struct ida ida;

Is this idea actually used somewhere? If yes then please turn it into a global variable.

> +	int ida_id;
> +};
> +
> +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> +struct tunable_attr_01 {
> +	u32 type_id;
> +	u32 device_id;
> +	u32 feature_id;
> +	u32 store_value;
> +	struct device *dev;
> +	struct capdata01 *capdata;
> +};
> +
> +/* Tunable Attributes */
> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> +				       .feature_id = WMI_FEATURE_ID_CPU_SPL,
> +				       .type_id = WMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> +					.feature_id = WMI_FEATURE_ID_CPU_SPPT,
> +					.type_id = WMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> +					.feature_id = WMI_FEATURE_ID_CPU_FPPT,
> +					.type_id = WMI_TYPE_ID_NONE };
> +
> +struct capdata01_attr_group {
> +	const struct attribute_group *attr_group;
> +	struct tunable_attr_01 *tunable_attr;
> +};
> +
> +#define FW_ATTR_FOLDER "lenovo-wmi-other"

Please use a IDA to give each device a unique name like "lenovo-wmi-otherX". Otherwise
the driver cannot be instantiated multiple times.

> +
> +/* Notifier Methods */
> +int lwmi_om_register_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_register(&om_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
> +
> +int lwmi_om_unregister_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_unregister(&om_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
> +
> +static void devm_lwmi_om_unregister_notifier(void *data)
> +{
> +	struct notifier_block *nb = data;
> +
> +	lwmi_om_unregister_notifier(nb);
> +}
> +
> +int devm_lwmi_om_register_notifier(struct device *dev,
> +				   struct notifier_block *nb)
> +{
> +	int ret;
> +
> +	ret = lwmi_om_register_notifier(nb);
> +	if (ret < 0)
> +		return ret;
> +
> +	return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier,
> +					nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
> +
> +static int lwmi_om_notifier_call(enum thermal_mode *mode)
> +{
> +	int ret;
> +
> +	ret = blocking_notifier_call_chain(&om_chain_head, THERMAL_MODE_EVENT,
> +					   mode);
> +
> +	if (ret != NOTIFY_OK)
> +		return -EINVAL;

Better remove the NOTIFY_STOP_MASK so that clients can return NOTIFY_STOP:

         (ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK

> +
> +	if (*mode < SMARTFAN_MODE_QUIET || *mode > SMARTFAN_MODE_CUSTOM)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +/* Attribute Methods */
> +/*

/* -> /**

The same applies to the other kernel doc comments as well.

> + * int_type_show() - Emit the data type for an integer attribute
> + * @kobj: Pointer to the driver object.
> + * @kobj_attribute: Pointer to the attribute calling this function.
> + * @buf: The buffer to write to.
> + *
> + * Returns: Number of characters written to buf.
> + */
> +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
> +			     char *buf)
> +{
> +	return sysfs_emit(buf, "integer\n");
> +}
> +
> +/*
> + * attr_capdata01_get - Get the data of the specified attribute
> + * from lwmi_om->cd01.
> + * @tunable_attr: The attribute to be populated.
> + *
> + * Returns: Either a pointer to capability data, or NULL.
> + */
> +static struct capdata01 *
> +attr_capdata01_get_data(struct lwmi_om_priv *priv,
> +			struct tunable_attr_01 *tunable_attr,
> +			enum thermal_mode mode)
> +{
> +	u32 attribute_id =
> +		FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> +		FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> +		FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> +		FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +	int idx;
> +
> +	for (idx = 0; idx < priv->cd01_list.count; idx++) {
> +		if (!priv->cd01_list.data[idx])
> +			continue;
> +
> +		if (priv->cd01_list.data[idx]->id != attribute_id)
> +			continue;
> +		return priv->cd01_list.data[idx];
> +	}
> +	return NULL;
> +}
> +
> +/**
> + * attr_capdata01_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.
> + * @tunable_attr: The attribute to be read.
> + * @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 number of characters written to buf, or an error.
> + */
> +static ssize_t attr_capdata01_show(struct kobject *kobj,
> +				   struct kobj_attribute *kattr, char *buf,
> +				   struct tunable_attr_01 *tunable_attr,
> +				   enum attribute_property prop)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> +	struct capdata01 *capdata;
> +	int value;
> +
> +	if (!priv)
> +		return -ENODEV;

Is this check really necessary? If not then please remove it.

> +
> +	capdata = attr_capdata01_get_data(priv, tunable_attr,
> +					  SMARTFAN_MODE_CUSTOM);
> +
> +	if (!capdata)
> +		return -ENODEV;
> +
> +	switch (prop) {
> +	case DEFAULT_VAL:
> +		value = capdata->default_value;
> +		break;
> +	case MAX_VAL:
> +		value = capdata->max_value;
> +		break;
> +	case MIN_VAL:
> +		value = capdata->min_value;
> +		break;
> +	case STEP_VAL:
> +		value = capdata->step;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return sysfs_emit(buf, "%d\n", value);
> +}
> +
> +/* 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.
> + * @tunable_attr: The attribute to be stored.
> + *
> + * 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.
> + */
> +static ssize_t attr_current_value_store(struct kobject *kobj,
> +					struct kobj_attribute *kattr,
> +					const char *buf, size_t count,
> +					struct tunable_attr_01 *tunable_attr)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> +	struct wmi_method_args_32 args;
> +	struct capdata01 *capdata;
> +	enum thermal_mode mode;
> +	u32 attribute_id;
> +	u32 value;
> +	int err;
> +
> +	if (!priv)
> +		return -ENODEV;

Same as above.

> +
> +	err = lwmi_om_notifier_call(&mode);
> +	if (err)
> +		return err;
> +
> +	if (mode != SMARTFAN_MODE_CUSTOM)
> +		return -EINVAL;

Better return -EBUSY here to signal userspace that the underlying device currently
cannot process this request.

> +
> +	capdata = attr_capdata01_get_data(priv, tunable_attr, mode);
> +
> +	if (!capdata)
> +		return -ENODEV;
> +
> +	attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> +		       FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> +		       FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> +		       FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> +	err = kstrtouint(buf, 10, &value);
> +	if (err)
> +		return err;
> +
> +	if (value < capdata->min_value || value > capdata->max_value)
> +		return -EINVAL;
> +
> +	args.arg0 = attribute_id;
> +	args.arg1 = value;
> +
> +	err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_SET,
> +				       (unsigned char *)&args, sizeof(args),
> +				       NULL);
> +
> +	if (err)
> +		return err;
> +
> +	tunable_attr->store_value = value;

Is store_value actually used somewhere? If no then please remove.

> +	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.
> + * @tunable_attr: The 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 number of characters written to buf, or an error.
> + */
> +static ssize_t attr_current_value_show(struct kobject *kobj,
> +				       struct kobj_attribute *kattr, char *buf,
> +				       struct tunable_attr_01 *tunable_attr)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> +	struct wmi_method_args_32 args;
> +	enum thermal_mode mode;
> +	u32 attribute_id;
> +	int retval;
> +	int err;
> +
> +	if (!priv)
> +		return -ENODEV;

Same as above.

> +
> +	err = lwmi_om_notifier_call(&mode);
> +	if (err)
> +		return err;
> +
> +	attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> +		       FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> +		       FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> +		       FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> +	args.arg0 = attribute_id;
> +
> +	err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_GET,
> +				       (unsigned char *)&args, sizeof(args),
> +				       &retval);
> +
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buf, "%d\n", retval);
> +}
> +
> +/* Attribute macros */
> +#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 *kattr, char *buf) \
> +	{                                                                      \
> +		return sysfs_emit(buf, _fmt, _val);                            \
> +	}                                                                      \
> +	static struct kobj_attribute attr_##_attrname##_##_prop =              \
> +		__LL_ATTR_RO(_attrname, _prop)
> +
> +/* Attribute current value read/write */
> +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname)                            \
> +	static ssize_t _attrname##_current_value_store(                        \
> +		struct kobject *kobj, struct kobj_attribute *kattr,            \
> +		const char *buf, size_t count)                                 \
> +	{                                                                      \
> +		return attr_current_value_store(kobj, kattr, buf, count,       \
> +						&_attrname);                   \
> +	}                                                                      \
> +	static ssize_t _attrname##_current_value_show(                         \
> +		struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> +	{                                                                      \
> +		return attr_current_value_show(kobj, kattr, buf, &_attrname);  \
> +	}                                                                      \
> +	static struct kobj_attribute attr_##_attrname##_current_value =        \
> +		__LL_ATTR_RW(_attrname, current_value)
> +
> +/* Attribute property read only */
> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type)                    \
> +	static ssize_t _attrname##_##_prop##_show(                             \
> +		struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> +	{                                                                      \
> +		return attr_capdata01_show(kobj, kattr, buf, &_attrname,       \
> +					   _prop_type);                        \
> +	}                                                                      \
> +	static struct kobj_attribute attr_##_attrname##_##_prop =              \
> +		__LL_ATTR_RO(_attrname, _prop)
> +
> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname)     \
> +	__LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname);                   \
> +	__LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL);  \
> +	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);   \
> +	__LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL);          \
> +	__LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL);          \
> +	__LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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            \
> +	}
> +
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> +			    "Set the CPU sustained power limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> +			    "Set the CPU slow package power tracking limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> +			    "Set the CPU fast package power tracking limit");
> +
> +static struct capdata01_attr_group capdata01_attr_groups[] = {
> +	{ &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> +	{ &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> +	{ &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> +	{},
> +};
> +
> +/*
> + * lwmi_om_fw_attr_add() - Registers all capdata01_attr_groups[] attributes as
> + * firmware_attributes_class members.
> + * @priv: The Other Mode driver data.
> + *
> + * Returns: Either 0, or an error.
> + */
> +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
> +{
> +	int err, i;
> +
> +	ida_init(&priv->ida);
> +	priv->ida_id = ida_alloc(&priv->ida, GFP_KERNEL);
> +	if (priv->ida_id < 0)
> +		return priv->ida_id;
> +
> +	priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
> +					  MKDEV(0, 0), NULL, "%s",
> +					  FW_ATTR_FOLDER);
> +	if (IS_ERR(priv->fw_attr_dev)) {
> +		err = PTR_ERR(priv->fw_attr_dev);
> +		return err;
> +	}
> +
> +	priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> +						 &priv->fw_attr_dev->kobj);
> +	if (!priv->fw_attr_kset) {
> +		err = -ENOMEM;
> +		goto err_destroy_classdev;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> +		err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> +					 capdata01_attr_groups[i].attr_group);
> +		if (err) {
> +			pr_debug("Failed to create sysfs-group for %s: %d\n",
> +				 capdata01_attr_groups[i].attr_group->name,
> +				 err);
> +			goto err_remove_groups;
> +		}
> +		capdata01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;

Since you already know which attributes are supported, maybe it would make sense to only
create attributes which are supported on a given machine?

> +	}
> +	return 0;
> +
> +err_remove_groups:
> +	ida_free(&priv->ida, priv->ida_id);

The IDA should be freed even when the class device failed to register. But such an IDA
should be a global variable anyway.

> +	while (i-- >= 0) {
> +		sysfs_remove_group(&priv->fw_attr_kset->kobj,
> +				   capdata01_attr_groups[i].attr_group);
> +	}
> +	kset_unregister(priv->fw_attr_kset);
> +
> +err_destroy_classdev:
> +	device_unregister(priv->fw_attr_dev);
> +	return err;
> +}
> +
> +/*
> + * lwmi_om_fw_attr_remove() - Unregisters all capdata01_attr_groups[] attributes as
> + * firmware_attributes_class members.
> + * @priv: The Other Mode driver data.
> + *
> + */
> +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
> +{
> +	int size = ARRAY_SIZE(capdata01_attr_groups);
> +
> +	while (--size >= 0) {

Please use a for-loop here.

Thanks,
Armin Wolf

> +		sysfs_remove_group(&priv->fw_attr_kset->kobj,
> +				   capdata01_attr_groups[size].attr_group);
> +	}
> +	kset_unregister(priv->fw_attr_kset);
> +	device_unregister(priv->fw_attr_dev);
> +}
> +
> +static int lwmi_om_master_bind(struct device *dev)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = component_bind_all(dev, &priv->cd01_list);
> +	if (ret)
> +		return ret;
> +
> +	return lwmi_om_fw_attr_add(priv);
> +}
> +
> +static void lwmi_om_master_unbind(struct device *dev)
> +{
> +	component_unbind_all(dev, NULL);

Please remove the firmware attributes here too since otherwise the driver will crash
should another compatible component bind to this master afterwards.

> +}
> +
> +static const struct component_master_ops lwmi_om_master_ops = {
> +	.bind = lwmi_om_master_bind,
> +	.unbind = lwmi_om_master_unbind,
> +};
> +
> +static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct component_match *master_match = NULL;
> +	struct lwmi_om_priv *priv;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->wdev = wdev;
> +	dev_set_drvdata(&wdev->dev, priv);
> +
> +	component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
> +	if (IS_ERR(master_match))
> +		return PTR_ERR(master_match);
> +
> +	return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
> +					       master_match);
> +}
> +
> +static void lwmi_other_remove(struct wmi_device *wdev)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> +	component_master_del(&wdev->dev, &lwmi_om_master_ops);
> +	lwmi_om_fw_attr_remove(priv);
> +	ida_free(&priv->ida, priv->ida_id);
> +}
> +
> +static const struct wmi_device_id lwmi_other_id_table[] = {
> +	{ LENOVO_OTHER_METHOD_GUID, NULL },
> +	{}
> +};
> +
> +static struct wmi_driver lwmi_other_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_other",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_other_id_table,
> +	.probe = lwmi_other_probe,
> +	.remove = lwmi_other_remove,
> +	.no_singleton = true,
> +};
> +
> +module_wmi_driver(lwmi_other_driver);
> +
> +MODULE_IMPORT_NS("LENOVO_WMI_CD01");
> +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> +MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi-other.h b/drivers/platform/x86/lenovo-wmi-other.h
> new file mode 100644
> index 000000000000..9fba35ef1137
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#ifndef _LENOVO_WMI_OTHER_H_
> +#define _LENOVO_WMI_OTHER_H_
> +
> +#include <linux/device.h>
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +
> +int lwmi_om_register_notifier(struct notifier_block *nb);
> +int lwmi_om_unregister_notifier(struct notifier_block *nb);
> +int devm_lwmi_om_register_notifier(struct device *dev,
> +				   struct notifier_block *nb);
> +
> +#endif /* !_LENOVO_WMI_H_ */

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

* Re: [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone WMI Driver
  2025-03-17 14:43 ` [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone " Derek J. Clark
  2025-03-26 19:48   ` Matthew Schwartz
@ 2025-03-27  3:49   ` Armin Wolf
  2025-04-02 20:58     ` Derek John Clark
  2025-03-27 13:56   ` Ilpo Järvinen
  2 siblings, 1 reply; 49+ messages in thread
From: Armin Wolf @ 2025-03-27  3:49 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel

Am 17.03.25 um 15:43 schrieb Derek J. Clark:

> Adds lenovo-wmi-gamezone driver which provides the Lenovo Gamezone WMI
> interface that comes on Lenovo "Gaming Series" hardware. Provides ACPI
> platform profiles over WMI.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
> - Add notifier blocks for the Events and Other Mode drivers.
> - Remove notifier block chain head and all reference to Thermal Mode
>    Event GUID.
> - Add header for Gamezone specific structs and functions.
> - Various fixes from review.
> v3:
> - Use notifier chain to report platform profile changes to any
>    subscribed drivers.
> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
>    chain.
> - Adds support for Extreme Mode profile on supported hardware, as well
>    as a DMI quirk table for some devices that report extreme mode version
>    support but so not have it fully implemented.
> - Update to include recent changes to platform-profile.
> v2:
> - Use devm_kmalloc to ensure driver can be instanced, remove global
>    reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> - Remove GZ_WMI symbol exporting.
> ---
>   MAINTAINERS                                |   2 +
>   drivers/platform/x86/Kconfig               |  13 +
>   drivers/platform/x86/Makefile              |   1 +
>   drivers/platform/x86/lenovo-wmi-gamezone.c | 380 +++++++++++++++++++++
>   drivers/platform/x86/lenovo-wmi-gamezone.h |  18 +
>   5 files changed, 414 insertions(+)
>   create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 87daee6075ad..0416afd997a0 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13168,6 +13168,8 @@ F:	drivers/platform/x86/lenovo-wmi-capdata01.c
>   F:	drivers/platform/x86/lenovo-wmi-capdata01.h
>   F:	drivers/platform/x86/lenovo-wmi-events.c
>   F:	drivers/platform/x86/lenovo-wmi-events.h
> +F:	drivers/platform/x86/lenovo-wmi-gamezone.c
> +F:	drivers/platform/x86/lenovo-wmi-gamezone.h
>   F:	drivers/platform/x86/lenovo-wmi-helpers.c
>   F:	drivers/platform/x86/lenovo-wmi-helpers.h
>   F:	drivers/platform/x86/lenovo-wmi-other.c
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index fc47604e37f7..ecf3246c8fda 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -467,6 +467,19 @@ config LENOVO_WMI_HELPERS
>   	tristate
>   	depends on ACPI_WMI
>
> +config LENOVO_WMI_GAMEZONE
> +	tristate "Lenovo GameZone WMI Driver"
> +	depends on ACPI_WMI

Hi,

please add a "depends on DMI" here.

> +	select ACPI_PLATFORM_PROFILE
> +	select LENOVO_WMI_EVENTS
> +	select LENOVO_WMI_HELPERS
> +	help
> +	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> +	  platform-profile firmware interface to manage power usage.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called lenovo-wmi-gamezone.
> +
>   config LENOVO_WMI_DATA01
>   	tristate
>   	depends on ACPI_WMI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index c6ce3c8594b1..f3e64926a96b 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -71,6 +71,7 @@ obj-$(CONFIG_YT2_1380)		+= lenovo-yoga-tab2-pro-1380-fastcharger.o
>   obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
>   obj-$(CONFIG_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
>   obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
> +obj-$(CONFIG_LENOVO_WMI_GAMEZONE)	+= lenovo-wmi-gamezone.o
>   obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>   obj-$(CONFIG_LENOVO_WMI_TUNING)	+= lenovo-wmi-other.o
>
> diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> new file mode 100644
> index 000000000000..9d453a836227
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> @@ -0,0 +1,380 @@
> +// 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/dmi.h>
> +#include <linux/list.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>

Please also include linux/acpi.h, linux/export.h and linux/module.h.

> +
> +#include "lenovo-wmi-events.h"
> +#include "lenovo-wmi-gamezone.h"
> +#include "lenovo-wmi-helpers.h"
> +#include "lenovo-wmi-other.h"
> +
> +/* Interface GUIDs */
> +#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 BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> +
> +struct lwmi_event_priv {
> +	enum thermal_mode current_mode;
> +	struct wmi_device *wdev;
> +	bool extreme_supported;
> +	struct device *ppdev; /*platform profile device */
> +	struct notifier_block event_nb;
> +	struct notifier_block mode_nb;
> +};
> +
> +struct quirk_entry {
> +	bool extreme_supported;
> +};
> +
> +static struct quirk_entry quirk_no_extreme_bug = {
> +	.extreme_supported = false,
> +};
> +
> +/* Notifier Methods */
> +/*

/* -> /**, same goes for the other kernel doc comments.

> + * lwmi_gz_mode_call() - Call method for lenovo-wmi-other notifier
> + * block call chain. For THERMAL_MODE_EVENT, returns current_mode
> + *
> + * @nb: The notifier_block registered to lenovo-wmi-other
> + * @cmd: The event triggered by lenovo-wmi-other
> + * @data: The data to be returned by the event.
> + *
> + * Returns: notifier_block status.
> + */
> +static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
> +			     void *data)
> +{
> +	struct lwmi_event_priv *priv;
> +
> +	priv = container_of(nb, struct lwmi_event_priv, mode_nb);
> +	if (!priv)
> +		return NOTIFY_BAD;
> +
> +	switch (cmd) {
> +	case THERMAL_MODE_EVENT:

I think it would be better to have a separate command code (maybe GAMEZONE_GET_THERMAL_MODE) for this
kind of request. Maybe you can define a separate enum for that?

> +		*(enum thermal_mode *)data = priv->current_mode;

I think you need to protect this variable from concurrent accesses. Maybe a spinlock would
be suitable here?

> +		break;

Please return NOTIFY_STOP here to prevent the notifier call chain from calling further.

> +	default:
> +		return NOTIFY_DONE;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +/*
> + * lwmi_gz_event_call() - Call method for lenovo-wmi-events notifier
> + * block call chain. For THERMAL_MODE_EVENT, sets current_mode and
> + * notifies platform_profile of a change.
> + *
> + * @nb: The notifier_block registered to lenovo-wmi-events
> + * @cmd: The event triggered by lenovo-wmi-events
> + * @data: The data to be updated by the event.
> + *
> + * Returns: notifier_block status.
> + */
> +static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
> +			      void *data)
> +{
> +	struct lwmi_event_priv *priv;
> +
> +	priv = container_of(nb, struct lwmi_event_priv, event_nb);
> +	if (!priv)
> +		return NOTIFY_BAD;

This check is unnecessary, please drop it?

> +
> +	switch (cmd) {
> +	case THERMAL_MODE_EVENT:
> +		priv->current_mode = *((enum thermal_mode *)data);

You do not need to explicitly cast void pointers. Also please validate that the event data
is actually a valid thermal mode. This check should IMHO happen inside the event driver itself.

> +		platform_profile_notify(&priv->wdev->dev);

You are supposed to pass the platform profile device as the argument.

> +		break;
> +	default:
> +		return NOTIFY_DONE;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +/* Platform Profile Methods & Setup */
> +/*
> + * lwmi_gz_platform_profile_supported() - Gets the version of the WMI
> + * interface to determine the support level.
> + *
> + * @wdev: The Gamezone WMI device.
> + * @supported: Pointer to return the support level with.
> + *
> + * Returns: 0, or an error.
> + */
> +static int lwmi_gz_platform_profile_supported(struct wmi_device *wdev,
> +					      int *supported)
> +{
> +	return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP,
> +					0, 0, supported);
> +}
> +
> +/*
> + * lwmi_gz_thermal_mode_get() - Gets the currently set thermal mode from
> + * the Gamezone WMI interface.
> + *
> + * @wdev: The Gamezone WMI device.
> + * @mode: Pointer to return the thermal mode with.
> + *
> + * Returns: 0, or an error.
> + */
> +static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
> +				    enum thermal_mode *mode)
> +{
> +	return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET,
> +					0, 0, mode);
> +}
> +
> +static int lwmi_gz_profile_get(struct device *dev,
> +			       enum platform_profile_option *profile)
> +{
> +	struct lwmi_event_priv *priv = dev_get_drvdata(dev);
> +	enum thermal_mode mode;
> +	int ret;
> +
> +	ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode);
> +	if (ret)
> +		return ret;
> +
> +	switch (mode) {
> +	case SMARTFAN_MODE_QUIET:
> +		*profile = PLATFORM_PROFILE_LOW_POWER;
> +		break;
> +	case SMARTFAN_MODE_BALANCED:
> +		*profile = PLATFORM_PROFILE_BALANCED;
> +		break;
> +	case SMARTFAN_MODE_PERFORMANCE:
> +		if (priv->extreme_supported) {
> +			*profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> +			break;
> +		}
> +		*profile = PLATFORM_PROFILE_PERFORMANCE;
> +		break;
> +	case SMARTFAN_MODE_EXTREME:
> +		*profile = PLATFORM_PROFILE_PERFORMANCE;
> +		break;
> +	case SMARTFAN_MODE_CUSTOM:
> +		*profile = PLATFORM_PROFILE_CUSTOM;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	priv->current_mode = mode;
> +
> +	return 0;
> +}
> +
> +static int lwmi_gz_profile_set(struct device *dev,
> +			       enum platform_profile_option profile)
> +{
> +	struct lwmi_event_priv *priv = dev_get_drvdata(dev);
> +	struct wmi_method_args_32 args;
> +	enum thermal_mode mode;
> +	int ret;
> +
> +	switch (profile) {
> +	case PLATFORM_PROFILE_LOW_POWER:
> +		mode = SMARTFAN_MODE_QUIET;
> +		break;
> +	case PLATFORM_PROFILE_BALANCED:
> +		mode = SMARTFAN_MODE_BALANCED;
> +		break;
> +	case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> +		mode = SMARTFAN_MODE_PERFORMANCE;
> +		break;
> +	case PLATFORM_PROFILE_PERFORMANCE:
> +		if (priv->extreme_supported) {
> +			mode = SMARTFAN_MODE_EXTREME;
> +			break;
> +		}
> +		mode = SMARTFAN_MODE_PERFORMANCE;
> +		break;
> +	case PLATFORM_PROFILE_CUSTOM:
> +		mode = SMARTFAN_MODE_CUSTOM;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	args.arg0 = mode;
> +
> +	ret = lwmi_dev_evaluate_method(priv->wdev, 0x0,
> +				       WMI_METHOD_ID_SMARTFAN_SET,
> +				       (unsigned char *)&args, sizeof(args),
> +				       NULL);
> +	if (ret)
> +		return ret;
> +
> +	priv->current_mode = mode;
> +
> +	return 0;
> +}
> +
> +static const struct dmi_system_id fwbug_list[] = {
> +	{
> +		.ident = "Legion Go 8APU1",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> +		},
> +		.driver_data = &quirk_no_extreme_bug,
> +	},
> +	{
> +		.ident = "Legion Go S 8ARP1",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> +		},
> +		.driver_data = &quirk_no_extreme_bug,
> +	},
> +	{
> +		.ident = "Legion Go S 8APU1",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> +		},
> +		.driver_data = &quirk_no_extreme_bug,
> +	},
> +	{},
> +
> +};
> +
> +/*
> + * extreme_supported() - Evaluate if a device supports extreme thermal mode.
> + * For devices that have a profile_support_ver of 6 or greater a DMI check
> + * is done. Some devices report a version that supports extreme mode but
> + * have an incomplete entry in the BIOS. To ensure this cannot be set, they
> + * are quirked to prevent assignment.
> + *
> + * @profile_support_ver: Version of WMI interface provided by
> + * lwmi_gz_platform_profile_supported.
> + *
> + * Returns: bool
> + */
> +static bool extreme_supported(int profile_support_ver)
> +{
> +	const struct dmi_system_id *dmi_id;
> +	struct quirk_entry *quirks;
> +
> +	if (profile_support_ver < 6)
> +		return false;
> +
> +	dmi_id = dmi_first_match(fwbug_list);
> +	if (!dmi_id)
> +		return true;
> +
> +	quirks = dmi_id->driver_data;
> +	return quirks->extreme_supported;
> +}
> +
> +static int lwmi_platform_profile_probe(void *drvdata, unsigned long *choices)
> +{
> +	struct lwmi_event_priv *priv = drvdata;
> +	int profile_support_ver;
> +	int ret;
> +
> +	ret = lwmi_gz_platform_profile_supported(priv->wdev,
> +						 &profile_support_ver);
> +	if (ret)
> +		return ret;
> +
> +	if (profile_support_ver < 1)
> +		return -ENODEV;
> +
> +	priv->extreme_supported = extreme_supported(profile_support_ver);
> +
> +	set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> +	set_bit(PLATFORM_PROFILE_BALANCED, choices);
> +	set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> +	set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> +
> +	if (priv->extreme_supported)
> +		set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> +
> +	return 0;
> +}
> +
> +static const struct platform_profile_ops lwmi_gz_platform_profile_ops = {
> +	.probe = lwmi_platform_profile_probe,
> +	.profile_get = lwmi_gz_profile_get,
> +	.profile_set = lwmi_gz_profile_set,
> +};
> +
> +/* Driver Methods */
> +static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct lwmi_event_priv *priv;
> +	int ret;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->event_nb.notifier_call = lwmi_gz_event_call;
> +	ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
> +	if (ret)
> +		return ret;

You should register the event notifier after registering the platform profile or else
a WMI event could arrive before the platform profile was registered, resulting in
platform_profile_notify() being called on a invalid device pointer.

> +
> +	priv->mode_nb.notifier_call = lwmi_gz_mode_call;
> +	ret = devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb);
> +	if (ret)
> +		return ret;
> +
> +	priv->wdev = wdev;
> +	dev_set_drvdata(&wdev->dev, priv);

This should happen before the notifiers are registered or else they might try to access
those values before they are actually initialized.

> +
> +	priv->ppdev = platform_profile_register(&wdev->dev,
> +						"lenovo-wmi-gamezone", priv,
> +						&lwmi_gz_platform_profile_ops);
> +
> +	if (IS_ERR(priv->ppdev))
> +		return -ENODEV;
> +
> +	ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode);
> +	if (ret)
> +		return ret;

The thermal mode should be initialized before any notifiers using it are registered.

> +
> +	return 0;
> +}
> +
> +static const struct wmi_device_id lwmi_gz_id_table[] = { { LENOVO_GAMEZONE_GUID,
> +							   NULL },
> +							 {} };

Please fix the formatting here.

Thanks,
Armin Wolf

> +
> +static struct wmi_driver lwmi_gz_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_gamezone",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_gz_id_table,
> +	.probe = lwmi_gz_probe,
> +	.no_singleton = true,
> +};
> +
> +module_wmi_driver(lwmi_gz_driver);
> +
> +MODULE_IMPORT_NS("LENOVO_WMI_EVENTS");
> +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> +MODULE_IMPORT_NS("LENOVO_WMI_OTHER");
> +MODULE_DEVICE_TABLE(wmi, lwmi_gz_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-wmi-gamezone.h b/drivers/platform/x86/lenovo-wmi-gamezone.h
> new file mode 100644
> index 000000000000..ac536803160b
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#ifndef _LENOVO_WMI_GAMEZONE_H_
> +#define _LENOVO_WMI_GAMEZONE_H_
> +
> +enum thermal_mode {
> +	SMARTFAN_MODE_QUIET = 0x01,
> +	SMARTFAN_MODE_BALANCED = 0x02,
> +	SMARTFAN_MODE_PERFORMANCE = 0x03,
> +	SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> +	SMARTFAN_MODE_CUSTOM = 0xFF,
> +};
> +
> +#endif /* !_LENOVO_WMI_GAMEZONE_H_ */

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

* Re: [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers
  2025-03-17 14:43 [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
                   ` (5 preceding siblings ...)
  2025-03-17 14:43 ` [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone " Derek J. Clark
@ 2025-03-27  3:52 ` Armin Wolf
  6 siblings, 0 replies; 49+ messages in thread
From: Armin Wolf @ 2025-03-27  3:52 UTC (permalink / raw)
  To: Derek J. Clark, Hans de Goede, Ilpo Järvinen
  Cc: Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel

Am 17.03.25 um 15:43 schrieb Derek J. Clark:

> Adds support for the Lenovo "Gaming Series" of laptop hardware that 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
> Mode", and "LENOVO_CAPABILITY_DATA_01" interfaces are implemented, but
> I attempted to structure the driver so that adding the "Custom Mode",
> "Lighting", and other data block interfaces would be trivial in a later
> patches.
>
> 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/platform-driver-x86/20250316230724.100165-1-luke@ljones.dev/
>
> The drivers have been tested by me on the Lenovo Legion Go and Legion Go
> S.

So far the patch series looks promising. The basic architecture seems to be finished by
now, only some implementation details need to be fleshed out.

Thanks,
Armin Wolf

>
> Suggested-by: Mario Limonciello <superm1@kernel.org>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
>   - Added lenovo-wmi-events driver
>   - Added headers for every driver
>   - Fixes requested from v3 review
> v3:
> https://lore.kernel.org/platform-driver-x86/20250225220037.16073-1-derekjohn.clark@gmail.com/
> v2:
> https://lore.kernel.org/platform-driver-x86/20250102004854.14874-1-derekjohn.clark@gmail.com/
> v1:
> https://lore.kernel.org/platform-driver-x86/20241217230645.15027-1-derekjohn.clark@gmail.com/
>
> Derek J. Clark (6):
>    platform/x86: Add lenovo-wmi-* driver Documentation
>    platform/x86: Add lenovo-wmi-helpers
>    platform/x86: Add Lenovo WMI Events Driver
>    platform/x86: Add Lenovo Capability Data 01 WMI Driver
>    platform/x86: Add Lenovo Other Mode WMI Driver
>    platform/x86: Add Lenovo Gamezone WMI Driver
>
>   .../wmi/devices/lenovo-wmi-gamezone.rst       | 203 ++++++
>   .../wmi/devices/lenovo-wmi-other-method.rst   | 108 +++
>   MAINTAINERS                                   |  17 +
>   drivers/platform/x86/Kconfig                  |  40 ++
>   drivers/platform/x86/Makefile                 |   5 +
>   drivers/platform/x86/lenovo-wmi-capdata01.c   | 136 ++++
>   drivers/platform/x86/lenovo-wmi-capdata01.h   |  29 +
>   drivers/platform/x86/lenovo-wmi-events.c      | 132 ++++
>   drivers/platform/x86/lenovo-wmi-events.h      |  21 +
>   drivers/platform/x86/lenovo-wmi-gamezone.c    | 380 +++++++++++
>   drivers/platform/x86/lenovo-wmi-gamezone.h    |  18 +
>   drivers/platform/x86/lenovo-wmi-helpers.c     |  64 ++
>   drivers/platform/x86/lenovo-wmi-helpers.h     |  24 +
>   drivers/platform/x86/lenovo-wmi-other.c       | 626 ++++++++++++++++++
>   drivers/platform/x86/lenovo-wmi-other.h       |  19 +
>   15 files changed, 1822 insertions(+)
>   create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>   create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst
>   create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
>   create mode 100644 drivers/platform/x86/lenovo-wmi-events.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-events.h
>   create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h
>   create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h
>   create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>   create mode 100644 drivers/platform/x86/lenovo-wmi-other.h
>

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

* Re: [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers
  2025-03-17 14:43 ` [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers Derek J. Clark
                     ` (2 preceding siblings ...)
  2025-03-27  0:40   ` Armin Wolf
@ 2025-03-27 12:43   ` Ilpo Järvinen
  2025-04-02 21:22     ` Derek John Clark
  3 siblings, 1 reply; 49+ messages in thread
From: Ilpo Järvinen @ 2025-03-27 12:43 UTC (permalink / raw)
  To: Derek J. Clark
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

On Mon, 17 Mar 2025, Derek J. Clark wrote:

> Adds documentation for all new lenovo-wmi drivers.
> 
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
>  - Changed namespace to LENOVO_WMI_HELPERS from LENOVO_WMI.
>  - Changed filenames to lenovo-wmi-helpers from lenovo-wmi.
>  - Removed structs and functions implemented by other drivers.
> ---
>  MAINTAINERS                               |  2 +
>  drivers/platform/x86/Kconfig              |  4 ++
>  drivers/platform/x86/Makefile             |  1 +
>  drivers/platform/x86/lenovo-wmi-helpers.c | 64 +++++++++++++++++++++++
>  drivers/platform/x86/lenovo-wmi-helpers.h | 24 +++++++++
>  5 files changed, 95 insertions(+)
>  create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c
>  create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 675f4b26426d..3a370a18b806 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>  F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +F:	drivers/platform/x86/lenovo-wmi-helpers.c
> +F:	drivers/platform/x86/lenovo-wmi-helpers.h
>  
>  LENOVO WMI HOTKEY UTILITIES DRIVER
>  M:	Jackie Dong <xy-jackie@139.com>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 43407e76476b..bece1ba61417 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,10 @@ config IBM_RTL
>  	 state = 0 (BIOS SMIs on)
>  	 state = 1 (BIOS SMIs off)
>  
> +config LENOVO_WMI_HELPERS
> +	tristate
> +	depends on ACPI_WMI
> +
>  config IDEAPAD_LAPTOP
>  	tristate "Lenovo IdeaPad Laptop Extras"
>  	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 650dfbebb6c8..5a9f4e94f78b 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>  
>  # Intel
>  obj-y				+= intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x86/lenovo-wmi-helpers.c
> new file mode 100644
> index 000000000000..36d553502223
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-helpers.c
> @@ -0,0 +1,64 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Legion WMI helpers driver.

Please add empty comment row here.

>  The Lenovo Legion WMI interface is
> + * broken up into multiple GUID interfaces that require cross-references
> + * between GUID's for some functionality. The "Custom Method" 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 Method" 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 drivers
> + * uses a common procedure to get data fro the WMI interface, enumerated here.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#include <linux/wmi.h>
> +#include "lenovo-wmi-helpers.h"

Put a space between these lines

> +
> +/*
> + * lwmi_dev_evaluate_method() - Helper function to call wmidev_evaluate_method
> + * for Lenovo WMI device method calls that return an ACPI integer.

This should be a shorter summary and the rest should be put into own 
paragraph between the arguments and Return: 

> + * @wdev: Pointer to the WMI device to be called.
> + * @instance: Instance of the called method.
> + * @method_id: WMI Method ID for the method to be called.
> + * @buf: Buffer of all arguments for the given method_id.
> + * @size: Length of the buffer.
> + * @retval: Pointer for the return value to be assigned.
> + *
> + * Returns: 0, or an error.

Return:

> + */
> +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> +			     u32 method_id, unsigned char *buf, size_t size,
> +			     u32 *retval)
> +{
> +	struct acpi_buffer input = { size, buf };
> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> +	union acpi_object *ret_obj __free(kfree) = NULL;
> +	acpi_status status;
> +
> +	status = wmidev_evaluate_method(wdev, instance, method_id, &input,
> +					&output);
> +
> +	if (ACPI_FAILURE(status))
> +		return -EIO;
> +
> +	if (retval) {
> +		ret_obj = output.pointer;
> +		if (!ret_obj)
> +			return -ENODATA;
> +
> +		if (ret_obj->type != ACPI_TYPE_INTEGER)
> +			return -ENXIO;
> +
> +		*retval = (u32)ret_obj->integer.value;
> +	}
> +	return 0;
> +};
> +EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_method, "LENOVO_WMI_HELPERS");
> +
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.h b/drivers/platform/x86/lenovo-wmi-helpers.h
> new file mode 100644
> index 000000000000..7e0d7870790e
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-helpers.h
> @@ -0,0 +1,24 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *

Please remove this extra line.

> + */
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>

What? Why are these on this side of the ifndef??? And neither is needed by 
this header AFAICT so please drop them.

> +#ifndef _LENOVO_WMI_HELPERS_H_
> +#define _LENOVO_WMI_HELPERS_H_
> +
> +#include <linux/types.h>
> +#include <linux/wmi.h>

This include is not needed (since you're only using it as a pointer). Do a 
forward declaration instead:

struct wmi_device;

> +
> +struct wmi_method_args_32 {
> +	u32 arg0;
> +	u32 arg1;
> +};
> +
> +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> +			     u32 method_id, unsigned char *buf, size_t size,
> +			     u32 *retval);
> +
> +#endif /* !_LENOVO_WMI_HELPERS_H_ */
> 

-- 
 i.


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

* Re: [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver
  2025-03-17 14:43 ` [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver Derek J. Clark
                     ` (2 preceding siblings ...)
  2025-03-27  1:03   ` Armin Wolf
@ 2025-03-27 12:47   ` Ilpo Järvinen
  3 siblings, 0 replies; 49+ messages in thread
From: Ilpo Järvinen @ 2025-03-27 12:47 UTC (permalink / raw)
  To: Derek J. Clark
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

On Mon, 17 Mar 2025, Derek J. Clark wrote:

> Adds lenovo-wmi-events driver. The events driver is designed as a
> general entrypoint for all Lenovo WMI Events. It acts as a notification
> chain head that will process event data and pass it on to registered
> drivers so they can react to the events.
> 
> Currently only the Gamezone interface Thermal Mode Event GUID is
> implemented in this driver. It is documented in the Gamezone
> documentation.
> 
> Suggested-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
>  - Remove the Thermal Mode Event GUID from Gamezone and add this driver.
> ---
>  MAINTAINERS                              |   2 +
>  drivers/platform/x86/Kconfig             |   4 +
>  drivers/platform/x86/Makefile            |   1 +
>  drivers/platform/x86/lenovo-wmi-events.c | 132 +++++++++++++++++++++++
>  drivers/platform/x86/lenovo-wmi-events.h |  21 ++++
>  5 files changed, 160 insertions(+)
>  create mode 100644 drivers/platform/x86/lenovo-wmi-events.c
>  create mode 100644 drivers/platform/x86/lenovo-wmi-events.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3a370a18b806..6dde75922aaf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>  F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +F:	drivers/platform/x86/lenovo-wmi-events.c
> +F:	drivers/platform/x86/lenovo-wmi-events.h
>  F:	drivers/platform/x86/lenovo-wmi-helpers.c
>  F:	drivers/platform/x86/lenovo-wmi-helpers.h
>  
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index bece1ba61417..13b8f4ac5dc5 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -459,6 +459,10 @@ config IBM_RTL
>  	 state = 0 (BIOS SMIs on)
>  	 state = 1 (BIOS SMIs off)
>  
> +config LENOVO_WMI_EVENTS
> +	tristate
> +	depends on ACPI_WMI
> +
>  config LENOVO_WMI_HELPERS
>  	tristate
>  	depends on ACPI_WMI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 5a9f4e94f78b..fc039839286a 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
>  obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>  
>  # Intel
> diff --git a/drivers/platform/x86/lenovo-wmi-events.c b/drivers/platform/x86/lenovo-wmi-events.c
> new file mode 100644
> index 000000000000..3ea0face3c0d
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-events.c
> @@ -0,0 +1,132 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo WMI Events driver. Lenovo WMI interfaces provide various
> + * hardware triggered events that many drivers need to have propagated.
> + * This driver provides a uniform entrypoint for these events so that
> + * any driver that needs to respond to these events can subscribe to a
> + * notifier chain.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/list.h>
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +#include "lenovo-wmi-events.h"
> +
> +/* Interface GUIDs */
> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> +
> +#define LENOVO_WMI_EVENT_DEVICE(guid, type)                        \
> +	.guid_string = (guid), .context = &(enum lwmi_events_type) \
> +	{                                                          \
> +		type                                               \
> +	}
> +
> +static BLOCKING_NOTIFIER_HEAD(events_chain_head);
> +
> +struct lwmi_events_priv {
> +	struct wmi_device *wdev;
> +	enum lwmi_events_type type;
> +};
> +
> +/* Notifier Methods */
> +int lwmi_events_register_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_register(&events_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
> +
> +int lwmi_events_unregister_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_unregister(&events_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS");
> +
> +static void devm_lwmi_events_unregister_notifier(void *data)
> +{
> +	struct notifier_block *nb = data;
> +
> +	lwmi_events_unregister_notifier(nb);
> +}
> +
> +int devm_lwmi_events_register_notifier(struct device *dev,
> +				       struct notifier_block *nb)
> +{
> +	int ret;
> +
> +	ret = lwmi_events_register_notifier(nb);
> +	if (ret < 0)
> +		return ret;
> +
> +	return devm_add_action_or_reset(dev,
> +				devm_lwmi_events_unregister_notifier, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
> +
> +/* Driver Methods */
> +static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj)
> +{
> +	struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev);
> +	int sel_prof;
> +	int ret;
> +
> +	switch (priv->type) {
> +	case THERMAL_MODE_EVENT:
> +		if (obj->type != ACPI_TYPE_INTEGER)
> +			return;
> +
> +		sel_prof = obj->integer.value;
> +		ret = blocking_notifier_call_chain(&events_chain_head,
> +						   THERMAL_MODE_EVENT, &sel_prof);
> +		if (ret == NOTIFY_BAD)
> +			dev_err(&wdev->dev,
> +				"Failed to send notification to call chain for WMI Events\n");
> +		break;
> +	default:
> +		return;
> +	}
> +}
> +
> +static int lwmi_events_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct lwmi_events_priv *priv;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	if (!context)
> +		return -EINVAL;
> +
> +	priv->wdev = wdev;
> +	priv->type = *(enum lwmi_events_type *)context;
> +
> +	dev_set_drvdata(&wdev->dev, priv);
> +	return 0;
> +}
> +
> +static const struct wmi_device_id lwmi_events_id_table[] = {
> +	{ LENOVO_WMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID,
> +				  THERMAL_MODE_EVENT) },
> +	{}
> +};
> +
> +static struct wmi_driver lwmi_events_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_events",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_events_id_table,
> +	.probe = lwmi_events_probe,
> +	.notify = lwmi_events_notify,
> +	.no_singleton = true,
> +};
> +
> +module_wmi_driver(lwmi_events_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo WMI Events Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi-events.h b/drivers/platform/x86/lenovo-wmi-events.h
> new file mode 100644
> index 000000000000..a3fa934eaa10
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-events.h
> @@ -0,0 +1,21 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/notifier.h>
> +#include <linux/types.h>

Includes should appear after the ifndef/define, however, neither of these 
are neaded as you can just forward declare struct notifier_block.

> +
> +#ifndef _LENOVO_WMI_EVENTS_H_
> +#define _LENOVO_WMI_EVENTS_H_
> +
> +enum lwmi_events_type {
> +	THERMAL_MODE_EVENT = 1,
> +};
> +
> +int lwmi_events_register_notifier(struct notifier_block *nb);
> +int lwmi_events_unregister_notifier(struct notifier_block *nb);
> +int devm_lwmi_events_register_notifier(struct device *dev,

Please forward declare struct device too.

> +				       struct notifier_block *nb);
> +
> +#endif /* !_LENOVO_WMI_EVENTS_H_ */
> 

-- 
 i.


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

* Re: [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver
  2025-03-17 14:43 ` [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver Derek J. Clark
  2025-03-26 19:47   ` Matthew Schwartz
  2025-03-27  1:29   ` Armin Wolf
@ 2025-03-27 12:56   ` Ilpo Järvinen
  2025-04-02 21:22     ` Derek John Clark
  2 siblings, 1 reply; 49+ messages in thread
From: Ilpo Järvinen @ 2025-03-27 12:56 UTC (permalink / raw)
  To: Derek J. Clark
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

On Mon, 17 Mar 2025, Derek J. Clark wrote:

> Adds lenovo-wmi-capdata01 driver which provides the
> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode"
> enabled hardware. Provides an interface for querying if a given
> attribute is supported by the hardware, as well as its default_value,
> max_value, min_value, and step increment.
> 
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
>  - Make driver data a private struct, remove references from Other Mode
>    driver.
>  - Don't cache data at device initialization. Instead, on component bind,
>    cache the data on a member variable of the Other Mode driver data
>    passed as a void pointer.
>  - Add header file for capdata01 structs.
>  - Add new struct to pass capdata01 array data and array length to Other
>    Mode.
> v3:
> - Add as component to lenovo-wmi-other driver.
> v2:
> - Use devm_kmalloc to ensure driver can be instanced, remove global
>   reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
> - Use list to get the lenovo_wmi_cd01_priv instance in
>   lenovo_wmi_capdata01_get as none of the data provided by the macros
>   that will use it can pass a member of the struct for use in
>   container_of.
> ---
>  MAINTAINERS                                 |   2 +
>  drivers/platform/x86/Kconfig                |   4 +
>  drivers/platform/x86/Makefile               |   1 +
>  drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++
>  drivers/platform/x86/lenovo-wmi-capdata01.h |  29 +++++
>  5 files changed, 172 insertions(+)
>  create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>  create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6dde75922aaf..56ead241a053 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,8 @@ L:	platform-driver-x86@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>  F:	Documentation/wmi/devices/lenovo-wmi-other.rst
> +F:	drivers/platform/x86/lenovo-wmi-capdata01.c
> +F:	drivers/platform/x86/lenovo-wmi-capdata01.h
>  F:	drivers/platform/x86/lenovo-wmi-events.c
>  F:	drivers/platform/x86/lenovo-wmi-events.h
>  F:	drivers/platform/x86/lenovo-wmi-helpers.c
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 13b8f4ac5dc5..64663667f0cb 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
>  	tristate
>  	depends on ACPI_WMI
>  
> +config LENOVO_WMI_DATA01
> +	tristate
> +	depends on ACPI_WMI
> +
>  config IDEAPAD_LAPTOP
>  	tristate "Lenovo IdeaPad Laptop Extras"
>  	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index fc039839286a..7a35c77221b7 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
>  obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
>  obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>  
> diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
> new file mode 100644
> index 000000000000..b6876611ffd9
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> @@ -0,0 +1,136 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * LENOVO_CAPABILITY_DATA_01 WMI data block driver.

Add a empty comment line here, you might want to rephrase the opening of 
the paragraph after splitting these apart.

> This interface provides
> + * information on tunable attributes used by the "Other Mode" WMI interface,
> + * including if it is supported by the hardware, the default_value, max_value,
> + * min_value, and step increment.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/component.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/gfp_types.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>

Add an empty line here please.

> +#include "lenovo-wmi-capdata01.h"
> +
> +/* Interface GUIDs */
> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +
> +struct lwmi_cd01_priv {
> +	struct wmi_device *wdev;
> +};
> +
> +/*
> + * lenovo_cd01_component_bind() - On master bind, caches all capability data on
> + * the master device.

Is this "On master bind" something that the caller should be doing? IMO, 
that would belong to description paragraph instead of the function 
summary.

> + * @cd01_dev: Pointer to the capability data 01 parent device.
> + * @om_dev: Pointer to the other mode parent device.
> + * @data: capdata01_list object pointer to return the capability data with.
> + *
> + * Returns: 0, or an error.

Return:

> + */
> +static int lenovo_cd01_component_bind(struct device *cd01_dev,
> +				      struct device *om_dev, void *data)
> +{
> +	struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
> +	int count, idx;
> +
> +	if (!priv)
> +		return -ENODEV;
> +
> +	count = wmidev_instance_count(priv->wdev);
> +
> +	if (count == 0)
> +		return -EINVAL;
> +
> +	((struct cd01_list *)data)->count = count;

Please create a local variable with the correct type and since data is 
void *, you don't need to cast it while assigning to that local variable.

> +	((struct cd01_list *)data)->data = devm_kmalloc_array(om_dev, count,
> +							      sizeof(struct capdata01 *),

sizeof() should preferrably take the type directly from ->data (with the 
correct amount of * chars).

> +							      GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	for (idx = 0; idx < count; idx++) {
> +		union acpi_object *ret_obj __free(kfree) = NULL;
> +
> +		ret_obj = wmidev_block_query(priv->wdev, idx);
> +		if (!ret_obj) {
> +			((struct cd01_list *)data)->data[idx] = NULL;
> +			continue;
> +		}
> +		if (ret_obj->type != ACPI_TYPE_BUFFER) {
> +			((struct cd01_list *)data)->data[idx] = NULL;
> +			continue;
> +		}
> +
> +		if (ret_obj->buffer.length != sizeof(struct capdata01)) {

You could consider joining these 3 if()s with || to avoid having to 
repeat the NULL assignment and continue.

> +			((struct cd01_list *)data)->data[idx] = NULL;
> +			continue;
> +		}
> +
> +		((struct cd01_list *)data)->data[idx] =
> +			devm_kmemdup(om_dev, ret_obj->buffer.pointer,
> +				     ret_obj->buffer.length, GFP_KERNEL);
> +	}
> +	return 0;
> +}
> +
> +static const struct component_ops lenovo_cd01_component_ops = {
> +	.bind = lenovo_cd01_component_bind,
> +};
> +
> +static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
> +
> +{
> +	struct lwmi_cd01_priv *priv;
> +	int ret;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->wdev = wdev;
> +	dev_set_drvdata(&wdev->dev, priv);
> +
> +	ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
> +
> +	return ret;
> +}
> +
> +static void lwmi_cd01_remove(struct wmi_device *wdev)
> +{
> +	component_del(&wdev->dev, &lenovo_cd01_component_ops);
> +}
> +
> +static const struct wmi_device_id lwmi_cd01_id_table[] = {
> +	{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> +	{}
> +};
> +
> +static struct wmi_driver lwmi_cd01_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_cd01",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_cd01_id_table,
> +	.probe = lwmi_cd01_probe,
> +	.remove = lwmi_cd01_remove,
> +	.no_singleton = true,
> +};
> +
> +int lwmi_cd01_match(struct device *dev, void *data)
> +{
> +	return dev->driver == &lwmi_cd01_driver.driver;
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
> +
> +module_wmi_driver(lwmi_cd01_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, lwmi_cd01_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-wmi-capdata01.h b/drivers/platform/x86/lenovo-wmi-capdata01.h
> new file mode 100644
> index 000000000000..c7067a8d0398
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *

Extra line

> + */
> +
> +#ifndef _LENOVO_WMI_CAPDATA01_H_
> +#define _LENOVO_WMI_CAPDATA01_H_
> +
> +#include <linux/device.h>

Please fwd declare struct device instead.

> +#include <linux/types.h>
> +
> +struct capdata01 {
> +	u32 id;
> +	u32 supported;
> +	u32 default_value;
> +	u32 step;
> +	u32 min_value;
> +	u32 max_value;
> +};
> +
> +struct cd01_list {
> +	struct capdata01 **data;
> +	int count;
> +};
> +
> +int lwmi_cd01_match(struct device *dev, void *data);
> +
> +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
> 

-- 
 i.


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

* Re: [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode WMI Driver
  2025-03-17 14:43 ` [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode " Derek J. Clark
  2025-03-26 19:48   ` Matthew Schwartz
  2025-03-27  3:28   ` Armin Wolf
@ 2025-03-27 13:49   ` Ilpo Järvinen
  2025-04-02 21:22     ` Derek John Clark
  2 siblings, 1 reply; 49+ messages in thread
From: Ilpo Järvinen @ 2025-03-27 13:49 UTC (permalink / raw)
  To: Derek J. Clark
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

On Mon, 17 Mar 2025, Derek J. Clark wrote:

> Adds lenovo-wmi-other driver which provides the Lenovo "Other Mode" WMI
> interface that comes on some Lenovo "Gaming Series" hardware. Provides a
> firmware-attributes class which enables the use of tunable knobs for SPL,
> SPPT, and FPPT.
> 
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
> - Treat Other Mode as a notifier chain head, use the notifier chain to
>   get the current mode from Gamezone.
> - Add header file for Other Mode specific structs and finctions.
> - Use component master bind to cache the capdata01 array locally.
> - Drop all reference to external driver private data structs.
> - Various fixes from review.
> v3:
> - Add notifier block and store result for getting the Gamezone interface
>   profile changes.
> - Add driver as master component of capdata01 driver.
> - Use FIELD_PREP where appropriate.
> - Move macros and associated functions out of lemovo-wmi.h that are only
>   used by this driver.
> v2:
> - Use devm_kmalloc to ensure driver can be instanced, remove global
>   reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> - Use list to get the lenovo_wmi_om_priv instance in some macro
>   called functions as the data provided by the macros that use it
>   doesn't pass a member of the struct for use in container_of.
> - Do not rely on GameZone interface to grab the current fan mode.
> ---
>  MAINTAINERS                             |   2 +
>  drivers/platform/x86/Kconfig            |  15 +
>  drivers/platform/x86/Makefile           |   1 +
>  drivers/platform/x86/lenovo-wmi-other.c | 626 ++++++++++++++++++++++++
>  drivers/platform/x86/lenovo-wmi-other.h |  19 +
>  5 files changed, 663 insertions(+)
>  create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>  create mode 100644 drivers/platform/x86/lenovo-wmi-other.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 56ead241a053..87daee6075ad 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13170,6 +13170,8 @@ F:	drivers/platform/x86/lenovo-wmi-events.c
>  F:	drivers/platform/x86/lenovo-wmi-events.h
>  F:	drivers/platform/x86/lenovo-wmi-helpers.c
>  F:	drivers/platform/x86/lenovo-wmi-helpers.h
> +F:	drivers/platform/x86/lenovo-wmi-other.c
> +F:	drivers/platform/x86/lenovo-wmi-other.h
>  
>  LENOVO WMI HOTKEY UTILITIES DRIVER
>  M:	Jackie Dong <xy-jackie@139.com>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 64663667f0cb..fc47604e37f7 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -471,6 +471,21 @@ config LENOVO_WMI_DATA01
>  	tristate
>  	depends on ACPI_WMI
>  
> +config LENOVO_WMI_TUNING
> +	tristate "Lenovo Other Mode WMI Driver"
> +	depends on ACPI_WMI
> +	select FW_ATTR_CLASS
> +	select LENOVO_WMI_DATA01
> +	select LENOVO_WMI_EVENTS
> +	select LENOVO_WMI_HELPERS
> +	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-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 7a35c77221b7..c6ce3c8594b1 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -72,6 +72,7 @@ obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
>  obj-$(CONFIG_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
>  obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
>  obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
> +obj-$(CONFIG_LENOVO_WMI_TUNING)	+= lenovo-wmi-other.o
>  
>  # Intel
>  obj-y				+= intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> new file mode 100644
> index 000000000000..b517e45338e0
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.c
> @@ -0,0 +1,626 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes

Again, put a summary on own line as mentioned for the other files.

> + * class to expose the various WMI functions provided by the "Other Mode" 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 laptop
> + * devices. Each attribute exposed by the "Other Mode"" 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.
> + *
> + * 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/bitfield.h>
> +#include <linux/cleanup.h>
> +#include <linux/component.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/gfp_types.h>
> +#include <linux/idr.h>
> +#include <linux/kobject.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +#include "lenovo-wmi-capdata01.h"
> +#include "lenovo-wmi-events.h"
> +#include "lenovo-wmi-gamezone.h"
> +#include "lenovo-wmi-helpers.h"
> +#include "lenovo-wmi-other.h"
> +#include "firmware_attributes_class.h"
> +
> +/* Interface GUIDs */
> +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> +
> +/* Device IDs */
> +#define WMI_DEVICE_ID_CPU 0x01

Two comments above add no value and can be dropped.

> +
> +/* WMI_DEVICE_ID_CPU feature IDs */
> +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */
> +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */
> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> +
> +/* Type IDs*/

This is missing space but it doesn't IMO add any value, so just drop it.

> +#define WMI_TYPE_ID_NONE 0x00
> +
> +/* Method IDs */
> +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
> +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
> +
> +/* Attribute ID bitmasks */

Neither of two comments for the defines seem to provide much value.

> +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
> +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
> +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
> +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)

Please align the GENMASK()s

> +static BLOCKING_NOTIFIER_HEAD(om_chain_head);
> +
> +enum attribute_property {
> +	DEFAULT_VAL,
> +	MAX_VAL,
> +	MIN_VAL,
> +	STEP_VAL,
> +	SUPPORTED,
> +};
> +
> +struct lwmi_om_priv {
> +	struct blocking_notifier_head nhead;
> +	struct component_master_ops *ops;
> +	struct cd01_list cd01_list;
> +	struct device *fw_attr_dev;
> +	struct kset *fw_attr_kset;
> +	struct notifier_block nb;
> +	struct wmi_device *wdev;
> +	struct ida ida;
> +	int ida_id;
> +};
> +
> +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> +struct tunable_attr_01 {
> +	u32 type_id;
> +	u32 device_id;
> +	u32 feature_id;
> +	u32 store_value;
> +	struct device *dev;
> +	struct capdata01 *capdata;
> +};
> +
> +/* Tunable Attributes */
> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> +				       .feature_id = WMI_FEATURE_ID_CPU_SPL,
> +				       .type_id = WMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> +					.feature_id = WMI_FEATURE_ID_CPU_SPPT,
> +					.type_id = WMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> +					.feature_id = WMI_FEATURE_ID_CPU_FPPT,
> +					.type_id = WMI_TYPE_ID_NONE };
> +
> +struct capdata01_attr_group {
> +	const struct attribute_group *attr_group;
> +	struct tunable_attr_01 *tunable_attr;
> +};
> +
> +#define FW_ATTR_FOLDER "lenovo-wmi-other"

Please add prefix and move to the top where the other defines are.

> +
> +/* Notifier Methods */
> +int lwmi_om_register_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_register(&om_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
> +
> +int lwmi_om_unregister_notifier(struct notifier_block *nb)
> +{
> +	return blocking_notifier_chain_unregister(&om_chain_head, nb);
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
> +
> +static void devm_lwmi_om_unregister_notifier(void *data)
> +{
> +	struct notifier_block *nb = data;
> +
> +	lwmi_om_unregister_notifier(nb);
> +}
> +
> +int devm_lwmi_om_register_notifier(struct device *dev,
> +				   struct notifier_block *nb)

I'd just put this on a single line.

> +{
> +	int ret;
> +
> +	ret = lwmi_om_register_notifier(nb);
> +	if (ret < 0)
> +		return ret;
> +
> +	return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier,
> +					nb);

80 chars isn't a hard rule so this and a few other ones could be put 
to a single line.

> +}
> +EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
> +
> +static int lwmi_om_notifier_call(enum thermal_mode *mode)
> +{
> +	int ret;
> +
> +	ret = blocking_notifier_call_chain(&om_chain_head, THERMAL_MODE_EVENT,
> +					   mode);

To one line?

> +

Please don't leave empty line between the func call and it's error 
handling.

> +	if (ret != NOTIFY_OK)
> +		return -EINVAL;
> +
> +	if (*mode < SMARTFAN_MODE_QUIET || *mode > SMARTFAN_MODE_CUSTOM)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +/* Attribute Methods */
> +/*
> + * int_type_show() - Emit the data type for an integer attribute
> + * @kobj: Pointer to the driver object.
> + * @kobj_attribute: Pointer to the attribute calling this function.
> + * @buf: The buffer to write to.
> + *
> + * Returns: Number of characters written to buf.

Return:

> + */
> +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
> +			     char *buf)
> +{
> +	return sysfs_emit(buf, "integer\n");
> +}
> +
> +/*
> + * attr_capdata01_get - Get the data of the specified attribute
> + * from lwmi_om->cd01.

"from lwmi_om->cd01" sounds a bit cryptic.

> + * @tunable_attr: The attribute to be populated.
> + *
> + * Returns: Either a pointer to capability data, or NULL.

Return:

> + */
> +static struct capdata01 *
> +attr_capdata01_get_data(struct lwmi_om_priv *priv,
> +			struct tunable_attr_01 *tunable_attr,
> +			enum thermal_mode mode)
> +{
> +	u32 attribute_id =
> +		FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> +		FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> +		FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> +		FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +	int idx;
> +
> +	for (idx = 0; idx < priv->cd01_list.count; idx++) {
> +		if (!priv->cd01_list.data[idx])
> +			continue;
> +
> +		if (priv->cd01_list.data[idx]->id != attribute_id)
> +			continue;
> +		return priv->cd01_list.data[idx];
> +	}
> +	return NULL;
> +}
> +
> +/**
> + * attr_capdata01_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.
> + * @tunable_attr: The attribute to be read.
> + * @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 number of characters written to buf, or an error.
> + */
> +static ssize_t attr_capdata01_show(struct kobject *kobj,
> +				   struct kobj_attribute *kattr, char *buf,
> +				   struct tunable_attr_01 *tunable_attr,
> +				   enum attribute_property prop)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> +	struct capdata01 *capdata;
> +	int value;
> +
> +	if (!priv)
> +		return -ENODEV;
> +
> +	capdata = attr_capdata01_get_data(priv, tunable_attr,
> +					  SMARTFAN_MODE_CUSTOM);
> +
> +	if (!capdata)
> +		return -ENODEV;
> +
> +	switch (prop) {
> +	case DEFAULT_VAL:
> +		value = capdata->default_value;
> +		break;
> +	case MAX_VAL:
> +		value = capdata->max_value;
> +		break;
> +	case MIN_VAL:
> +		value = capdata->min_value;
> +		break;
> +	case STEP_VAL:
> +		value = capdata->step;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return sysfs_emit(buf, "%d\n", value);
> +}
> +
> +/* 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.
> + * @tunable_attr: The attribute to be stored.
> + *
> + * 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.
> + */
> +static ssize_t attr_current_value_store(struct kobject *kobj,
> +					struct kobj_attribute *kattr,
> +					const char *buf, size_t count,
> +					struct tunable_attr_01 *tunable_attr)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> +	struct wmi_method_args_32 args;
> +	struct capdata01 *capdata;
> +	enum thermal_mode mode;
> +	u32 attribute_id;
> +	u32 value;
> +	int err;
> +
> +	if (!priv)
> +		return -ENODEV;
> +
> +	err = lwmi_om_notifier_call(&mode);
> +	if (err)
> +		return err;
> +
> +	if (mode != SMARTFAN_MODE_CUSTOM)
> +		return -EINVAL;
> +
> +	capdata = attr_capdata01_get_data(priv, tunable_attr, mode);
> +
> +	if (!capdata)
> +		return -ENODEV;
> +
> +	attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> +		       FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> +		       FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> +		       FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> +	err = kstrtouint(buf, 10, &value);
> +	if (err)
> +		return err;
> +
> +	if (value < capdata->min_value || value > capdata->max_value)
> +		return -EINVAL;
> +
> +	args.arg0 = attribute_id;
> +	args.arg1 = value;
> +
> +	err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_SET,
> +				       (unsigned char *)&args, sizeof(args),
> +				       NULL);
> +
> +	if (err)
> +		return err;
> +
> +	tunable_attr->store_value = value;
> +	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.
> + * @tunable_attr: The 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 number of characters written to buf, or an error.
> + */
> +static ssize_t attr_current_value_show(struct kobject *kobj,
> +				       struct kobj_attribute *kattr, char *buf,
> +				       struct tunable_attr_01 *tunable_attr)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> +	struct wmi_method_args_32 args;
> +	enum thermal_mode mode;
> +	u32 attribute_id;
> +	int retval;
> +	int err;
> +
> +	if (!priv)
> +		return -ENODEV;
> +
> +	err = lwmi_om_notifier_call(&mode);
> +	if (err)
> +		return err;
> +
> +	attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> +		       FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> +		       FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> +		       FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> +	args.arg0 = attribute_id;
> +
> +	err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_GET,
> +				       (unsigned char *)&args, sizeof(args),
> +				       &retval);
> +
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buf, "%d\n", retval);
> +}
> +
> +/* Attribute macros */
> +#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 *kattr, char *buf) \
> +	{                                                                      \
> +		return sysfs_emit(buf, _fmt, _val);                            \
> +	}                                                                      \
> +	static struct kobj_attribute attr_##_attrname##_##_prop =              \
> +		__LL_ATTR_RO(_attrname, _prop)
> +
> +/* Attribute current value read/write */
> +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname)                            \
> +	static ssize_t _attrname##_current_value_store(                        \
> +		struct kobject *kobj, struct kobj_attribute *kattr,            \
> +		const char *buf, size_t count)                                 \
> +	{                                                                      \
> +		return attr_current_value_store(kobj, kattr, buf, count,       \
> +						&_attrname);                   \
> +	}                                                                      \
> +	static ssize_t _attrname##_current_value_show(                         \
> +		struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> +	{                                                                      \
> +		return attr_current_value_show(kobj, kattr, buf, &_attrname);  \
> +	}                                                                      \
> +	static struct kobj_attribute attr_##_attrname##_current_value =        \
> +		__LL_ATTR_RW(_attrname, current_value)
> +
> +/* Attribute property read only */
> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type)                    \
> +	static ssize_t _attrname##_##_prop##_show(                             \
> +		struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> +	{                                                                      \
> +		return attr_capdata01_show(kobj, kattr, buf, &_attrname,       \
> +					   _prop_type);                        \
> +	}                                                                      \
> +	static struct kobj_attribute attr_##_attrname##_##_prop =              \
> +		__LL_ATTR_RO(_attrname, _prop)
> +
> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname)     \
> +	__LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname);                   \
> +	__LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL);  \
> +	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);   \
> +	__LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL);          \
> +	__LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL);          \
> +	__LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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            \
> +	}
> +
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> +			    "Set the CPU sustained power limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> +			    "Set the CPU slow package power tracking limit");
> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> +			    "Set the CPU fast package power tracking limit");
> +
> +static struct capdata01_attr_group capdata01_attr_groups[] = {
> +	{ &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> +	{ &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> +	{ &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> +	{},
> +};
> +
> +/*
> + * lwmi_om_fw_attr_add() - Registers all capdata01_attr_groups[] attributes as
> + * firmware_attributes_class members.

Why this cannot simply be:

Register all firmware_attributes_class members

?

> + * @priv: The Other Mode driver data.

IMO, if you want to go to specific details such as mentioning the variable 
name you can add the description here and write the longer explanation 
(but in this case I'm not sure if it's worth the effort really).

> + * Returns: Either 0, or an error.

Return:

> + */
> +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
> +{
> +	int err, i;
> +
> +	ida_init(&priv->ida);
> +	priv->ida_id = ida_alloc(&priv->ida, GFP_KERNEL);
> +	if (priv->ida_id < 0)
> +		return priv->ida_id;
> +
> +	priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
> +					  MKDEV(0, 0), NULL, "%s",
> +					  FW_ATTR_FOLDER);
> +	if (IS_ERR(priv->fw_attr_dev)) {
> +		err = PTR_ERR(priv->fw_attr_dev);
> +		return err;

Leaks the allocated ida?

> +	}
> +
> +	priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> +						 &priv->fw_attr_dev->kobj);
> +	if (!priv->fw_attr_kset) {
> +		err = -ENOMEM;
> +		goto err_destroy_classdev;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {

Change i to unsigned when used in loops like this.

> +		err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> +					 capdata01_attr_groups[i].attr_group);
> +		if (err) {
> +			pr_debug("Failed to create sysfs-group for %s: %d\n",
> +				 capdata01_attr_groups[i].attr_group->name,
> +				 err);
> +			goto err_remove_groups;
> +		}
> +		capdata01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
> +	}
> +	return 0;
> +
> +err_remove_groups:
> +	ida_free(&priv->ida, priv->ida_id);
> +	while (i-- >= 0) {

>= 0 is not necessary.

> +		sysfs_remove_group(&priv->fw_attr_kset->kobj,
> +				   capdata01_attr_groups[i].attr_group);
> +	}
> +	kset_unregister(priv->fw_attr_kset);
> +
> +err_destroy_classdev:
> +	device_unregister(priv->fw_attr_dev);
> +	return err;
> +}
> +
> +/*
> + * lwmi_om_fw_attr_remove() - Unregisters all capdata01_attr_groups[] attributes as
> + * firmware_attributes_class members.
> + * @priv: The Other Mode driver data.
> + *
> + */
> +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
> +{
> +	int size = ARRAY_SIZE(capdata01_attr_groups);

unsigned int i = ARRAY_SIZE(capdata01_attr_groups) - 1;

> +
> +	while (--size >= 0) {

while (i--) {

> +		sysfs_remove_group(&priv->fw_attr_kset->kobj,
> +				   capdata01_attr_groups[size].attr_group);
> +	}
> +	kset_unregister(priv->fw_attr_kset);
> +	device_unregister(priv->fw_attr_dev);
> +}
> +
> +static int lwmi_om_master_bind(struct device *dev)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = component_bind_all(dev, &priv->cd01_list);
> +	if (ret)
> +		return ret;
> +
> +	return lwmi_om_fw_attr_add(priv);
> +}
> +
> +static void lwmi_om_master_unbind(struct device *dev)
> +{
> +	component_unbind_all(dev, NULL);
> +}
> +
> +static const struct component_master_ops lwmi_om_master_ops = {
> +	.bind = lwmi_om_master_bind,
> +	.unbind = lwmi_om_master_unbind,
> +};
> +
> +static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct component_match *master_match = NULL;
> +	struct lwmi_om_priv *priv;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->wdev = wdev;
> +	dev_set_drvdata(&wdev->dev, priv);
> +
> +	component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
> +	if (IS_ERR(master_match))
> +		return PTR_ERR(master_match);
> +
> +	return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
> +					       master_match);
> +}
> +
> +static void lwmi_other_remove(struct wmi_device *wdev)
> +{
> +	struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> +	component_master_del(&wdev->dev, &lwmi_om_master_ops);
> +	lwmi_om_fw_attr_remove(priv);
> +	ida_free(&priv->ida, priv->ida_id);
> +}
> +
> +static const struct wmi_device_id lwmi_other_id_table[] = {
> +	{ LENOVO_OTHER_METHOD_GUID, NULL },
> +	{}
> +};
> +
> +static struct wmi_driver lwmi_other_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_other",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_other_id_table,
> +	.probe = lwmi_other_probe,
> +	.remove = lwmi_other_remove,
> +	.no_singleton = true,
> +};
> +
> +module_wmi_driver(lwmi_other_driver);
> +
> +MODULE_IMPORT_NS("LENOVO_WMI_CD01");
> +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> +MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/lenovo-wmi-other.h b/drivers/platform/x86/lenovo-wmi-other.h
> new file mode 100644
> index 000000000000..9fba35ef1137
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#ifndef _LENOVO_WMI_OTHER_H_
> +#define _LENOVO_WMI_OTHER_H_
> +
> +#include <linux/device.h>
> +#include <linux/notifier.h>
> +#include <linux/types.h>

Replace with struct forward declarations.

> +
> +int lwmi_om_register_notifier(struct notifier_block *nb);
> +int lwmi_om_unregister_notifier(struct notifier_block *nb);
> +int devm_lwmi_om_register_notifier(struct device *dev,
> +				   struct notifier_block *nb);
> +
> +#endif /* !_LENOVO_WMI_H_ */
> 

-- 
 i.


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

* Re: [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone WMI Driver
  2025-03-17 14:43 ` [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone " Derek J. Clark
  2025-03-26 19:48   ` Matthew Schwartz
  2025-03-27  3:49   ` Armin Wolf
@ 2025-03-27 13:56   ` Ilpo Järvinen
  2025-04-02 21:22     ` Derek John Clark
  2 siblings, 1 reply; 49+ messages in thread
From: Ilpo Järvinen @ 2025-03-27 13:56 UTC (permalink / raw)
  To: Derek J. Clark
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

On Mon, 17 Mar 2025, Derek J. Clark wrote:

> Adds lenovo-wmi-gamezone driver which provides the Lenovo Gamezone WMI
> interface that comes on Lenovo "Gaming Series" hardware. Provides ACPI
> platform profiles over WMI.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>

This has a few similar nits I flagged for the other patches but I won't 
mark them here again but please go through the patches to find similar 
cases.

> ---
> v4:
> - Add notifier blocks for the Events and Other Mode drivers.
> - Remove notifier block chain head and all reference to Thermal Mode
>   Event GUID.
> - Add header for Gamezone specific structs and functions.
> - Various fixes from review.
> v3:
> - Use notifier chain to report platform profile changes to any
>   subscribed drivers.
> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
>   chain.
> - Adds support for Extreme Mode profile on supported hardware, as well
>   as a DMI quirk table for some devices that report extreme mode version
>   support but so not have it fully implemented.
> - Update to include recent changes to platform-profile.
> v2:
> - Use devm_kmalloc to ensure driver can be instanced, remove global
>   reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> - Remove GZ_WMI symbol exporting.
> ---
>  MAINTAINERS                                |   2 +
>  drivers/platform/x86/Kconfig               |  13 +
>  drivers/platform/x86/Makefile              |   1 +
>  drivers/platform/x86/lenovo-wmi-gamezone.c | 380 +++++++++++++++++++++
>  drivers/platform/x86/lenovo-wmi-gamezone.h |  18 +
>  5 files changed, 414 insertions(+)
>  create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>  create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 87daee6075ad..0416afd997a0 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13168,6 +13168,8 @@ F:	drivers/platform/x86/lenovo-wmi-capdata01.c
>  F:	drivers/platform/x86/lenovo-wmi-capdata01.h
>  F:	drivers/platform/x86/lenovo-wmi-events.c
>  F:	drivers/platform/x86/lenovo-wmi-events.h
> +F:	drivers/platform/x86/lenovo-wmi-gamezone.c
> +F:	drivers/platform/x86/lenovo-wmi-gamezone.h
>  F:	drivers/platform/x86/lenovo-wmi-helpers.c
>  F:	drivers/platform/x86/lenovo-wmi-helpers.h
>  F:	drivers/platform/x86/lenovo-wmi-other.c
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index fc47604e37f7..ecf3246c8fda 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -467,6 +467,19 @@ config LENOVO_WMI_HELPERS
>  	tristate
>  	depends on ACPI_WMI
>  
> +config LENOVO_WMI_GAMEZONE
> +	tristate "Lenovo GameZone WMI Driver"
> +	depends on ACPI_WMI
> +	select ACPI_PLATFORM_PROFILE
> +	select LENOVO_WMI_EVENTS
> +	select LENOVO_WMI_HELPERS
> +	help
> +	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> +	  platform-profile firmware interface to manage power usage.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called lenovo-wmi-gamezone.
> +
>  config LENOVO_WMI_DATA01
>  	tristate
>  	depends on ACPI_WMI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index c6ce3c8594b1..f3e64926a96b 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -71,6 +71,7 @@ obj-$(CONFIG_YT2_1380)		+= lenovo-yoga-tab2-pro-1380-fastcharger.o
>  obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
>  obj-$(CONFIG_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
>  obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
> +obj-$(CONFIG_LENOVO_WMI_GAMEZONE)	+= lenovo-wmi-gamezone.o
>  obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
>  obj-$(CONFIG_LENOVO_WMI_TUNING)	+= lenovo-wmi-other.o
>  
> diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> new file mode 100644
> index 000000000000..9d453a836227
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> @@ -0,0 +1,380 @@
> +// 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/dmi.h>
> +#include <linux/list.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_profile.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +#include "lenovo-wmi-events.h"
> +#include "lenovo-wmi-gamezone.h"
> +#include "lenovo-wmi-helpers.h"
> +#include "lenovo-wmi-other.h"
> +
> +/* Interface GUIDs */
> +#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 BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> +
> +struct lwmi_event_priv {
> +	enum thermal_mode current_mode;
> +	struct wmi_device *wdev;
> +	bool extreme_supported;
> +	struct device *ppdev; /*platform profile device */
> +	struct notifier_block event_nb;
> +	struct notifier_block mode_nb;
> +};
> +
> +struct quirk_entry {
> +	bool extreme_supported;
> +};
> +
> +static struct quirk_entry quirk_no_extreme_bug = {
> +	.extreme_supported = false,
> +};
> +
> +/* Notifier Methods */
> +/*
> + * lwmi_gz_mode_call() - Call method for lenovo-wmi-other notifier
> + * block call chain. For THERMAL_MODE_EVENT, returns current_mode
> + *
> + * @nb: The notifier_block registered to lenovo-wmi-other
> + * @cmd: The event triggered by lenovo-wmi-other
> + * @data: The data to be returned by the event.
> + *
> + * Returns: notifier_block status.
> + */
> +static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
> +			     void *data)
> +{
> +	struct lwmi_event_priv *priv;
> +
> +	priv = container_of(nb, struct lwmi_event_priv, mode_nb);
> +	if (!priv)
> +		return NOTIFY_BAD;
> +
> +	switch (cmd) {
> +	case THERMAL_MODE_EVENT:
> +		*(enum thermal_mode *)data = priv->current_mode;
> +		break;
> +	default:
> +		return NOTIFY_DONE;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +/*
> + * lwmi_gz_event_call() - Call method for lenovo-wmi-events notifier
> + * block call chain. For THERMAL_MODE_EVENT, sets current_mode and
> + * notifies platform_profile of a change.
> + *
> + * @nb: The notifier_block registered to lenovo-wmi-events
> + * @cmd: The event triggered by lenovo-wmi-events
> + * @data: The data to be updated by the event.
> + *
> + * Returns: notifier_block status.
> + */
> +static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
> +			      void *data)
> +{
> +	struct lwmi_event_priv *priv;
> +
> +	priv = container_of(nb, struct lwmi_event_priv, event_nb);
> +	if (!priv)
> +		return NOTIFY_BAD;
> +
> +	switch (cmd) {
> +	case THERMAL_MODE_EVENT:
> +		priv->current_mode = *((enum thermal_mode *)data);
> +		platform_profile_notify(&priv->wdev->dev);
> +		break;
> +	default:
> +		return NOTIFY_DONE;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +/* Platform Profile Methods & Setup */
> +/*
> + * lwmi_gz_platform_profile_supported() - Gets the version of the WMI
> + * interface to determine the support level.
> + *
> + * @wdev: The Gamezone WMI device.
> + * @supported: Pointer to return the support level with.
> + *
> + * Returns: 0, or an error.
> + */
> +static int lwmi_gz_platform_profile_supported(struct wmi_device *wdev,
> +					      int *supported)
> +{
> +	return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP,
> +					0, 0, supported);
> +}
> +
> +/*
> + * lwmi_gz_thermal_mode_get() - Gets the currently set thermal mode from
> + * the Gamezone WMI interface.
> + *
> + * @wdev: The Gamezone WMI device.
> + * @mode: Pointer to return the thermal mode with.
> + *
> + * Returns: 0, or an error.
> + */
> +static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
> +				    enum thermal_mode *mode)
> +{
> +	return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET,
> +					0, 0, mode);
> +}
> +
> +static int lwmi_gz_profile_get(struct device *dev,
> +			       enum platform_profile_option *profile)
> +{
> +	struct lwmi_event_priv *priv = dev_get_drvdata(dev);
> +	enum thermal_mode mode;
> +	int ret;
> +
> +	ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode);
> +	if (ret)
> +		return ret;
> +
> +	switch (mode) {
> +	case SMARTFAN_MODE_QUIET:
> +		*profile = PLATFORM_PROFILE_LOW_POWER;
> +		break;
> +	case SMARTFAN_MODE_BALANCED:
> +		*profile = PLATFORM_PROFILE_BALANCED;
> +		break;
> +	case SMARTFAN_MODE_PERFORMANCE:
> +		if (priv->extreme_supported) {
> +			*profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> +			break;
> +		}
> +		*profile = PLATFORM_PROFILE_PERFORMANCE;
> +		break;
> +	case SMARTFAN_MODE_EXTREME:
> +		*profile = PLATFORM_PROFILE_PERFORMANCE;
> +		break;
> +	case SMARTFAN_MODE_CUSTOM:
> +		*profile = PLATFORM_PROFILE_CUSTOM;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	priv->current_mode = mode;
> +
> +	return 0;
> +}
> +
> +static int lwmi_gz_profile_set(struct device *dev,
> +			       enum platform_profile_option profile)
> +{
> +	struct lwmi_event_priv *priv = dev_get_drvdata(dev);
> +	struct wmi_method_args_32 args;
> +	enum thermal_mode mode;
> +	int ret;
> +
> +	switch (profile) {
> +	case PLATFORM_PROFILE_LOW_POWER:
> +		mode = SMARTFAN_MODE_QUIET;
> +		break;
> +	case PLATFORM_PROFILE_BALANCED:
> +		mode = SMARTFAN_MODE_BALANCED;
> +		break;
> +	case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> +		mode = SMARTFAN_MODE_PERFORMANCE;
> +		break;
> +	case PLATFORM_PROFILE_PERFORMANCE:
> +		if (priv->extreme_supported) {
> +			mode = SMARTFAN_MODE_EXTREME;
> +			break;
> +		}
> +		mode = SMARTFAN_MODE_PERFORMANCE;
> +		break;
> +	case PLATFORM_PROFILE_CUSTOM:
> +		mode = SMARTFAN_MODE_CUSTOM;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	args.arg0 = mode;
> +
> +	ret = lwmi_dev_evaluate_method(priv->wdev, 0x0,
> +				       WMI_METHOD_ID_SMARTFAN_SET,
> +				       (unsigned char *)&args, sizeof(args),
> +				       NULL);
> +	if (ret)
> +		return ret;
> +
> +	priv->current_mode = mode;
> +
> +	return 0;
> +}
> +
> +static const struct dmi_system_id fwbug_list[] = {
> +	{
> +		.ident = "Legion Go 8APU1",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> +		},
> +		.driver_data = &quirk_no_extreme_bug,
> +	},
> +	{
> +		.ident = "Legion Go S 8ARP1",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> +		},
> +		.driver_data = &quirk_no_extreme_bug,
> +	},
> +	{
> +		.ident = "Legion Go S 8APU1",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> +			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> +		},
> +		.driver_data = &quirk_no_extreme_bug,
> +	},
> +	{},
> +
> +};
> +
> +/*
> + * extreme_supported() - Evaluate if a device supports extreme thermal mode.
> + * For devices that have a profile_support_ver of 6 or greater a DMI check
> + * is done. Some devices report a version that supports extreme mode but
> + * have an incomplete entry in the BIOS. To ensure this cannot be set, they
> + * are quirked to prevent assignment.
> + *
> + * @profile_support_ver: Version of WMI interface provided by
> + * lwmi_gz_platform_profile_supported.
> + *
> + * Returns: bool
> + */
> +static bool extreme_supported(int profile_support_ver)
> +{
> +	const struct dmi_system_id *dmi_id;
> +	struct quirk_entry *quirks;
> +
> +	if (profile_support_ver < 6)
> +		return false;
> +
> +	dmi_id = dmi_first_match(fwbug_list);
> +	if (!dmi_id)
> +		return true;
> +
> +	quirks = dmi_id->driver_data;
> +	return quirks->extreme_supported;
> +}
> +
> +static int lwmi_platform_profile_probe(void *drvdata, unsigned long *choices)
> +{
> +	struct lwmi_event_priv *priv = drvdata;
> +	int profile_support_ver;
> +	int ret;
> +
> +	ret = lwmi_gz_platform_profile_supported(priv->wdev,
> +						 &profile_support_ver);
> +	if (ret)
> +		return ret;
> +
> +	if (profile_support_ver < 1)
> +		return -ENODEV;
> +
> +	priv->extreme_supported = extreme_supported(profile_support_ver);
> +
> +	set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> +	set_bit(PLATFORM_PROFILE_BALANCED, choices);
> +	set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> +	set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> +
> +	if (priv->extreme_supported)
> +		set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> +
> +	return 0;
> +}
> +
> +static const struct platform_profile_ops lwmi_gz_platform_profile_ops = {
> +	.probe = lwmi_platform_profile_probe,
> +	.profile_get = lwmi_gz_profile_get,
> +	.profile_set = lwmi_gz_profile_set,
> +};
> +
> +/* Driver Methods */
> +static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct lwmi_event_priv *priv;
> +	int ret;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->event_nb.notifier_call = lwmi_gz_event_call;
> +	ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
> +	if (ret)
> +		return ret;
> +
> +	priv->mode_nb.notifier_call = lwmi_gz_mode_call;
> +	ret = devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb);
> +	if (ret)
> +		return ret;
> +
> +	priv->wdev = wdev;
> +	dev_set_drvdata(&wdev->dev, priv);
> +
> +	priv->ppdev = platform_profile_register(&wdev->dev,
> +						"lenovo-wmi-gamezone", priv,
> +						&lwmi_gz_platform_profile_ops);
> +
> +	if (IS_ERR(priv->ppdev))
> +		return -ENODEV;
> +
> +	ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static const struct wmi_device_id lwmi_gz_id_table[] = { { LENOVO_GAMEZONE_GUID,
> +							   NULL },
> +							 {} };
> +
> +static struct wmi_driver lwmi_gz_driver = {
> +	.driver = {
> +		.name = "lenovo_wmi_gamezone",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.id_table = lwmi_gz_id_table,
> +	.probe = lwmi_gz_probe,
> +	.no_singleton = true,
> +};
> +
> +module_wmi_driver(lwmi_gz_driver);
> +
> +MODULE_IMPORT_NS("LENOVO_WMI_EVENTS");
> +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> +MODULE_IMPORT_NS("LENOVO_WMI_OTHER");
> +MODULE_DEVICE_TABLE(wmi, lwmi_gz_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-wmi-gamezone.h b/drivers/platform/x86/lenovo-wmi-gamezone.h
> new file mode 100644
> index 000000000000..ac536803160b
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + *
> + */
> +
> +#ifndef _LENOVO_WMI_GAMEZONE_H_
> +#define _LENOVO_WMI_GAMEZONE_H_
> +
> +enum thermal_mode {
> +	SMARTFAN_MODE_QUIET = 0x01,
> +	SMARTFAN_MODE_BALANCED = 0x02,
> +	SMARTFAN_MODE_PERFORMANCE = 0x03,
> +	SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> +	SMARTFAN_MODE_CUSTOM = 0xFF,
> +};
> +
> +#endif /* !_LENOVO_WMI_GAMEZONE_H_ */
> 

Are these going the be used by other .c files?

-- 
 i.


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

* Re: [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation
  2025-03-27  0:26   ` Armin Wolf
@ 2025-03-30  4:49     ` Derek John Clark
  0 siblings, 0 replies; 49+ messages in thread
From: Derek John Clark @ 2025-03-30  4:49 UTC (permalink / raw)
  To: Armin Wolf
  Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
	Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
	Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
	John Martens, platform-driver-x86, linux-doc, linux-kernel

On Wed, Mar 26, 2025 at 5:26 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 17.03.25 um 15:43 schrieb Derek J. Clark:
>
> > Adds documentation for new lenovo-wmi drivers.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > v4:
> >   - Fixed MOF formatting issues.
> >   - Fixed spelling mistakes.
> >   - Updated description of balanced-performance profile for Gamezone.
> >   - Updated description of thermal mode event GUID for Gamezone.
> > v3:
> > - Split documentation into multiple files, one for each parent
> >    driver for the Gamezone and Other Mode WMI interfaces.
> > - Add MOF data for all parent and child interfaces.
> > - Remove lenovo-wmi-camera.c driver from v2 documentation.
> > v2:
> > - Update description of Custom Profile to include the need to manually
> >    set it.
> > - Remove all references to Legion hardware.
> > - Add section for lenovo-wmi-camera.c driver as it follows the same
> >    naming convention.
> > ---
> >   .../wmi/devices/lenovo-wmi-gamezone.rst       | 203 ++++++++++++++++++
> >   .../wmi/devices/lenovo-wmi-other-method.rst   | 108 ++++++++++
> >   MAINTAINERS                                   |   7 +
> >   3 files changed, 318 insertions(+)
> >   create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> >   create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst
> >
> > diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > new file mode 100644
> > index 000000000000..bde63dde285d
> > --- /dev/null
> > +++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > @@ -0,0 +1,203 @@
> > +.. SPDX-License-Identifier: GPL-2.0-or-later
> > +==========================================================
> > +Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
> > +==========================================================
> > +
> > +Introduction
> > +============
> > +The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
> > +The priamry "Gamezone" GUID provides advanced features such as fan
> > +profiles and overclocking. It is paired with multiple event GUIDs
> > +and data block GUIDs that provide context for the various methods.
> > +
> > +Gamezone Data
> > +-------------
> > +
> > +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> > +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > +
> > +The Gamezone Data WMI interface provides platform-profile and fan curve
> > +settings for devices that fall under the "Gaming Series" of Lenovo devices.
> > +It uses a notifier chain to inform other Lenovo WMI interface drivers of the
> > +current platform profile when it changes.
> > +
> > +The following platform profiles are supported:
> > + - low-power
> > + - balanced
> > + - balanced-performance*
> > + - performance
> > + - custom
> > +
> > +Balanced-Performance
> > +~~~~~~~~~~~~~~~~~~~~
> > +Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
> > +enabled in their BIOS. For these devices, the performance platform profile
> > +will correspond to the BIOS Extreme Mode, while the balanced-performance
> > +platform profile will correspond to the BIOS Performance mode. For legacy
> > +devices, the performance platform profile will correspond with the BIOS
> > +Performance mode. For some newer devices the "Extreme Mode" profile is
> > +incomplete in the BIOS and setting it will cause undefined behavior. A
> > +BIOS bug quirk table is provided to ensure these devices cannot set
> > +"Extreme Mode" from the driver.
> > +
> > +Custom Profile
> > +~~~~~~~~~~~~~~
> > +The custom profile represents a hardware mode on Lenovo devices that enables
> > +user modifications to Package Power Tracking (PPT) and fan curve settings.
> > +When an attribute exposed by the Other Mode WMI interface is to be modified,
> > +the Gamezone driver must first be switched to the "custom" profile manually,
> > +or the setting will have no effect. If another profile is set from the list
> > +of supported profiles, the BIOS will override any user PPT settings when
> > +switching to that profile.
> > +
> > +Gamezone Thermal Mode Event
> > +---------------------------
> > +
> > +WMI GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
>
> Hi,
>
> if i remember correctly we agreed that the GUIDs could use some special markup (monospace, ...)
> instead of using "".
>

I misunderstood your meaning and added the ~'s below based on some
documentation I can't find anymore. There are some conflicting
accounts of how this should be done that I can find and I cant find
any reStructuredText documentation that states it specifically. For
clarity, are you looking for:

WMI GUID ``D320289E-8FEA-41E0-86F9-911D83151B5F``

> > +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > +
> > +The Gamezone Thermal Mode Event interface notifies the system when the platform
> > +profile has changed, either through the hardware event (Fn+Q for laptops or
> > +Legion + Y for Go Series), or through the Gamezone WMI interface. This event is
> > +implemented in the Lenovo WMI Events driver (lenovo-wmi-events).
> > +
> > +
> > +WMI interface description
> > +=========================
> > +
> > +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> > +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> > +
> > +::
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_GAMEZONE_DATA class"), guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
> > +  class LENOVO_GAMEZONE_DATA {
> > +     [key, read] string InstanceName;
> > +     [read] boolean Active;
>
> When building the html documentation the following errors appear:
>
> /home/wolf/Dokumente/Kernel/platform-drivers-x86/Documentation/wmi/devices/lenovo-wmi-gamezone.rst:2: WARNING: Explicit markup ends without a blank line; unexpected unindent.
> /home/wolf/Dokumente/Kernel/platform-drivers-x86/Documentation/wmi/devices/lenovo-wmi-other-method.rst:2: WARNING: Explicit markup ends without a blank line; unexpected unindent.
>
> Please fix those errors.
>

Yes, I caught those in my self-reply.

> > +
> > +    [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")] void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
> > +    [WmiMethodId(11), Implemented, Description("Get AslCode Version")] void GetVersion ([out, Description("AslCode version")] UINT32 Data);
> > +    [WmiMethodId(12), Implemented, Description("Fan cooling capability")] void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
> > +    [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")] void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
> > +    [WmiMethodId(14), Implemented, Description("cpu oc capability")] void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
> > +    [WmiMethodId(15), Implemented, Description("bios has overclock capability")] void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
> > +    [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")] void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
> > +    [WmiMethodId(18), Implemented, Description("Get CPU temperature")] void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
> > +    [WmiMethodId(19), Implemented, Description("Get GPU temperature")] void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
> > +    [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")] void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
> > +    [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")] void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
> > +    [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")] void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
> > +    [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")] void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
> > +    [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")] void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
> > +    [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")] void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
> > +    [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")] void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
> > +    [WmiMethodId(30), Implemented, Description("Get Keyboard feature list")] void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
> > +    [WmiMethodId(31), Implemented, Description("Get Memory OC Information")] void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
> > +    [WmiMethodId(32), Implemented, Description("Water Cooling feature capability")] void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
> > +    [WmiMethodId(33), Implemented, Description("Set Water Cooling status")] void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
> > +    [WmiMethodId(34), Implemented, Description("Get Water Cooling status")] void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
> > +    [WmiMethodId(35), Implemented, Description("Lighting feature capability")] void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
> > +    [WmiMethodId(36), Implemented, Description("Set keyboard light off or on to max")] void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
> > +    [WmiMethodId(37), Implemented, Description("Get keyboard light on/off status")] void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
> > +    [WmiMethodId(38), Implemented, Description("Get Macrokey scan code")] void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
> > +    [WmiMethodId(39), Implemented, Description("Get Macrokey count")] void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
> > +    [WmiMethodId(40), Implemented, Description("Support G-Sync feature")] void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
> > +    [WmiMethodId(41), Implemented, Description("Get G-Sync Status")] void GetGSyncStatus ([out, Description("Get G-Sync Status")] UINT32 Data);
> > +    [WmiMethodId(42), Implemented, Description("Set G-Sync Status")] void SetGSyncStatus ([in, Description("Set G-Sync Status")] UINT32 Data);
> > +    [WmiMethodId(43), Implemented, Description("Support Smart Fan feature")] void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
> > +    [WmiMethodId(44), Implemented, Description("Set Smart Fan Mode")] void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
> > +    [WmiMethodId(45), Implemented, Description("Get Smart Fan Mode")] void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
> > +    [WmiMethodId(46), Implemented, Description("Get Smart Fan Setting Mode")] void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
> > +    [WmiMethodId(47), Implemented, Description("Get Power Charge Mode")] void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
> > +    [WmiMethodId(48), Implemented, Description("Get Gaming Product Info")] void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
> > +    [WmiMethodId(49), Implemented, Description("Over Drive feature capability")] void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
> > +    [WmiMethodId(50), Implemented, Description("Get Over Drive status")] void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
> > +    [WmiMethodId(51), Implemented, Description("Set Over Drive status")] void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
> > +    [WmiMethodId(52), Implemented, Description("Set Light Control Owner")] void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
> > +    [WmiMethodId(53), Implemented, Description("Set DDS Control Owner")] void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
> > +    [WmiMethodId(54), Implemented, Description("Get the flag of restore OC value")] void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
> > +    [WmiMethodId(55), Implemented, Description("Get Real Thremal Mode")] void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
> > +    [WmiMethodId(56), Implemented, Description("Get the OC switch status in BIOS")] void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
> > +    [WmiMethodId(59), Implemented, Description("Get hardware info support version")] void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
> > +    [WmiMethodId(60), Implemented, Description("Get Cpu core 0 max frequency")] void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
> > +    [WmiMethodId(62), Implemented, Description("Check the Adapter type fit for OC")] void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
> > +    [WmiMethodId(63), Implemented, Description("Is support IGPU mode")] void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
> > +    [WmiMethodId(64), Implemented, Description("Get IGPU Mode Status")] void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
> > +    [WmiMethodId(65), Implemented, Description("Set IGPU Mode")] void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
> > +    [WmiMethodId(66), Implemented, Description("Notify DGPU Status")] void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
> > +    [WmiMethodId(67), Implemented, Description("Is changed Y log")] void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
> > +    [WmiMethodId(68), Implemented, Description("Get DGPU Hardwawre ID")] void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
> > +  };
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of CPU OC parameter list"), guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
> > +  class LENOVO_GAMEZONE_CPU_OC_DATA {
> > +    [key, read] string InstanceName;
> > +    [read] boolean Active;
> > +
> > +    [WmiDataId(1), read, Description("OC tune id.")] uint32 Tuneid;
> > +    [WmiDataId(2), read, Description("Default value.")] uint32 DefaultValue;
> > +    [WmiDataId(3), read, Description("OC Value.")] uint32 OCValue;
> > +    [WmiDataId(4), read, Description("Min Value.")] uint32 MinValue;
> > +    [WmiDataId(5), read, Description("Max Value.")] uint32 MaxValue;
> > +    [WmiDataId(6), read, Description("Scale Value.")] uint32 ScaleValue;
> > +    [WmiDataId(7), read, Description("OC Order id.")] uint32 OCOrderid;
> > +    [WmiDataId(8), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
> > +    [WmiDataId(9), read, Description("Delay time in ms.")] uint32 Interval;
> > +  };
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of GPU OC parameter list"), guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
> > +  class LENOVO_GAMEZONE_GPU_OC_DATA {
> > +    [key, read] string InstanceName;
> > +    [read] boolean Active;
> > +
> > +    [WmiDataId(1), read, Description("P-State ID.")] uint32 PStateID;
> > +    [WmiDataId(2), read, Description("CLOCK ID.")] uint32 ClockID;
> > +    [WmiDataId(3), read, Description("Default value.")] uint32 defaultvalue;
> > +    [WmiDataId(4), read, Description("OC Offset freqency.")] uint32 OCOffsetFreq;
> > +    [WmiDataId(5), read, Description("OC Min offset value.")] uint32 OCMinOffset;
> > +    [WmiDataId(6), read, Description("OC Max offset value.")] uint32 OCMaxOffset;
> > +    [WmiDataId(7), read, Description("OC Offset Scale.")] uint32 OCOffsetScale;
> > +    [WmiDataId(8), read, Description("OC Order id.")] uint32 OCOrderid;
> > +    [WmiDataId(9), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
> > +  };
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Fancooling finish event"), guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
> > +  class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
> > +    [key, read] string InstanceName;
> > +    [read] boolean Active;
> > +
> > +    [WmiDataId(1), read, Description("Fancooling clean finish event")] uint32 EventId;
> > +  };
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan mode change event"), guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
> > +  class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
> > +    [key, read] string InstanceName;
> > +    [read] boolean Active;
> > +
> > +    [WmiDataId(1), read, Description("Smart Fan Mode change event")] uint32 mode;
> > +    [WmiDataId(2), read, Description("version of FN+Q")] uint32 version;
> > +  };
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan setting mode change event"), guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
> > +  class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
> > +    [key, read] string InstanceName;
> > +    [read] boolean Active;
> > +
> > +    [WmiDataId(1), read, Description("Smart Fan Setting mode change event")] uint32 mode;
> > +  };
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("POWER CHARGE MODE Change EVENT"), guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
> > +  class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
> > +    [key, read] string InstanceName;
> > +    [read] boolean Active;
> > +
> > +    [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")] uint32 mode;
> > +  };
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Thermal Mode Real Mode change event"), guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
> > +  class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
> > +    [key, read] string InstanceName;
> > +    [read] boolean Active;
> > +
> > +    [WmiDataId(1), read, Description("Thermal Mode Real Mode")] uint32 mode;
> > +  };
> > diff --git a/Documentation/wmi/devices/lenovo-wmi-other-method.rst b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> > new file mode 100644
> > index 000000000000..b48832726311
> > --- /dev/null
> > +++ b/Documentation/wmi/devices/lenovo-wmi-other-method.rst
> > @@ -0,0 +1,108 @@
> > +.. SPDX-License-Identifier: GPL-2.0-or-later
> > +===========================================================
> > +Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
> > +===========================================================
> > +
> > +Introduction
> > +============
> > +Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
> > +The primary Other Mode interface provides advanced power tuning features
> > +such as Package Power Tracking (PPT). It is paired with multiple data block
> > +GUIDs that provide context for the various methods.
> > +
> > +
> > +Other Mode
> > +----------
> > +
> > +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> > +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > +
> > +The Other Mode WMI interface uses the firmware_attributes class to expose
> > +various WMI attributes 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 devices. Each
> > +attribute exposed by the Other Mode interface has corresponding
> > +capability data blocks which allow the driver to probe details about the
> > +attribute. Each attribute has multiple pages, one for each of the platform
> > +profiles managed by the Gamezone interface. Attributes are exposed in sysfs
> > +under the following path:
> > +
> > +::
> > +/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
>
> Something is wrong with the formatting, the text is being rendered as-is.
> Please indent the path with two spaces and add a blank line between the "::"
> and the path.
>

Can do.

Thanks,
Derek

> Thanks,
> Armin Wolf
>
> > +
> > +LENOVO_CAPABILITY_DATA_01
> > +-------------------------
> > +
> > +WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> > +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > +
> > +The LENOVO_CAPABILITY_DATA_01 interface provides information on various
> > +power limits of integrated CPU and GPU components.
> > +
> > +Each attribute has the following properties:
> > + - current_value
> > + - default_value
> > + - display_name
> > + - max_value
> > + - min_value
> > + - scalar_increment
> > + - type
> > +
> > +The following attributes are implemented:
> > + - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
> > + - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
> > + - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
> > +
> > +
> > +WMI interface description
> > +=========================
> > +
> > +The WMI interface description can be decoded from the embedded binary MOF (bmof)
> > +data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> > +
> > +::
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_OTHER_METHOD class"), guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
> > +  class LENOVO_OTHER_METHOD {
> > +    [key, read] string InstanceName;
> > +    [read] boolean Active;
> > +
> > +    [WmiMethodId(17), Implemented, Description("Get Feature Value ")] void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
> > +    [WmiMethodId(18), Implemented, Description("Set Feature Value ")] void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
> > +    [WmiMethodId(19), Implemented, Description("Get Data By Command ")] void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
> > +    [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")] void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
> > +  };
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 00"), guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
> > +  class LENOVO_CAPABILITY_DATA_00 {
> > +    [key, read] string InstanceName;
> > +    [read] boolean Active;
> > +
> > +    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
> > +    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
> > +    [WmiDataId(3), read, Description("Capability Default Value.")] uint32 DefaultValue;
> > +  };
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 01"), guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
> > +  class LENOVO_CAPABILITY_DATA_01 {
> > +    [key, read] string InstanceName;
> > +    [read] boolean Active;
> > +
> > +    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
> > +    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
> > +    [WmiDataId(3), read, Description("Default Value.")] uint32 DefaultValue;
> > +    [WmiDataId(4), read, Description("Step.")] uint32 Step;
> > +    [WmiDataId(5), read, Description("Minimum Value.")] uint32 MinValue;
> > +    [WmiDataId(6), read, Description("Maximum Value.")] uint32 MaxValue;
> > +  };
> > +
> > +  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 02"), guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
> > +  class LENOVO_CAPABILITY_DATA_02 {
> > +    [key, read] string InstanceName;
> > +    [read] boolean Active;
> > +
> > +    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
> > +    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
> > +    [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize;
> > +    [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[];
> > +  };
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 1afd30d00aec..675f4b26426d 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13158,6 +13158,13 @@ S:   Maintained
> >   W:  http://legousb.sourceforge.net/
> >   F:  drivers/usb/misc/legousbtower.c
> >
> > +LENOVO WMI DRIVERS
> > +M:   Derek J. Clark <derekjohn.clark@gmail.com>
> > +L:   platform-driver-x86@vger.kernel.org
> > +S:   Maintained
> > +F:   Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > +F:   Documentation/wmi/devices/lenovo-wmi-other.rst
> > +
> >   LENOVO WMI HOTKEY UTILITIES DRIVER
> >   M:  Jackie Dong <xy-jackie@139.com>
> >   L:  platform-driver-x86@vger.kernel.org

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

* Re: [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver
  2025-03-27  1:03   ` Armin Wolf
@ 2025-03-30  4:55     ` Derek John Clark
  0 siblings, 0 replies; 49+ messages in thread
From: Derek John Clark @ 2025-03-30  4:55 UTC (permalink / raw)
  To: Armin Wolf
  Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
	Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
	Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
	John Martens, platform-driver-x86, linux-doc, linux-kernel

On Wed, Mar 26, 2025 at 6:04 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 17.03.25 um 15:43 schrieb Derek J. Clark:
>
> > Adds lenovo-wmi-events driver. The events driver is designed as a
> > general entrypoint for all Lenovo WMI Events. It acts as a notification
> > chain head that will process event data and pass it on to registered
> > drivers so they can react to the events.
> >
> > Currently only the Gamezone interface Thermal Mode Event GUID is
> > implemented in this driver. It is documented in the Gamezone
> > documentation.
> >
> > Suggested-by: Armin Wolf <W_Armin@gmx.de>
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > v4:
> >   - Remove the Thermal Mode Event GUID from Gamezone and add this driver.
> > ---
> >   MAINTAINERS                              |   2 +
> >   drivers/platform/x86/Kconfig             |   4 +
> >   drivers/platform/x86/Makefile            |   1 +
> >   drivers/platform/x86/lenovo-wmi-events.c | 132 +++++++++++++++++++++++
> >   drivers/platform/x86/lenovo-wmi-events.h |  21 ++++
> >   5 files changed, 160 insertions(+)
> >   create mode 100644 drivers/platform/x86/lenovo-wmi-events.c
> >   create mode 100644 drivers/platform/x86/lenovo-wmi-events.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 3a370a18b806..6dde75922aaf 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13164,6 +13164,8 @@ L:    platform-driver-x86@vger.kernel.org
> >   S:  Maintained
> >   F:  Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> >   F:  Documentation/wmi/devices/lenovo-wmi-other.rst
> > +F:   drivers/platform/x86/lenovo-wmi-events.c
> > +F:   drivers/platform/x86/lenovo-wmi-events.h
> >   F:  drivers/platform/x86/lenovo-wmi-helpers.c
> >   F:  drivers/platform/x86/lenovo-wmi-helpers.h
> >
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index bece1ba61417..13b8f4ac5dc5 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -459,6 +459,10 @@ config IBM_RTL
> >        state = 0 (BIOS SMIs on)
> >        state = 1 (BIOS SMIs off)
> >
> > +config LENOVO_WMI_EVENTS
> > +     tristate
> > +     depends on ACPI_WMI
> > +
> >   config LENOVO_WMI_HELPERS
> >       tristate
> >       depends on ACPI_WMI
> > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> > index 5a9f4e94f78b..fc039839286a 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -69,6 +69,7 @@ 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_LENOVO_WMI_EVENTS)      += lenovo-wmi-events.o
> >   obj-$(CONFIG_LENOVO_WMI_HELPERS)    += lenovo-wmi-helpers.o
> >
> >   # Intel
> > diff --git a/drivers/platform/x86/lenovo-wmi-events.c b/drivers/platform/x86/lenovo-wmi-events.c
> > new file mode 100644
> > index 000000000000..3ea0face3c0d
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-events.c
> > @@ -0,0 +1,132 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Lenovo WMI Events driver. Lenovo WMI interfaces provide various
> > + * hardware triggered events that many drivers need to have propagated.
> > + * This driver provides a uniform entrypoint for these events so that
> > + * any driver that needs to respond to these events can subscribe to a
> > + * notifier chain.
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + */
> > +
> > +#include <linux/list.h>
> > +#include <linux/notifier.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +#include "lenovo-wmi-events.h"
>
> Hi,
>
> please also include linux/acpi.h, linux/export.h and linux/module.h. Also why do you import
> linux/list.h?
>
> > +
> > +/* Interface GUIDs */
> > +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> > +
> > +#define LENOVO_WMI_EVENT_DEVICE(guid, type)                        \
> > +     .guid_string = (guid), .context = &(enum lwmi_events_type) \
> > +     {                                                          \
> > +             type                                               \
> > +     }
> > +
> > +static BLOCKING_NOTIFIER_HEAD(events_chain_head);
> > +
> > +struct lwmi_events_priv {
> > +     struct wmi_device *wdev;
> > +     enum lwmi_events_type type;
> > +};
> > +
> > +/* Notifier Methods */
> > +int lwmi_events_register_notifier(struct notifier_block *nb)
> > +{
> > +     return blocking_notifier_chain_register(&events_chain_head, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
> > +
> > +int lwmi_events_unregister_notifier(struct notifier_block *nb)
> > +{
> > +     return blocking_notifier_chain_unregister(&events_chain_head, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS");
> > +
> > +static void devm_lwmi_events_unregister_notifier(void *data)
> > +{
> > +     struct notifier_block *nb = data;
> > +
> > +     lwmi_events_unregister_notifier(nb);
> > +}
> > +
> > +int devm_lwmi_events_register_notifier(struct device *dev,
> > +                                    struct notifier_block *nb)
> > +{
> > +     int ret;
> > +
> > +     ret = lwmi_events_register_notifier(nb);
> > +     if (ret < 0)
> > +             return ret;
> > +
> > +     return devm_add_action_or_reset(dev,
> > +                             devm_lwmi_events_unregister_notifier, nb);
>
> Please remove this line break here.
>
> > +}
> > +EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
> > +
> > +/* Driver Methods */
> > +static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj)
> > +{
> > +     struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev);
> > +     int sel_prof;
> > +     int ret;
> > +
> > +     switch (priv->type) {
> > +     case THERMAL_MODE_EVENT:
> > +             if (obj->type != ACPI_TYPE_INTEGER)
> > +                     return;
> > +
> > +             sel_prof = obj->integer.value;
> > +             ret = blocking_notifier_call_chain(&events_chain_head,
> > +                                                THERMAL_MODE_EVENT, &sel_prof);
> > +             if (ret == NOTIFY_BAD)
> > +                     dev_err(&wdev->dev,
> > +                             "Failed to send notification to call chain for WMI Events\n");
> > +             break;
> > +     default:
> > +             return;
> > +     }
> > +}
> > +
> > +static int lwmi_events_probe(struct wmi_device *wdev, const void *context)
> > +{
> > +     struct lwmi_events_priv *priv;
> > +
> > +     priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > +     if (!priv)
> > +             return -ENOMEM;
> > +
> > +     if (!context)
> > +             return -EINVAL;
> > +
> > +     priv->wdev = wdev;
> > +     priv->type = *(enum lwmi_events_type *)context;
> > +
> > +     dev_set_drvdata(&wdev->dev, priv);
> > +     return 0;
> > +}
> > +
> > +static const struct wmi_device_id lwmi_events_id_table[] = {
> > +     { LENOVO_WMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID,
> > +                               THERMAL_MODE_EVENT) },
> > +     {}
> > +};
> > +
> > +static struct wmi_driver lwmi_events_driver = {
> > +     .driver = {
> > +             .name = "lenovo_wmi_events",
> > +             .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> > +     },
> > +     .id_table = lwmi_events_id_table,
> > +     .probe = lwmi_events_probe,
> > +     .notify = lwmi_events_notify,
> > +     .no_singleton = true,
> > +};
> > +
> > +module_wmi_driver(lwmi_events_driver);
> > +
> > +MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table);
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Lenovo WMI Events Driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/platform/x86/lenovo-wmi-events.h b/drivers/platform/x86/lenovo-wmi-events.h
> > new file mode 100644
> > index 000000000000..a3fa934eaa10
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-events.h
> > @@ -0,0 +1,21 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later
>
> The SPDX license identifier needs to be a separate comment.
>
> With those minor issues being fixed:
> Reviewed-by: Armin Wolf <W_Armin@gmx.de>
>

Will fix, thanks,
Derek

> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + */
> > +
> > +#include <linux/notifier.h>
> > +#include <linux/types.h>
> > +
> > +#ifndef _LENOVO_WMI_EVENTS_H_
> > +#define _LENOVO_WMI_EVENTS_H_
> > +
> > +enum lwmi_events_type {
> > +     THERMAL_MODE_EVENT = 1,
> > +};
> > +
> > +int lwmi_events_register_notifier(struct notifier_block *nb);
> > +int lwmi_events_unregister_notifier(struct notifier_block *nb);
> > +int devm_lwmi_events_register_notifier(struct device *dev,
> > +                                    struct notifier_block *nb);
> > +
> > +#endif /* !_LENOVO_WMI_EVENTS_H_ */

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

* Re: [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers
  2025-03-27  0:40   ` Armin Wolf
@ 2025-03-30  4:55     ` Derek John Clark
  0 siblings, 0 replies; 49+ messages in thread
From: Derek John Clark @ 2025-03-30  4:55 UTC (permalink / raw)
  To: Armin Wolf
  Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
	Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
	Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
	John Martens, platform-driver-x86, linux-doc, linux-kernel

On Wed, Mar 26, 2025 at 5:41 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 17.03.25 um 15:43 schrieb Derek J. Clark:
>
> > Adds documentation for all new lenovo-wmi drivers.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > v4:
> >   - Changed namespace to LENOVO_WMI_HELPERS from LENOVO_WMI.
> >   - Changed filenames to lenovo-wmi-helpers from lenovo-wmi.
> >   - Removed structs and functions implemented by other drivers.
> > ---
> >   MAINTAINERS                               |  2 +
> >   drivers/platform/x86/Kconfig              |  4 ++
> >   drivers/platform/x86/Makefile             |  1 +
> >   drivers/platform/x86/lenovo-wmi-helpers.c | 64 +++++++++++++++++++++++
> >   drivers/platform/x86/lenovo-wmi-helpers.h | 24 +++++++++
> >   5 files changed, 95 insertions(+)
> >   create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c
> >   create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 675f4b26426d..3a370a18b806 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13164,6 +13164,8 @@ L:    platform-driver-x86@vger.kernel.org
> >   S:  Maintained
> >   F:  Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> >   F:  Documentation/wmi/devices/lenovo-wmi-other.rst
> > +F:   drivers/platform/x86/lenovo-wmi-helpers.c
> > +F:   drivers/platform/x86/lenovo-wmi-helpers.h
> >
> >   LENOVO WMI HOTKEY UTILITIES DRIVER
> >   M:  Jackie Dong <xy-jackie@139.com>
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 43407e76476b..bece1ba61417 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -459,6 +459,10 @@ config IBM_RTL
> >        state = 0 (BIOS SMIs on)
> >        state = 1 (BIOS SMIs off)
> >
> > +config LENOVO_WMI_HELPERS
> > +     tristate
> > +     depends on ACPI_WMI
> > +
> >   config IDEAPAD_LAPTOP
> >       tristate "Lenovo IdeaPad Laptop Extras"
> >       depends on ACPI
> > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> > index 650dfbebb6c8..5a9f4e94f78b 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -69,6 +69,7 @@ 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_LENOVO_WMI_HELPERS)     += lenovo-wmi-helpers.o
> >
> >   # Intel
> >   obj-y                               += intel/
> > diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x86/lenovo-wmi-helpers.c
> > new file mode 100644
> > index 000000000000..36d553502223
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-helpers.c
> > @@ -0,0 +1,64 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Lenovo Legion WMI helpers 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 Method" 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 Method" 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 drivers
> > + * uses a common procedure to get data fro the WMI interface, enumerated here.
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
> > + */
> > +
> > +#include <linux/wmi.h>
>
> Hi,
>
> please also include linux/acpi.h, linux/cleanup.h, linux/errno.h, linux/module.h and linux/export.h.
>

Acked all include requests for the series.

> > +#include "lenovo-wmi-helpers.h"
> > +
> > +/*
>
> Please make sure that the doc comments are valid kernel doc comments, see https://docs.kernel.org/doc-guide/kernel-doc.html
> for details. For example please make sure that the comments start with a "/**".

Will do, thanks

> It would also be nice if you actually describe the purpose of each function.

I can do that.

> > + * lwmi_dev_evaluate_method() - Helper function to call wmidev_evaluate_method
> > + * for Lenovo WMI device method calls that return an ACPI integer.
> > + * @wdev: Pointer to the WMI device to be called.
> > + * @instance: Instance of the called method.
> > + * @method_id: WMI Method ID for the method to be called.
> > + * @buf: Buffer of all arguments for the given method_id.
> > + * @size: Length of the buffer.
> > + * @retval: Pointer for the return value to be assigned.
> > + *
> > + * Returns: 0, or an error.
> > + */
> > +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> > +                          u32 method_id, unsigned char *buf, size_t size,
> > +                          u32 *retval)
> > +{
> > +     struct acpi_buffer input = { size, buf };
>
> Reverse X-mas tree declaration order please.
>
Acked

Thanks,
Derek


> Thanks,
> Armin Wolf
>
> > +     struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> > +     union acpi_object *ret_obj __free(kfree) = NULL;
> > +     acpi_status status;
> > +
> > +     status = wmidev_evaluate_method(wdev, instance, method_id, &input,
> > +                                     &output);
> > +
> > +     if (ACPI_FAILURE(status))
> > +             return -EIO;
> > +
> > +     if (retval) {
> > +             ret_obj = output.pointer;
> > +             if (!ret_obj)
> > +                     return -ENODATA;
> > +
> > +             if (ret_obj->type != ACPI_TYPE_INTEGER)
> > +                     return -ENXIO;
> > +
> > +             *retval = (u32)ret_obj->integer.value;
> > +     }
> > +     return 0;
> > +};
> > +EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_method, "LENOVO_WMI_HELPERS");
> > +
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/platform/x86/lenovo-wmi-helpers.h b/drivers/platform/x86/lenovo-wmi-helpers.h
> > new file mode 100644
> > index 000000000000..7e0d7870790e
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-helpers.h
> > @@ -0,0 +1,24 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
> > + */
> > +#include <linux/notifier.h>
> > +#include <linux/platform_profile.h>
> > +
> > +#ifndef _LENOVO_WMI_HELPERS_H_
> > +#define _LENOVO_WMI_HELPERS_H_
> > +
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +
> > +struct wmi_method_args_32 {
> > +     u32 arg0;
> > +     u32 arg1;
> > +};
> > +
> > +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> > +                          u32 method_id, unsigned char *buf, size_t size,
> > +                          u32 *retval);
> > +
> > +#endif /* !_LENOVO_WMI_HELPERS_H_ */

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

* Re: [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver
  2025-03-27  1:29   ` Armin Wolf
@ 2025-04-02 20:47     ` Derek John Clark
  2025-04-03  1:40       ` Armin Wolf
  0 siblings, 1 reply; 49+ messages in thread
From: Derek John Clark @ 2025-04-02 20:47 UTC (permalink / raw)
  To: Armin Wolf
  Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
	Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
	Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
	John Martens, platform-driver-x86, linux-doc, linux-kernel

On Wed, Mar 26, 2025 at 6:29 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 17.03.25 um 15:43 schrieb Derek J. Clark:
>
> > Adds lenovo-wmi-capdata01 driver which provides the
> > LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode"
> > enabled hardware. Provides an interface for querying if a given
> > attribute is supported by the hardware, as well as its default_value,
> > max_value, min_value, and step increment.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > v4:
> >   - Make driver data a private struct, remove references from Other Mode
> >     driver.
> >   - Don't cache data at device initialization. Instead, on component bind,
> >     cache the data on a member variable of the Other Mode driver data
> >     passed as a void pointer.
> >   - Add header file for capdata01 structs.
> >   - Add new struct to pass capdata01 array data and array length to Other
> >     Mode.
> > v3:
> > - Add as component to lenovo-wmi-other driver.
> > v2:
> > - Use devm_kmalloc to ensure driver can be instanced, remove global
> >    reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
> > - Use list to get the lenovo_wmi_cd01_priv instance in
> >    lenovo_wmi_capdata01_get as none of the data provided by the macros
> >    that will use it can pass a member of the struct for use in
> >    container_of.
> > ---
> >   MAINTAINERS                                 |   2 +
> >   drivers/platform/x86/Kconfig                |   4 +
> >   drivers/platform/x86/Makefile               |   1 +
> >   drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++
> >   drivers/platform/x86/lenovo-wmi-capdata01.h |  29 +++++
> >   5 files changed, 172 insertions(+)
> >   create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> >   create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 6dde75922aaf..56ead241a053 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13164,6 +13164,8 @@ L:    platform-driver-x86@vger.kernel.org
> >   S:  Maintained
> >   F:  Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> >   F:  Documentation/wmi/devices/lenovo-wmi-other.rst
> > +F:   drivers/platform/x86/lenovo-wmi-capdata01.c
> > +F:   drivers/platform/x86/lenovo-wmi-capdata01.h
> >   F:  drivers/platform/x86/lenovo-wmi-events.c
> >   F:  drivers/platform/x86/lenovo-wmi-events.h
> >   F:  drivers/platform/x86/lenovo-wmi-helpers.c
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 13b8f4ac5dc5..64663667f0cb 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
> >       tristate
> >       depends on ACPI_WMI
> >
> > +config LENOVO_WMI_DATA01
> > +     tristate
> > +     depends on ACPI_WMI
> > +
> >   config IDEAPAD_LAPTOP
> >       tristate "Lenovo IdeaPad Laptop Extras"
> >       depends on ACPI
> > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> > index fc039839286a..7a35c77221b7 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -69,6 +69,7 @@ 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_LENOVO_WMI_DATA01)      += lenovo-wmi-capdata01.o
> >   obj-$(CONFIG_LENOVO_WMI_EVENTS)     += lenovo-wmi-events.o
> >   obj-$(CONFIG_LENOVO_WMI_HELPERS)    += lenovo-wmi-helpers.o
> >
> > diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
> > new file mode 100644
> > index 000000000000..b6876611ffd9
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> > @@ -0,0 +1,136 @@
> > +// 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 Mode" WMI interface,
> > + * including if it is supported by the hardware, the default_value, max_value,
> > + * min_value, and step increment.
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + */
> > +
> > +#include <linux/cleanup.h>
> > +#include <linux/component.h>
> > +#include <linux/container_of.h>
> > +#include <linux/device.h>
> > +#include <linux/gfp_types.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +#include "lenovo-wmi-capdata01.h"
>
> Hi,
>
> please also include linux/acpi.h, linux/export.h and linux/module.h.
>
> > +
> > +/* Interface GUIDs */
> > +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> > +
> > +struct lwmi_cd01_priv {
> > +     struct wmi_device *wdev;
> > +};
> > +
> > +/*
>
> /* -> /**
>
> > + * lenovo_cd01_component_bind() - On master bind, caches all capability data on
> > + * the master device.
> > + * @cd01_dev: Pointer to the capability data 01 parent device.
> > + * @om_dev: Pointer to the other mode parent device.
> > + * @data: capdata01_list object pointer to return the capability data with.
> > + *
> > + * Returns: 0, or an error.
> > + */
> > +static int lenovo_cd01_component_bind(struct device *cd01_dev,
> > +                                   struct device *om_dev, void *data)
> > +{
> > +     struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
> > +     int count, idx;
> > +
> > +     if (!priv)
> > +             return -ENODEV;
>
> This check is unnecessary, please drop.
>

Acked

> > +
> > +     count = wmidev_instance_count(priv->wdev);
> > +
> > +     if (count == 0)
> > +             return -EINVAL;
>
> The WMI driver core already ensures that WMI devices with 0 instances are
> rejected. Please drop this check.
>

Good to know, thanks.

> > +
> > +     ((struct cd01_list *)data)->count = count;
> > +     ((struct cd01_list *)data)->data = devm_kmalloc_array(om_dev, count,
> > +                                                           sizeof(struct capdata01 *),
> > +                                                           GFP_KERNEL);
>
> Two things:
>
>   - using a local variable with a type of struct cd01_list * results in cleaner source code here
>
>   - using devres is not possible inside the component callbacks, since the lifetime of the component
>     device is not necessarily tied to the lifetime of the underlying device.
>
> I suggest you move the whole WMI data querying into lwmi_cd01_probe(), because then you can keep using
> devres.
>

Doing this in probe() puts the list on lwmi_cd01_priv. Should I copy
that data on bind, or pass back a pointer to the cd01 device struct
and use an exported function on cd01 and dev_get_drvdata to access
priv->list->data[idx] when needed? I prefer the latter as this avoids
needing to do devm memory allocation in component/master binds, then I
can check for NULL when accessing and clear the pointer on
master_unbind to avoid calling to a removed device driver.

(snip)

> > +struct cd01_list {
> > +     struct capdata01 **data;
> > +     int count;
> > +};
>
> In order to save memory you could try something like this:
>
> struct cd01_list {
>         size_t count;
>         struct capdata01 data[];
> };
>
> This way you
>
> 1. Avoid the memory fragmentation resulting from multiple memory allocations.
>
> 2. Omit two pointers when accessing the data.
>
> You can use struct_size() from linux/overflow.h to calculate the size of such
> an array with a trailing flexible array.
>
> Thanks,
> Armin Wolf
>

I think I have this part working in my branch. Using devm_kzalloc also
allows me to omit manually setting NULL in a few cases, which is
cleaner. Is it preferred to use struct_size() directly in the
devm_kzalloc call, or create a separate `size` variable to set the
result to and pass that into the function?

- Derek


> > +
> > +int lwmi_cd01_match(struct device *dev, void *data);
> > +
> > +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */

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

* Re: [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone WMI Driver
  2025-03-27  3:49   ` Armin Wolf
@ 2025-04-02 20:58     ` Derek John Clark
  2025-04-03  1:32       ` Armin Wolf
  0 siblings, 1 reply; 49+ messages in thread
From: Derek John Clark @ 2025-04-02 20:58 UTC (permalink / raw)
  To: Armin Wolf
  Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
	Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
	Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
	John Martens, platform-driver-x86, linux-doc, linux-kernel

On Wed, Mar 26, 2025 at 8:49 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 17.03.25 um 15:43 schrieb Derek J. Clark:
>
> > Adds lenovo-wmi-gamezone driver which provides the Lenovo Gamezone WMI
> > interface that comes on Lenovo "Gaming Series" hardware. Provides ACPI
> > platform profiles over WMI.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > v4:
> > - Add notifier blocks for the Events and Other Mode drivers.
> > - Remove notifier block chain head and all reference to Thermal Mode
> >    Event GUID.
> > - Add header for Gamezone specific structs and functions.
> > - Various fixes from review.
> > v3:
> > - Use notifier chain to report platform profile changes to any
> >    subscribed drivers.
> > - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
> >    chain.
> > - Adds support for Extreme Mode profile on supported hardware, as well
> >    as a DMI quirk table for some devices that report extreme mode version
> >    support but so not have it fully implemented.
> > - Update to include recent changes to platform-profile.
> > v2:
> > - Use devm_kmalloc to ensure driver can be instanced, remove global
> >    reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> > - Remove GZ_WMI symbol exporting.
> > ---
> >   MAINTAINERS                                |   2 +
> >   drivers/platform/x86/Kconfig               |  13 +
> >   drivers/platform/x86/Makefile              |   1 +
> >   drivers/platform/x86/lenovo-wmi-gamezone.c | 380 +++++++++++++++++++++
> >   drivers/platform/x86/lenovo-wmi-gamezone.h |  18 +
> >   5 files changed, 414 insertions(+)
> >   create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> >   create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 87daee6075ad..0416afd997a0 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13168,6 +13168,8 @@ F:    drivers/platform/x86/lenovo-wmi-capdata01.c
> >   F:  drivers/platform/x86/lenovo-wmi-capdata01.h
> >   F:  drivers/platform/x86/lenovo-wmi-events.c
> >   F:  drivers/platform/x86/lenovo-wmi-events.h
> > +F:   drivers/platform/x86/lenovo-wmi-gamezone.c
> > +F:   drivers/platform/x86/lenovo-wmi-gamezone.h
> >   F:  drivers/platform/x86/lenovo-wmi-helpers.c
> >   F:  drivers/platform/x86/lenovo-wmi-helpers.h
> >   F:  drivers/platform/x86/lenovo-wmi-other.c
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index fc47604e37f7..ecf3246c8fda 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -467,6 +467,19 @@ config LENOVO_WMI_HELPERS
> >       tristate
> >       depends on ACPI_WMI
> >
> > +config LENOVO_WMI_GAMEZONE
> > +     tristate "Lenovo GameZone WMI Driver"
> > +     depends on ACPI_WMI
>
> Hi,
>
> please add a "depends on DMI" here.
>

Acked

> > +     select ACPI_PLATFORM_PROFILE
> > +     select LENOVO_WMI_EVENTS
> > +     select LENOVO_WMI_HELPERS
> > +     help
> > +       Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> > +       platform-profile firmware interface to manage power usage.
> > +
> > +       To compile this driver as a module, choose M here: the module will
> > +       be called lenovo-wmi-gamezone.
> > +
> >   config LENOVO_WMI_DATA01
> >       tristate
> >       depends on ACPI_WMI
> > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> > index c6ce3c8594b1..f3e64926a96b 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -71,6 +71,7 @@ obj-$(CONFIG_YT2_1380)              += lenovo-yoga-tab2-pro-1380-fastcharger.o
> >   obj-$(CONFIG_LENOVO_WMI_CAMERA)     += lenovo-wmi-camera.o
> >   obj-$(CONFIG_LENOVO_WMI_DATA01)     += lenovo-wmi-capdata01.o
> >   obj-$(CONFIG_LENOVO_WMI_EVENTS)     += lenovo-wmi-events.o
> > +obj-$(CONFIG_LENOVO_WMI_GAMEZONE)    += lenovo-wmi-gamezone.o
> >   obj-$(CONFIG_LENOVO_WMI_HELPERS)    += lenovo-wmi-helpers.o
> >   obj-$(CONFIG_LENOVO_WMI_TUNING)     += lenovo-wmi-other.o
> >
> > diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > new file mode 100644
> > index 000000000000..9d453a836227
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > @@ -0,0 +1,380 @@
> > +// 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + */
> > +
> > +#include <linux/dmi.h>
> > +#include <linux/list.h>
> > +#include <linux/notifier.h>
> > +#include <linux/platform_profile.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
>
> Please also include linux/acpi.h, linux/export.h and linux/module.h.
>
> > +
> > +#include "lenovo-wmi-events.h"
> > +#include "lenovo-wmi-gamezone.h"
> > +#include "lenovo-wmi-helpers.h"
> > +#include "lenovo-wmi-other.h"
> > +
> > +/* Interface GUIDs */
> > +#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 BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> > +
> > +struct lwmi_event_priv {
> > +     enum thermal_mode current_mode;
> > +     struct wmi_device *wdev;
> > +     bool extreme_supported;
> > +     struct device *ppdev; /*platform profile device */
> > +     struct notifier_block event_nb;
> > +     struct notifier_block mode_nb;
> > +};
> > +
> > +struct quirk_entry {
> > +     bool extreme_supported;
> > +};
> > +
> > +static struct quirk_entry quirk_no_extreme_bug = {
> > +     .extreme_supported = false,
> > +};
> > +
> > +/* Notifier Methods */
> > +/*
>
> /* -> /**, same goes for the other kernel doc comments.
>
> > + * lwmi_gz_mode_call() - Call method for lenovo-wmi-other notifier
> > + * block call chain. For THERMAL_MODE_EVENT, returns current_mode
> > + *
> > + * @nb: The notifier_block registered to lenovo-wmi-other
> > + * @cmd: The event triggered by lenovo-wmi-other
> > + * @data: The data to be returned by the event.
> > + *
> > + * Returns: notifier_block status.
> > + */
> > +static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
> > +                          void *data)
> > +{
> > +     struct lwmi_event_priv *priv;
> > +
> > +     priv = container_of(nb, struct lwmi_event_priv, mode_nb);
> > +     if (!priv)
> > +             return NOTIFY_BAD;
> > +
> > +     switch (cmd) {
> > +     case THERMAL_MODE_EVENT:
>
> I think it would be better to have a separate command code (maybe GAMEZONE_GET_THERMAL_MODE) for this
> kind of request. Maybe you can define a separate enum for that?
>

I originally had two enums for this but it seemed redundant. TBS this
is probably a better idea as the spec has two different event types
for thermal mode events (one is a buffer with additional data) and
getting the thermal mode from GZ would be agnostic from the events
that are triggered.

> > +             *(enum thermal_mode *)data = priv->current_mode;
>
> I think you need to protect this variable from concurrent accesses. Maybe a spinlock would
> be suitable here?
>

Acked

> > +             break;
>
> Please return NOTIFY_STOP here to prevent the notifier call chain from calling further.
>

Acked

> > +     default:
> > +             return NOTIFY_DONE;
> > +     }
> > +
> > +     return NOTIFY_OK;
> > +}
> > +
> > +/*
> > + * lwmi_gz_event_call() - Call method for lenovo-wmi-events notifier
> > + * block call chain. For THERMAL_MODE_EVENT, sets current_mode and
> > + * notifies platform_profile of a change.
> > + *
> > + * @nb: The notifier_block registered to lenovo-wmi-events
> > + * @cmd: The event triggered by lenovo-wmi-events
> > + * @data: The data to be updated by the event.
> > + *
> > + * Returns: notifier_block status.
> > + */
> > +static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
> > +                           void *data)
> > +{
> > +     struct lwmi_event_priv *priv;
> > +
> > +     priv = container_of(nb, struct lwmi_event_priv, event_nb);
> > +     if (!priv)
> > +             return NOTIFY_BAD;
>
> This check is unnecessary, please drop it?
>

Will do

> > +
> > +     switch (cmd) {
> > +     case THERMAL_MODE_EVENT:
> > +             priv->current_mode = *((enum thermal_mode *)data);
>
> You do not need to explicitly cast void pointers. Also please validate that the event data
> is actually a valid thermal mode. This check should IMHO happen inside the event driver itself.
>
> > +             platform_profile_notify(&priv->wdev->dev);
>
> You are supposed to pass the platform profile device as the argument.
>

That makes more sense. I'll correct this, thanks.

> > +             break;
> > +     default:
> > +             return NOTIFY_DONE;
> > +     }
> > +
> > +     return NOTIFY_OK;
> > +}
> > +
> > +/* Platform Profile Methods & Setup */
> > +/*
> > + * lwmi_gz_platform_profile_supported() - Gets the version of the WMI
> > + * interface to determine the support level.
> > + *
> > + * @wdev: The Gamezone WMI device.
> > + * @supported: Pointer to return the support level with.
> > + *
> > + * Returns: 0, or an error.
> > + */
> > +static int lwmi_gz_platform_profile_supported(struct wmi_device *wdev,
> > +                                           int *supported)
> > +{
> > +     return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP,
> > +                                     0, 0, supported);
> > +}
> > +
> > +/*
> > + * lwmi_gz_thermal_mode_get() - Gets the currently set thermal mode from
> > + * the Gamezone WMI interface.
> > + *
> > + * @wdev: The Gamezone WMI device.
> > + * @mode: Pointer to return the thermal mode with.
> > + *
> > + * Returns: 0, or an error.
> > + */
> > +static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
> > +                                 enum thermal_mode *mode)
> > +{
> > +     return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET,
> > +                                     0, 0, mode);
> > +}
> > +
> > +static int lwmi_gz_profile_get(struct device *dev,
> > +                            enum platform_profile_option *profile)
> > +{
> > +     struct lwmi_event_priv *priv = dev_get_drvdata(dev);
> > +     enum thermal_mode mode;
> > +     int ret;
> > +
> > +     ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode);
> > +     if (ret)
> > +             return ret;
> > +
> > +     switch (mode) {
> > +     case SMARTFAN_MODE_QUIET:
> > +             *profile = PLATFORM_PROFILE_LOW_POWER;
> > +             break;
> > +     case SMARTFAN_MODE_BALANCED:
> > +             *profile = PLATFORM_PROFILE_BALANCED;
> > +             break;
> > +     case SMARTFAN_MODE_PERFORMANCE:
> > +             if (priv->extreme_supported) {
> > +                     *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> > +                     break;
> > +             }
> > +             *profile = PLATFORM_PROFILE_PERFORMANCE;
> > +             break;
> > +     case SMARTFAN_MODE_EXTREME:
> > +             *profile = PLATFORM_PROFILE_PERFORMANCE;
> > +             break;
> > +     case SMARTFAN_MODE_CUSTOM:
> > +             *profile = PLATFORM_PROFILE_CUSTOM;
> > +             break;
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +
> > +     priv->current_mode = mode;
> > +
> > +     return 0;
> > +}
> > +
> > +static int lwmi_gz_profile_set(struct device *dev,
> > +                            enum platform_profile_option profile)
> > +{
> > +     struct lwmi_event_priv *priv = dev_get_drvdata(dev);
> > +     struct wmi_method_args_32 args;
> > +     enum thermal_mode mode;
> > +     int ret;
> > +
> > +     switch (profile) {
> > +     case PLATFORM_PROFILE_LOW_POWER:
> > +             mode = SMARTFAN_MODE_QUIET;
> > +             break;
> > +     case PLATFORM_PROFILE_BALANCED:
> > +             mode = SMARTFAN_MODE_BALANCED;
> > +             break;
> > +     case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> > +             mode = SMARTFAN_MODE_PERFORMANCE;
> > +             break;
> > +     case PLATFORM_PROFILE_PERFORMANCE:
> > +             if (priv->extreme_supported) {
> > +                     mode = SMARTFAN_MODE_EXTREME;
> > +                     break;
> > +             }
> > +             mode = SMARTFAN_MODE_PERFORMANCE;
> > +             break;
> > +     case PLATFORM_PROFILE_CUSTOM:
> > +             mode = SMARTFAN_MODE_CUSTOM;
> > +             break;
> > +     default:
> > +             return -EOPNOTSUPP;
> > +     }
> > +
> > +     args.arg0 = mode;
> > +
> > +     ret = lwmi_dev_evaluate_method(priv->wdev, 0x0,
> > +                                    WMI_METHOD_ID_SMARTFAN_SET,
> > +                                    (unsigned char *)&args, sizeof(args),
> > +                                    NULL);
> > +     if (ret)
> > +             return ret;
> > +
> > +     priv->current_mode = mode;
> > +
> > +     return 0;
> > +}
> > +
> > +static const struct dmi_system_id fwbug_list[] = {
> > +     {
> > +             .ident = "Legion Go 8APU1",
> > +             .matches = {
> > +                     DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > +                     DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> > +             },
> > +             .driver_data = &quirk_no_extreme_bug,
> > +     },
> > +     {
> > +             .ident = "Legion Go S 8ARP1",
> > +             .matches = {
> > +                     DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > +                     DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> > +             },
> > +             .driver_data = &quirk_no_extreme_bug,
> > +     },
> > +     {
> > +             .ident = "Legion Go S 8APU1",
> > +             .matches = {
> > +                     DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > +                     DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> > +             },
> > +             .driver_data = &quirk_no_extreme_bug,
> > +     },
> > +     {},
> > +
> > +};
> > +
> > +/*
> > + * extreme_supported() - Evaluate if a device supports extreme thermal mode.
> > + * For devices that have a profile_support_ver of 6 or greater a DMI check
> > + * is done. Some devices report a version that supports extreme mode but
> > + * have an incomplete entry in the BIOS. To ensure this cannot be set, they
> > + * are quirked to prevent assignment.
> > + *
> > + * @profile_support_ver: Version of WMI interface provided by
> > + * lwmi_gz_platform_profile_supported.
> > + *
> > + * Returns: bool
> > + */
> > +static bool extreme_supported(int profile_support_ver)
> > +{
> > +     const struct dmi_system_id *dmi_id;
> > +     struct quirk_entry *quirks;
> > +
> > +     if (profile_support_ver < 6)
> > +             return false;
> > +
> > +     dmi_id = dmi_first_match(fwbug_list);
> > +     if (!dmi_id)
> > +             return true;
> > +
> > +     quirks = dmi_id->driver_data;
> > +     return quirks->extreme_supported;
> > +}
> > +
> > +static int lwmi_platform_profile_probe(void *drvdata, unsigned long *choices)
> > +{
> > +     struct lwmi_event_priv *priv = drvdata;
> > +     int profile_support_ver;
> > +     int ret;
> > +
> > +     ret = lwmi_gz_platform_profile_supported(priv->wdev,
> > +                                              &profile_support_ver);
> > +     if (ret)
> > +             return ret;
> > +
> > +     if (profile_support_ver < 1)
> > +             return -ENODEV;
> > +
> > +     priv->extreme_supported = extreme_supported(profile_support_ver);
> > +
> > +     set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> > +     set_bit(PLATFORM_PROFILE_BALANCED, choices);
> > +     set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> > +     set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> > +
> > +     if (priv->extreme_supported)
> > +             set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> > +
> > +     return 0;
> > +}
> > +
> > +static const struct platform_profile_ops lwmi_gz_platform_profile_ops = {
> > +     .probe = lwmi_platform_profile_probe,
> > +     .profile_get = lwmi_gz_profile_get,
> > +     .profile_set = lwmi_gz_profile_set,
> > +};
> > +
> > +/* Driver Methods */
> > +static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
> > +{
> > +     struct lwmi_event_priv *priv;
> > +     int ret;
> > +
> > +     priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > +     if (!priv)
> > +             return -ENOMEM;
> > +
> > +     priv->event_nb.notifier_call = lwmi_gz_event_call;
> > +     ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
> > +     if (ret)
> > +             return ret;
>
> You should register the event notifier after registering the platform profile or else
> a WMI event could arrive before the platform profile was registered, resulting in
> platform_profile_notify() being called on a invalid device pointer.
>

Acked

> > +
> > +     priv->mode_nb.notifier_call = lwmi_gz_mode_call;
> > +     ret = devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb);
> > +     if (ret)
> > +             return ret;
> > +
> > +     priv->wdev = wdev;
> > +     dev_set_drvdata(&wdev->dev, priv);
>
> This should happen before the notifiers are registered or else they might try to access
> those values before they are actually initialized.
>

Acked

> > +
> > +     priv->ppdev = platform_profile_register(&wdev->dev,
> > +                                             "lenovo-wmi-gamezone", priv,
> > +                                             &lwmi_gz_platform_profile_ops);
> > +
> > +     if (IS_ERR(priv->ppdev))
> > +             return -ENODEV;
> > +
> > +     ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode);
> > +     if (ret)
> > +             return ret;
>
> The thermal mode should be initialized before any notifiers using it are registered.
>

I'll register notifiers last in this func.

> > +
> > +     return 0;
> > +}
> > +
> > +static const struct wmi_device_id lwmi_gz_id_table[] = { { LENOVO_GAMEZONE_GUID,
> > +                                                        NULL },
> > +                                                      {} };
>
> Please fix the formatting here.
>

Will do.

Thanks,
Derek

> Thanks,
> Armin Wolf
>
> > +
> > +static struct wmi_driver lwmi_gz_driver = {
> > +     .driver = {
> > +             .name = "lenovo_wmi_gamezone",
> > +             .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> > +     },
> > +     .id_table = lwmi_gz_id_table,
> > +     .probe = lwmi_gz_probe,
> > +     .no_singleton = true,
> > +};
> > +
> > +module_wmi_driver(lwmi_gz_driver);
> > +
> > +MODULE_IMPORT_NS("LENOVO_WMI_EVENTS");
> > +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> > +MODULE_IMPORT_NS("LENOVO_WMI_OTHER");
> > +MODULE_DEVICE_TABLE(wmi, lwmi_gz_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-wmi-gamezone.h b/drivers/platform/x86/lenovo-wmi-gamezone.h
> > new file mode 100644
> > index 000000000000..ac536803160b
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-gamezone.h
> > @@ -0,0 +1,18 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
> > + */
> > +
> > +#ifndef _LENOVO_WMI_GAMEZONE_H_
> > +#define _LENOVO_WMI_GAMEZONE_H_
> > +
> > +enum thermal_mode {
> > +     SMARTFAN_MODE_QUIET = 0x01,
> > +     SMARTFAN_MODE_BALANCED = 0x02,
> > +     SMARTFAN_MODE_PERFORMANCE = 0x03,
> > +     SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> > +     SMARTFAN_MODE_CUSTOM = 0xFF,
> > +};
> > +
> > +#endif /* !_LENOVO_WMI_GAMEZONE_H_ */

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

* Re: [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers
  2025-03-27 12:43   ` Ilpo Järvinen
@ 2025-04-02 21:22     ` Derek John Clark
  0 siblings, 0 replies; 49+ messages in thread
From: Derek John Clark @ 2025-04-02 21:22 UTC (permalink / raw)
  To: Ilpo Järvinen
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

On Thu, Mar 27, 2025 at 5:44 AM Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Mon, 17 Mar 2025, Derek J. Clark wrote:
>
> > Adds documentation for all new lenovo-wmi drivers.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > v4:
> >  - Changed namespace to LENOVO_WMI_HELPERS from LENOVO_WMI.
> >  - Changed filenames to lenovo-wmi-helpers from lenovo-wmi.
> >  - Removed structs and functions implemented by other drivers.
> > ---
> >  MAINTAINERS                               |  2 +
> >  drivers/platform/x86/Kconfig              |  4 ++
> >  drivers/platform/x86/Makefile             |  1 +
> >  drivers/platform/x86/lenovo-wmi-helpers.c | 64 +++++++++++++++++++++++
> >  drivers/platform/x86/lenovo-wmi-helpers.h | 24 +++++++++
> >  5 files changed, 95 insertions(+)
> >  create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c
> >  create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 675f4b26426d..3a370a18b806 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13164,6 +13164,8 @@ L:    platform-driver-x86@vger.kernel.org
> >  S:   Maintained
> >  F:   Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> >  F:   Documentation/wmi/devices/lenovo-wmi-other.rst
> > +F:   drivers/platform/x86/lenovo-wmi-helpers.c
> > +F:   drivers/platform/x86/lenovo-wmi-helpers.h
> >
> >  LENOVO WMI HOTKEY UTILITIES DRIVER
> >  M:   Jackie Dong <xy-jackie@139.com>
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 43407e76476b..bece1ba61417 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -459,6 +459,10 @@ config IBM_RTL
> >        state = 0 (BIOS SMIs on)
> >        state = 1 (BIOS SMIs off)
> >
> > +config LENOVO_WMI_HELPERS
> > +     tristate
> > +     depends on ACPI_WMI
> > +
> >  config IDEAPAD_LAPTOP
> >       tristate "Lenovo IdeaPad Laptop Extras"
> >       depends on ACPI
> > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> > index 650dfbebb6c8..5a9f4e94f78b 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -69,6 +69,7 @@ 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_LENOVO_WMI_HELPERS)     += lenovo-wmi-helpers.o
> >
> >  # Intel
> >  obj-y                                += intel/
> > diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x86/lenovo-wmi-helpers.c
> > new file mode 100644
> > index 000000000000..36d553502223
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-helpers.c
> > @@ -0,0 +1,64 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Lenovo Legion WMI helpers driver.
>
> Please add empty comment row here.
>

Acked for all instances in the series.

> >  The Lenovo Legion WMI interface is
> > + * broken up into multiple GUID interfaces that require cross-references
> > + * between GUID's for some functionality. The "Custom Method" 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 Method" 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 drivers
> > + * uses a common procedure to get data fro the WMI interface, enumerated here.
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
> > + */
> > +
> > +#include <linux/wmi.h>
> > +#include "lenovo-wmi-helpers.h"
>
> Put a space between these lines
>

Acked for all instances in the series.

> > +
> > +/*
> > + * lwmi_dev_evaluate_method() - Helper function to call wmidev_evaluate_method
> > + * for Lenovo WMI device method calls that return an ACPI integer.
>
> This should be a shorter summary and the rest should be put into own
> paragraph between the arguments and Return:

Acked for all instances in the series.

> > + * @wdev: Pointer to the WMI device to be called.
> > + * @instance: Instance of the called method.
> > + * @method_id: WMI Method ID for the method to be called.
> > + * @buf: Buffer of all arguments for the given method_id.
> > + * @size: Length of the buffer.
> > + * @retval: Pointer for the return value to be assigned.
> > + *
> > + * Returns: 0, or an error.
>
> Return:
>

Acked for all instances in the series.

> > + */
> > +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> > +                          u32 method_id, unsigned char *buf, size_t size,
> > +                          u32 *retval)
> > +{
> > +     struct acpi_buffer input = { size, buf };
> > +     struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> > +     union acpi_object *ret_obj __free(kfree) = NULL;
> > +     acpi_status status;
> > +
> > +     status = wmidev_evaluate_method(wdev, instance, method_id, &input,
> > +                                     &output);
> > +
> > +     if (ACPI_FAILURE(status))
> > +             return -EIO;
> > +
> > +     if (retval) {
> > +             ret_obj = output.pointer;
> > +             if (!ret_obj)
> > +                     return -ENODATA;
> > +
> > +             if (ret_obj->type != ACPI_TYPE_INTEGER)
> > +                     return -ENXIO;
> > +
> > +             *retval = (u32)ret_obj->integer.value;
> > +     }
> > +     return 0;
> > +};
> > +EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_method, "LENOVO_WMI_HELPERS");
> > +
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/platform/x86/lenovo-wmi-helpers.h b/drivers/platform/x86/lenovo-wmi-helpers.h
> > new file mode 100644
> > index 000000000000..7e0d7870790e
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-helpers.h
> > @@ -0,0 +1,24 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
>
> Please remove this extra line.
>

Acked for all instances in the series.

> > + */
> > +#include <linux/notifier.h>
> > +#include <linux/platform_profile.h>
>
> What? Why are these on this side of the ifndef??? And neither is needed by
> this header AFAICT so please drop them.

Leftover from when the header was a single file. I'll remove it.

> > +#ifndef _LENOVO_WMI_HELPERS_H_
> > +#define _LENOVO_WMI_HELPERS_H_
> > +
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
>
> This include is not needed (since you're only using it as a pointer). Do a
> forward declaration instead:
>
> struct wmi_device;
>

I wasn't aware you could do this, that is useful. Acked for all
similar instances in the series.

Thanks,
Derek

> > +
> > +struct wmi_method_args_32 {
> > +     u32 arg0;
> > +     u32 arg1;
> > +};
> > +
> > +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance,
> > +                          u32 method_id, unsigned char *buf, size_t size,
> > +                          u32 *retval);
> > +
> > +#endif /* !_LENOVO_WMI_HELPERS_H_ */
> >
>
> --
>  i.
>

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

* Re: [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver
  2025-03-27 12:56   ` Ilpo Järvinen
@ 2025-04-02 21:22     ` Derek John Clark
  2025-04-03  1:21       ` Armin Wolf
  2025-04-03 11:01       ` Ilpo Järvinen
  0 siblings, 2 replies; 49+ messages in thread
From: Derek John Clark @ 2025-04-02 21:22 UTC (permalink / raw)
  To: Ilpo Järvinen
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

On Thu, Mar 27, 2025 at 5:56 AM Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Mon, 17 Mar 2025, Derek J. Clark wrote:
>
> > Adds lenovo-wmi-capdata01 driver which provides the
> > LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode"
> > enabled hardware. Provides an interface for querying if a given
> > attribute is supported by the hardware, as well as its default_value,
> > max_value, min_value, and step increment.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > v4:
> >  - Make driver data a private struct, remove references from Other Mode
> >    driver.
> >  - Don't cache data at device initialization. Instead, on component bind,
> >    cache the data on a member variable of the Other Mode driver data
> >    passed as a void pointer.
> >  - Add header file for capdata01 structs.
> >  - Add new struct to pass capdata01 array data and array length to Other
> >    Mode.
> > v3:
> > - Add as component to lenovo-wmi-other driver.
> > v2:
> > - Use devm_kmalloc to ensure driver can be instanced, remove global
> >   reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
> > - Use list to get the lenovo_wmi_cd01_priv instance in
> >   lenovo_wmi_capdata01_get as none of the data provided by the macros
> >   that will use it can pass a member of the struct for use in
> >   container_of.
> > ---
> >  MAINTAINERS                                 |   2 +
> >  drivers/platform/x86/Kconfig                |   4 +
> >  drivers/platform/x86/Makefile               |   1 +
> >  drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++
> >  drivers/platform/x86/lenovo-wmi-capdata01.h |  29 +++++
> >  5 files changed, 172 insertions(+)
> >  create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> >  create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 6dde75922aaf..56ead241a053 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13164,6 +13164,8 @@ L:    platform-driver-x86@vger.kernel.org
> >  S:   Maintained
> >  F:   Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> >  F:   Documentation/wmi/devices/lenovo-wmi-other.rst
> > +F:   drivers/platform/x86/lenovo-wmi-capdata01.c
> > +F:   drivers/platform/x86/lenovo-wmi-capdata01.h
> >  F:   drivers/platform/x86/lenovo-wmi-events.c
> >  F:   drivers/platform/x86/lenovo-wmi-events.h
> >  F:   drivers/platform/x86/lenovo-wmi-helpers.c
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 13b8f4ac5dc5..64663667f0cb 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
> >       tristate
> >       depends on ACPI_WMI
> >
> > +config LENOVO_WMI_DATA01
> > +     tristate
> > +     depends on ACPI_WMI
> > +
> >  config IDEAPAD_LAPTOP
> >       tristate "Lenovo IdeaPad Laptop Extras"
> >       depends on ACPI
> > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> > index fc039839286a..7a35c77221b7 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -69,6 +69,7 @@ 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_LENOVO_WMI_DATA01)      += lenovo-wmi-capdata01.o
> >  obj-$(CONFIG_LENOVO_WMI_EVENTS)      += lenovo-wmi-events.o
> >  obj-$(CONFIG_LENOVO_WMI_HELPERS)     += lenovo-wmi-helpers.o
> >
> > diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
> > new file mode 100644
> > index 000000000000..b6876611ffd9
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> > @@ -0,0 +1,136 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * LENOVO_CAPABILITY_DATA_01 WMI data block driver.
>
> Add a empty comment line here, you might want to rephrase the opening of
> the paragraph after splitting these apart.
>
> > This interface provides
> > + * information on tunable attributes used by the "Other Mode" WMI interface,
> > + * including if it is supported by the hardware, the default_value, max_value,
> > + * min_value, and step increment.
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + */
> > +
> > +#include <linux/cleanup.h>
> > +#include <linux/component.h>
> > +#include <linux/container_of.h>
> > +#include <linux/device.h>
> > +#include <linux/gfp_types.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
>
> Add an empty line here please.
>
> > +#include "lenovo-wmi-capdata01.h"
> > +
> > +/* Interface GUIDs */
> > +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> > +
> > +struct lwmi_cd01_priv {
> > +     struct wmi_device *wdev;
> > +};
> > +
> > +/*
> > + * lenovo_cd01_component_bind() - On master bind, caches all capability data on
> > + * the master device.
>
> Is this "On master bind" something that the caller should be doing? IMO,
> that would belong to description paragraph instead of the function
> summary.
>

Acked

> > + * @cd01_dev: Pointer to the capability data 01 parent device.
> > + * @om_dev: Pointer to the other mode parent device.
> > + * @data: capdata01_list object pointer to return the capability data with.
> > + *
> > + * Returns: 0, or an error.
>
> Return:
>
> > + */
> > +static int lenovo_cd01_component_bind(struct device *cd01_dev,
> > +                                   struct device *om_dev, void *data)
> > +{
> > +     struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
> > +     int count, idx;
> > +
> > +     if (!priv)
> > +             return -ENODEV;
> > +
> > +     count = wmidev_instance_count(priv->wdev);
> > +
> > +     if (count == 0)
> > +             return -EINVAL;
> > +
> > +     ((struct cd01_list *)data)->count = count;
>
> Please create a local variable with the correct type and since data is
> void *, you don't need to cast it while assigning to that local variable.
>

This will be reworked a bit with Armin's suggestions.

> > +     ((struct cd01_list *)data)->data = devm_kmalloc_array(om_dev, count,
> > +                                                           sizeof(struct capdata01 *),
>
> sizeof() should preferrably take the type directly from ->data (with the
> correct amount of * chars).
>

With Armin's suggestion I'll be using struct_size, but I'll note this
for future reference, thanks.

> > +                                                           GFP_KERNEL);
> > +     if (!data)
> > +             return -ENOMEM;
> > +
> > +     for (idx = 0; idx < count; idx++) {
> > +             union acpi_object *ret_obj __free(kfree) = NULL;
> > +
> > +             ret_obj = wmidev_block_query(priv->wdev, idx);
> > +             if (!ret_obj) {
> > +                     ((struct cd01_list *)data)->data[idx] = NULL;
> > +                     continue;
> > +             }
> > +             if (ret_obj->type != ACPI_TYPE_BUFFER) {
> > +                     ((struct cd01_list *)data)->data[idx] = NULL;
> > +                     continue;
> > +             }
> > +
> > +             if (ret_obj->buffer.length != sizeof(struct capdata01)) {
>
> You could consider joining these 3 if()s with || to avoid having to
> repeat the NULL assignment and continue.
>

devm_kzalloc will take care of the NULL assignment going forward. for
!ret_obj, should I return an error here instead? It seems like a
problem if that fails.

> > +                     ((struct cd01_list *)data)->data[idx] = NULL;
> > +                     continue;
> > +             }
> > +
> > +             ((struct cd01_list *)data)->data[idx] =
> > +                     devm_kmemdup(om_dev, ret_obj->buffer.pointer,
> > +                                  ret_obj->buffer.length, GFP_KERNEL);
> > +     }
> > +     return 0;
> > +}
> > +
> > +static const struct component_ops lenovo_cd01_component_ops = {
> > +     .bind = lenovo_cd01_component_bind,
> > +};
> > +
> > +static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
> > +
> > +{
> > +     struct lwmi_cd01_priv *priv;
> > +     int ret;
> > +
> > +     priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > +     if (!priv)
> > +             return -ENOMEM;
> > +
> > +     priv->wdev = wdev;
> > +     dev_set_drvdata(&wdev->dev, priv);
> > +
> > +     ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
> > +
> > +     return ret;
> > +}
> > +
> > +static void lwmi_cd01_remove(struct wmi_device *wdev)
> > +{
> > +     component_del(&wdev->dev, &lenovo_cd01_component_ops);
> > +}
> > +
> > +static const struct wmi_device_id lwmi_cd01_id_table[] = {
> > +     { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
> > +     {}
> > +};
> > +
> > +static struct wmi_driver lwmi_cd01_driver = {
> > +     .driver = {
> > +             .name = "lenovo_wmi_cd01",
> > +             .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> > +     },
> > +     .id_table = lwmi_cd01_id_table,
> > +     .probe = lwmi_cd01_probe,
> > +     .remove = lwmi_cd01_remove,
> > +     .no_singleton = true,
> > +};
> > +
> > +int lwmi_cd01_match(struct device *dev, void *data)
> > +{
> > +     return dev->driver == &lwmi_cd01_driver.driver;
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
> > +
> > +module_wmi_driver(lwmi_cd01_driver);
> > +
> > +MODULE_DEVICE_TABLE(wmi, lwmi_cd01_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-wmi-capdata01.h b/drivers/platform/x86/lenovo-wmi-capdata01.h
> > new file mode 100644
> > index 000000000000..c7067a8d0398
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-capdata01.h
> > @@ -0,0 +1,29 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
>
> Extra line
>
> > + */
> > +
> > +#ifndef _LENOVO_WMI_CAPDATA01_H_
> > +#define _LENOVO_WMI_CAPDATA01_H_
> > +
> > +#include <linux/device.h>
>
> Please fwd declare struct device instead.
>
> > +#include <linux/types.h>
> > +
> > +struct capdata01 {
> > +     u32 id;
> > +     u32 supported;
> > +     u32 default_value;
> > +     u32 step;
> > +     u32 min_value;
> > +     u32 max_value;
> > +};
> > +
> > +struct cd01_list {
> > +     struct capdata01 **data;
> > +     int count;
> > +};
> > +
> > +int lwmi_cd01_match(struct device *dev, void *data);
> > +
> > +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
> >
>
> --
>  i.
>

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

* Re: [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode WMI Driver
  2025-03-27 13:49   ` Ilpo Järvinen
@ 2025-04-02 21:22     ` Derek John Clark
  2025-04-03 10:49       ` Ilpo Järvinen
  0 siblings, 1 reply; 49+ messages in thread
From: Derek John Clark @ 2025-04-02 21:22 UTC (permalink / raw)
  To: Ilpo Järvinen
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

On Thu, Mar 27, 2025 at 6:49 AM Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Mon, 17 Mar 2025, Derek J. Clark wrote:
>
> > Adds lenovo-wmi-other driver which provides the Lenovo "Other Mode" WMI
> > interface that comes on some Lenovo "Gaming Series" hardware. Provides a
> > firmware-attributes class which enables the use of tunable knobs for SPL,
> > SPPT, and FPPT.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > v4:
> > - Treat Other Mode as a notifier chain head, use the notifier chain to
> >   get the current mode from Gamezone.
> > - Add header file for Other Mode specific structs and finctions.
> > - Use component master bind to cache the capdata01 array locally.
> > - Drop all reference to external driver private data structs.
> > - Various fixes from review.
> > v3:
> > - Add notifier block and store result for getting the Gamezone interface
> >   profile changes.
> > - Add driver as master component of capdata01 driver.
> > - Use FIELD_PREP where appropriate.
> > - Move macros and associated functions out of lemovo-wmi.h that are only
> >   used by this driver.
> > v2:
> > - Use devm_kmalloc to ensure driver can be instanced, remove global
> >   reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> > - Use list to get the lenovo_wmi_om_priv instance in some macro
> >   called functions as the data provided by the macros that use it
> >   doesn't pass a member of the struct for use in container_of.
> > - Do not rely on GameZone interface to grab the current fan mode.
> > ---
> >  MAINTAINERS                             |   2 +
> >  drivers/platform/x86/Kconfig            |  15 +
> >  drivers/platform/x86/Makefile           |   1 +
> >  drivers/platform/x86/lenovo-wmi-other.c | 626 ++++++++++++++++++++++++
> >  drivers/platform/x86/lenovo-wmi-other.h |  19 +
> >  5 files changed, 663 insertions(+)
> >  create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> >  create mode 100644 drivers/platform/x86/lenovo-wmi-other.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 56ead241a053..87daee6075ad 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13170,6 +13170,8 @@ F:    drivers/platform/x86/lenovo-wmi-events.c
> >  F:   drivers/platform/x86/lenovo-wmi-events.h
> >  F:   drivers/platform/x86/lenovo-wmi-helpers.c
> >  F:   drivers/platform/x86/lenovo-wmi-helpers.h
> > +F:   drivers/platform/x86/lenovo-wmi-other.c
> > +F:   drivers/platform/x86/lenovo-wmi-other.h
> >
> >  LENOVO WMI HOTKEY UTILITIES DRIVER
> >  M:   Jackie Dong <xy-jackie@139.com>
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 64663667f0cb..fc47604e37f7 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -471,6 +471,21 @@ config LENOVO_WMI_DATA01
> >       tristate
> >       depends on ACPI_WMI
> >
> > +config LENOVO_WMI_TUNING
> > +     tristate "Lenovo Other Mode WMI Driver"
> > +     depends on ACPI_WMI
> > +     select FW_ATTR_CLASS
> > +     select LENOVO_WMI_DATA01
> > +     select LENOVO_WMI_EVENTS
> > +     select LENOVO_WMI_HELPERS
> > +     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-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 7a35c77221b7..c6ce3c8594b1 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -72,6 +72,7 @@ obj-$(CONFIG_LENOVO_WMI_CAMERA)     += lenovo-wmi-camera.o
> >  obj-$(CONFIG_LENOVO_WMI_DATA01)      += lenovo-wmi-capdata01.o
> >  obj-$(CONFIG_LENOVO_WMI_EVENTS)      += lenovo-wmi-events.o
> >  obj-$(CONFIG_LENOVO_WMI_HELPERS)     += lenovo-wmi-helpers.o
> > +obj-$(CONFIG_LENOVO_WMI_TUNING)      += lenovo-wmi-other.o
> >
> >  # Intel
> >  obj-y                                += intel/
> > diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> > new file mode 100644
> > index 000000000000..b517e45338e0
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-other.c
> > @@ -0,0 +1,626 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
>
> Again, put a summary on own line as mentioned for the other files.
>
> > + * class to expose the various WMI functions provided by the "Other Mode" 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 laptop
> > + * devices. Each attribute exposed by the "Other Mode"" 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.
> > + *
> > + * 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + */
> > +
> > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/cleanup.h>
> > +#include <linux/component.h>
> > +#include <linux/container_of.h>
> > +#include <linux/device.h>
> > +#include <linux/gfp_types.h>
> > +#include <linux/idr.h>
> > +#include <linux/kobject.h>
> > +#include <linux/notifier.h>
> > +#include <linux/platform_profile.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +
> > +#include "lenovo-wmi-capdata01.h"
> > +#include "lenovo-wmi-events.h"
> > +#include "lenovo-wmi-gamezone.h"
> > +#include "lenovo-wmi-helpers.h"
> > +#include "lenovo-wmi-other.h"
> > +#include "firmware_attributes_class.h"
> > +
> > +/* Interface GUIDs */
> > +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> > +
> > +/* Device IDs */
> > +#define WMI_DEVICE_ID_CPU 0x01
>
> Two comments above add no value and can be dropped.
>
> > +
> > +/* WMI_DEVICE_ID_CPU feature IDs */
> > +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */
> > +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */
> > +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> > +
> > +/* Type IDs*/
>
> This is missing space but it doesn't IMO add any value, so just drop it.
>

Acked

> > +#define WMI_TYPE_ID_NONE 0x00
> > +
> > +/* Method IDs */
> > +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
> > +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
> > +
> > +/* Attribute ID bitmasks */
>
> Neither of two comments for the defines seem to provide much value.
>

Acked

> > +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
> > +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
> > +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
> > +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
>
> Please align the GENMASK()s
>

Acked
> > +static BLOCKING_NOTIFIER_HEAD(om_chain_head);
> > +
> > +enum attribute_property {
> > +     DEFAULT_VAL,
> > +     MAX_VAL,
> > +     MIN_VAL,
> > +     STEP_VAL,
> > +     SUPPORTED,
> > +};
> > +
> > +struct lwmi_om_priv {
> > +     struct blocking_notifier_head nhead;
> > +     struct component_master_ops *ops;
> > +     struct cd01_list cd01_list;
> > +     struct device *fw_attr_dev;
> > +     struct kset *fw_attr_kset;
> > +     struct notifier_block nb;
> > +     struct wmi_device *wdev;
> > +     struct ida ida;
> > +     int ida_id;
> > +};
> > +
> > +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> > +struct tunable_attr_01 {
> > +     u32 type_id;
> > +     u32 device_id;
> > +     u32 feature_id;
> > +     u32 store_value;
> > +     struct device *dev;
> > +     struct capdata01 *capdata;
> > +};
> > +
> > +/* Tunable Attributes */
> > +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> > +                                    .feature_id = WMI_FEATURE_ID_CPU_SPL,
> > +                                    .type_id = WMI_TYPE_ID_NONE };
> > +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> > +                                     .feature_id = WMI_FEATURE_ID_CPU_SPPT,
> > +                                     .type_id = WMI_TYPE_ID_NONE };
> > +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> > +                                     .feature_id = WMI_FEATURE_ID_CPU_FPPT,
> > +                                     .type_id = WMI_TYPE_ID_NONE };
> > +
> > +struct capdata01_attr_group {
> > +     const struct attribute_group *attr_group;
> > +     struct tunable_attr_01 *tunable_attr;
> > +};
> > +
> > +#define FW_ATTR_FOLDER "lenovo-wmi-other"
>
> Please add prefix and move to the top where the other defines are.
>

What do you mean by prefix?

> > +
> > +/* Notifier Methods */
> > +int lwmi_om_register_notifier(struct notifier_block *nb)
> > +{
> > +     return blocking_notifier_chain_register(&om_chain_head, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
> > +
> > +int lwmi_om_unregister_notifier(struct notifier_block *nb)
> > +{
> > +     return blocking_notifier_chain_unregister(&om_chain_head, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
> > +
> > +static void devm_lwmi_om_unregister_notifier(void *data)
> > +{
> > +     struct notifier_block *nb = data;
> > +
> > +     lwmi_om_unregister_notifier(nb);
> > +}
> > +
> > +int devm_lwmi_om_register_notifier(struct device *dev,
> > +                                struct notifier_block *nb)
>
> I'd just put this on a single line.
>

Acked, as well as for similar instances

> > +{
> > +     int ret;
> > +
> > +     ret = lwmi_om_register_notifier(nb);
> > +     if (ret < 0)
> > +             return ret;
> > +
> > +     return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier,
> > +                                     nb);
>
> 80 chars isn't a hard rule so this and a few other ones could be put
> to a single line.
>
> > +}
> > +EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
> > +
> > +static int lwmi_om_notifier_call(enum thermal_mode *mode)
> > +{
> > +     int ret;
> > +
> > +     ret = blocking_notifier_call_chain(&om_chain_head, THERMAL_MODE_EVENT,
> > +                                        mode);
>
> To one line?
>
> > +
>
> Please don't leave empty line between the func call and it's error
> handling.
>

Acked

> > +     if (ret != NOTIFY_OK)
> > +             return -EINVAL;
> > +
> > +     if (*mode < SMARTFAN_MODE_QUIET || *mode > SMARTFAN_MODE_CUSTOM)
> > +             return -EINVAL;
> > +
> > +     return 0;
> > +}
> > +
> > +/* Attribute Methods */
> > +/*
> > + * int_type_show() - Emit the data type for an integer attribute
> > + * @kobj: Pointer to the driver object.
> > + * @kobj_attribute: Pointer to the attribute calling this function.
> > + * @buf: The buffer to write to.
> > + *
> > + * Returns: Number of characters written to buf.
>
> Return:
>
> > + */
> > +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
> > +                          char *buf)
> > +{
> > +     return sysfs_emit(buf, "integer\n");
> > +}
> > +
> > +/*
> > + * attr_capdata01_get - Get the data of the specified attribute
> > + * from lwmi_om->cd01.
>
> "from lwmi_om->cd01" sounds a bit cryptic.
>

I'll elaborate.

> > + * @tunable_attr: The attribute to be populated.
> > + *
> > + * Returns: Either a pointer to capability data, or NULL.
>
> Return:
>
> > + */
> > +static struct capdata01 *
> > +attr_capdata01_get_data(struct lwmi_om_priv *priv,
> > +                     struct tunable_attr_01 *tunable_attr,
> > +                     enum thermal_mode mode)
> > +{
> > +     u32 attribute_id =
> > +             FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > +             FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > +             FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> > +             FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > +     int idx;
> > +
> > +     for (idx = 0; idx < priv->cd01_list.count; idx++) {
> > +             if (!priv->cd01_list.data[idx])
> > +                     continue;
> > +
> > +             if (priv->cd01_list.data[idx]->id != attribute_id)
> > +                     continue;
> > +             return priv->cd01_list.data[idx];
> > +     }
> > +     return NULL;
> > +}
> > +
> > +/**
> > + * attr_capdata01_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.
> > + * @tunable_attr: The attribute to be read.
> > + * @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 number of characters written to buf, or an error.
> > + */
> > +static ssize_t attr_capdata01_show(struct kobject *kobj,
> > +                                struct kobj_attribute *kattr, char *buf,
> > +                                struct tunable_attr_01 *tunable_attr,
> > +                                enum attribute_property prop)
> > +{
> > +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> > +     struct capdata01 *capdata;
> > +     int value;
> > +
> > +     if (!priv)
> > +             return -ENODEV;
> > +
> > +     capdata = attr_capdata01_get_data(priv, tunable_attr,
> > +                                       SMARTFAN_MODE_CUSTOM);
> > +
> > +     if (!capdata)
> > +             return -ENODEV;
> > +
> > +     switch (prop) {
> > +     case DEFAULT_VAL:
> > +             value = capdata->default_value;
> > +             break;
> > +     case MAX_VAL:
> > +             value = capdata->max_value;
> > +             break;
> > +     case MIN_VAL:
> > +             value = capdata->min_value;
> > +             break;
> > +     case STEP_VAL:
> > +             value = capdata->step;
> > +             break;
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +     return sysfs_emit(buf, "%d\n", value);
> > +}
> > +
> > +/* 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.
> > + * @tunable_attr: The attribute to be stored.
> > + *
> > + * 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.
> > + */
> > +static ssize_t attr_current_value_store(struct kobject *kobj,
> > +                                     struct kobj_attribute *kattr,
> > +                                     const char *buf, size_t count,
> > +                                     struct tunable_attr_01 *tunable_attr)
> > +{
> > +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> > +     struct wmi_method_args_32 args;
> > +     struct capdata01 *capdata;
> > +     enum thermal_mode mode;
> > +     u32 attribute_id;
> > +     u32 value;
> > +     int err;
> > +
> > +     if (!priv)
> > +             return -ENODEV;
> > +
> > +     err = lwmi_om_notifier_call(&mode);
> > +     if (err)
> > +             return err;
> > +
> > +     if (mode != SMARTFAN_MODE_CUSTOM)
> > +             return -EINVAL;
> > +
> > +     capdata = attr_capdata01_get_data(priv, tunable_attr, mode);
> > +
> > +     if (!capdata)
> > +             return -ENODEV;
> > +
> > +     attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > +                    FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > +                    FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> > +                    FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > +
> > +     err = kstrtouint(buf, 10, &value);
> > +     if (err)
> > +             return err;
> > +
> > +     if (value < capdata->min_value || value > capdata->max_value)
> > +             return -EINVAL;
> > +
> > +     args.arg0 = attribute_id;
> > +     args.arg1 = value;
> > +
> > +     err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_SET,
> > +                                    (unsigned char *)&args, sizeof(args),
> > +                                    NULL);
> > +
> > +     if (err)
> > +             return err;
> > +
> > +     tunable_attr->store_value = value;
> > +     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.
> > + * @tunable_attr: The 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 number of characters written to buf, or an error.
> > + */
> > +static ssize_t attr_current_value_show(struct kobject *kobj,
> > +                                    struct kobj_attribute *kattr, char *buf,
> > +                                    struct tunable_attr_01 *tunable_attr)
> > +{
> > +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> > +     struct wmi_method_args_32 args;
> > +     enum thermal_mode mode;
> > +     u32 attribute_id;
> > +     int retval;
> > +     int err;
> > +
> > +     if (!priv)
> > +             return -ENODEV;
> > +
> > +     err = lwmi_om_notifier_call(&mode);
> > +     if (err)
> > +             return err;
> > +
> > +     attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > +                    FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > +                    FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> > +                    FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > +
> > +     args.arg0 = attribute_id;
> > +
> > +     err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_GET,
> > +                                    (unsigned char *)&args, sizeof(args),
> > +                                    &retval);
> > +
> > +     if (err)
> > +             return err;
> > +
> > +     return sysfs_emit(buf, "%d\n", retval);
> > +}
> > +
> > +/* Attribute macros */
> > +#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 *kattr, char *buf) \
> > +     {                                                                      \
> > +             return sysfs_emit(buf, _fmt, _val);                            \
> > +     }                                                                      \
> > +     static struct kobj_attribute attr_##_attrname##_##_prop =              \
> > +             __LL_ATTR_RO(_attrname, _prop)
> > +
> > +/* Attribute current value read/write */
> > +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname)                            \
> > +     static ssize_t _attrname##_current_value_store(                        \
> > +             struct kobject *kobj, struct kobj_attribute *kattr,            \
> > +             const char *buf, size_t count)                                 \
> > +     {                                                                      \
> > +             return attr_current_value_store(kobj, kattr, buf, count,       \
> > +                                             &_attrname);                   \
> > +     }                                                                      \
> > +     static ssize_t _attrname##_current_value_show(                         \
> > +             struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> > +     {                                                                      \
> > +             return attr_current_value_show(kobj, kattr, buf, &_attrname);  \
> > +     }                                                                      \
> > +     static struct kobj_attribute attr_##_attrname##_current_value =        \
> > +             __LL_ATTR_RW(_attrname, current_value)
> > +
> > +/* Attribute property read only */
> > +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type)                    \
> > +     static ssize_t _attrname##_##_prop##_show(                             \
> > +             struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> > +     {                                                                      \
> > +             return attr_capdata01_show(kobj, kattr, buf, &_attrname,       \
> > +                                        _prop_type);                        \
> > +     }                                                                      \
> > +     static struct kobj_attribute attr_##_attrname##_##_prop =              \
> > +             __LL_ATTR_RO(_attrname, _prop)
> > +
> > +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname)     \
> > +     __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname);                   \
> > +     __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL);  \
> > +     __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);   \
> > +     __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL);          \
> > +     __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL);          \
> > +     __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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            \
> > +     }
> > +
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> > +                         "Set the CPU sustained power limit");
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> > +                         "Set the CPU slow package power tracking limit");
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> > +                         "Set the CPU fast package power tracking limit");
> > +
> > +static struct capdata01_attr_group capdata01_attr_groups[] = {
> > +     { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> > +     { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> > +     { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> > +     {},
> > +};
> > +
> > +/*
> > + * lwmi_om_fw_attr_add() - Registers all capdata01_attr_groups[] attributes as
> > + * firmware_attributes_class members.
>
> Why this cannot simply be:
>
> Register all firmware_attributes_class members
>
> ?

Sure

> > + * @priv: The Other Mode driver data.
>
> IMO, if you want to go to specific details such as mentioning the variable
> name you can add the description here and write the longer explanation
> (but in this case I'm not sure if it's worth the effort really).
>
> > + * Returns: Either 0, or an error.
>
> Return:
>
> > + */
> > +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
> > +{
> > +     int err, i;
> > +
> > +     ida_init(&priv->ida);
> > +     priv->ida_id = ida_alloc(&priv->ida, GFP_KERNEL);
> > +     if (priv->ida_id < 0)
> > +             return priv->ida_id;
> > +
> > +     priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
> > +                                       MKDEV(0, 0), NULL, "%s",
> > +                                       FW_ATTR_FOLDER);
> > +     if (IS_ERR(priv->fw_attr_dev)) {
> > +             err = PTR_ERR(priv->fw_attr_dev);
> > +             return err;
>
> Leaks the allocated ida?
>

Will fix.

> > +     }
> > +
> > +     priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> > +                                              &priv->fw_attr_dev->kobj);
> > +     if (!priv->fw_attr_kset) {
> > +             err = -ENOMEM;
> > +             goto err_destroy_classdev;
> > +     }
> > +
> > +     for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
>
> Change i to unsigned when used in loops like this.
>
> > +             err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> > +                                      capdata01_attr_groups[i].attr_group);
> > +             if (err) {
> > +                     pr_debug("Failed to create sysfs-group for %s: %d\n",
> > +                              capdata01_attr_groups[i].attr_group->name,
> > +                              err);
> > +                     goto err_remove_groups;
> > +             }
> > +             capdata01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
> > +     }
> > +     return 0;
> > +
> > +err_remove_groups:
> > +     ida_free(&priv->ida, priv->ida_id);
> > +     while (i-- >= 0) {
>
> >= 0 is not necessary.
>

I suppose with an unsigned int it will never go below 0, so that's fine.
FYI this was discussed at length in the last version and added at
Mario's request.

From Mario in that series:
> Well what you've got right now is an unwind routine where if you say
> have 5 attributes and the 5th (0-indexed so "4th") fails, you try to
> free 3rd, 2nd, 1st.
>
> The only problem is the boundary doesn't free the 0th index attribute.
>
> I think that just changing the loop to (i-- >= 0) would solve it.

> > +             sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > +                                capdata01_attr_groups[i].attr_group);
> > +     }
> > +     kset_unregister(priv->fw_attr_kset);
> > +
> > +err_destroy_classdev:
> > +     device_unregister(priv->fw_attr_dev);
> > +     return err;
> > +}
> > +
> > +/*
> > + * lwmi_om_fw_attr_remove() - Unregisters all capdata01_attr_groups[] attributes as
> > + * firmware_attributes_class members.
> > + * @priv: The Other Mode driver data.
> > + *
> > + */
> > +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
> > +{
> > +     int size = ARRAY_SIZE(capdata01_attr_groups);
>
> unsigned int i = ARRAY_SIZE(capdata01_attr_groups) - 1;
>
> > +
> > +     while (--size >= 0) {
>
> while (i--) {
>

Acked

> > +             sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > +                                capdata01_attr_groups[size].attr_group);
> > +     }
> > +     kset_unregister(priv->fw_attr_kset);
> > +     device_unregister(priv->fw_attr_dev);
> > +}
> > +
> > +static int lwmi_om_master_bind(struct device *dev)
> > +{
> > +     struct lwmi_om_priv *priv = dev_get_drvdata(dev);
> > +     int ret;
> > +
> > +     ret = component_bind_all(dev, &priv->cd01_list);
> > +     if (ret)
> > +             return ret;
> > +
> > +     return lwmi_om_fw_attr_add(priv);
> > +}
> > +
> > +static void lwmi_om_master_unbind(struct device *dev)
> > +{
> > +     component_unbind_all(dev, NULL);
> > +}
> > +
> > +static const struct component_master_ops lwmi_om_master_ops = {
> > +     .bind = lwmi_om_master_bind,
> > +     .unbind = lwmi_om_master_unbind,
> > +};
> > +
> > +static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
> > +{
> > +     struct component_match *master_match = NULL;
> > +     struct lwmi_om_priv *priv;
> > +
> > +     priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > +     if (!priv)
> > +             return -ENOMEM;
> > +
> > +     priv->wdev = wdev;
> > +     dev_set_drvdata(&wdev->dev, priv);
> > +
> > +     component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
> > +     if (IS_ERR(master_match))
> > +             return PTR_ERR(master_match);
> > +
> > +     return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
> > +                                            master_match);
> > +}
> > +
> > +static void lwmi_other_remove(struct wmi_device *wdev)
> > +{
> > +     struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
> > +
> > +     component_master_del(&wdev->dev, &lwmi_om_master_ops);
> > +     lwmi_om_fw_attr_remove(priv);
> > +     ida_free(&priv->ida, priv->ida_id);
> > +}
> > +
> > +static const struct wmi_device_id lwmi_other_id_table[] = {
> > +     { LENOVO_OTHER_METHOD_GUID, NULL },
> > +     {}
> > +};
> > +
> > +static struct wmi_driver lwmi_other_driver = {
> > +     .driver = {
> > +             .name = "lenovo_wmi_other",
> > +             .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> > +     },
> > +     .id_table = lwmi_other_id_table,
> > +     .probe = lwmi_other_probe,
> > +     .remove = lwmi_other_remove,
> > +     .no_singleton = true,
> > +};
> > +
> > +module_wmi_driver(lwmi_other_driver);
> > +
> > +MODULE_IMPORT_NS("LENOVO_WMI_CD01");
> > +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> > +MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/platform/x86/lenovo-wmi-other.h b/drivers/platform/x86/lenovo-wmi-other.h
> > new file mode 100644
> > index 000000000000..9fba35ef1137
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-other.h
> > @@ -0,0 +1,19 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
> > + */
> > +
> > +#ifndef _LENOVO_WMI_OTHER_H_
> > +#define _LENOVO_WMI_OTHER_H_
> > +
> > +#include <linux/device.h>
> > +#include <linux/notifier.h>
> > +#include <linux/types.h>
>
> Replace with struct forward declarations.
>
> > +
> > +int lwmi_om_register_notifier(struct notifier_block *nb);
> > +int lwmi_om_unregister_notifier(struct notifier_block *nb);
> > +int devm_lwmi_om_register_notifier(struct device *dev,
> > +                                struct notifier_block *nb);
> > +
> > +#endif /* !_LENOVO_WMI_H_ */
> >
>
> --
>  i.
>

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

* Re: [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone WMI Driver
  2025-03-27 13:56   ` Ilpo Järvinen
@ 2025-04-02 21:22     ` Derek John Clark
  2025-04-03 10:53       ` Ilpo Järvinen
  0 siblings, 1 reply; 49+ messages in thread
From: Derek John Clark @ 2025-04-02 21:22 UTC (permalink / raw)
  To: Ilpo Järvinen
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

On Thu, Mar 27, 2025 at 6:56 AM Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Mon, 17 Mar 2025, Derek J. Clark wrote:
>
> > Adds lenovo-wmi-gamezone driver which provides the Lenovo Gamezone WMI
> > interface that comes on Lenovo "Gaming Series" hardware. Provides ACPI
> > platform profiles over WMI.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>
> This has a few similar nits I flagged for the other patches but I won't
> mark them here again but please go through the patches to find similar
> cases.
>
> > ---
> > v4:
> > - Add notifier blocks for the Events and Other Mode drivers.
> > - Remove notifier block chain head and all reference to Thermal Mode
> >   Event GUID.
> > - Add header for Gamezone specific structs and functions.
> > - Various fixes from review.
> > v3:
> > - Use notifier chain to report platform profile changes to any
> >   subscribed drivers.
> > - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
> >   chain.
> > - Adds support for Extreme Mode profile on supported hardware, as well
> >   as a DMI quirk table for some devices that report extreme mode version
> >   support but so not have it fully implemented.
> > - Update to include recent changes to platform-profile.
> > v2:
> > - Use devm_kmalloc to ensure driver can be instanced, remove global
> >   reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> > - Remove GZ_WMI symbol exporting.
> > ---
> >  MAINTAINERS                                |   2 +
> >  drivers/platform/x86/Kconfig               |  13 +
> >  drivers/platform/x86/Makefile              |   1 +
> >  drivers/platform/x86/lenovo-wmi-gamezone.c | 380 +++++++++++++++++++++
> >  drivers/platform/x86/lenovo-wmi-gamezone.h |  18 +
> >  5 files changed, 414 insertions(+)
> >  create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> >  create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 87daee6075ad..0416afd997a0 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13168,6 +13168,8 @@ F:    drivers/platform/x86/lenovo-wmi-capdata01.c
> >  F:   drivers/platform/x86/lenovo-wmi-capdata01.h
> >  F:   drivers/platform/x86/lenovo-wmi-events.c
> >  F:   drivers/platform/x86/lenovo-wmi-events.h
> > +F:   drivers/platform/x86/lenovo-wmi-gamezone.c
> > +F:   drivers/platform/x86/lenovo-wmi-gamezone.h
> >  F:   drivers/platform/x86/lenovo-wmi-helpers.c
> >  F:   drivers/platform/x86/lenovo-wmi-helpers.h
> >  F:   drivers/platform/x86/lenovo-wmi-other.c
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index fc47604e37f7..ecf3246c8fda 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -467,6 +467,19 @@ config LENOVO_WMI_HELPERS
> >       tristate
> >       depends on ACPI_WMI
> >
> > +config LENOVO_WMI_GAMEZONE
> > +     tristate "Lenovo GameZone WMI Driver"
> > +     depends on ACPI_WMI
> > +     select ACPI_PLATFORM_PROFILE
> > +     select LENOVO_WMI_EVENTS
> > +     select LENOVO_WMI_HELPERS
> > +     help
> > +       Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
> > +       platform-profile firmware interface to manage power usage.
> > +
> > +       To compile this driver as a module, choose M here: the module will
> > +       be called lenovo-wmi-gamezone.
> > +
> >  config LENOVO_WMI_DATA01
> >       tristate
> >       depends on ACPI_WMI
> > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> > index c6ce3c8594b1..f3e64926a96b 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -71,6 +71,7 @@ obj-$(CONFIG_YT2_1380)              += lenovo-yoga-tab2-pro-1380-fastcharger.o
> >  obj-$(CONFIG_LENOVO_WMI_CAMERA)      += lenovo-wmi-camera.o
> >  obj-$(CONFIG_LENOVO_WMI_DATA01)      += lenovo-wmi-capdata01.o
> >  obj-$(CONFIG_LENOVO_WMI_EVENTS)      += lenovo-wmi-events.o
> > +obj-$(CONFIG_LENOVO_WMI_GAMEZONE)    += lenovo-wmi-gamezone.o
> >  obj-$(CONFIG_LENOVO_WMI_HELPERS)     += lenovo-wmi-helpers.o
> >  obj-$(CONFIG_LENOVO_WMI_TUNING)      += lenovo-wmi-other.o
> >
> > diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > new file mode 100644
> > index 000000000000..9d453a836227
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> > @@ -0,0 +1,380 @@
> > +// 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + */
> > +
> > +#include <linux/dmi.h>
> > +#include <linux/list.h>
> > +#include <linux/notifier.h>
> > +#include <linux/platform_profile.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
> > +
> > +#include "lenovo-wmi-events.h"
> > +#include "lenovo-wmi-gamezone.h"
> > +#include "lenovo-wmi-helpers.h"
> > +#include "lenovo-wmi-other.h"
> > +
> > +/* Interface GUIDs */
> > +#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 BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> > +
> > +struct lwmi_event_priv {
> > +     enum thermal_mode current_mode;
> > +     struct wmi_device *wdev;
> > +     bool extreme_supported;
> > +     struct device *ppdev; /*platform profile device */
> > +     struct notifier_block event_nb;
> > +     struct notifier_block mode_nb;
> > +};
> > +
> > +struct quirk_entry {
> > +     bool extreme_supported;
> > +};
> > +
> > +static struct quirk_entry quirk_no_extreme_bug = {
> > +     .extreme_supported = false,
> > +};
> > +
> > +/* Notifier Methods */
> > +/*
> > + * lwmi_gz_mode_call() - Call method for lenovo-wmi-other notifier
> > + * block call chain. For THERMAL_MODE_EVENT, returns current_mode
> > + *
> > + * @nb: The notifier_block registered to lenovo-wmi-other
> > + * @cmd: The event triggered by lenovo-wmi-other
> > + * @data: The data to be returned by the event.
> > + *
> > + * Returns: notifier_block status.
> > + */
> > +static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
> > +                          void *data)
> > +{
> > +     struct lwmi_event_priv *priv;
> > +
> > +     priv = container_of(nb, struct lwmi_event_priv, mode_nb);
> > +     if (!priv)
> > +             return NOTIFY_BAD;
> > +
> > +     switch (cmd) {
> > +     case THERMAL_MODE_EVENT:
> > +             *(enum thermal_mode *)data = priv->current_mode;
> > +             break;
> > +     default:
> > +             return NOTIFY_DONE;
> > +     }
> > +
> > +     return NOTIFY_OK;
> > +}
> > +
> > +/*
> > + * lwmi_gz_event_call() - Call method for lenovo-wmi-events notifier
> > + * block call chain. For THERMAL_MODE_EVENT, sets current_mode and
> > + * notifies platform_profile of a change.
> > + *
> > + * @nb: The notifier_block registered to lenovo-wmi-events
> > + * @cmd: The event triggered by lenovo-wmi-events
> > + * @data: The data to be updated by the event.
> > + *
> > + * Returns: notifier_block status.
> > + */
> > +static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
> > +                           void *data)
> > +{
> > +     struct lwmi_event_priv *priv;
> > +
> > +     priv = container_of(nb, struct lwmi_event_priv, event_nb);
> > +     if (!priv)
> > +             return NOTIFY_BAD;
> > +
> > +     switch (cmd) {
> > +     case THERMAL_MODE_EVENT:
> > +             priv->current_mode = *((enum thermal_mode *)data);
> > +             platform_profile_notify(&priv->wdev->dev);
> > +             break;
> > +     default:
> > +             return NOTIFY_DONE;
> > +     }
> > +
> > +     return NOTIFY_OK;
> > +}
> > +
> > +/* Platform Profile Methods & Setup */
> > +/*
> > + * lwmi_gz_platform_profile_supported() - Gets the version of the WMI
> > + * interface to determine the support level.
> > + *
> > + * @wdev: The Gamezone WMI device.
> > + * @supported: Pointer to return the support level with.
> > + *
> > + * Returns: 0, or an error.
> > + */
> > +static int lwmi_gz_platform_profile_supported(struct wmi_device *wdev,
> > +                                           int *supported)
> > +{
> > +     return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP,
> > +                                     0, 0, supported);
> > +}
> > +
> > +/*
> > + * lwmi_gz_thermal_mode_get() - Gets the currently set thermal mode from
> > + * the Gamezone WMI interface.
> > + *
> > + * @wdev: The Gamezone WMI device.
> > + * @mode: Pointer to return the thermal mode with.
> > + *
> > + * Returns: 0, or an error.
> > + */
> > +static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
> > +                                 enum thermal_mode *mode)
> > +{
> > +     return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET,
> > +                                     0, 0, mode);
> > +}
> > +
> > +static int lwmi_gz_profile_get(struct device *dev,
> > +                            enum platform_profile_option *profile)
> > +{
> > +     struct lwmi_event_priv *priv = dev_get_drvdata(dev);
> > +     enum thermal_mode mode;
> > +     int ret;
> > +
> > +     ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode);
> > +     if (ret)
> > +             return ret;
> > +
> > +     switch (mode) {
> > +     case SMARTFAN_MODE_QUIET:
> > +             *profile = PLATFORM_PROFILE_LOW_POWER;
> > +             break;
> > +     case SMARTFAN_MODE_BALANCED:
> > +             *profile = PLATFORM_PROFILE_BALANCED;
> > +             break;
> > +     case SMARTFAN_MODE_PERFORMANCE:
> > +             if (priv->extreme_supported) {
> > +                     *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> > +                     break;
> > +             }
> > +             *profile = PLATFORM_PROFILE_PERFORMANCE;
> > +             break;
> > +     case SMARTFAN_MODE_EXTREME:
> > +             *profile = PLATFORM_PROFILE_PERFORMANCE;
> > +             break;
> > +     case SMARTFAN_MODE_CUSTOM:
> > +             *profile = PLATFORM_PROFILE_CUSTOM;
> > +             break;
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +
> > +     priv->current_mode = mode;
> > +
> > +     return 0;
> > +}
> > +
> > +static int lwmi_gz_profile_set(struct device *dev,
> > +                            enum platform_profile_option profile)
> > +{
> > +     struct lwmi_event_priv *priv = dev_get_drvdata(dev);
> > +     struct wmi_method_args_32 args;
> > +     enum thermal_mode mode;
> > +     int ret;
> > +
> > +     switch (profile) {
> > +     case PLATFORM_PROFILE_LOW_POWER:
> > +             mode = SMARTFAN_MODE_QUIET;
> > +             break;
> > +     case PLATFORM_PROFILE_BALANCED:
> > +             mode = SMARTFAN_MODE_BALANCED;
> > +             break;
> > +     case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> > +             mode = SMARTFAN_MODE_PERFORMANCE;
> > +             break;
> > +     case PLATFORM_PROFILE_PERFORMANCE:
> > +             if (priv->extreme_supported) {
> > +                     mode = SMARTFAN_MODE_EXTREME;
> > +                     break;
> > +             }
> > +             mode = SMARTFAN_MODE_PERFORMANCE;
> > +             break;
> > +     case PLATFORM_PROFILE_CUSTOM:
> > +             mode = SMARTFAN_MODE_CUSTOM;
> > +             break;
> > +     default:
> > +             return -EOPNOTSUPP;
> > +     }
> > +
> > +     args.arg0 = mode;
> > +
> > +     ret = lwmi_dev_evaluate_method(priv->wdev, 0x0,
> > +                                    WMI_METHOD_ID_SMARTFAN_SET,
> > +                                    (unsigned char *)&args, sizeof(args),
> > +                                    NULL);
> > +     if (ret)
> > +             return ret;
> > +
> > +     priv->current_mode = mode;
> > +
> > +     return 0;
> > +}
> > +
> > +static const struct dmi_system_id fwbug_list[] = {
> > +     {
> > +             .ident = "Legion Go 8APU1",
> > +             .matches = {
> > +                     DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > +                     DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
> > +             },
> > +             .driver_data = &quirk_no_extreme_bug,
> > +     },
> > +     {
> > +             .ident = "Legion Go S 8ARP1",
> > +             .matches = {
> > +                     DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > +                     DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
> > +             },
> > +             .driver_data = &quirk_no_extreme_bug,
> > +     },
> > +     {
> > +             .ident = "Legion Go S 8APU1",
> > +             .matches = {
> > +                     DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> > +                     DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
> > +             },
> > +             .driver_data = &quirk_no_extreme_bug,
> > +     },
> > +     {},
> > +
> > +};
> > +
> > +/*
> > + * extreme_supported() - Evaluate if a device supports extreme thermal mode.
> > + * For devices that have a profile_support_ver of 6 or greater a DMI check
> > + * is done. Some devices report a version that supports extreme mode but
> > + * have an incomplete entry in the BIOS. To ensure this cannot be set, they
> > + * are quirked to prevent assignment.
> > + *
> > + * @profile_support_ver: Version of WMI interface provided by
> > + * lwmi_gz_platform_profile_supported.
> > + *
> > + * Returns: bool
> > + */
> > +static bool extreme_supported(int profile_support_ver)
> > +{
> > +     const struct dmi_system_id *dmi_id;
> > +     struct quirk_entry *quirks;
> > +
> > +     if (profile_support_ver < 6)
> > +             return false;
> > +
> > +     dmi_id = dmi_first_match(fwbug_list);
> > +     if (!dmi_id)
> > +             return true;
> > +
> > +     quirks = dmi_id->driver_data;
> > +     return quirks->extreme_supported;
> > +}
> > +
> > +static int lwmi_platform_profile_probe(void *drvdata, unsigned long *choices)
> > +{
> > +     struct lwmi_event_priv *priv = drvdata;
> > +     int profile_support_ver;
> > +     int ret;
> > +
> > +     ret = lwmi_gz_platform_profile_supported(priv->wdev,
> > +                                              &profile_support_ver);
> > +     if (ret)
> > +             return ret;
> > +
> > +     if (profile_support_ver < 1)
> > +             return -ENODEV;
> > +
> > +     priv->extreme_supported = extreme_supported(profile_support_ver);
> > +
> > +     set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> > +     set_bit(PLATFORM_PROFILE_BALANCED, choices);
> > +     set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> > +     set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> > +
> > +     if (priv->extreme_supported)
> > +             set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> > +
> > +     return 0;
> > +}
> > +
> > +static const struct platform_profile_ops lwmi_gz_platform_profile_ops = {
> > +     .probe = lwmi_platform_profile_probe,
> > +     .profile_get = lwmi_gz_profile_get,
> > +     .profile_set = lwmi_gz_profile_set,
> > +};
> > +
> > +/* Driver Methods */
> > +static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
> > +{
> > +     struct lwmi_event_priv *priv;
> > +     int ret;
> > +
> > +     priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > +     if (!priv)
> > +             return -ENOMEM;
> > +
> > +     priv->event_nb.notifier_call = lwmi_gz_event_call;
> > +     ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
> > +     if (ret)
> > +             return ret;
> > +
> > +     priv->mode_nb.notifier_call = lwmi_gz_mode_call;
> > +     ret = devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb);
> > +     if (ret)
> > +             return ret;
> > +
> > +     priv->wdev = wdev;
> > +     dev_set_drvdata(&wdev->dev, priv);
> > +
> > +     priv->ppdev = platform_profile_register(&wdev->dev,
> > +                                             "lenovo-wmi-gamezone", priv,
> > +                                             &lwmi_gz_platform_profile_ops);
> > +
> > +     if (IS_ERR(priv->ppdev))
> > +             return -ENODEV;
> > +
> > +     ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode);
> > +     if (ret)
> > +             return ret;
> > +
> > +     return 0;
> > +}
> > +
> > +static const struct wmi_device_id lwmi_gz_id_table[] = { { LENOVO_GAMEZONE_GUID,
> > +                                                        NULL },
> > +                                                      {} };
> > +
> > +static struct wmi_driver lwmi_gz_driver = {
> > +     .driver = {
> > +             .name = "lenovo_wmi_gamezone",
> > +             .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> > +     },
> > +     .id_table = lwmi_gz_id_table,
> > +     .probe = lwmi_gz_probe,
> > +     .no_singleton = true,
> > +};
> > +
> > +module_wmi_driver(lwmi_gz_driver);
> > +
> > +MODULE_IMPORT_NS("LENOVO_WMI_EVENTS");
> > +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> > +MODULE_IMPORT_NS("LENOVO_WMI_OTHER");
> > +MODULE_DEVICE_TABLE(wmi, lwmi_gz_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-wmi-gamezone.h b/drivers/platform/x86/lenovo-wmi-gamezone.h
> > new file mode 100644
> > index 000000000000..ac536803160b
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-gamezone.h
> > @@ -0,0 +1,18 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
> > + */
> > +
> > +#ifndef _LENOVO_WMI_GAMEZONE_H_
> > +#define _LENOVO_WMI_GAMEZONE_H_
> > +
> > +enum thermal_mode {
> > +     SMARTFAN_MODE_QUIET = 0x01,
> > +     SMARTFAN_MODE_BALANCED = 0x02,
> > +     SMARTFAN_MODE_PERFORMANCE = 0x03,
> > +     SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> > +     SMARTFAN_MODE_CUSTOM = 0xFF,
> > +};
> > +
> > +#endif /* !_LENOVO_WMI_GAMEZONE_H_ */
> >
>
> Are these going the be used by other .c files?
>

They are used across different c files in this series. The
lenovo-wmi-other driver uses every header.

- Derek

> --
>  i.
>

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

* Re: [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode WMI Driver
  2025-03-27  3:28   ` Armin Wolf
@ 2025-04-02 22:24     ` Derek John Clark
  2025-04-03  1:28       ` Armin Wolf
  0 siblings, 1 reply; 49+ messages in thread
From: Derek John Clark @ 2025-04-02 22:24 UTC (permalink / raw)
  To: Armin Wolf
  Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
	Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
	Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
	John Martens, platform-driver-x86, linux-doc, linux-kernel

On Wed, Mar 26, 2025 at 8:29 PM Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 17.03.25 um 15:43 schrieb Derek J. Clark:
>
> > Adds lenovo-wmi-other driver which provides the Lenovo "Other Mode" WMI
> > interface that comes on some Lenovo "Gaming Series" hardware. Provides a
> > firmware-attributes class which enables the use of tunable knobs for SPL,
> > SPPT, and FPPT.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > v4:
> > - Treat Other Mode as a notifier chain head, use the notifier chain to
> >    get the current mode from Gamezone.
> > - Add header file for Other Mode specific structs and finctions.
> > - Use component master bind to cache the capdata01 array locally.
> > - Drop all reference to external driver private data structs.
> > - Various fixes from review.
> > v3:
> > - Add notifier block and store result for getting the Gamezone interface
> >    profile changes.
> > - Add driver as master component of capdata01 driver.
> > - Use FIELD_PREP where appropriate.
> > - Move macros and associated functions out of lemovo-wmi.h that are only
> >    used by this driver.
> > v2:
> > - Use devm_kmalloc to ensure driver can be instanced, remove global
> >    reference.
> > - Ensure reverse Christmas tree for all variable declarations.
> > - Remove extra whitespace.
> > - Use guard(mutex) in all mutex instances, global mutex.
> > - Use pr_fmt instead of adding the driver name to each pr_err.
> > - Remove noisy pr_info usage.
> > - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> > - Use list to get the lenovo_wmi_om_priv instance in some macro
> >    called functions as the data provided by the macros that use it
> >    doesn't pass a member of the struct for use in container_of.
> > - Do not rely on GameZone interface to grab the current fan mode.
> > ---
> >   MAINTAINERS                             |   2 +
> >   drivers/platform/x86/Kconfig            |  15 +
> >   drivers/platform/x86/Makefile           |   1 +
> >   drivers/platform/x86/lenovo-wmi-other.c | 626 ++++++++++++++++++++++++
> >   drivers/platform/x86/lenovo-wmi-other.h |  19 +
> >   5 files changed, 663 insertions(+)
> >   create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> >   create mode 100644 drivers/platform/x86/lenovo-wmi-other.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 56ead241a053..87daee6075ad 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13170,6 +13170,8 @@ F:    drivers/platform/x86/lenovo-wmi-events.c
> >   F:  drivers/platform/x86/lenovo-wmi-events.h
> >   F:  drivers/platform/x86/lenovo-wmi-helpers.c
> >   F:  drivers/platform/x86/lenovo-wmi-helpers.h
> > +F:   drivers/platform/x86/lenovo-wmi-other.c
> > +F:   drivers/platform/x86/lenovo-wmi-other.h
> >
> >   LENOVO WMI HOTKEY UTILITIES DRIVER
> >   M:  Jackie Dong <xy-jackie@139.com>
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 64663667f0cb..fc47604e37f7 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -471,6 +471,21 @@ config LENOVO_WMI_DATA01
> >       tristate
> >       depends on ACPI_WMI
> >
> > +config LENOVO_WMI_TUNING
> > +     tristate "Lenovo Other Mode WMI Driver"
> > +     depends on ACPI_WMI
> > +     select FW_ATTR_CLASS
> > +     select LENOVO_WMI_DATA01
> > +     select LENOVO_WMI_EVENTS
> > +     select LENOVO_WMI_HELPERS
> > +     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-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 7a35c77221b7..c6ce3c8594b1 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -72,6 +72,7 @@ obj-$(CONFIG_LENOVO_WMI_CAMERA)     += lenovo-wmi-camera.o
> >   obj-$(CONFIG_LENOVO_WMI_DATA01)     += lenovo-wmi-capdata01.o
> >   obj-$(CONFIG_LENOVO_WMI_EVENTS)     += lenovo-wmi-events.o
> >   obj-$(CONFIG_LENOVO_WMI_HELPERS)    += lenovo-wmi-helpers.o
> > +obj-$(CONFIG_LENOVO_WMI_TUNING)      += lenovo-wmi-other.o
> >
> >   # Intel
> >   obj-y                               += intel/
> > diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> > new file mode 100644
> > index 000000000000..b517e45338e0
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-other.c
> > @@ -0,0 +1,626 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
> > + * class to expose the various WMI functions provided by the "Other Mode" 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 laptop
> > + * devices. Each attribute exposed by the "Other Mode"" 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.
> > + *
> > + * 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + */
> > +
> > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/cleanup.h>
> > +#include <linux/component.h>
> > +#include <linux/container_of.h>
> > +#include <linux/device.h>
> > +#include <linux/gfp_types.h>
> > +#include <linux/idr.h>
> > +#include <linux/kobject.h>
> > +#include <linux/notifier.h>
> > +#include <linux/platform_profile.h>
> > +#include <linux/types.h>
> > +#include <linux/wmi.h>
>
> Hi,
>
> please also include linux/acpi.h, linux/export.h and linux/module.h.
>
> > +
> > +#include "lenovo-wmi-capdata01.h"
> > +#include "lenovo-wmi-events.h"
> > +#include "lenovo-wmi-gamezone.h"
> > +#include "lenovo-wmi-helpers.h"
> > +#include "lenovo-wmi-other.h"
> > +#include "firmware_attributes_class.h"
> > +
> > +/* Interface GUIDs */
> > +#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_FPPT 0x03 /* Long Term Power Limit */
> > +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> > +
> > +/* Type IDs*/
> > +#define WMI_TYPE_ID_NONE 0x00
> > +
> > +/* Method IDs */
> > +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
> > +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
> > +
> > +/* Attribute ID bitmasks */
> > +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
> > +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
> > +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
> > +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
> > +
> > +static BLOCKING_NOTIFIER_HEAD(om_chain_head);
> > +
> > +enum attribute_property {
> > +     DEFAULT_VAL,
> > +     MAX_VAL,
> > +     MIN_VAL,
> > +     STEP_VAL,
> > +     SUPPORTED,
> > +};
> > +
> > +struct lwmi_om_priv {
> > +     struct blocking_notifier_head nhead;
>
> Is nhead actually used somewhere?
>

No, I switched to a global. I'll remove.

> > +     struct component_master_ops *ops;
> > +     struct cd01_list cd01_list;
> > +     struct device *fw_attr_dev;
> > +     struct kset *fw_attr_kset;
> > +     struct notifier_block nb;
> > +     struct wmi_device *wdev;
> > +     struct ida ida;
>
> Is this idea actually used somewhere? If yes then please turn it into a global variable.
>

It's something I added for v4 that you requested in v2 and I forgot in
v3. If I'm not using it correctly then I'll rework it. Intent was to
make it unique per instance. Based on your other comment below I
should use this as a postfix when instantiating the FW_ATTR_FOLDER?

> > +     int ida_id;
> > +};
> > +
> > +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> > +struct tunable_attr_01 {
> > +     u32 type_id;
> > +     u32 device_id;
> > +     u32 feature_id;
> > +     u32 store_value;
> > +     struct device *dev;
> > +     struct capdata01 *capdata;
> > +};
> > +
> > +/* Tunable Attributes */
> > +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> > +                                    .feature_id = WMI_FEATURE_ID_CPU_SPL,
> > +                                    .type_id = WMI_TYPE_ID_NONE };
> > +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> > +                                     .feature_id = WMI_FEATURE_ID_CPU_SPPT,
> > +                                     .type_id = WMI_TYPE_ID_NONE };
> > +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> > +                                     .feature_id = WMI_FEATURE_ID_CPU_FPPT,
> > +                                     .type_id = WMI_TYPE_ID_NONE };
> > +
> > +struct capdata01_attr_group {
> > +     const struct attribute_group *attr_group;
> > +     struct tunable_attr_01 *tunable_attr;
> > +};
> > +
> > +#define FW_ATTR_FOLDER "lenovo-wmi-other"
>
> Please use a IDA to give each device a unique name like "lenovo-wmi-otherX". Otherwise
> the driver cannot be instantiated multiple times.
>
> > +
> > +/* Notifier Methods */
> > +int lwmi_om_register_notifier(struct notifier_block *nb)
> > +{
> > +     return blocking_notifier_chain_register(&om_chain_head, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
> > +
> > +int lwmi_om_unregister_notifier(struct notifier_block *nb)
> > +{
> > +     return blocking_notifier_chain_unregister(&om_chain_head, nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
> > +
> > +static void devm_lwmi_om_unregister_notifier(void *data)
> > +{
> > +     struct notifier_block *nb = data;
> > +
> > +     lwmi_om_unregister_notifier(nb);
> > +}
> > +
> > +int devm_lwmi_om_register_notifier(struct device *dev,
> > +                                struct notifier_block *nb)
> > +{
> > +     int ret;
> > +
> > +     ret = lwmi_om_register_notifier(nb);
> > +     if (ret < 0)
> > +             return ret;
> > +
> > +     return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier,
> > +                                     nb);
> > +}
> > +EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
> > +
> > +static int lwmi_om_notifier_call(enum thermal_mode *mode)
> > +{
> > +     int ret;
> > +
> > +     ret = blocking_notifier_call_chain(&om_chain_head, THERMAL_MODE_EVENT,
> > +                                        mode);
> > +
> > +     if (ret != NOTIFY_OK)
> > +             return -EINVAL;
>
> Better remove the NOTIFY_STOP_MASK so that clients can return NOTIFY_STOP:
>
>          (ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK
>

Acked

> > +
> > +     if (*mode < SMARTFAN_MODE_QUIET || *mode > SMARTFAN_MODE_CUSTOM)
> > +             return -EINVAL;
> > +
> > +     return 0;
> > +}
> > +
> > +/* Attribute Methods */
> > +/*
>
> /* -> /**
>
> The same applies to the other kernel doc comments as well.
>
> > + * int_type_show() - Emit the data type for an integer attribute
> > + * @kobj: Pointer to the driver object.
> > + * @kobj_attribute: Pointer to the attribute calling this function.
> > + * @buf: The buffer to write to.
> > + *
> > + * Returns: Number of characters written to buf.
> > + */
> > +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
> > +                          char *buf)
> > +{
> > +     return sysfs_emit(buf, "integer\n");
> > +}
> > +
> > +/*
> > + * attr_capdata01_get - Get the data of the specified attribute
> > + * from lwmi_om->cd01.
> > + * @tunable_attr: The attribute to be populated.
> > + *
> > + * Returns: Either a pointer to capability data, or NULL.
> > + */
> > +static struct capdata01 *
> > +attr_capdata01_get_data(struct lwmi_om_priv *priv,
> > +                     struct tunable_attr_01 *tunable_attr,
> > +                     enum thermal_mode mode)
> > +{
> > +     u32 attribute_id =
> > +             FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > +             FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > +             FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> > +             FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > +     int idx;
> > +
> > +     for (idx = 0; idx < priv->cd01_list.count; idx++) {
> > +             if (!priv->cd01_list.data[idx])
> > +                     continue;
> > +
> > +             if (priv->cd01_list.data[idx]->id != attribute_id)
> > +                     continue;
> > +             return priv->cd01_list.data[idx];
> > +     }
> > +     return NULL;
> > +}
> > +
> > +/**
> > + * attr_capdata01_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.
> > + * @tunable_attr: The attribute to be read.
> > + * @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 number of characters written to buf, or an error.
> > + */
> > +static ssize_t attr_capdata01_show(struct kobject *kobj,
> > +                                struct kobj_attribute *kattr, char *buf,
> > +                                struct tunable_attr_01 *tunable_attr,
> > +                                enum attribute_property prop)
> > +{
> > +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> > +     struct capdata01 *capdata;
> > +     int value;
> > +
> > +     if (!priv)
> > +             return -ENODEV;
>
> Is this check really necessary? If not then please remove it.
>

Acked

> > +
> > +     capdata = attr_capdata01_get_data(priv, tunable_attr,
> > +                                       SMARTFAN_MODE_CUSTOM);
> > +
> > +     if (!capdata)
> > +             return -ENODEV;
> > +
> > +     switch (prop) {
> > +     case DEFAULT_VAL:
> > +             value = capdata->default_value;
> > +             break;
> > +     case MAX_VAL:
> > +             value = capdata->max_value;
> > +             break;
> > +     case MIN_VAL:
> > +             value = capdata->min_value;
> > +             break;
> > +     case STEP_VAL:
> > +             value = capdata->step;
> > +             break;
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +     return sysfs_emit(buf, "%d\n", value);
> > +}
> > +
> > +/* 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.
> > + * @tunable_attr: The attribute to be stored.
> > + *
> > + * 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.
> > + */
> > +static ssize_t attr_current_value_store(struct kobject *kobj,
> > +                                     struct kobj_attribute *kattr,
> > +                                     const char *buf, size_t count,
> > +                                     struct tunable_attr_01 *tunable_attr)
> > +{
> > +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> > +     struct wmi_method_args_32 args;
> > +     struct capdata01 *capdata;
> > +     enum thermal_mode mode;
> > +     u32 attribute_id;
> > +     u32 value;
> > +     int err;
> > +
> > +     if (!priv)
> > +             return -ENODEV;
>
> Same as above.
>
> > +
> > +     err = lwmi_om_notifier_call(&mode);
> > +     if (err)
> > +             return err;
> > +
> > +     if (mode != SMARTFAN_MODE_CUSTOM)
> > +             return -EINVAL;
>
> Better return -EBUSY here to signal userspace that the underlying device currently
> cannot process this request.
>

Acked

> > +
> > +     capdata = attr_capdata01_get_data(priv, tunable_attr, mode);
> > +
> > +     if (!capdata)
> > +             return -ENODEV;
> > +
> > +     attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > +                    FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > +                    FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> > +                    FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > +
> > +     err = kstrtouint(buf, 10, &value);
> > +     if (err)
> > +             return err;
> > +
> > +     if (value < capdata->min_value || value > capdata->max_value)
> > +             return -EINVAL;
> > +
> > +     args.arg0 = attribute_id;
> > +     args.arg1 = value;
> > +
> > +     err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_SET,
> > +                                    (unsigned char *)&args, sizeof(args),
> > +                                    NULL);
> > +
> > +     if (err)
> > +             return err;
> > +
> > +     tunable_attr->store_value = value;
>
> Is store_value actually used somewhere? If no then please remove.
>

No, it's a carry over from when I modeled after asus-armoury. Since
I'm looking up the value each time vice using the last set value I can
drop this. Thanks.

> > +     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.
> > + * @tunable_attr: The 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 number of characters written to buf, or an error.
> > + */
> > +static ssize_t attr_current_value_show(struct kobject *kobj,
> > +                                    struct kobj_attribute *kattr, char *buf,
> > +                                    struct tunable_attr_01 *tunable_attr)
> > +{
> > +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> > +     struct wmi_method_args_32 args;
> > +     enum thermal_mode mode;
> > +     u32 attribute_id;
> > +     int retval;
> > +     int err;
> > +
> > +     if (!priv)
> > +             return -ENODEV;
>
> Same as above.
>

Acked

> > +
> > +     err = lwmi_om_notifier_call(&mode);
> > +     if (err)
> > +             return err;
> > +
> > +     attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > +                    FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > +                    FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> > +                    FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > +
> > +     args.arg0 = attribute_id;
> > +
> > +     err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_GET,
> > +                                    (unsigned char *)&args, sizeof(args),
> > +                                    &retval);
> > +
> > +     if (err)
> > +             return err;
> > +
> > +     return sysfs_emit(buf, "%d\n", retval);
> > +}
> > +
> > +/* Attribute macros */
> > +#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 *kattr, char *buf) \
> > +     {                                                                      \
> > +             return sysfs_emit(buf, _fmt, _val);                            \
> > +     }                                                                      \
> > +     static struct kobj_attribute attr_##_attrname##_##_prop =              \
> > +             __LL_ATTR_RO(_attrname, _prop)
> > +
> > +/* Attribute current value read/write */
> > +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname)                            \
> > +     static ssize_t _attrname##_current_value_store(                        \
> > +             struct kobject *kobj, struct kobj_attribute *kattr,            \
> > +             const char *buf, size_t count)                                 \
> > +     {                                                                      \
> > +             return attr_current_value_store(kobj, kattr, buf, count,       \
> > +                                             &_attrname);                   \
> > +     }                                                                      \
> > +     static ssize_t _attrname##_current_value_show(                         \
> > +             struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> > +     {                                                                      \
> > +             return attr_current_value_show(kobj, kattr, buf, &_attrname);  \
> > +     }                                                                      \
> > +     static struct kobj_attribute attr_##_attrname##_current_value =        \
> > +             __LL_ATTR_RW(_attrname, current_value)
> > +
> > +/* Attribute property read only */
> > +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type)                    \
> > +     static ssize_t _attrname##_##_prop##_show(                             \
> > +             struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
> > +     {                                                                      \
> > +             return attr_capdata01_show(kobj, kattr, buf, &_attrname,       \
> > +                                        _prop_type);                        \
> > +     }                                                                      \
> > +     static struct kobj_attribute attr_##_attrname##_##_prop =              \
> > +             __LL_ATTR_RO(_attrname, _prop)
> > +
> > +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname)     \
> > +     __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname);                   \
> > +     __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL);  \
> > +     __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);   \
> > +     __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL);          \
> > +     __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL);          \
> > +     __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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            \
> > +     }
> > +
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> > +                         "Set the CPU sustained power limit");
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> > +                         "Set the CPU slow package power tracking limit");
> > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> > +                         "Set the CPU fast package power tracking limit");
> > +
> > +static struct capdata01_attr_group capdata01_attr_groups[] = {
> > +     { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> > +     { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> > +     { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> > +     {},
> > +};
> > +
> > +/*
> > + * lwmi_om_fw_attr_add() - Registers all capdata01_attr_groups[] attributes as
> > + * firmware_attributes_class members.
> > + * @priv: The Other Mode driver data.
> > + *
> > + * Returns: Either 0, or an error.
> > + */
> > +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
> > +{
> > +     int err, i;
> > +
> > +     ida_init(&priv->ida);
> > +     priv->ida_id = ida_alloc(&priv->ida, GFP_KERNEL);
> > +     if (priv->ida_id < 0)
> > +             return priv->ida_id;
> > +
> > +     priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
> > +                                       MKDEV(0, 0), NULL, "%s",
> > +                                       FW_ATTR_FOLDER);
> > +     if (IS_ERR(priv->fw_attr_dev)) {
> > +             err = PTR_ERR(priv->fw_attr_dev);
> > +             return err;
> > +     }
> > +
> > +     priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> > +                                              &priv->fw_attr_dev->kobj);
> > +     if (!priv->fw_attr_kset) {
> > +             err = -ENOMEM;
> > +             goto err_destroy_classdev;
> > +     }
> > +
> > +     for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> > +             err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> > +                                      capdata01_attr_groups[i].attr_group);
> > +             if (err) {
> > +                     pr_debug("Failed to create sysfs-group for %s: %d\n",
> > +                              capdata01_attr_groups[i].attr_group->name,
> > +                              err);
> > +                     goto err_remove_groups;
> > +             }
> > +             capdata01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
>
> Since you already know which attributes are supported, maybe it would make sense to only
> create attributes which are supported on a given machine?
>

This is a bit challenging. Since the data is stored per thermal mode I
would need to look through all supported thermal events and aggregate
the supported values. That's an additional call to gamezone and 4-5
additional calls to capdata01. It might be easier to return
-EOPNOTSUPP when accessed for these attributes? I could also assume if
an attribute is available for custom mode it is available for all
modes.

> > +     }
> > +     return 0;
> > +
> > +err_remove_groups:
> > +     ida_free(&priv->ida, priv->ida_id);
>
> The IDA should be freed even when the class device failed to register. But such an IDA
> should be a global variable anyway.
>

Acked

> > +     while (i-- >= 0) {
> > +             sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > +                                capdata01_attr_groups[i].attr_group);
> > +     }
> > +     kset_unregister(priv->fw_attr_kset);
> > +
> > +err_destroy_classdev:
> > +     device_unregister(priv->fw_attr_dev);
> > +     return err;
> > +}
> > +
> > +/*
> > + * lwmi_om_fw_attr_remove() - Unregisters all capdata01_attr_groups[] attributes as
> > + * firmware_attributes_class members.
> > + * @priv: The Other Mode driver data.
> > + *
> > + */
> > +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
> > +{
> > +     int size = ARRAY_SIZE(capdata01_attr_groups);
> > +
> > +     while (--size >= 0) {
>
> Please use a for-loop here.
>

Ilpo and you conflict on this. He wants an unsigned int here and no >=
0. Please advise on the way ahead.

> Thanks,
> Armin Wolf
>
> > +             sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > +                                capdata01_attr_groups[size].attr_group);
> > +     }
> > +     kset_unregister(priv->fw_attr_kset);
> > +     device_unregister(priv->fw_attr_dev);
> > +}
> > +
> > +static int lwmi_om_master_bind(struct device *dev)
> > +{
> > +     struct lwmi_om_priv *priv = dev_get_drvdata(dev);
> > +     int ret;
> > +
> > +     ret = component_bind_all(dev, &priv->cd01_list);
> > +     if (ret)
> > +             return ret;
> > +
> > +     return lwmi_om_fw_attr_add(priv);
> > +}
> > +
> > +static void lwmi_om_master_unbind(struct device *dev)
> > +{
> > +     component_unbind_all(dev, NULL);
>
> Please remove the firmware attributes here too since otherwise the driver will crash
> should another compatible component bind to this master afterwards.
>

Acked.

> > +}
> > +
> > +static const struct component_master_ops lwmi_om_master_ops = {
> > +     .bind = lwmi_om_master_bind,
> > +     .unbind = lwmi_om_master_unbind,
> > +};
> > +
> > +static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
> > +{
> > +     struct component_match *master_match = NULL;
> > +     struct lwmi_om_priv *priv;
> > +
> > +     priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> > +     if (!priv)
> > +             return -ENOMEM;
> > +
> > +     priv->wdev = wdev;
> > +     dev_set_drvdata(&wdev->dev, priv);
> > +
> > +     component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
> > +     if (IS_ERR(master_match))
> > +             return PTR_ERR(master_match);
> > +
> > +     return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
> > +                                            master_match);
> > +}
> > +
> > +static void lwmi_other_remove(struct wmi_device *wdev)
> > +{
> > +     struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
> > +
> > +     component_master_del(&wdev->dev, &lwmi_om_master_ops);
> > +     lwmi_om_fw_attr_remove(priv);
> > +     ida_free(&priv->ida, priv->ida_id);
> > +}
> > +
> > +static const struct wmi_device_id lwmi_other_id_table[] = {
> > +     { LENOVO_OTHER_METHOD_GUID, NULL },
> > +     {}
> > +};
> > +
> > +static struct wmi_driver lwmi_other_driver = {
> > +     .driver = {
> > +             .name = "lenovo_wmi_other",
> > +             .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> > +     },
> > +     .id_table = lwmi_other_id_table,
> > +     .probe = lwmi_other_probe,
> > +     .remove = lwmi_other_remove,
> > +     .no_singleton = true,
> > +};
> > +
> > +module_wmi_driver(lwmi_other_driver);
> > +
> > +MODULE_IMPORT_NS("LENOVO_WMI_CD01");
> > +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> > +MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/platform/x86/lenovo-wmi-other.h b/drivers/platform/x86/lenovo-wmi-other.h
> > new file mode 100644
> > index 000000000000..9fba35ef1137
> > --- /dev/null
> > +++ b/drivers/platform/x86/lenovo-wmi-other.h
> > @@ -0,0 +1,19 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later
> > + *
> > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + *
> > + */
> > +
> > +#ifndef _LENOVO_WMI_OTHER_H_
> > +#define _LENOVO_WMI_OTHER_H_
> > +
> > +#include <linux/device.h>
> > +#include <linux/notifier.h>
> > +#include <linux/types.h>
> > +
> > +int lwmi_om_register_notifier(struct notifier_block *nb);
> > +int lwmi_om_unregister_notifier(struct notifier_block *nb);
> > +int devm_lwmi_om_register_notifier(struct device *dev,
> > +                                struct notifier_block *nb);
> > +
> > +#endif /* !_LENOVO_WMI_H_ */

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

* Re: [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver
  2025-04-02 21:22     ` Derek John Clark
@ 2025-04-03  1:21       ` Armin Wolf
  2025-04-03 11:01       ` Ilpo Järvinen
  1 sibling, 0 replies; 49+ messages in thread
From: Armin Wolf @ 2025-04-03  1:21 UTC (permalink / raw)
  To: Derek John Clark, Ilpo Järvinen
  Cc: Hans de Goede, Jonathan Corbet, Mario Limonciello, Luke Jones,
	Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

Am 02.04.25 um 23:22 schrieb Derek John Clark:

> On Thu, Mar 27, 2025 at 5:56 AM Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
>> On Mon, 17 Mar 2025, Derek J. Clark wrote:
>>
>>> Adds lenovo-wmi-capdata01 driver which provides the
>>> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode"
>>> enabled hardware. Provides an interface for querying if a given
>>> attribute is supported by the hardware, as well as its default_value,
>>> max_value, min_value, and step increment.
>>>
>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>> ---
>>> v4:
>>>   - Make driver data a private struct, remove references from Other Mode
>>>     driver.
>>>   - Don't cache data at device initialization. Instead, on component bind,
>>>     cache the data on a member variable of the Other Mode driver data
>>>     passed as a void pointer.
>>>   - Add header file for capdata01 structs.
>>>   - Add new struct to pass capdata01 array data and array length to Other
>>>     Mode.
>>> v3:
>>> - Add as component to lenovo-wmi-other driver.
>>> v2:
>>> - Use devm_kmalloc to ensure driver can be instanced, remove global
>>>    reference.
>>> - Ensure reverse Christmas tree for all variable declarations.
>>> - Remove extra whitespace.
>>> - Use guard(mutex) in all mutex instances, global mutex.
>>> - Use pr_fmt instead of adding the driver name to each pr_err.
>>> - Remove noisy pr_info usage.
>>> - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
>>> - Use list to get the lenovo_wmi_cd01_priv instance in
>>>    lenovo_wmi_capdata01_get as none of the data provided by the macros
>>>    that will use it can pass a member of the struct for use in
>>>    container_of.
>>> ---
>>>   MAINTAINERS                                 |   2 +
>>>   drivers/platform/x86/Kconfig                |   4 +
>>>   drivers/platform/x86/Makefile               |   1 +
>>>   drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++
>>>   drivers/platform/x86/lenovo-wmi-capdata01.h |  29 +++++
>>>   5 files changed, 172 insertions(+)
>>>   create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>>>   create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index 6dde75922aaf..56ead241a053 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13164,6 +13164,8 @@ L:    platform-driver-x86@vger.kernel.org
>>>   S:   Maintained
>>>   F:   Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>>>   F:   Documentation/wmi/devices/lenovo-wmi-other.rst
>>> +F:   drivers/platform/x86/lenovo-wmi-capdata01.c
>>> +F:   drivers/platform/x86/lenovo-wmi-capdata01.h
>>>   F:   drivers/platform/x86/lenovo-wmi-events.c
>>>   F:   drivers/platform/x86/lenovo-wmi-events.h
>>>   F:   drivers/platform/x86/lenovo-wmi-helpers.c
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 13b8f4ac5dc5..64663667f0cb 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
>>>        tristate
>>>        depends on ACPI_WMI
>>>
>>> +config LENOVO_WMI_DATA01
>>> +     tristate
>>> +     depends on ACPI_WMI
>>> +
>>>   config IDEAPAD_LAPTOP
>>>        tristate "Lenovo IdeaPad Laptop Extras"
>>>        depends on ACPI
>>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>>> index fc039839286a..7a35c77221b7 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_DATA01)      += lenovo-wmi-capdata01.o
>>>   obj-$(CONFIG_LENOVO_WMI_EVENTS)      += lenovo-wmi-events.o
>>>   obj-$(CONFIG_LENOVO_WMI_HELPERS)     += lenovo-wmi-helpers.o
>>>
>>> diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
>>> new file mode 100644
>>> index 000000000000..b6876611ffd9
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
>>> @@ -0,0 +1,136 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * LENOVO_CAPABILITY_DATA_01 WMI data block driver.
>> Add a empty comment line here, you might want to rephrase the opening of
>> the paragraph after splitting these apart.
>>
>>> This interface provides
>>> + * information on tunable attributes used by the "Other Mode" WMI interface,
>>> + * including if it is supported by the hardware, the default_value, max_value,
>>> + * min_value, and step increment.
>>> + *
>>> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + */
>>> +
>>> +#include <linux/cleanup.h>
>>> +#include <linux/component.h>
>>> +#include <linux/container_of.h>
>>> +#include <linux/device.h>
>>> +#include <linux/gfp_types.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>> Add an empty line here please.
>>
>>> +#include "lenovo-wmi-capdata01.h"
>>> +
>>> +/* Interface GUIDs */
>>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>>> +
>>> +struct lwmi_cd01_priv {
>>> +     struct wmi_device *wdev;
>>> +};
>>> +
>>> +/*
>>> + * lenovo_cd01_component_bind() - On master bind, caches all capability data on
>>> + * the master device.
>> Is this "On master bind" something that the caller should be doing? IMO,
>> that would belong to description paragraph instead of the function
>> summary.
>>
> Acked
>
>>> + * @cd01_dev: Pointer to the capability data 01 parent device.
>>> + * @om_dev: Pointer to the other mode parent device.
>>> + * @data: capdata01_list object pointer to return the capability data with.
>>> + *
>>> + * Returns: 0, or an error.
>> Return:
>>
>>> + */
>>> +static int lenovo_cd01_component_bind(struct device *cd01_dev,
>>> +                                   struct device *om_dev, void *data)
>>> +{
>>> +     struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
>>> +     int count, idx;
>>> +
>>> +     if (!priv)
>>> +             return -ENODEV;
>>> +
>>> +     count = wmidev_instance_count(priv->wdev);
>>> +
>>> +     if (count == 0)
>>> +             return -EINVAL;
>>> +
>>> +     ((struct cd01_list *)data)->count = count;
>> Please create a local variable with the correct type and since data is
>> void *, you don't need to cast it while assigning to that local variable.
>>
> This will be reworked a bit with Armin's suggestions.
>
>>> +     ((struct cd01_list *)data)->data = devm_kmalloc_array(om_dev, count,
>>> +                                                           sizeof(struct capdata01 *),
>> sizeof() should preferrably take the type directly from ->data (with the
>> correct amount of * chars).
>>
> With Armin's suggestion I'll be using struct_size, but I'll note this
> for future reference, thanks.
>
>>> +                                                           GFP_KERNEL);
>>> +     if (!data)
>>> +             return -ENOMEM;
>>> +
>>> +     for (idx = 0; idx < count; idx++) {
>>> +             union acpi_object *ret_obj __free(kfree) = NULL;
>>> +
>>> +             ret_obj = wmidev_block_query(priv->wdev, idx);
>>> +             if (!ret_obj) {
>>> +                     ((struct cd01_list *)data)->data[idx] = NULL;
>>> +                     continue;
>>> +             }
>>> +             if (ret_obj->type != ACPI_TYPE_BUFFER) {
>>> +                     ((struct cd01_list *)data)->data[idx] = NULL;
>>> +                     continue;
>>> +             }
>>> +
>>> +             if (ret_obj->buffer.length != sizeof(struct capdata01)) {
>> You could consider joining these 3 if()s with || to avoid having to
>> repeat the NULL assignment and continue.
>>
> devm_kzalloc will take care of the NULL assignment going forward. for
> !ret_obj, should I return an error here instead? It seems like a
> problem if that fails.

Returning an error if a ACPI object is invalid should be OK here. This way the driver
ensures that it either exports all capdata entries on a given machine or none at all.

Just a side note: the check "ret_obj->buffer.length != sizeof(struct capdata01)" should
be turned into "ret_obj->buffer.length < sizeof(struct capdata01)" to also accept oversized
buffers. This would mimic the behavior of the Windows WMI-ACPI driver.

Thanks,
Armin Wolf

>>> +                     ((struct cd01_list *)data)->data[idx] = NULL;
>>> +                     continue;
>>> +             }
>>> +
>>> +             ((struct cd01_list *)data)->data[idx] =
>>> +                     devm_kmemdup(om_dev, ret_obj->buffer.pointer,
>>> +                                  ret_obj->buffer.length, GFP_KERNEL);
>>> +     }
>>> +     return 0;
>>> +}
>>> +
>>> +static const struct component_ops lenovo_cd01_component_ops = {
>>> +     .bind = lenovo_cd01_component_bind,
>>> +};
>>> +
>>> +static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
>>> +
>>> +{
>>> +     struct lwmi_cd01_priv *priv;
>>> +     int ret;
>>> +
>>> +     priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>> +     if (!priv)
>>> +             return -ENOMEM;
>>> +
>>> +     priv->wdev = wdev;
>>> +     dev_set_drvdata(&wdev->dev, priv);
>>> +
>>> +     ret = component_add(&wdev->dev, &lenovo_cd01_component_ops);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static void lwmi_cd01_remove(struct wmi_device *wdev)
>>> +{
>>> +     component_del(&wdev->dev, &lenovo_cd01_component_ops);
>>> +}
>>> +
>>> +static const struct wmi_device_id lwmi_cd01_id_table[] = {
>>> +     { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
>>> +     {}
>>> +};
>>> +
>>> +static struct wmi_driver lwmi_cd01_driver = {
>>> +     .driver = {
>>> +             .name = "lenovo_wmi_cd01",
>>> +             .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>>> +     },
>>> +     .id_table = lwmi_cd01_id_table,
>>> +     .probe = lwmi_cd01_probe,
>>> +     .remove = lwmi_cd01_remove,
>>> +     .no_singleton = true,
>>> +};
>>> +
>>> +int lwmi_cd01_match(struct device *dev, void *data)
>>> +{
>>> +     return dev->driver == &lwmi_cd01_driver.driver;
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
>>> +
>>> +module_wmi_driver(lwmi_cd01_driver);
>>> +
>>> +MODULE_DEVICE_TABLE(wmi, lwmi_cd01_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-wmi-capdata01.h b/drivers/platform/x86/lenovo-wmi-capdata01.h
>>> new file mode 100644
>>> index 000000000000..c7067a8d0398
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.h
>>> @@ -0,0 +1,29 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-or-later
>>> + *
>>> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + *
>> Extra line
>>
>>> + */
>>> +
>>> +#ifndef _LENOVO_WMI_CAPDATA01_H_
>>> +#define _LENOVO_WMI_CAPDATA01_H_
>>> +
>>> +#include <linux/device.h>
>> Please fwd declare struct device instead.
>>
>>> +#include <linux/types.h>
>>> +
>>> +struct capdata01 {
>>> +     u32 id;
>>> +     u32 supported;
>>> +     u32 default_value;
>>> +     u32 step;
>>> +     u32 min_value;
>>> +     u32 max_value;
>>> +};
>>> +
>>> +struct cd01_list {
>>> +     struct capdata01 **data;
>>> +     int count;
>>> +};
>>> +
>>> +int lwmi_cd01_match(struct device *dev, void *data);
>>> +
>>> +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
>>>
>> --
>>   i.
>>

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

* Re: [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode WMI Driver
  2025-04-02 22:24     ` Derek John Clark
@ 2025-04-03  1:28       ` Armin Wolf
  2025-04-03 11:05         ` Ilpo Järvinen
  0 siblings, 1 reply; 49+ messages in thread
From: Armin Wolf @ 2025-04-03  1:28 UTC (permalink / raw)
  To: Derek John Clark
  Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
	Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
	Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
	John Martens, platform-driver-x86, linux-doc, linux-kernel

Am 03.04.25 um 00:24 schrieb Derek John Clark:

> On Wed, Mar 26, 2025 at 8:29 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 17.03.25 um 15:43 schrieb Derek J. Clark:
>>
>>> Adds lenovo-wmi-other driver which provides the Lenovo "Other Mode" WMI
>>> interface that comes on some Lenovo "Gaming Series" hardware. Provides a
>>> firmware-attributes class which enables the use of tunable knobs for SPL,
>>> SPPT, and FPPT.
>>>
>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>> ---
>>> v4:
>>> - Treat Other Mode as a notifier chain head, use the notifier chain to
>>>     get the current mode from Gamezone.
>>> - Add header file for Other Mode specific structs and finctions.
>>> - Use component master bind to cache the capdata01 array locally.
>>> - Drop all reference to external driver private data structs.
>>> - Various fixes from review.
>>> v3:
>>> - Add notifier block and store result for getting the Gamezone interface
>>>     profile changes.
>>> - Add driver as master component of capdata01 driver.
>>> - Use FIELD_PREP where appropriate.
>>> - Move macros and associated functions out of lemovo-wmi.h that are only
>>>     used by this driver.
>>> v2:
>>> - Use devm_kmalloc to ensure driver can be instanced, remove global
>>>     reference.
>>> - Ensure reverse Christmas tree for all variable declarations.
>>> - Remove extra whitespace.
>>> - Use guard(mutex) in all mutex instances, global mutex.
>>> - Use pr_fmt instead of adding the driver name to each pr_err.
>>> - Remove noisy pr_info usage.
>>> - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
>>> - Use list to get the lenovo_wmi_om_priv instance in some macro
>>>     called functions as the data provided by the macros that use it
>>>     doesn't pass a member of the struct for use in container_of.
>>> - Do not rely on GameZone interface to grab the current fan mode.
>>> ---
>>>    MAINTAINERS                             |   2 +
>>>    drivers/platform/x86/Kconfig            |  15 +
>>>    drivers/platform/x86/Makefile           |   1 +
>>>    drivers/platform/x86/lenovo-wmi-other.c | 626 ++++++++++++++++++++++++
>>>    drivers/platform/x86/lenovo-wmi-other.h |  19 +
>>>    5 files changed, 663 insertions(+)
>>>    create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
>>>    create mode 100644 drivers/platform/x86/lenovo-wmi-other.h
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index 56ead241a053..87daee6075ad 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13170,6 +13170,8 @@ F:    drivers/platform/x86/lenovo-wmi-events.c
>>>    F:  drivers/platform/x86/lenovo-wmi-events.h
>>>    F:  drivers/platform/x86/lenovo-wmi-helpers.c
>>>    F:  drivers/platform/x86/lenovo-wmi-helpers.h
>>> +F:   drivers/platform/x86/lenovo-wmi-other.c
>>> +F:   drivers/platform/x86/lenovo-wmi-other.h
>>>
>>>    LENOVO WMI HOTKEY UTILITIES DRIVER
>>>    M:  Jackie Dong <xy-jackie@139.com>
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 64663667f0cb..fc47604e37f7 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -471,6 +471,21 @@ config LENOVO_WMI_DATA01
>>>        tristate
>>>        depends on ACPI_WMI
>>>
>>> +config LENOVO_WMI_TUNING
>>> +     tristate "Lenovo Other Mode WMI Driver"
>>> +     depends on ACPI_WMI
>>> +     select FW_ATTR_CLASS
>>> +     select LENOVO_WMI_DATA01
>>> +     select LENOVO_WMI_EVENTS
>>> +     select LENOVO_WMI_HELPERS
>>> +     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-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 7a35c77221b7..c6ce3c8594b1 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -72,6 +72,7 @@ obj-$(CONFIG_LENOVO_WMI_CAMERA)     += lenovo-wmi-camera.o
>>>    obj-$(CONFIG_LENOVO_WMI_DATA01)     += lenovo-wmi-capdata01.o
>>>    obj-$(CONFIG_LENOVO_WMI_EVENTS)     += lenovo-wmi-events.o
>>>    obj-$(CONFIG_LENOVO_WMI_HELPERS)    += lenovo-wmi-helpers.o
>>> +obj-$(CONFIG_LENOVO_WMI_TUNING)      += lenovo-wmi-other.o
>>>
>>>    # Intel
>>>    obj-y                               += intel/
>>> diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
>>> new file mode 100644
>>> index 000000000000..b517e45338e0
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-other.c
>>> @@ -0,0 +1,626 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
>>> + * class to expose the various WMI functions provided by the "Other Mode" 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 laptop
>>> + * devices. Each attribute exposed by the "Other Mode"" 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.
>>> + *
>>> + * 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + */
>>> +
>>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>>> +
>>> +#include <linux/bitfield.h>
>>> +#include <linux/cleanup.h>
>>> +#include <linux/component.h>
>>> +#include <linux/container_of.h>
>>> +#include <linux/device.h>
>>> +#include <linux/gfp_types.h>
>>> +#include <linux/idr.h>
>>> +#include <linux/kobject.h>
>>> +#include <linux/notifier.h>
>>> +#include <linux/platform_profile.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>> Hi,
>>
>> please also include linux/acpi.h, linux/export.h and linux/module.h.
>>
>>> +
>>> +#include "lenovo-wmi-capdata01.h"
>>> +#include "lenovo-wmi-events.h"
>>> +#include "lenovo-wmi-gamezone.h"
>>> +#include "lenovo-wmi-helpers.h"
>>> +#include "lenovo-wmi-other.h"
>>> +#include "firmware_attributes_class.h"
>>> +
>>> +/* Interface GUIDs */
>>> +#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_FPPT 0x03 /* Long Term Power Limit */
>>> +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
>>> +
>>> +/* Type IDs*/
>>> +#define WMI_TYPE_ID_NONE 0x00
>>> +
>>> +/* Method IDs */
>>> +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
>>> +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
>>> +
>>> +/* Attribute ID bitmasks */
>>> +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
>>> +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
>>> +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
>>> +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
>>> +
>>> +static BLOCKING_NOTIFIER_HEAD(om_chain_head);
>>> +
>>> +enum attribute_property {
>>> +     DEFAULT_VAL,
>>> +     MAX_VAL,
>>> +     MIN_VAL,
>>> +     STEP_VAL,
>>> +     SUPPORTED,
>>> +};
>>> +
>>> +struct lwmi_om_priv {
>>> +     struct blocking_notifier_head nhead;
>> Is nhead actually used somewhere?
>>
> No, I switched to a global. I'll remove.
>
>>> +     struct component_master_ops *ops;
>>> +     struct cd01_list cd01_list;
>>> +     struct device *fw_attr_dev;
>>> +     struct kset *fw_attr_kset;
>>> +     struct notifier_block nb;
>>> +     struct wmi_device *wdev;
>>> +     struct ida ida;
>> Is this idea actually used somewhere? If yes then please turn it into a global variable.
>>
> It's something I added for v4 that you requested in v2 and I forgot in
> v3. If I'm not using it correctly then I'll rework it. Intent was to
> make it unique per instance. Based on your other comment below I
> should use this as a postfix when instantiating the FW_ATTR_FOLDER?

Yes.

>>> +     int ida_id;
>>> +};
>>> +
>>> +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
>>> +struct tunable_attr_01 {
>>> +     u32 type_id;
>>> +     u32 device_id;
>>> +     u32 feature_id;
>>> +     u32 store_value;
>>> +     struct device *dev;
>>> +     struct capdata01 *capdata;
>>> +};
>>> +
>>> +/* Tunable Attributes */
>>> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
>>> +                                    .feature_id = WMI_FEATURE_ID_CPU_SPL,
>>> +                                    .type_id = WMI_TYPE_ID_NONE };
>>> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
>>> +                                     .feature_id = WMI_FEATURE_ID_CPU_SPPT,
>>> +                                     .type_id = WMI_TYPE_ID_NONE };
>>> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
>>> +                                     .feature_id = WMI_FEATURE_ID_CPU_FPPT,
>>> +                                     .type_id = WMI_TYPE_ID_NONE };
>>> +
>>> +struct capdata01_attr_group {
>>> +     const struct attribute_group *attr_group;
>>> +     struct tunable_attr_01 *tunable_attr;
>>> +};
>>> +
>>> +#define FW_ATTR_FOLDER "lenovo-wmi-other"
>> Please use a IDA to give each device a unique name like "lenovo-wmi-otherX". Otherwise
>> the driver cannot be instantiated multiple times.
>>
>>> +
>>> +/* Notifier Methods */
>>> +int lwmi_om_register_notifier(struct notifier_block *nb)
>>> +{
>>> +     return blocking_notifier_chain_register(&om_chain_head, nb);
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
>>> +
>>> +int lwmi_om_unregister_notifier(struct notifier_block *nb)
>>> +{
>>> +     return blocking_notifier_chain_unregister(&om_chain_head, nb);
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
>>> +
>>> +static void devm_lwmi_om_unregister_notifier(void *data)
>>> +{
>>> +     struct notifier_block *nb = data;
>>> +
>>> +     lwmi_om_unregister_notifier(nb);
>>> +}
>>> +
>>> +int devm_lwmi_om_register_notifier(struct device *dev,
>>> +                                struct notifier_block *nb)
>>> +{
>>> +     int ret;
>>> +
>>> +     ret = lwmi_om_register_notifier(nb);
>>> +     if (ret < 0)
>>> +             return ret;
>>> +
>>> +     return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier,
>>> +                                     nb);
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
>>> +
>>> +static int lwmi_om_notifier_call(enum thermal_mode *mode)
>>> +{
>>> +     int ret;
>>> +
>>> +     ret = blocking_notifier_call_chain(&om_chain_head, THERMAL_MODE_EVENT,
>>> +                                        mode);
>>> +
>>> +     if (ret != NOTIFY_OK)
>>> +             return -EINVAL;
>> Better remove the NOTIFY_STOP_MASK so that clients can return NOTIFY_STOP:
>>
>>           (ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK
>>
> Acked
>
>>> +
>>> +     if (*mode < SMARTFAN_MODE_QUIET || *mode > SMARTFAN_MODE_CUSTOM)
>>> +             return -EINVAL;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +/* Attribute Methods */
>>> +/*
>> /* -> /**
>>
>> The same applies to the other kernel doc comments as well.
>>
>>> + * int_type_show() - Emit the data type for an integer attribute
>>> + * @kobj: Pointer to the driver object.
>>> + * @kobj_attribute: Pointer to the attribute calling this function.
>>> + * @buf: The buffer to write to.
>>> + *
>>> + * Returns: Number of characters written to buf.
>>> + */
>>> +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
>>> +                          char *buf)
>>> +{
>>> +     return sysfs_emit(buf, "integer\n");
>>> +}
>>> +
>>> +/*
>>> + * attr_capdata01_get - Get the data of the specified attribute
>>> + * from lwmi_om->cd01.
>>> + * @tunable_attr: The attribute to be populated.
>>> + *
>>> + * Returns: Either a pointer to capability data, or NULL.
>>> + */
>>> +static struct capdata01 *
>>> +attr_capdata01_get_data(struct lwmi_om_priv *priv,
>>> +                     struct tunable_attr_01 *tunable_attr,
>>> +                     enum thermal_mode mode)
>>> +{
>>> +     u32 attribute_id =
>>> +             FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>>> +             FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>>> +             FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
>>> +             FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>>> +     int idx;
>>> +
>>> +     for (idx = 0; idx < priv->cd01_list.count; idx++) {
>>> +             if (!priv->cd01_list.data[idx])
>>> +                     continue;
>>> +
>>> +             if (priv->cd01_list.data[idx]->id != attribute_id)
>>> +                     continue;
>>> +             return priv->cd01_list.data[idx];
>>> +     }
>>> +     return NULL;
>>> +}
>>> +
>>> +/**
>>> + * attr_capdata01_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.
>>> + * @tunable_attr: The attribute to be read.
>>> + * @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 number of characters written to buf, or an error.
>>> + */
>>> +static ssize_t attr_capdata01_show(struct kobject *kobj,
>>> +                                struct kobj_attribute *kattr, char *buf,
>>> +                                struct tunable_attr_01 *tunable_attr,
>>> +                                enum attribute_property prop)
>>> +{
>>> +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
>>> +     struct capdata01 *capdata;
>>> +     int value;
>>> +
>>> +     if (!priv)
>>> +             return -ENODEV;
>> Is this check really necessary? If not then please remove it.
>>
> Acked
>
>>> +
>>> +     capdata = attr_capdata01_get_data(priv, tunable_attr,
>>> +                                       SMARTFAN_MODE_CUSTOM);
>>> +
>>> +     if (!capdata)
>>> +             return -ENODEV;
>>> +
>>> +     switch (prop) {
>>> +     case DEFAULT_VAL:
>>> +             value = capdata->default_value;
>>> +             break;
>>> +     case MAX_VAL:
>>> +             value = capdata->max_value;
>>> +             break;
>>> +     case MIN_VAL:
>>> +             value = capdata->min_value;
>>> +             break;
>>> +     case STEP_VAL:
>>> +             value = capdata->step;
>>> +             break;
>>> +     default:
>>> +             return -EINVAL;
>>> +     }
>>> +     return sysfs_emit(buf, "%d\n", value);
>>> +}
>>> +
>>> +/* 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.
>>> + * @tunable_attr: The attribute to be stored.
>>> + *
>>> + * 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.
>>> + */
>>> +static ssize_t attr_current_value_store(struct kobject *kobj,
>>> +                                     struct kobj_attribute *kattr,
>>> +                                     const char *buf, size_t count,
>>> +                                     struct tunable_attr_01 *tunable_attr)
>>> +{
>>> +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
>>> +     struct wmi_method_args_32 args;
>>> +     struct capdata01 *capdata;
>>> +     enum thermal_mode mode;
>>> +     u32 attribute_id;
>>> +     u32 value;
>>> +     int err;
>>> +
>>> +     if (!priv)
>>> +             return -ENODEV;
>> Same as above.
>>
>>> +
>>> +     err = lwmi_om_notifier_call(&mode);
>>> +     if (err)
>>> +             return err;
>>> +
>>> +     if (mode != SMARTFAN_MODE_CUSTOM)
>>> +             return -EINVAL;
>> Better return -EBUSY here to signal userspace that the underlying device currently
>> cannot process this request.
>>
> Acked
>
>>> +
>>> +     capdata = attr_capdata01_get_data(priv, tunable_attr, mode);
>>> +
>>> +     if (!capdata)
>>> +             return -ENODEV;
>>> +
>>> +     attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>>> +                    FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>>> +                    FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
>>> +                    FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>>> +
>>> +     err = kstrtouint(buf, 10, &value);
>>> +     if (err)
>>> +             return err;
>>> +
>>> +     if (value < capdata->min_value || value > capdata->max_value)
>>> +             return -EINVAL;
>>> +
>>> +     args.arg0 = attribute_id;
>>> +     args.arg1 = value;
>>> +
>>> +     err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_SET,
>>> +                                    (unsigned char *)&args, sizeof(args),
>>> +                                    NULL);
>>> +
>>> +     if (err)
>>> +             return err;
>>> +
>>> +     tunable_attr->store_value = value;
>> Is store_value actually used somewhere? If no then please remove.
>>
> No, it's a carry over from when I modeled after asus-armoury. Since
> I'm looking up the value each time vice using the last set value I can
> drop this. Thanks.
>
>>> +     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.
>>> + * @tunable_attr: The 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 number of characters written to buf, or an error.
>>> + */
>>> +static ssize_t attr_current_value_show(struct kobject *kobj,
>>> +                                    struct kobj_attribute *kattr, char *buf,
>>> +                                    struct tunable_attr_01 *tunable_attr)
>>> +{
>>> +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
>>> +     struct wmi_method_args_32 args;
>>> +     enum thermal_mode mode;
>>> +     u32 attribute_id;
>>> +     int retval;
>>> +     int err;
>>> +
>>> +     if (!priv)
>>> +             return -ENODEV;
>> Same as above.
>>
> Acked
>
>>> +
>>> +     err = lwmi_om_notifier_call(&mode);
>>> +     if (err)
>>> +             return err;
>>> +
>>> +     attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>>> +                    FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>>> +                    FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
>>> +                    FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>>> +
>>> +     args.arg0 = attribute_id;
>>> +
>>> +     err = lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_GET,
>>> +                                    (unsigned char *)&args, sizeof(args),
>>> +                                    &retval);
>>> +
>>> +     if (err)
>>> +             return err;
>>> +
>>> +     return sysfs_emit(buf, "%d\n", retval);
>>> +}
>>> +
>>> +/* Attribute macros */
>>> +#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 *kattr, char *buf) \
>>> +     {                                                                      \
>>> +             return sysfs_emit(buf, _fmt, _val);                            \
>>> +     }                                                                      \
>>> +     static struct kobj_attribute attr_##_attrname##_##_prop =              \
>>> +             __LL_ATTR_RO(_attrname, _prop)
>>> +
>>> +/* Attribute current value read/write */
>>> +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname)                            \
>>> +     static ssize_t _attrname##_current_value_store(                        \
>>> +             struct kobject *kobj, struct kobj_attribute *kattr,            \
>>> +             const char *buf, size_t count)                                 \
>>> +     {                                                                      \
>>> +             return attr_current_value_store(kobj, kattr, buf, count,       \
>>> +                                             &_attrname);                   \
>>> +     }                                                                      \
>>> +     static ssize_t _attrname##_current_value_show(                         \
>>> +             struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
>>> +     {                                                                      \
>>> +             return attr_current_value_show(kobj, kattr, buf, &_attrname);  \
>>> +     }                                                                      \
>>> +     static struct kobj_attribute attr_##_attrname##_current_value =        \
>>> +             __LL_ATTR_RW(_attrname, current_value)
>>> +
>>> +/* Attribute property read only */
>>> +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type)                    \
>>> +     static ssize_t _attrname##_##_prop##_show(                             \
>>> +             struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
>>> +     {                                                                      \
>>> +             return attr_capdata01_show(kobj, kattr, buf, &_attrname,       \
>>> +                                        _prop_type);                        \
>>> +     }                                                                      \
>>> +     static struct kobj_attribute attr_##_attrname##_##_prop =              \
>>> +             __LL_ATTR_RO(_attrname, _prop)
>>> +
>>> +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname)     \
>>> +     __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname);                   \
>>> +     __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL);  \
>>> +     __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);   \
>>> +     __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL);          \
>>> +     __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL);          \
>>> +     __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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            \
>>> +     }
>>> +
>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
>>> +                         "Set the CPU sustained power limit");
>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
>>> +                         "Set the CPU slow package power tracking limit");
>>> +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
>>> +                         "Set the CPU fast package power tracking limit");
>>> +
>>> +static struct capdata01_attr_group capdata01_attr_groups[] = {
>>> +     { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
>>> +     { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
>>> +     { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
>>> +     {},
>>> +};
>>> +
>>> +/*
>>> + * lwmi_om_fw_attr_add() - Registers all capdata01_attr_groups[] attributes as
>>> + * firmware_attributes_class members.
>>> + * @priv: The Other Mode driver data.
>>> + *
>>> + * Returns: Either 0, or an error.
>>> + */
>>> +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
>>> +{
>>> +     int err, i;
>>> +
>>> +     ida_init(&priv->ida);
>>> +     priv->ida_id = ida_alloc(&priv->ida, GFP_KERNEL);
>>> +     if (priv->ida_id < 0)
>>> +             return priv->ida_id;
>>> +
>>> +     priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
>>> +                                       MKDEV(0, 0), NULL, "%s",
>>> +                                       FW_ATTR_FOLDER);
>>> +     if (IS_ERR(priv->fw_attr_dev)) {
>>> +             err = PTR_ERR(priv->fw_attr_dev);
>>> +             return err;
>>> +     }
>>> +
>>> +     priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
>>> +                                              &priv->fw_attr_dev->kobj);
>>> +     if (!priv->fw_attr_kset) {
>>> +             err = -ENOMEM;
>>> +             goto err_destroy_classdev;
>>> +     }
>>> +
>>> +     for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
>>> +             err = sysfs_create_group(&priv->fw_attr_kset->kobj,
>>> +                                      capdata01_attr_groups[i].attr_group);
>>> +             if (err) {
>>> +                     pr_debug("Failed to create sysfs-group for %s: %d\n",
>>> +                              capdata01_attr_groups[i].attr_group->name,
>>> +                              err);
>>> +                     goto err_remove_groups;
>>> +             }
>>> +             capdata01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
>> Since you already know which attributes are supported, maybe it would make sense to only
>> create attributes which are supported on a given machine?
>>
> This is a bit challenging. Since the data is stored per thermal mode I
> would need to look through all supported thermal events and aggregate
> the supported values. That's an additional call to gamezone and 4-5
> additional calls to capdata01. It might be easier to return
> -EOPNOTSUPP when accessed for these attributes? I could also assume if
> an attribute is available for custom mode it is available for all
> modes.

Assuming that a given attribute exists if it is defined for custom mode is OK here.

>>> +     }
>>> +     return 0;
>>> +
>>> +err_remove_groups:
>>> +     ida_free(&priv->ida, priv->ida_id);
>> The IDA should be freed even when the class device failed to register. But such an IDA
>> should be a global variable anyway.
>>
> Acked
>
>>> +     while (i-- >= 0) {
>>> +             sysfs_remove_group(&priv->fw_attr_kset->kobj,
>>> +                                capdata01_attr_groups[i].attr_group);
>>> +     }
>>> +     kset_unregister(priv->fw_attr_kset);
>>> +
>>> +err_destroy_classdev:
>>> +     device_unregister(priv->fw_attr_dev);
>>> +     return err;
>>> +}
>>> +
>>> +/*
>>> + * lwmi_om_fw_attr_remove() - Unregisters all capdata01_attr_groups[] attributes as
>>> + * firmware_attributes_class members.
>>> + * @priv: The Other Mode driver data.
>>> + *
>>> + */
>>> +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
>>> +{
>>> +     int size = ARRAY_SIZE(capdata01_attr_groups);
>>> +
>>> +     while (--size >= 0) {
>> Please use a for-loop here.
>>
> Ilpo and you conflict on this. He wants an unsigned int here and no >=
> 0. Please advise on the way ahead.

I suggest you use a for-loop with an unsigned int counter variable here:

	for (unsigned int i = 0; i < ARRAY_SIZE(capdata01_attr_groups); i++) {
		/* Unregister each group */
	}

Thanks,
Armin Wolf

>> Thanks,
>> Armin Wolf
>>
>>> +             sysfs_remove_group(&priv->fw_attr_kset->kobj,
>>> +                                capdata01_attr_groups[size].attr_group);
>>> +     }
>>> +     kset_unregister(priv->fw_attr_kset);
>>> +     device_unregister(priv->fw_attr_dev);
>>> +}
>>> +
>>> +static int lwmi_om_master_bind(struct device *dev)
>>> +{
>>> +     struct lwmi_om_priv *priv = dev_get_drvdata(dev);
>>> +     int ret;
>>> +
>>> +     ret = component_bind_all(dev, &priv->cd01_list);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     return lwmi_om_fw_attr_add(priv);
>>> +}
>>> +
>>> +static void lwmi_om_master_unbind(struct device *dev)
>>> +{
>>> +     component_unbind_all(dev, NULL);
>> Please remove the firmware attributes here too since otherwise the driver will crash
>> should another compatible component bind to this master afterwards.
>>
> Acked.
>
>>> +}
>>> +
>>> +static const struct component_master_ops lwmi_om_master_ops = {
>>> +     .bind = lwmi_om_master_bind,
>>> +     .unbind = lwmi_om_master_unbind,
>>> +};
>>> +
>>> +static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
>>> +{
>>> +     struct component_match *master_match = NULL;
>>> +     struct lwmi_om_priv *priv;
>>> +
>>> +     priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>> +     if (!priv)
>>> +             return -ENOMEM;
>>> +
>>> +     priv->wdev = wdev;
>>> +     dev_set_drvdata(&wdev->dev, priv);
>>> +
>>> +     component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
>>> +     if (IS_ERR(master_match))
>>> +             return PTR_ERR(master_match);
>>> +
>>> +     return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
>>> +                                            master_match);
>>> +}
>>> +
>>> +static void lwmi_other_remove(struct wmi_device *wdev)
>>> +{
>>> +     struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
>>> +
>>> +     component_master_del(&wdev->dev, &lwmi_om_master_ops);
>>> +     lwmi_om_fw_attr_remove(priv);
>>> +     ida_free(&priv->ida, priv->ida_id);
>>> +}
>>> +
>>> +static const struct wmi_device_id lwmi_other_id_table[] = {
>>> +     { LENOVO_OTHER_METHOD_GUID, NULL },
>>> +     {}
>>> +};
>>> +
>>> +static struct wmi_driver lwmi_other_driver = {
>>> +     .driver = {
>>> +             .name = "lenovo_wmi_other",
>>> +             .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>>> +     },
>>> +     .id_table = lwmi_other_id_table,
>>> +     .probe = lwmi_other_probe,
>>> +     .remove = lwmi_other_remove,
>>> +     .no_singleton = true,
>>> +};
>>> +
>>> +module_wmi_driver(lwmi_other_driver);
>>> +
>>> +MODULE_IMPORT_NS("LENOVO_WMI_CD01");
>>> +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
>>> +MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
>>> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>> +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/drivers/platform/x86/lenovo-wmi-other.h b/drivers/platform/x86/lenovo-wmi-other.h
>>> new file mode 100644
>>> index 000000000000..9fba35ef1137
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-other.h
>>> @@ -0,0 +1,19 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-or-later
>>> + *
>>> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + *
>>> + */
>>> +
>>> +#ifndef _LENOVO_WMI_OTHER_H_
>>> +#define _LENOVO_WMI_OTHER_H_
>>> +
>>> +#include <linux/device.h>
>>> +#include <linux/notifier.h>
>>> +#include <linux/types.h>
>>> +
>>> +int lwmi_om_register_notifier(struct notifier_block *nb);
>>> +int lwmi_om_unregister_notifier(struct notifier_block *nb);
>>> +int devm_lwmi_om_register_notifier(struct device *dev,
>>> +                                struct notifier_block *nb);
>>> +
>>> +#endif /* !_LENOVO_WMI_H_ */

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

* Re: [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone WMI Driver
  2025-04-02 20:58     ` Derek John Clark
@ 2025-04-03  1:32       ` Armin Wolf
  0 siblings, 0 replies; 49+ messages in thread
From: Armin Wolf @ 2025-04-03  1:32 UTC (permalink / raw)
  To: Derek John Clark
  Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
	Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
	Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
	John Martens, platform-driver-x86, linux-doc, linux-kernel

Am 02.04.25 um 22:58 schrieb Derek John Clark:

> On Wed, Mar 26, 2025 at 8:49 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 17.03.25 um 15:43 schrieb Derek J. Clark:
>>
>>> Adds lenovo-wmi-gamezone driver which provides the Lenovo Gamezone WMI
>>> interface that comes on Lenovo "Gaming Series" hardware. Provides ACPI
>>> platform profiles over WMI.
>>>
>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>> ---
>>> v4:
>>> - Add notifier blocks for the Events and Other Mode drivers.
>>> - Remove notifier block chain head and all reference to Thermal Mode
>>>     Event GUID.
>>> - Add header for Gamezone specific structs and functions.
>>> - Various fixes from review.
>>> v3:
>>> - Use notifier chain to report platform profile changes to any
>>>     subscribed drivers.
>>> - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
>>>     chain.
>>> - Adds support for Extreme Mode profile on supported hardware, as well
>>>     as a DMI quirk table for some devices that report extreme mode version
>>>     support but so not have it fully implemented.
>>> - Update to include recent changes to platform-profile.
>>> v2:
>>> - Use devm_kmalloc to ensure driver can be instanced, remove global
>>>     reference.
>>> - Ensure reverse Christmas tree for all variable declarations.
>>> - Remove extra whitespace.
>>> - Use guard(mutex) in all mutex instances, global mutex.
>>> - Use pr_fmt instead of adding the driver name to each pr_err.
>>> - Remove noisy pr_info usage.
>>> - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
>>> - Remove GZ_WMI symbol exporting.
>>> ---
>>>    MAINTAINERS                                |   2 +
>>>    drivers/platform/x86/Kconfig               |  13 +
>>>    drivers/platform/x86/Makefile              |   1 +
>>>    drivers/platform/x86/lenovo-wmi-gamezone.c | 380 +++++++++++++++++++++
>>>    drivers/platform/x86/lenovo-wmi-gamezone.h |  18 +
>>>    5 files changed, 414 insertions(+)
>>>    create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
>>>    create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index 87daee6075ad..0416afd997a0 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13168,6 +13168,8 @@ F:    drivers/platform/x86/lenovo-wmi-capdata01.c
>>>    F:  drivers/platform/x86/lenovo-wmi-capdata01.h
>>>    F:  drivers/platform/x86/lenovo-wmi-events.c
>>>    F:  drivers/platform/x86/lenovo-wmi-events.h
>>> +F:   drivers/platform/x86/lenovo-wmi-gamezone.c
>>> +F:   drivers/platform/x86/lenovo-wmi-gamezone.h
>>>    F:  drivers/platform/x86/lenovo-wmi-helpers.c
>>>    F:  drivers/platform/x86/lenovo-wmi-helpers.h
>>>    F:  drivers/platform/x86/lenovo-wmi-other.c
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index fc47604e37f7..ecf3246c8fda 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -467,6 +467,19 @@ config LENOVO_WMI_HELPERS
>>>        tristate
>>>        depends on ACPI_WMI
>>>
>>> +config LENOVO_WMI_GAMEZONE
>>> +     tristate "Lenovo GameZone WMI Driver"
>>> +     depends on ACPI_WMI
>> Hi,
>>
>> please add a "depends on DMI" here.
>>
> Acked
>
>>> +     select ACPI_PLATFORM_PROFILE
>>> +     select LENOVO_WMI_EVENTS
>>> +     select LENOVO_WMI_HELPERS
>>> +     help
>>> +       Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
>>> +       platform-profile firmware interface to manage power usage.
>>> +
>>> +       To compile this driver as a module, choose M here: the module will
>>> +       be called lenovo-wmi-gamezone.
>>> +
>>>    config LENOVO_WMI_DATA01
>>>        tristate
>>>        depends on ACPI_WMI
>>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>>> index c6ce3c8594b1..f3e64926a96b 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -71,6 +71,7 @@ obj-$(CONFIG_YT2_1380)              += lenovo-yoga-tab2-pro-1380-fastcharger.o
>>>    obj-$(CONFIG_LENOVO_WMI_CAMERA)     += lenovo-wmi-camera.o
>>>    obj-$(CONFIG_LENOVO_WMI_DATA01)     += lenovo-wmi-capdata01.o
>>>    obj-$(CONFIG_LENOVO_WMI_EVENTS)     += lenovo-wmi-events.o
>>> +obj-$(CONFIG_LENOVO_WMI_GAMEZONE)    += lenovo-wmi-gamezone.o
>>>    obj-$(CONFIG_LENOVO_WMI_HELPERS)    += lenovo-wmi-helpers.o
>>>    obj-$(CONFIG_LENOVO_WMI_TUNING)     += lenovo-wmi-other.o
>>>
>>> diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
>>> new file mode 100644
>>> index 000000000000..9d453a836227
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
>>> @@ -0,0 +1,380 @@
>>> +// 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + */
>>> +
>>> +#include <linux/dmi.h>
>>> +#include <linux/list.h>
>>> +#include <linux/notifier.h>
>>> +#include <linux/platform_profile.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>> Please also include linux/acpi.h, linux/export.h and linux/module.h.
>>
>>> +
>>> +#include "lenovo-wmi-events.h"
>>> +#include "lenovo-wmi-gamezone.h"
>>> +#include "lenovo-wmi-helpers.h"
>>> +#include "lenovo-wmi-other.h"
>>> +
>>> +/* Interface GUIDs */
>>> +#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 BLOCKING_NOTIFIER_HEAD(gz_chain_head);
>>> +
>>> +struct lwmi_event_priv {
>>> +     enum thermal_mode current_mode;
>>> +     struct wmi_device *wdev;
>>> +     bool extreme_supported;
>>> +     struct device *ppdev; /*platform profile device */
>>> +     struct notifier_block event_nb;
>>> +     struct notifier_block mode_nb;
>>> +};
>>> +
>>> +struct quirk_entry {
>>> +     bool extreme_supported;
>>> +};
>>> +
>>> +static struct quirk_entry quirk_no_extreme_bug = {
>>> +     .extreme_supported = false,
>>> +};
>>> +
>>> +/* Notifier Methods */
>>> +/*
>> /* -> /**, same goes for the other kernel doc comments.
>>
>>> + * lwmi_gz_mode_call() - Call method for lenovo-wmi-other notifier
>>> + * block call chain. For THERMAL_MODE_EVENT, returns current_mode
>>> + *
>>> + * @nb: The notifier_block registered to lenovo-wmi-other
>>> + * @cmd: The event triggered by lenovo-wmi-other
>>> + * @data: The data to be returned by the event.
>>> + *
>>> + * Returns: notifier_block status.
>>> + */
>>> +static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
>>> +                          void *data)
>>> +{
>>> +     struct lwmi_event_priv *priv;
>>> +
>>> +     priv = container_of(nb, struct lwmi_event_priv, mode_nb);
>>> +     if (!priv)
>>> +             return NOTIFY_BAD;
>>> +
>>> +     switch (cmd) {
>>> +     case THERMAL_MODE_EVENT:
>> I think it would be better to have a separate command code (maybe GAMEZONE_GET_THERMAL_MODE) for this
>> kind of request. Maybe you can define a separate enum for that?
>>
> I originally had two enums for this but it seemed redundant. TBS this
> is probably a better idea as the spec has two different event types
> for thermal mode events (one is a buffer with additional data) and
> getting the thermal mode from GZ would be agnostic from the events
> that are triggered.

Exactly.

Thanks,
Armin Wolf

>>> +             *(enum thermal_mode *)data = priv->current_mode;
>> I think you need to protect this variable from concurrent accesses. Maybe a spinlock would
>> be suitable here?
>>
> Acked
>
>>> +             break;
>> Please return NOTIFY_STOP here to prevent the notifier call chain from calling further.
>>
> Acked
>
>>> +     default:
>>> +             return NOTIFY_DONE;
>>> +     }
>>> +
>>> +     return NOTIFY_OK;
>>> +}
>>> +
>>> +/*
>>> + * lwmi_gz_event_call() - Call method for lenovo-wmi-events notifier
>>> + * block call chain. For THERMAL_MODE_EVENT, sets current_mode and
>>> + * notifies platform_profile of a change.
>>> + *
>>> + * @nb: The notifier_block registered to lenovo-wmi-events
>>> + * @cmd: The event triggered by lenovo-wmi-events
>>> + * @data: The data to be updated by the event.
>>> + *
>>> + * Returns: notifier_block status.
>>> + */
>>> +static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
>>> +                           void *data)
>>> +{
>>> +     struct lwmi_event_priv *priv;
>>> +
>>> +     priv = container_of(nb, struct lwmi_event_priv, event_nb);
>>> +     if (!priv)
>>> +             return NOTIFY_BAD;
>> This check is unnecessary, please drop it?
>>
> Will do
>
>>> +
>>> +     switch (cmd) {
>>> +     case THERMAL_MODE_EVENT:
>>> +             priv->current_mode = *((enum thermal_mode *)data);
>> You do not need to explicitly cast void pointers. Also please validate that the event data
>> is actually a valid thermal mode. This check should IMHO happen inside the event driver itself.
>>
>>> +             platform_profile_notify(&priv->wdev->dev);
>> You are supposed to pass the platform profile device as the argument.
>>
> That makes more sense. I'll correct this, thanks.
>
>>> +             break;
>>> +     default:
>>> +             return NOTIFY_DONE;
>>> +     }
>>> +
>>> +     return NOTIFY_OK;
>>> +}
>>> +
>>> +/* Platform Profile Methods & Setup */
>>> +/*
>>> + * lwmi_gz_platform_profile_supported() - Gets the version of the WMI
>>> + * interface to determine the support level.
>>> + *
>>> + * @wdev: The Gamezone WMI device.
>>> + * @supported: Pointer to return the support level with.
>>> + *
>>> + * Returns: 0, or an error.
>>> + */
>>> +static int lwmi_gz_platform_profile_supported(struct wmi_device *wdev,
>>> +                                           int *supported)
>>> +{
>>> +     return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP,
>>> +                                     0, 0, supported);
>>> +}
>>> +
>>> +/*
>>> + * lwmi_gz_thermal_mode_get() - Gets the currently set thermal mode from
>>> + * the Gamezone WMI interface.
>>> + *
>>> + * @wdev: The Gamezone WMI device.
>>> + * @mode: Pointer to return the thermal mode with.
>>> + *
>>> + * Returns: 0, or an error.
>>> + */
>>> +static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
>>> +                                 enum thermal_mode *mode)
>>> +{
>>> +     return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET,
>>> +                                     0, 0, mode);
>>> +}
>>> +
>>> +static int lwmi_gz_profile_get(struct device *dev,
>>> +                            enum platform_profile_option *profile)
>>> +{
>>> +     struct lwmi_event_priv *priv = dev_get_drvdata(dev);
>>> +     enum thermal_mode mode;
>>> +     int ret;
>>> +
>>> +     ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     switch (mode) {
>>> +     case SMARTFAN_MODE_QUIET:
>>> +             *profile = PLATFORM_PROFILE_LOW_POWER;
>>> +             break;
>>> +     case SMARTFAN_MODE_BALANCED:
>>> +             *profile = PLATFORM_PROFILE_BALANCED;
>>> +             break;
>>> +     case SMARTFAN_MODE_PERFORMANCE:
>>> +             if (priv->extreme_supported) {
>>> +                     *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>>> +                     break;
>>> +             }
>>> +             *profile = PLATFORM_PROFILE_PERFORMANCE;
>>> +             break;
>>> +     case SMARTFAN_MODE_EXTREME:
>>> +             *profile = PLATFORM_PROFILE_PERFORMANCE;
>>> +             break;
>>> +     case SMARTFAN_MODE_CUSTOM:
>>> +             *profile = PLATFORM_PROFILE_CUSTOM;
>>> +             break;
>>> +     default:
>>> +             return -EINVAL;
>>> +     }
>>> +
>>> +     priv->current_mode = mode;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int lwmi_gz_profile_set(struct device *dev,
>>> +                            enum platform_profile_option profile)
>>> +{
>>> +     struct lwmi_event_priv *priv = dev_get_drvdata(dev);
>>> +     struct wmi_method_args_32 args;
>>> +     enum thermal_mode mode;
>>> +     int ret;
>>> +
>>> +     switch (profile) {
>>> +     case PLATFORM_PROFILE_LOW_POWER:
>>> +             mode = SMARTFAN_MODE_QUIET;
>>> +             break;
>>> +     case PLATFORM_PROFILE_BALANCED:
>>> +             mode = SMARTFAN_MODE_BALANCED;
>>> +             break;
>>> +     case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
>>> +             mode = SMARTFAN_MODE_PERFORMANCE;
>>> +             break;
>>> +     case PLATFORM_PROFILE_PERFORMANCE:
>>> +             if (priv->extreme_supported) {
>>> +                     mode = SMARTFAN_MODE_EXTREME;
>>> +                     break;
>>> +             }
>>> +             mode = SMARTFAN_MODE_PERFORMANCE;
>>> +             break;
>>> +     case PLATFORM_PROFILE_CUSTOM:
>>> +             mode = SMARTFAN_MODE_CUSTOM;
>>> +             break;
>>> +     default:
>>> +             return -EOPNOTSUPP;
>>> +     }
>>> +
>>> +     args.arg0 = mode;
>>> +
>>> +     ret = lwmi_dev_evaluate_method(priv->wdev, 0x0,
>>> +                                    WMI_METHOD_ID_SMARTFAN_SET,
>>> +                                    (unsigned char *)&args, sizeof(args),
>>> +                                    NULL);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     priv->current_mode = mode;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static const struct dmi_system_id fwbug_list[] = {
>>> +     {
>>> +             .ident = "Legion Go 8APU1",
>>> +             .matches = {
>>> +                     DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>> +                     DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
>>> +             },
>>> +             .driver_data = &quirk_no_extreme_bug,
>>> +     },
>>> +     {
>>> +             .ident = "Legion Go S 8ARP1",
>>> +             .matches = {
>>> +                     DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>> +                     DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
>>> +             },
>>> +             .driver_data = &quirk_no_extreme_bug,
>>> +     },
>>> +     {
>>> +             .ident = "Legion Go S 8APU1",
>>> +             .matches = {
>>> +                     DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>> +                     DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
>>> +             },
>>> +             .driver_data = &quirk_no_extreme_bug,
>>> +     },
>>> +     {},
>>> +
>>> +};
>>> +
>>> +/*
>>> + * extreme_supported() - Evaluate if a device supports extreme thermal mode.
>>> + * For devices that have a profile_support_ver of 6 or greater a DMI check
>>> + * is done. Some devices report a version that supports extreme mode but
>>> + * have an incomplete entry in the BIOS. To ensure this cannot be set, they
>>> + * are quirked to prevent assignment.
>>> + *
>>> + * @profile_support_ver: Version of WMI interface provided by
>>> + * lwmi_gz_platform_profile_supported.
>>> + *
>>> + * Returns: bool
>>> + */
>>> +static bool extreme_supported(int profile_support_ver)
>>> +{
>>> +     const struct dmi_system_id *dmi_id;
>>> +     struct quirk_entry *quirks;
>>> +
>>> +     if (profile_support_ver < 6)
>>> +             return false;
>>> +
>>> +     dmi_id = dmi_first_match(fwbug_list);
>>> +     if (!dmi_id)
>>> +             return true;
>>> +
>>> +     quirks = dmi_id->driver_data;
>>> +     return quirks->extreme_supported;
>>> +}
>>> +
>>> +static int lwmi_platform_profile_probe(void *drvdata, unsigned long *choices)
>>> +{
>>> +     struct lwmi_event_priv *priv = drvdata;
>>> +     int profile_support_ver;
>>> +     int ret;
>>> +
>>> +     ret = lwmi_gz_platform_profile_supported(priv->wdev,
>>> +                                              &profile_support_ver);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     if (profile_support_ver < 1)
>>> +             return -ENODEV;
>>> +
>>> +     priv->extreme_supported = extreme_supported(profile_support_ver);
>>> +
>>> +     set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
>>> +     set_bit(PLATFORM_PROFILE_BALANCED, choices);
>>> +     set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
>>> +     set_bit(PLATFORM_PROFILE_CUSTOM, choices);
>>> +
>>> +     if (priv->extreme_supported)
>>> +             set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static const struct platform_profile_ops lwmi_gz_platform_profile_ops = {
>>> +     .probe = lwmi_platform_profile_probe,
>>> +     .profile_get = lwmi_gz_profile_get,
>>> +     .profile_set = lwmi_gz_profile_set,
>>> +};
>>> +
>>> +/* Driver Methods */
>>> +static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
>>> +{
>>> +     struct lwmi_event_priv *priv;
>>> +     int ret;
>>> +
>>> +     priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>>> +     if (!priv)
>>> +             return -ENOMEM;
>>> +
>>> +     priv->event_nb.notifier_call = lwmi_gz_event_call;
>>> +     ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
>>> +     if (ret)
>>> +             return ret;
>> You should register the event notifier after registering the platform profile or else
>> a WMI event could arrive before the platform profile was registered, resulting in
>> platform_profile_notify() being called on a invalid device pointer.
>>
> Acked
>
>>> +
>>> +     priv->mode_nb.notifier_call = lwmi_gz_mode_call;
>>> +     ret = devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     priv->wdev = wdev;
>>> +     dev_set_drvdata(&wdev->dev, priv);
>> This should happen before the notifiers are registered or else they might try to access
>> those values before they are actually initialized.
>>
> Acked
>
>>> +
>>> +     priv->ppdev = platform_profile_register(&wdev->dev,
>>> +                                             "lenovo-wmi-gamezone", priv,
>>> +                                             &lwmi_gz_platform_profile_ops);
>>> +
>>> +     if (IS_ERR(priv->ppdev))
>>> +             return -ENODEV;
>>> +
>>> +     ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode);
>>> +     if (ret)
>>> +             return ret;
>> The thermal mode should be initialized before any notifiers using it are registered.
>>
> I'll register notifiers last in this func.
>
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static const struct wmi_device_id lwmi_gz_id_table[] = { { LENOVO_GAMEZONE_GUID,
>>> +                                                        NULL },
>>> +                                                      {} };
>> Please fix the formatting here.
>>
> Will do.
>
> Thanks,
> Derek
>
>> Thanks,
>> Armin Wolf
>>
>>> +
>>> +static struct wmi_driver lwmi_gz_driver = {
>>> +     .driver = {
>>> +             .name = "lenovo_wmi_gamezone",
>>> +             .probe_type = PROBE_PREFER_ASYNCHRONOUS,
>>> +     },
>>> +     .id_table = lwmi_gz_id_table,
>>> +     .probe = lwmi_gz_probe,
>>> +     .no_singleton = true,
>>> +};
>>> +
>>> +module_wmi_driver(lwmi_gz_driver);
>>> +
>>> +MODULE_IMPORT_NS("LENOVO_WMI_EVENTS");
>>> +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
>>> +MODULE_IMPORT_NS("LENOVO_WMI_OTHER");
>>> +MODULE_DEVICE_TABLE(wmi, lwmi_gz_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-wmi-gamezone.h b/drivers/platform/x86/lenovo-wmi-gamezone.h
>>> new file mode 100644
>>> index 000000000000..ac536803160b
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.h
>>> @@ -0,0 +1,18 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-or-later
>>> + *
>>> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + *
>>> + */
>>> +
>>> +#ifndef _LENOVO_WMI_GAMEZONE_H_
>>> +#define _LENOVO_WMI_GAMEZONE_H_
>>> +
>>> +enum thermal_mode {
>>> +     SMARTFAN_MODE_QUIET = 0x01,
>>> +     SMARTFAN_MODE_BALANCED = 0x02,
>>> +     SMARTFAN_MODE_PERFORMANCE = 0x03,
>>> +     SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
>>> +     SMARTFAN_MODE_CUSTOM = 0xFF,
>>> +};
>>> +
>>> +#endif /* !_LENOVO_WMI_GAMEZONE_H_ */

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

* Re: [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver
  2025-04-02 20:47     ` Derek John Clark
@ 2025-04-03  1:40       ` Armin Wolf
  0 siblings, 0 replies; 49+ messages in thread
From: Armin Wolf @ 2025-04-03  1:40 UTC (permalink / raw)
  To: Derek John Clark
  Cc: Hans de Goede, Ilpo Järvinen, Jonathan Corbet,
	Mario Limonciello, Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao,
	Mark Pearson, Pierre-Loup A . Griffais, Cody T . -H . Chiu,
	John Martens, platform-driver-x86, linux-doc, linux-kernel

Am 02.04.25 um 22:47 schrieb Derek John Clark:

> On Wed, Mar 26, 2025 at 6:29 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 17.03.25 um 15:43 schrieb Derek J. Clark:
>>
>>> Adds lenovo-wmi-capdata01 driver which provides the
>>> LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode"
>>> enabled hardware. Provides an interface for querying if a given
>>> attribute is supported by the hardware, as well as its default_value,
>>> max_value, min_value, and step increment.
>>>
>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>> ---
>>> v4:
>>>    - Make driver data a private struct, remove references from Other Mode
>>>      driver.
>>>    - Don't cache data at device initialization. Instead, on component bind,
>>>      cache the data on a member variable of the Other Mode driver data
>>>      passed as a void pointer.
>>>    - Add header file for capdata01 structs.
>>>    - Add new struct to pass capdata01 array data and array length to Other
>>>      Mode.
>>> v3:
>>> - Add as component to lenovo-wmi-other driver.
>>> v2:
>>> - Use devm_kmalloc to ensure driver can be instanced, remove global
>>>     reference.
>>> - Ensure reverse Christmas tree for all variable declarations.
>>> - Remove extra whitespace.
>>> - Use guard(mutex) in all mutex instances, global mutex.
>>> - Use pr_fmt instead of adding the driver name to each pr_err.
>>> - Remove noisy pr_info usage.
>>> - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
>>> - Use list to get the lenovo_wmi_cd01_priv instance in
>>>     lenovo_wmi_capdata01_get as none of the data provided by the macros
>>>     that will use it can pass a member of the struct for use in
>>>     container_of.
>>> ---
>>>    MAINTAINERS                                 |   2 +
>>>    drivers/platform/x86/Kconfig                |   4 +
>>>    drivers/platform/x86/Makefile               |   1 +
>>>    drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++
>>>    drivers/platform/x86/lenovo-wmi-capdata01.h |  29 +++++
>>>    5 files changed, 172 insertions(+)
>>>    create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>>>    create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index 6dde75922aaf..56ead241a053 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13164,6 +13164,8 @@ L:    platform-driver-x86@vger.kernel.org
>>>    S:  Maintained
>>>    F:  Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>>>    F:  Documentation/wmi/devices/lenovo-wmi-other.rst
>>> +F:   drivers/platform/x86/lenovo-wmi-capdata01.c
>>> +F:   drivers/platform/x86/lenovo-wmi-capdata01.h
>>>    F:  drivers/platform/x86/lenovo-wmi-events.c
>>>    F:  drivers/platform/x86/lenovo-wmi-events.h
>>>    F:  drivers/platform/x86/lenovo-wmi-helpers.c
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index 13b8f4ac5dc5..64663667f0cb 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
>>>        tristate
>>>        depends on ACPI_WMI
>>>
>>> +config LENOVO_WMI_DATA01
>>> +     tristate
>>> +     depends on ACPI_WMI
>>> +
>>>    config IDEAPAD_LAPTOP
>>>        tristate "Lenovo IdeaPad Laptop Extras"
>>>        depends on ACPI
>>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>>> index fc039839286a..7a35c77221b7 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -69,6 +69,7 @@ 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_LENOVO_WMI_DATA01)      += lenovo-wmi-capdata01.o
>>>    obj-$(CONFIG_LENOVO_WMI_EVENTS)     += lenovo-wmi-events.o
>>>    obj-$(CONFIG_LENOVO_WMI_HELPERS)    += lenovo-wmi-helpers.o
>>>
>>> diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
>>> new file mode 100644
>>> index 000000000000..b6876611ffd9
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
>>> @@ -0,0 +1,136 @@
>>> +// 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 Mode" WMI interface,
>>> + * including if it is supported by the hardware, the default_value, max_value,
>>> + * min_value, and step increment.
>>> + *
>>> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + */
>>> +
>>> +#include <linux/cleanup.h>
>>> +#include <linux/component.h>
>>> +#include <linux/container_of.h>
>>> +#include <linux/device.h>
>>> +#include <linux/gfp_types.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +#include "lenovo-wmi-capdata01.h"
>> Hi,
>>
>> please also include linux/acpi.h, linux/export.h and linux/module.h.
>>
>>> +
>>> +/* Interface GUIDs */
>>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>>> +
>>> +struct lwmi_cd01_priv {
>>> +     struct wmi_device *wdev;
>>> +};
>>> +
>>> +/*
>> /* -> /**
>>
>>> + * lenovo_cd01_component_bind() - On master bind, caches all capability data on
>>> + * the master device.
>>> + * @cd01_dev: Pointer to the capability data 01 parent device.
>>> + * @om_dev: Pointer to the other mode parent device.
>>> + * @data: capdata01_list object pointer to return the capability data with.
>>> + *
>>> + * Returns: 0, or an error.
>>> + */
>>> +static int lenovo_cd01_component_bind(struct device *cd01_dev,
>>> +                                   struct device *om_dev, void *data)
>>> +{
>>> +     struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
>>> +     int count, idx;
>>> +
>>> +     if (!priv)
>>> +             return -ENODEV;
>> This check is unnecessary, please drop.
>>
> Acked
>
>>> +
>>> +     count = wmidev_instance_count(priv->wdev);
>>> +
>>> +     if (count == 0)
>>> +             return -EINVAL;
>> The WMI driver core already ensures that WMI devices with 0 instances are
>> rejected. Please drop this check.
>>
> Good to know, thanks.
>
>>> +
>>> +     ((struct cd01_list *)data)->count = count;
>>> +     ((struct cd01_list *)data)->data = devm_kmalloc_array(om_dev, count,
>>> +                                                           sizeof(struct capdata01 *),
>>> +                                                           GFP_KERNEL);
>> Two things:
>>
>>    - using a local variable with a type of struct cd01_list * results in cleaner source code here
>>
>>    - using devres is not possible inside the component callbacks, since the lifetime of the component
>>      device is not necessarily tied to the lifetime of the underlying device.
>>
>> I suggest you move the whole WMI data querying into lwmi_cd01_probe(), because then you can keep using
>> devres.
>>
> Doing this in probe() puts the list on lwmi_cd01_priv. Should I copy
> that data on bind, or pass back a pointer to the cd01 device struct
> and use an exported function on cd01 and dev_get_drvdata to access
> priv->list->data[idx] when needed? I prefer the latter as this avoids
> needing to do devm memory allocation in component/master binds, then I
> can check for NULL when accessing and clear the pointer on
> master_unbind to avoid calling to a removed device driver.

Passing a pointer is OK, but please only pass a pointer to the struct cd01_list itself,
not the full cd01 device struct. Maybe you can also add a comment explaining that this
pointer will become invalid when unbinding from the component.

Explicitly NULL-ing this pointer upon unbinding seems unnecessary to me.

> (snip)
>
>>> +struct cd01_list {
>>> +     struct capdata01 **data;
>>> +     int count;
>>> +};
>> In order to save memory you could try something like this:
>>
>> struct cd01_list {
>>          size_t count;
>>          struct capdata01 data[];
>> };
>>
>> This way you
>>
>> 1. Avoid the memory fragmentation resulting from multiple memory allocations.
>>
>> 2. Omit two pointers when accessing the data.
>>
>> You can use struct_size() from linux/overflow.h to calculate the size of such
>> an array with a trailing flexible array.
>>
>> Thanks,
>> Armin Wolf
>>
> I think I have this part working in my branch. Using devm_kzalloc also
> allows me to omit manually setting NULL in a few cases, which is
> cleaner. Is it preferred to use struct_size() directly in the
> devm_kzalloc call, or create a separate `size` variable to set the
> result to and pass that into the function?
>
> - Derek
>
In most cases using struct_size() directly inside the devm_kzalloc() call is preferred.

Thanks,
Armin Wolf

>>> +
>>> +int lwmi_cd01_match(struct device *dev, void *data);
>>> +
>>> +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */

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

* Re: [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode WMI Driver
  2025-04-02 21:22     ` Derek John Clark
@ 2025-04-03 10:49       ` Ilpo Järvinen
  0 siblings, 0 replies; 49+ messages in thread
From: Ilpo Järvinen @ 2025-04-03 10:49 UTC (permalink / raw)
  To: Derek John Clark
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

[-- Attachment #1: Type: text/plain, Size: 15069 bytes --]

On Wed, 2 Apr 2025, Derek John Clark wrote:

> On Thu, Mar 27, 2025 at 6:49 AM Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
> >
> > On Mon, 17 Mar 2025, Derek J. Clark wrote:
> >
> > > Adds lenovo-wmi-other driver which provides the Lenovo "Other Mode" WMI
> > > interface that comes on some Lenovo "Gaming Series" hardware. Provides a
> > > firmware-attributes class which enables the use of tunable knobs for SPL,
> > > SPPT, and FPPT.
> > >
> > > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > > ---
> > > v4:
> > > - Treat Other Mode as a notifier chain head, use the notifier chain to
> > >   get the current mode from Gamezone.
> > > - Add header file for Other Mode specific structs and finctions.
> > > - Use component master bind to cache the capdata01 array locally.
> > > - Drop all reference to external driver private data structs.
> > > - Various fixes from review.
> > > v3:
> > > - Add notifier block and store result for getting the Gamezone interface
> > >   profile changes.
> > > - Add driver as master component of capdata01 driver.
> > > - Use FIELD_PREP where appropriate.
> > > - Move macros and associated functions out of lemovo-wmi.h that are only
> > >   used by this driver.
> > > v2:
> > > - Use devm_kmalloc to ensure driver can be instanced, remove global
> > >   reference.
> > > - Ensure reverse Christmas tree for all variable declarations.
> > > - Remove extra whitespace.
> > > - Use guard(mutex) in all mutex instances, global mutex.
> > > - Use pr_fmt instead of adding the driver name to each pr_err.
> > > - Remove noisy pr_info usage.
> > > - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> > > - Use list to get the lenovo_wmi_om_priv instance in some macro
> > >   called functions as the data provided by the macros that use it
> > >   doesn't pass a member of the struct for use in container_of.
> > > - Do not rely on GameZone interface to grab the current fan mode.
> > > ---
> > >  MAINTAINERS                             |   2 +
> > >  drivers/platform/x86/Kconfig            |  15 +
> > >  drivers/platform/x86/Makefile           |   1 +
> > >  drivers/platform/x86/lenovo-wmi-other.c | 626 ++++++++++++++++++++++++
> > >  drivers/platform/x86/lenovo-wmi-other.h |  19 +
> > >  5 files changed, 663 insertions(+)
> > >  create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> > >  create mode 100644 drivers/platform/x86/lenovo-wmi-other.h
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index 56ead241a053..87daee6075ad 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -13170,6 +13170,8 @@ F:    drivers/platform/x86/lenovo-wmi-events.c
> > >  F:   drivers/platform/x86/lenovo-wmi-events.h
> > >  F:   drivers/platform/x86/lenovo-wmi-helpers.c
> > >  F:   drivers/platform/x86/lenovo-wmi-helpers.h
> > > +F:   drivers/platform/x86/lenovo-wmi-other.c
> > > +F:   drivers/platform/x86/lenovo-wmi-other.h
> > >
> > >  LENOVO WMI HOTKEY UTILITIES DRIVER
> > >  M:   Jackie Dong <xy-jackie@139.com>
> > > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > > index 64663667f0cb..fc47604e37f7 100644
> > > --- a/drivers/platform/x86/Kconfig
> > > +++ b/drivers/platform/x86/Kconfig
> > > @@ -471,6 +471,21 @@ config LENOVO_WMI_DATA01
> > >       tristate
> > >       depends on ACPI_WMI
> > >
> > > +config LENOVO_WMI_TUNING
> > > +     tristate "Lenovo Other Mode WMI Driver"
> > > +     depends on ACPI_WMI
> > > +     select FW_ATTR_CLASS
> > > +     select LENOVO_WMI_DATA01
> > > +     select LENOVO_WMI_EVENTS
> > > +     select LENOVO_WMI_HELPERS
> > > +     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-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 7a35c77221b7..c6ce3c8594b1 100644
> > > --- a/drivers/platform/x86/Makefile
> > > +++ b/drivers/platform/x86/Makefile
> > > @@ -72,6 +72,7 @@ obj-$(CONFIG_LENOVO_WMI_CAMERA)     += lenovo-wmi-camera.o
> > >  obj-$(CONFIG_LENOVO_WMI_DATA01)      += lenovo-wmi-capdata01.o
> > >  obj-$(CONFIG_LENOVO_WMI_EVENTS)      += lenovo-wmi-events.o
> > >  obj-$(CONFIG_LENOVO_WMI_HELPERS)     += lenovo-wmi-helpers.o
> > > +obj-$(CONFIG_LENOVO_WMI_TUNING)      += lenovo-wmi-other.o
> > >
> > >  # Intel
> > >  obj-y                                += intel/
> > > diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> > > new file mode 100644
> > > index 000000000000..b517e45338e0
> > > --- /dev/null
> > > +++ b/drivers/platform/x86/lenovo-wmi-other.c
> > > @@ -0,0 +1,626 @@
> > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > +/*
> > > + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attributes
> >
> > Again, put a summary on own line as mentioned for the other files.
> >
> > > + * class to expose the various WMI functions provided by the "Other Mode" 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 laptop
> > > + * devices. Each attribute exposed by the "Other Mode"" 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.
> > > + *
> > > + * 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > > + */
> > > +
> > > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> > > +
> > > +#include <linux/bitfield.h>
> > > +#include <linux/cleanup.h>
> > > +#include <linux/component.h>
> > > +#include <linux/container_of.h>
> > > +#include <linux/device.h>
> > > +#include <linux/gfp_types.h>
> > > +#include <linux/idr.h>
> > > +#include <linux/kobject.h>
> > > +#include <linux/notifier.h>
> > > +#include <linux/platform_profile.h>
> > > +#include <linux/types.h>
> > > +#include <linux/wmi.h>
> > > +
> > > +#include "lenovo-wmi-capdata01.h"
> > > +#include "lenovo-wmi-events.h"
> > > +#include "lenovo-wmi-gamezone.h"
> > > +#include "lenovo-wmi-helpers.h"
> > > +#include "lenovo-wmi-other.h"
> > > +#include "firmware_attributes_class.h"
> > > +
> > > +/* Interface GUIDs */
> > > +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> > > +
> > > +/* Device IDs */
> > > +#define WMI_DEVICE_ID_CPU 0x01
> >
> > Two comments above add no value and can be dropped.
> >
> > > +
> > > +/* WMI_DEVICE_ID_CPU feature IDs */
> > > +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */
> > > +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */
> > > +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> > > +
> > > +/* Type IDs*/
> >
> > This is missing space but it doesn't IMO add any value, so just drop it.
> >
> 
> Acked
> 
> > > +#define WMI_TYPE_ID_NONE 0x00
> > > +
> > > +/* Method IDs */
> > > +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
> > > +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
> > > +
> > > +/* Attribute ID bitmasks */
> >
> > Neither of two comments for the defines seem to provide much value.
> >
> 
> Acked
> 
> > > +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
> > > +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
> > > +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
> > > +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
> >
> > Please align the GENMASK()s
> >
> 
> Acked
> > > +static BLOCKING_NOTIFIER_HEAD(om_chain_head);
> > > +
> > > +enum attribute_property {
> > > +     DEFAULT_VAL,
> > > +     MAX_VAL,
> > > +     MIN_VAL,
> > > +     STEP_VAL,
> > > +     SUPPORTED,
> > > +};
> > > +
> > > +struct lwmi_om_priv {
> > > +     struct blocking_notifier_head nhead;
> > > +     struct component_master_ops *ops;
> > > +     struct cd01_list cd01_list;
> > > +     struct device *fw_attr_dev;
> > > +     struct kset *fw_attr_kset;
> > > +     struct notifier_block nb;
> > > +     struct wmi_device *wdev;
> > > +     struct ida ida;
> > > +     int ida_id;
> > > +};
> > > +
> > > +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> > > +struct tunable_attr_01 {
> > > +     u32 type_id;
> > > +     u32 device_id;
> > > +     u32 feature_id;
> > > +     u32 store_value;
> > > +     struct device *dev;
> > > +     struct capdata01 *capdata;
> > > +};
> > > +
> > > +/* Tunable Attributes */
> > > +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> > > +                                    .feature_id = WMI_FEATURE_ID_CPU_SPL,
> > > +                                    .type_id = WMI_TYPE_ID_NONE };
> > > +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> > > +                                     .feature_id = WMI_FEATURE_ID_CPU_SPPT,
> > > +                                     .type_id = WMI_TYPE_ID_NONE };
> > > +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> > > +                                     .feature_id = WMI_FEATURE_ID_CPU_FPPT,
> > > +                                     .type_id = WMI_TYPE_ID_NONE };
> > > +
> > > +struct capdata01_attr_group {
> > > +     const struct attribute_group *attr_group;
> > > +     struct tunable_attr_01 *tunable_attr;
> > > +};
> > > +
> > > +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> >
> > Please add prefix and move to the top where the other defines are.
> >
> 
> What do you mean by prefix?

Add a driver specific prefix to the name. Now that I looked, you seem to 
have quite many defines above without prefix.

Generic names are slightly annoying because when you're looking at an 
usage site, the name doesn't reveal if that's specific to the driver or 
something more generic. When I see this (taken from below):

> > > +     priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
> > > +                                       MKDEV(0, 0), NULL, "%s",
> > > +                                       FW_ATTR_FOLDER);

...I cannot know whether FW_ATTR_FOLDER is provided but something under 
include/linux or if it's something this driver has defined.


> > > + */
> > > +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
> > > +{
> > > +     int err, i;
> > > +
> > > +     ida_init(&priv->ida);
> > > +     priv->ida_id = ida_alloc(&priv->ida, GFP_KERNEL);
> > > +     if (priv->ida_id < 0)
> > > +             return priv->ida_id;
> > > +
> > > +     priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
> > > +                                       MKDEV(0, 0), NULL, "%s",
> > > +                                       FW_ATTR_FOLDER);
> > > +     if (IS_ERR(priv->fw_attr_dev)) {
> > > +             err = PTR_ERR(priv->fw_attr_dev);
> > > +             return err;
> >
> > Leaks the allocated ida?
> >
> 
> Will fix.
> 
> > > +     }
> > > +
> > > +     priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> > > +                                              &priv->fw_attr_dev->kobj);
> > > +     if (!priv->fw_attr_kset) {
> > > +             err = -ENOMEM;
> > > +             goto err_destroy_classdev;
> > > +     }
> > > +
> > > +     for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> >
> > Change i to unsigned when used in loops like this.
> >
> > > +             err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> > > +                                      capdata01_attr_groups[i].attr_group);
> > > +             if (err) {
> > > +                     pr_debug("Failed to create sysfs-group for %s: %d\n",
> > > +                              capdata01_attr_groups[i].attr_group->name,
> > > +                              err);
> > > +                     goto err_remove_groups;
> > > +             }
> > > +             capdata01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
> > > +     }
> > > +     return 0;
> > > +
> > > +err_remove_groups:
> > > +     ida_free(&priv->ida, priv->ida_id);
> > > +     while (i-- >= 0) {
> >
> > >= 0 is not necessary.
> >
> 
> I suppose with an unsigned int it will never go below 0, so that's fine.
> FYI this was discussed at length in the last version and added at
> Mario's request.

Actually, it's a bug because i-- is postdecremented so when while (0 >= 0),
i has already been post-decremented by 1 to -1 or UINT_MAX in case of 
unsigned type.

> >From Mario in that series:
> > Well what you've got right now is an unwind routine where if you say
> > have 5 attributes and the 5th (0-indexed so "4th") fails, you try to
> > free 3rd, 2nd, 1st.
> >
> > The only problem is the boundary doesn't free the 0th index attribute.
> >
> > I think that just changing the loop to (i-- >= 0) would solve it.

No, his comment is not correct because i-- is post-decremented so the 
expression returns the old value to the compare.

while (i-- > 0) will free the 0th item on the last iteration.

But the usual unroll pattern is without the explicit compare so please use 
just while (i--).

> > > +             sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > > +                                capdata01_attr_groups[i].attr_group);
> > > +     }
> > > +     kset_unregister(priv->fw_attr_kset);
> > > +
> > > +err_destroy_classdev:
> > > +     device_unregister(priv->fw_attr_dev);
> > > +     return err;
> > > +}
> > > +
> > > +/*
> > > + * lwmi_om_fw_attr_remove() - Unregisters all capdata01_attr_groups[] attributes as
> > > + * firmware_attributes_class members.
> > > + * @priv: The Other Mode driver data.
> > > + *
> > > + */
> > > +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
> > > +{
> > > +     int size = ARRAY_SIZE(capdata01_attr_groups);
> >
> > unsigned int i = ARRAY_SIZE(capdata01_attr_groups) - 1;
> >
> > > +
> > > +     while (--size >= 0) {
> >
> > while (i--) {
> >
> 
> Acked
> 
> > > +             sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > > +                                capdata01_attr_groups[size].attr_group);
> > > +     }
> > > +     kset_unregister(priv->fw_attr_kset);
> > > +     device_unregister(priv->fw_attr_dev);
> > > +}


-- 
 i.

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

* Re: [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone WMI Driver
  2025-04-02 21:22     ` Derek John Clark
@ 2025-04-03 10:53       ` Ilpo Järvinen
  0 siblings, 0 replies; 49+ messages in thread
From: Ilpo Järvinen @ 2025-04-03 10:53 UTC (permalink / raw)
  To: Derek John Clark
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

[-- Attachment #1: Type: text/plain, Size: 3594 bytes --]

On Wed, 2 Apr 2025, Derek John Clark wrote:

> On Thu, Mar 27, 2025 at 6:56 AM Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
> >
> > On Mon, 17 Mar 2025, Derek J. Clark wrote:
> >
> > > Adds lenovo-wmi-gamezone driver which provides the Lenovo Gamezone WMI
> > > interface that comes on Lenovo "Gaming Series" hardware. Provides ACPI
> > > platform profiles over WMI.
> > >
> > > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> >
> > This has a few similar nits I flagged for the other patches but I won't
> > mark them here again but please go through the patches to find similar
> > cases.
> >
> > > ---
> > > v4:
> > > - Add notifier blocks for the Events and Other Mode drivers.
> > > - Remove notifier block chain head and all reference to Thermal Mode
> > >   Event GUID.
> > > - Add header for Gamezone specific structs and functions.
> > > - Various fixes from review.
> > > v3:
> > > - Use notifier chain to report platform profile changes to any
> > >   subscribed drivers.
> > > - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier
> > >   chain.
> > > - Adds support for Extreme Mode profile on supported hardware, as well
> > >   as a DMI quirk table for some devices that report extreme mode version
> > >   support but so not have it fully implemented.
> > > - Update to include recent changes to platform-profile.
> > > v2:
> > > - Use devm_kmalloc to ensure driver can be instanced, remove global
> > >   reference.
> > > - Ensure reverse Christmas tree for all variable declarations.
> > > - Remove extra whitespace.
> > > - Use guard(mutex) in all mutex instances, global mutex.
> > > - Use pr_fmt instead of adding the driver name to each pr_err.
> > > - Remove noisy pr_info usage.
> > > - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv.
> > > - Remove GZ_WMI symbol exporting.
> > > ---
> > >  MAINTAINERS                                |   2 +
> > >  drivers/platform/x86/Kconfig               |  13 +
> > >  drivers/platform/x86/Makefile              |   1 +
> > >  drivers/platform/x86/lenovo-wmi-gamezone.c | 380 +++++++++++++++++++++
> > >  drivers/platform/x86/lenovo-wmi-gamezone.h |  18 +
> > >  5 files changed, 414 insertions(+)
> > >  create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
> > >  create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h
> > >

> > > diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.h b/drivers/platform/x86/lenovo-wmi-gamezone.h
> > > new file mode 100644
> > > index 000000000000..ac536803160b
> > > --- /dev/null
> > > +++ b/drivers/platform/x86/lenovo-wmi-gamezone.h
> > > @@ -0,0 +1,18 @@
> > > +/* SPDX-License-Identifier: GPL-2.0-or-later
> > > + *
> > > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > > + *
> > > + */
> > > +
> > > +#ifndef _LENOVO_WMI_GAMEZONE_H_
> > > +#define _LENOVO_WMI_GAMEZONE_H_
> > > +
> > > +enum thermal_mode {
> > > +     SMARTFAN_MODE_QUIET = 0x01,
> > > +     SMARTFAN_MODE_BALANCED = 0x02,
> > > +     SMARTFAN_MODE_PERFORMANCE = 0x03,
> > > +     SMARTFAN_MODE_EXTREME = 0xE0, /* Ver 6+ */
> > > +     SMARTFAN_MODE_CUSTOM = 0xFF,
> > > +};
> > > +
> > > +#endif /* !_LENOVO_WMI_GAMEZONE_H_ */
> > >
> >
> > Are these going the be used by other .c files?
> >
> 
> They are used across different c files in this series. The
> lenovo-wmi-other driver uses every header.

Oh, how can this then be the last patch of the series???

Won't the build fail before this patch for lenovo-wmi-other.c?

-- 
 i.

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

* Re: [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver
  2025-04-02 21:22     ` Derek John Clark
  2025-04-03  1:21       ` Armin Wolf
@ 2025-04-03 11:01       ` Ilpo Järvinen
  1 sibling, 0 replies; 49+ messages in thread
From: Ilpo Järvinen @ 2025-04-03 11:01 UTC (permalink / raw)
  To: Derek John Clark
  Cc: Hans de Goede, Armin Wolf, Jonathan Corbet, Mario Limonciello,
	Luke Jones, Xino Ni, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, Cody T . -H . Chiu, John Martens,
	platform-driver-x86, linux-doc, LKML

[-- Attachment #1: Type: text/plain, Size: 8970 bytes --]

On Wed, 2 Apr 2025, Derek John Clark wrote:

> On Thu, Mar 27, 2025 at 5:56 AM Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
> >
> > On Mon, 17 Mar 2025, Derek J. Clark wrote:
> >
> > > Adds lenovo-wmi-capdata01 driver which provides the
> > > LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode"
> > > enabled hardware. Provides an interface for querying if a given
> > > attribute is supported by the hardware, as well as its default_value,
> > > max_value, min_value, and step increment.
> > >
> > > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > > ---
> > > v4:
> > >  - Make driver data a private struct, remove references from Other Mode
> > >    driver.
> > >  - Don't cache data at device initialization. Instead, on component bind,
> > >    cache the data on a member variable of the Other Mode driver data
> > >    passed as a void pointer.
> > >  - Add header file for capdata01 structs.
> > >  - Add new struct to pass capdata01 array data and array length to Other
> > >    Mode.
> > > v3:
> > > - Add as component to lenovo-wmi-other driver.
> > > v2:
> > > - Use devm_kmalloc to ensure driver can be instanced, remove global
> > >   reference.
> > > - Ensure reverse Christmas tree for all variable declarations.
> > > - Remove extra whitespace.
> > > - Use guard(mutex) in all mutex instances, global mutex.
> > > - Use pr_fmt instead of adding the driver name to each pr_err.
> > > - Remove noisy pr_info usage.
> > > - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
> > > - Use list to get the lenovo_wmi_cd01_priv instance in
> > >   lenovo_wmi_capdata01_get as none of the data provided by the macros
> > >   that will use it can pass a member of the struct for use in
> > >   container_of.
> > > ---
> > >  MAINTAINERS                                 |   2 +
> > >  drivers/platform/x86/Kconfig                |   4 +
> > >  drivers/platform/x86/Makefile               |   1 +
> > >  drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++
> > >  drivers/platform/x86/lenovo-wmi-capdata01.h |  29 +++++
> > >  5 files changed, 172 insertions(+)
> > >  create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> > >  create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index 6dde75922aaf..56ead241a053 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -13164,6 +13164,8 @@ L:    platform-driver-x86@vger.kernel.org
> > >  S:   Maintained
> > >  F:   Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> > >  F:   Documentation/wmi/devices/lenovo-wmi-other.rst
> > > +F:   drivers/platform/x86/lenovo-wmi-capdata01.c
> > > +F:   drivers/platform/x86/lenovo-wmi-capdata01.h
> > >  F:   drivers/platform/x86/lenovo-wmi-events.c
> > >  F:   drivers/platform/x86/lenovo-wmi-events.h
> > >  F:   drivers/platform/x86/lenovo-wmi-helpers.c
> > > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > > index 13b8f4ac5dc5..64663667f0cb 100644
> > > --- a/drivers/platform/x86/Kconfig
> > > +++ b/drivers/platform/x86/Kconfig
> > > @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
> > >       tristate
> > >       depends on ACPI_WMI
> > >
> > > +config LENOVO_WMI_DATA01
> > > +     tristate
> > > +     depends on ACPI_WMI
> > > +
> > >  config IDEAPAD_LAPTOP
> > >       tristate "Lenovo IdeaPad Laptop Extras"
> > >       depends on ACPI
> > > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> > > index fc039839286a..7a35c77221b7 100644
> > > --- a/drivers/platform/x86/Makefile
> > > +++ b/drivers/platform/x86/Makefile
> > > @@ -69,6 +69,7 @@ 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_LENOVO_WMI_DATA01)      += lenovo-wmi-capdata01.o
> > >  obj-$(CONFIG_LENOVO_WMI_EVENTS)      += lenovo-wmi-events.o
> > >  obj-$(CONFIG_LENOVO_WMI_HELPERS)     += lenovo-wmi-helpers.o
> > >
> > > diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
> > > new file mode 100644
> > > index 000000000000..b6876611ffd9
> > > --- /dev/null
> > > +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> > > @@ -0,0 +1,136 @@
> > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > +/*
> > > + * LENOVO_CAPABILITY_DATA_01 WMI data block driver.
> >
> > Add a empty comment line here, you might want to rephrase the opening of
> > the paragraph after splitting these apart.
> >
> > > This interface provides
> > > + * information on tunable attributes used by the "Other Mode" WMI interface,
> > > + * including if it is supported by the hardware, the default_value, max_value,
> > > + * min_value, and step increment.
> > > + *
> > > + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > > + */
> > > +
> > > +#include <linux/cleanup.h>
> > > +#include <linux/component.h>
> > > +#include <linux/container_of.h>
> > > +#include <linux/device.h>
> > > +#include <linux/gfp_types.h>
> > > +#include <linux/types.h>
> > > +#include <linux/wmi.h>
> >
> > Add an empty line here please.
> >
> > > +#include "lenovo-wmi-capdata01.h"
> > > +
> > > +/* Interface GUIDs */
> > > +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> > > +
> > > +struct lwmi_cd01_priv {
> > > +     struct wmi_device *wdev;
> > > +};
> > > +
> > > +/*
> > > + * lenovo_cd01_component_bind() - On master bind, caches all capability data on
> > > + * the master device.
> >
> > Is this "On master bind" something that the caller should be doing? IMO,
> > that would belong to description paragraph instead of the function
> > summary.
> >
> 
> Acked
> 
> > > + * @cd01_dev: Pointer to the capability data 01 parent device.
> > > + * @om_dev: Pointer to the other mode parent device.
> > > + * @data: capdata01_list object pointer to return the capability data with.
> > > + *
> > > + * Returns: 0, or an error.
> >
> > Return:
> >
> > > + */
> > > +static int lenovo_cd01_component_bind(struct device *cd01_dev,
> > > +                                   struct device *om_dev, void *data)
> > > +{
> > > +     struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
> > > +     int count, idx;
> > > +
> > > +     if (!priv)
> > > +             return -ENODEV;
> > > +
> > > +     count = wmidev_instance_count(priv->wdev);
> > > +
> > > +     if (count == 0)
> > > +             return -EINVAL;
> > > +
> > > +     ((struct cd01_list *)data)->count = count;
> >
> > Please create a local variable with the correct type and since data is
> > void *, you don't need to cast it while assigning to that local variable.
> >
> 
> This will be reworked a bit with Armin's suggestions.
> 
> > > +     ((struct cd01_list *)data)->data = devm_kmalloc_array(om_dev, count,
> > > +                                                           sizeof(struct capdata01 *),
> >
> > sizeof() should preferrably take the type directly from ->data (with the
> > correct amount of * chars).
> >
> 
> With Armin's suggestion I'll be using struct_size, but I'll note this
> for future reference, thanks.
> 
> > > +                                                           GFP_KERNEL);
> > > +     if (!data)
> > > +             return -ENOMEM;
> > > +
> > > +     for (idx = 0; idx < count; idx++) {
> > > +             union acpi_object *ret_obj __free(kfree) = NULL;
> > > +
> > > +             ret_obj = wmidev_block_query(priv->wdev, idx);
> > > +             if (!ret_obj) {
> > > +                     ((struct cd01_list *)data)->data[idx] = NULL;
> > > +                     continue;
> > > +             }
> > > +             if (ret_obj->type != ACPI_TYPE_BUFFER) {
> > > +                     ((struct cd01_list *)data)->data[idx] = NULL;
> > > +                     continue;
> > > +             }
> > > +
> > > +             if (ret_obj->buffer.length != sizeof(struct capdata01)) {
> >
> > You could consider joining these 3 if()s with || to avoid having to
> > repeat the NULL assignment and continue.
> >
> 
> devm_kzalloc will take care of the NULL assignment going forward. for
> !ret_obj, should I return an error here instead? It seems like a
> problem if that fails.

Sure, if you think something is an indication of a bigger problem that 
requires giving up.


BTW, in general (not related to this particular comment), you don't have 
reply/ack those review comments you're going to implement as I suggested. 
I trust you to make the changes you don't contest :-).

That way, we can focus on the points that need further discussion.

-- 
 i.

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

* Re: [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode WMI Driver
  2025-04-03  1:28       ` Armin Wolf
@ 2025-04-03 11:05         ` Ilpo Järvinen
  0 siblings, 0 replies; 49+ messages in thread
From: Ilpo Järvinen @ 2025-04-03 11:05 UTC (permalink / raw)
  To: Armin Wolf
  Cc: Derek John Clark, Hans de Goede, Ilpo Järvinen,
	Jonathan Corbet, Mario Limonciello, Luke Jones, Xino Ni,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	Cody T . -H . Chiu, John Martens, platform-driver-x86, linux-doc,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 33566 bytes --]

On Thu, 3 Apr 2025, Armin Wolf wrote:

> Am 03.04.25 um 00:24 schrieb Derek John Clark:
> 
> > On Wed, Mar 26, 2025 at 8:29 PM Armin Wolf <W_Armin@gmx.de> wrote:
> > > Am 17.03.25 um 15:43 schrieb Derek J. Clark:
> > > 
> > > > Adds lenovo-wmi-other driver which provides the Lenovo "Other Mode" WMI
> > > > interface that comes on some Lenovo "Gaming Series" hardware. Provides a
> > > > firmware-attributes class which enables the use of tunable knobs for
> > > > SPL,
> > > > SPPT, and FPPT.
> > > > 
> > > > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > > > ---
> > > > v4:
> > > > - Treat Other Mode as a notifier chain head, use the notifier chain to
> > > >     get the current mode from Gamezone.
> > > > - Add header file for Other Mode specific structs and finctions.
> > > > - Use component master bind to cache the capdata01 array locally.
> > > > - Drop all reference to external driver private data structs.
> > > > - Various fixes from review.
> > > > v3:
> > > > - Add notifier block and store result for getting the Gamezone interface
> > > >     profile changes.
> > > > - Add driver as master component of capdata01 driver.
> > > > - Use FIELD_PREP where appropriate.
> > > > - Move macros and associated functions out of lemovo-wmi.h that are only
> > > >     used by this driver.
> > > > v2:
> > > > - Use devm_kmalloc to ensure driver can be instanced, remove global
> > > >     reference.
> > > > - Ensure reverse Christmas tree for all variable declarations.
> > > > - Remove extra whitespace.
> > > > - Use guard(mutex) in all mutex instances, global mutex.
> > > > - Use pr_fmt instead of adding the driver name to each pr_err.
> > > > - Remove noisy pr_info usage.
> > > > - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv.
> > > > - Use list to get the lenovo_wmi_om_priv instance in some macro
> > > >     called functions as the data provided by the macros that use it
> > > >     doesn't pass a member of the struct for use in container_of.
> > > > - Do not rely on GameZone interface to grab the current fan mode.
> > > > ---
> > > >    MAINTAINERS                             |   2 +
> > > >    drivers/platform/x86/Kconfig            |  15 +
> > > >    drivers/platform/x86/Makefile           |   1 +
> > > >    drivers/platform/x86/lenovo-wmi-other.c | 626
> > > > ++++++++++++++++++++++++
> > > >    drivers/platform/x86/lenovo-wmi-other.h |  19 +
> > > >    5 files changed, 663 insertions(+)
> > > >    create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
> > > >    create mode 100644 drivers/platform/x86/lenovo-wmi-other.h
> > > > 
> > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > index 56ead241a053..87daee6075ad 100644
> > > > --- a/MAINTAINERS
> > > > +++ b/MAINTAINERS
> > > > @@ -13170,6 +13170,8 @@ F:    drivers/platform/x86/lenovo-wmi-events.c
> > > >    F:  drivers/platform/x86/lenovo-wmi-events.h
> > > >    F:  drivers/platform/x86/lenovo-wmi-helpers.c
> > > >    F:  drivers/platform/x86/lenovo-wmi-helpers.h
> > > > +F:   drivers/platform/x86/lenovo-wmi-other.c
> > > > +F:   drivers/platform/x86/lenovo-wmi-other.h
> > > > 
> > > >    LENOVO WMI HOTKEY UTILITIES DRIVER
> > > >    M:  Jackie Dong <xy-jackie@139.com>
> > > > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > > > index 64663667f0cb..fc47604e37f7 100644
> > > > --- a/drivers/platform/x86/Kconfig
> > > > +++ b/drivers/platform/x86/Kconfig
> > > > @@ -471,6 +471,21 @@ config LENOVO_WMI_DATA01
> > > >        tristate
> > > >        depends on ACPI_WMI
> > > > 
> > > > +config LENOVO_WMI_TUNING
> > > > +     tristate "Lenovo Other Mode WMI Driver"
> > > > +     depends on ACPI_WMI
> > > > +     select FW_ATTR_CLASS
> > > > +     select LENOVO_WMI_DATA01
> > > > +     select LENOVO_WMI_EVENTS
> > > > +     select LENOVO_WMI_HELPERS
> > > > +     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-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 7a35c77221b7..c6ce3c8594b1 100644
> > > > --- a/drivers/platform/x86/Makefile
> > > > +++ b/drivers/platform/x86/Makefile
> > > > @@ -72,6 +72,7 @@ obj-$(CONFIG_LENOVO_WMI_CAMERA)     +=
> > > > lenovo-wmi-camera.o
> > > >    obj-$(CONFIG_LENOVO_WMI_DATA01)     += lenovo-wmi-capdata01.o
> > > >    obj-$(CONFIG_LENOVO_WMI_EVENTS)     += lenovo-wmi-events.o
> > > >    obj-$(CONFIG_LENOVO_WMI_HELPERS)    += lenovo-wmi-helpers.o
> > > > +obj-$(CONFIG_LENOVO_WMI_TUNING)      += lenovo-wmi-other.o
> > > > 
> > > >    # Intel
> > > >    obj-y                               += intel/
> > > > diff --git a/drivers/platform/x86/lenovo-wmi-other.c
> > > > b/drivers/platform/x86/lenovo-wmi-other.c
> > > > new file mode 100644
> > > > index 000000000000..b517e45338e0
> > > > --- /dev/null
> > > > +++ b/drivers/platform/x86/lenovo-wmi-other.c
> > > > @@ -0,0 +1,626 @@
> > > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > > +/*
> > > > + * Lenovo Other Mode WMI interface driver. This driver uses the
> > > > fw_attributes
> > > > + * class to expose the various WMI functions provided by the "Other
> > > > Mode" 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
> > > > laptop
> > > > + * devices. Each attribute exposed by the "Other Mode"" 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.
> > > > + *
> > > > + * 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) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > > > + */
> > > > +
> > > > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> > > > +
> > > > +#include <linux/bitfield.h>
> > > > +#include <linux/cleanup.h>
> > > > +#include <linux/component.h>
> > > > +#include <linux/container_of.h>
> > > > +#include <linux/device.h>
> > > > +#include <linux/gfp_types.h>
> > > > +#include <linux/idr.h>
> > > > +#include <linux/kobject.h>
> > > > +#include <linux/notifier.h>
> > > > +#include <linux/platform_profile.h>
> > > > +#include <linux/types.h>
> > > > +#include <linux/wmi.h>
> > > Hi,
> > > 
> > > please also include linux/acpi.h, linux/export.h and linux/module.h.
> > > 
> > > > +
> > > > +#include "lenovo-wmi-capdata01.h"
> > > > +#include "lenovo-wmi-events.h"
> > > > +#include "lenovo-wmi-gamezone.h"
> > > > +#include "lenovo-wmi-helpers.h"
> > > > +#include "lenovo-wmi-other.h"
> > > > +#include "firmware_attributes_class.h"
> > > > +
> > > > +/* Interface GUIDs */
> > > > +#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_FPPT 0x03 /* Long Term Power Limit */
> > > > +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */
> > > > +
> > > > +/* Type IDs*/
> > > > +#define WMI_TYPE_ID_NONE 0x00
> > > > +
> > > > +/* Method IDs */
> > > > +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */
> > > > +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */
> > > > +
> > > > +/* Attribute ID bitmasks */
> > > > +#define ATTR_DEV_ID_MASK GENMASK(31, 24)
> > > > +#define ATTR_FEAT_ID_MASK GENMASK(23, 16)
> > > > +#define ATTR_MODE_ID_MASK GENMASK(15, 8)
> > > > +#define ATTR_TYPE_ID_MASK GENMASK(7, 0)
> > > > +
> > > > +static BLOCKING_NOTIFIER_HEAD(om_chain_head);
> > > > +
> > > > +enum attribute_property {
> > > > +     DEFAULT_VAL,
> > > > +     MAX_VAL,
> > > > +     MIN_VAL,
> > > > +     STEP_VAL,
> > > > +     SUPPORTED,
> > > > +};
> > > > +
> > > > +struct lwmi_om_priv {
> > > > +     struct blocking_notifier_head nhead;
> > > Is nhead actually used somewhere?
> > > 
> > No, I switched to a global. I'll remove.
> > 
> > > > +     struct component_master_ops *ops;
> > > > +     struct cd01_list cd01_list;
> > > > +     struct device *fw_attr_dev;
> > > > +     struct kset *fw_attr_kset;
> > > > +     struct notifier_block nb;
> > > > +     struct wmi_device *wdev;
> > > > +     struct ida ida;
> > > Is this idea actually used somewhere? If yes then please turn it into a
> > > global variable.
> > > 
> > It's something I added for v4 that you requested in v2 and I forgot in
> > v3. If I'm not using it correctly then I'll rework it. Intent was to
> > make it unique per instance. Based on your other comment below I
> > should use this as a postfix when instantiating the FW_ATTR_FOLDER?
> 
> Yes.
> 
> > > > +     int ida_id;
> > > > +};
> > > > +
> > > > +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */
> > > > +struct tunable_attr_01 {
> > > > +     u32 type_id;
> > > > +     u32 device_id;
> > > > +     u32 feature_id;
> > > > +     u32 store_value;
> > > > +     struct device *dev;
> > > > +     struct capdata01 *capdata;
> > > > +};
> > > > +
> > > > +/* Tunable Attributes */
> > > > +struct tunable_attr_01 ppt_pl1_spl = { .device_id = WMI_DEVICE_ID_CPU,
> > > > +                                    .feature_id =
> > > > WMI_FEATURE_ID_CPU_SPL,
> > > > +                                    .type_id = WMI_TYPE_ID_NONE };
> > > > +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = WMI_DEVICE_ID_CPU,
> > > > +                                     .feature_id =
> > > > WMI_FEATURE_ID_CPU_SPPT,
> > > > +                                     .type_id = WMI_TYPE_ID_NONE };
> > > > +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = WMI_DEVICE_ID_CPU,
> > > > +                                     .feature_id =
> > > > WMI_FEATURE_ID_CPU_FPPT,
> > > > +                                     .type_id = WMI_TYPE_ID_NONE };
> > > > +
> > > > +struct capdata01_attr_group {
> > > > +     const struct attribute_group *attr_group;
> > > > +     struct tunable_attr_01 *tunable_attr;
> > > > +};
> > > > +
> > > > +#define FW_ATTR_FOLDER "lenovo-wmi-other"
> > > Please use a IDA to give each device a unique name like
> > > "lenovo-wmi-otherX". Otherwise
> > > the driver cannot be instantiated multiple times.
> > > 
> > > > +
> > > > +/* Notifier Methods */
> > > > +int lwmi_om_register_notifier(struct notifier_block *nb)
> > > > +{
> > > > +     return blocking_notifier_chain_register(&om_chain_head, nb);
> > > > +}
> > > > +EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
> > > > +
> > > > +int lwmi_om_unregister_notifier(struct notifier_block *nb)
> > > > +{
> > > > +     return blocking_notifier_chain_unregister(&om_chain_head, nb);
> > > > +}
> > > > +EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
> > > > +
> > > > +static void devm_lwmi_om_unregister_notifier(void *data)
> > > > +{
> > > > +     struct notifier_block *nb = data;
> > > > +
> > > > +     lwmi_om_unregister_notifier(nb);
> > > > +}
> > > > +
> > > > +int devm_lwmi_om_register_notifier(struct device *dev,
> > > > +                                struct notifier_block *nb)
> > > > +{
> > > > +     int ret;
> > > > +
> > > > +     ret = lwmi_om_register_notifier(nb);
> > > > +     if (ret < 0)
> > > > +             return ret;
> > > > +
> > > > +     return devm_add_action_or_reset(dev,
> > > > devm_lwmi_om_unregister_notifier,
> > > > +                                     nb);
> > > > +}
> > > > +EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier,
> > > > "LENOVO_WMI_OTHER");
> > > > +
> > > > +static int lwmi_om_notifier_call(enum thermal_mode *mode)
> > > > +{
> > > > +     int ret;
> > > > +
> > > > +     ret = blocking_notifier_call_chain(&om_chain_head,
> > > > THERMAL_MODE_EVENT,
> > > > +                                        mode);
> > > > +
> > > > +     if (ret != NOTIFY_OK)
> > > > +             return -EINVAL;
> > > Better remove the NOTIFY_STOP_MASK so that clients can return NOTIFY_STOP:
> > > 
> > >           (ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK
> > > 
> > Acked
> > 
> > > > +
> > > > +     if (*mode < SMARTFAN_MODE_QUIET || *mode > SMARTFAN_MODE_CUSTOM)
> > > > +             return -EINVAL;
> > > > +
> > > > +     return 0;
> > > > +}
> > > > +
> > > > +/* Attribute Methods */
> > > > +/*
> > > /* -> /**
> > > 
> > > The same applies to the other kernel doc comments as well.
> > > 
> > > > + * int_type_show() - Emit the data type for an integer attribute
> > > > + * @kobj: Pointer to the driver object.
> > > > + * @kobj_attribute: Pointer to the attribute calling this function.
> > > > + * @buf: The buffer to write to.
> > > > + *
> > > > + * Returns: Number of characters written to buf.
> > > > + */
> > > > +static ssize_t int_type_show(struct kobject *kobj, struct
> > > > kobj_attribute *kattr,
> > > > +                          char *buf)
> > > > +{
> > > > +     return sysfs_emit(buf, "integer\n");
> > > > +}
> > > > +
> > > > +/*
> > > > + * attr_capdata01_get - Get the data of the specified attribute
> > > > + * from lwmi_om->cd01.
> > > > + * @tunable_attr: The attribute to be populated.
> > > > + *
> > > > + * Returns: Either a pointer to capability data, or NULL.
> > > > + */
> > > > +static struct capdata01 *
> > > > +attr_capdata01_get_data(struct lwmi_om_priv *priv,
> > > > +                     struct tunable_attr_01 *tunable_attr,
> > > > +                     enum thermal_mode mode)
> > > > +{
> > > > +     u32 attribute_id =
> > > > +             FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> > > > +             FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> > > > +             FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> > > > +             FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> > > > +     int idx;
> > > > +
> > > > +     for (idx = 0; idx < priv->cd01_list.count; idx++) {
> > > > +             if (!priv->cd01_list.data[idx])
> > > > +                     continue;
> > > > +
> > > > +             if (priv->cd01_list.data[idx]->id != attribute_id)
> > > > +                     continue;
> > > > +             return priv->cd01_list.data[idx];
> > > > +     }
> > > > +     return NULL;
> > > > +}
> > > > +
> > > > +/**
> > > > + * attr_capdata01_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.
> > > > + * @tunable_attr: The attribute to be read.
> > > > + * @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 number of characters written to buf, or an error.
> > > > + */
> > > > +static ssize_t attr_capdata01_show(struct kobject *kobj,
> > > > +                                struct kobj_attribute *kattr, char
> > > > *buf,
> > > > +                                struct tunable_attr_01 *tunable_attr,
> > > > +                                enum attribute_property prop)
> > > > +{
> > > > +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> > > > +     struct capdata01 *capdata;
> > > > +     int value;
> > > > +
> > > > +     if (!priv)
> > > > +             return -ENODEV;
> > > Is this check really necessary? If not then please remove it.
> > > 
> > Acked
> > 
> > > > +
> > > > +     capdata = attr_capdata01_get_data(priv, tunable_attr,
> > > > +                                       SMARTFAN_MODE_CUSTOM);
> > > > +
> > > > +     if (!capdata)
> > > > +             return -ENODEV;
> > > > +
> > > > +     switch (prop) {
> > > > +     case DEFAULT_VAL:
> > > > +             value = capdata->default_value;
> > > > +             break;
> > > > +     case MAX_VAL:
> > > > +             value = capdata->max_value;
> > > > +             break;
> > > > +     case MIN_VAL:
> > > > +             value = capdata->min_value;
> > > > +             break;
> > > > +     case STEP_VAL:
> > > > +             value = capdata->step;
> > > > +             break;
> > > > +     default:
> > > > +             return -EINVAL;
> > > > +     }
> > > > +     return sysfs_emit(buf, "%d\n", value);
> > > > +}
> > > > +
> > > > +/* 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.
> > > > + * @tunable_attr: The attribute to be stored.
> > > > + *
> > > > + * 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.
> > > > + */
> > > > +static ssize_t attr_current_value_store(struct kobject *kobj,
> > > > +                                     struct kobj_attribute *kattr,
> > > > +                                     const char *buf, size_t count,
> > > > +                                     struct tunable_attr_01
> > > > *tunable_attr)
> > > > +{
> > > > +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> > > > +     struct wmi_method_args_32 args;
> > > > +     struct capdata01 *capdata;
> > > > +     enum thermal_mode mode;
> > > > +     u32 attribute_id;
> > > > +     u32 value;
> > > > +     int err;
> > > > +
> > > > +     if (!priv)
> > > > +             return -ENODEV;
> > > Same as above.
> > > 
> > > > +
> > > > +     err = lwmi_om_notifier_call(&mode);
> > > > +     if (err)
> > > > +             return err;
> > > > +
> > > > +     if (mode != SMARTFAN_MODE_CUSTOM)
> > > > +             return -EINVAL;
> > > Better return -EBUSY here to signal userspace that the underlying device
> > > currently
> > > cannot process this request.
> > > 
> > Acked
> > 
> > > > +
> > > > +     capdata = attr_capdata01_get_data(priv, tunable_attr, mode);
> > > > +
> > > > +     if (!capdata)
> > > > +             return -ENODEV;
> > > > +
> > > > +     attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK,
> > > > tunable_attr->device_id) |
> > > > +                    FIELD_PREP(ATTR_FEAT_ID_MASK,
> > > > tunable_attr->feature_id) |
> > > > +                    FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> > > > +                    FIELD_PREP(ATTR_TYPE_ID_MASK,
> > > > tunable_attr->type_id);
> > > > +
> > > > +     err = kstrtouint(buf, 10, &value);
> > > > +     if (err)
> > > > +             return err;
> > > > +
> > > > +     if (value < capdata->min_value || value > capdata->max_value)
> > > > +             return -EINVAL;
> > > > +
> > > > +     args.arg0 = attribute_id;
> > > > +     args.arg1 = value;
> > > > +
> > > > +     err = lwmi_dev_evaluate_method(priv->wdev, 0x0,
> > > > WMI_FEATURE_VALUE_SET,
> > > > +                                    (unsigned char *)&args,
> > > > sizeof(args),
> > > > +                                    NULL);
> > > > +
> > > > +     if (err)
> > > > +             return err;
> > > > +
> > > > +     tunable_attr->store_value = value;
> > > Is store_value actually used somewhere? If no then please remove.
> > > 
> > No, it's a carry over from when I modeled after asus-armoury. Since
> > I'm looking up the value each time vice using the last set value I can
> > drop this. Thanks.
> > 
> > > > +     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.
> > > > + * @tunable_attr: The 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 number of characters written to buf, or an error.
> > > > + */
> > > > +static ssize_t attr_current_value_show(struct kobject *kobj,
> > > > +                                    struct kobj_attribute *kattr, char
> > > > *buf,
> > > > +                                    struct tunable_attr_01
> > > > *tunable_attr)
> > > > +{
> > > > +     struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> > > > +     struct wmi_method_args_32 args;
> > > > +     enum thermal_mode mode;
> > > > +     u32 attribute_id;
> > > > +     int retval;
> > > > +     int err;
> > > > +
> > > > +     if (!priv)
> > > > +             return -ENODEV;
> > > Same as above.
> > > 
> > Acked
> > 
> > > > +
> > > > +     err = lwmi_om_notifier_call(&mode);
> > > > +     if (err)
> > > > +             return err;
> > > > +
> > > > +     attribute_id = FIELD_PREP(ATTR_DEV_ID_MASK,
> > > > tunable_attr->device_id) |
> > > > +                    FIELD_PREP(ATTR_FEAT_ID_MASK,
> > > > tunable_attr->feature_id) |
> > > > +                    FIELD_PREP(ATTR_MODE_ID_MASK, mode) |
> > > > +                    FIELD_PREP(ATTR_TYPE_ID_MASK,
> > > > tunable_attr->type_id);
> > > > +
> > > > +     args.arg0 = attribute_id;
> > > > +
> > > > +     err = lwmi_dev_evaluate_method(priv->wdev, 0x0,
> > > > WMI_FEATURE_VALUE_GET,
> > > > +                                    (unsigned char *)&args,
> > > > sizeof(args),
> > > > +                                    &retval);
> > > > +
> > > > +     if (err)
> > > > +             return err;
> > > > +
> > > > +     return sysfs_emit(buf, "%d\n", retval);
> > > > +}
> > > > +
> > > > +/* Attribute macros */
> > > > +#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 *kattr, char
> > > > *buf) \
> > > > +     {
> > > > \
> > > > +             return sysfs_emit(buf, _fmt, _val);
> > > > \
> > > > +     }
> > > > \
> > > > +     static struct kobj_attribute attr_##_attrname##_##_prop =
> > > > \
> > > > +             __LL_ATTR_RO(_attrname, _prop)
> > > > +
> > > > +/* Attribute current value read/write */
> > > > +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname)
> > > > \
> > > > +     static ssize_t _attrname##_current_value_store(
> > > > \
> > > > +             struct kobject *kobj, struct kobj_attribute *kattr,
> > > > \
> > > > +             const char *buf, size_t count)
> > > > \
> > > > +     {
> > > > \
> > > > +             return attr_current_value_store(kobj, kattr, buf, count,
> > > > \
> > > > +                                             &_attrname);
> > > > \
> > > > +     }
> > > > \
> > > > +     static ssize_t _attrname##_current_value_show(
> > > > \
> > > > +             struct kobject *kobj, struct kobj_attribute *kattr, char
> > > > *buf) \
> > > > +     {
> > > > \
> > > > +             return attr_current_value_show(kobj, kattr, buf,
> > > > &_attrname);  \
> > > > +     }
> > > > \
> > > > +     static struct kobj_attribute attr_##_attrname##_current_value =
> > > > \
> > > > +             __LL_ATTR_RW(_attrname, current_value)
> > > > +
> > > > +/* Attribute property read only */
> > > > +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type)
> > > > \
> > > > +     static ssize_t _attrname##_##_prop##_show(
> > > > \
> > > > +             struct kobject *kobj, struct kobj_attribute *kattr, char
> > > > *buf) \
> > > > +     {
> > > > \
> > > > +             return attr_capdata01_show(kobj, kattr, buf, &_attrname,
> > > > \
> > > > +                                        _prop_type);
> > > > \
> > > > +     }
> > > > \
> > > > +     static struct kobj_attribute attr_##_attrname##_##_prop =
> > > > \
> > > > +             __LL_ATTR_RO(_attrname, _prop)
> > > > +
> > > > +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname)
> > > > \
> > > > +     __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname);                   \
> > > > +     __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL);  \
> > > > +     __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);   \
> > > > +     __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL);          \
> > > > +     __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL);          \
> > > > +     __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, 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            \
> > > > +     }
> > > > +
> > > > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> > > > +                         "Set the CPU sustained power limit");
> > > > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> > > > +                         "Set the CPU slow package power tracking
> > > > limit");
> > > > +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> > > > +                         "Set the CPU fast package power tracking
> > > > limit");
> > > > +
> > > > +static struct capdata01_attr_group capdata01_attr_groups[] = {
> > > > +     { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
> > > > +     { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
> > > > +     { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
> > > > +     {},
> > > > +};
> > > > +
> > > > +/*
> > > > + * lwmi_om_fw_attr_add() - Registers all capdata01_attr_groups[]
> > > > attributes as
> > > > + * firmware_attributes_class members.
> > > > + * @priv: The Other Mode driver data.
> > > > + *
> > > > + * Returns: Either 0, or an error.
> > > > + */
> > > > +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
> > > > +{
> > > > +     int err, i;
> > > > +
> > > > +     ida_init(&priv->ida);
> > > > +     priv->ida_id = ida_alloc(&priv->ida, GFP_KERNEL);
> > > > +     if (priv->ida_id < 0)
> > > > +             return priv->ida_id;
> > > > +
> > > > +     priv->fw_attr_dev = device_create(&firmware_attributes_class,
> > > > NULL,
> > > > +                                       MKDEV(0, 0), NULL, "%s",
> > > > +                                       FW_ATTR_FOLDER);
> > > > +     if (IS_ERR(priv->fw_attr_dev)) {
> > > > +             err = PTR_ERR(priv->fw_attr_dev);
> > > > +             return err;
> > > > +     }
> > > > +
> > > > +     priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
> > > > +
> > > > &priv->fw_attr_dev->kobj);
> > > > +     if (!priv->fw_attr_kset) {
> > > > +             err = -ENOMEM;
> > > > +             goto err_destroy_classdev;
> > > > +     }
> > > > +
> > > > +     for (i = 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) {
> > > > +             err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> > > > +
> > > > capdata01_attr_groups[i].attr_group);
> > > > +             if (err) {
> > > > +                     pr_debug("Failed to create sysfs-group for %s:
> > > > %d\n",
> > > > +
> > > > capdata01_attr_groups[i].attr_group->name,
> > > > +                              err);
> > > > +                     goto err_remove_groups;
> > > > +             }
> > > > +             capdata01_attr_groups[i].tunable_attr->dev =
> > > > &priv->wdev->dev;
> > > Since you already know which attributes are supported, maybe it would make
> > > sense to only
> > > create attributes which are supported on a given machine?
> > > 
> > This is a bit challenging. Since the data is stored per thermal mode I
> > would need to look through all supported thermal events and aggregate
> > the supported values. That's an additional call to gamezone and 4-5
> > additional calls to capdata01. It might be easier to return
> > -EOPNOTSUPP when accessed for these attributes? I could also assume if
> > an attribute is available for custom mode it is available for all
> > modes.
> 
> Assuming that a given attribute exists if it is defined for custom mode is OK
> here.
> 
> > > > +     }
> > > > +     return 0;
> > > > +
> > > > +err_remove_groups:
> > > > +     ida_free(&priv->ida, priv->ida_id);
> > > The IDA should be freed even when the class device failed to register. But
> > > such an IDA
> > > should be a global variable anyway.
> > > 
> > Acked
> > 
> > > > +     while (i-- >= 0) {
> > > > +             sysfs_remove_group(&priv->fw_attr_kset->kobj,
> > > > +                                capdata01_attr_groups[i].attr_group);
> > > > +     }
> > > > +     kset_unregister(priv->fw_attr_kset);
> > > > +
> > > > +err_destroy_classdev:
> > > > +     device_unregister(priv->fw_attr_dev);
> > > > +     return err;
> > > > +}
> > > > +
> > > > +/*
> > > > + * lwmi_om_fw_attr_remove() - Unregisters all capdata01_attr_groups[]
> > > > attributes as
> > > > + * firmware_attributes_class members.
> > > > + * @priv: The Other Mode driver data.
> > > > + *
> > > > + */
> > > > +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
> > > > +{
> > > > +     int size = ARRAY_SIZE(capdata01_attr_groups);
> > > > +
> > > > +     while (--size >= 0) {
> > > Please use a for-loop here.
> > > 
> > Ilpo and you conflict on this. He wants an unsigned int here and no >=
> > 0. Please advise on the way ahead.
> 
> I suggest you use a for-loop with an unsigned int counter variable here:
> 
> 	for (unsigned int i = 0; i < ARRAY_SIZE(capdata01_attr_groups); i++) {
> 		/* Unregister each group */
> 	}

I'm sorry, as I was going throughb the patch, I thought it was just 
another instance of the within function rollback pattern.

Armin is right here, for loop is the way to go.

--
 i.

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

end of thread, other threads:[~2025-04-03 11:05 UTC | newest]

Thread overview: 49+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-03-17 14:43 [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers Derek J. Clark
2025-03-17 14:43 ` [PATCH v4 1/6 RESEND] platform/x86: Add lenovo-wmi-* driver Documentation Derek J. Clark
2025-03-18  4:24   ` Mario Limonciello
2025-03-19  2:48   ` Derek J. Clark
2025-03-19  4:41   ` Bagas Sanjaya
2025-03-19  4:50     ` Derek J. Clark
2025-03-27  0:26   ` Armin Wolf
2025-03-30  4:49     ` Derek John Clark
2025-03-17 14:43 ` [PATCH v4 2/6 RESEND] platform/x86: Add lenovo-wmi-helpers Derek J. Clark
2025-03-18  4:27   ` Mario Limonciello
2025-03-19  2:50     ` Derek J. Clark
2025-03-26 19:45   ` Matthew Schwartz
2025-03-27  0:40   ` Armin Wolf
2025-03-30  4:55     ` Derek John Clark
2025-03-27 12:43   ` Ilpo Järvinen
2025-04-02 21:22     ` Derek John Clark
2025-03-17 14:43 ` [PATCH v4 3/6 RESEND] platform/x86: Add Lenovo WMI Events Driver Derek J. Clark
2025-03-18  4:30   ` Mario Limonciello
2025-03-26 19:47   ` Matthew Schwartz
2025-03-27  1:03   ` Armin Wolf
2025-03-30  4:55     ` Derek John Clark
2025-03-27 12:47   ` Ilpo Järvinen
2025-03-17 14:43 ` [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver Derek J. Clark
2025-03-26 19:47   ` Matthew Schwartz
2025-03-27  1:29   ` Armin Wolf
2025-04-02 20:47     ` Derek John Clark
2025-04-03  1:40       ` Armin Wolf
2025-03-27 12:56   ` Ilpo Järvinen
2025-04-02 21:22     ` Derek John Clark
2025-04-03  1:21       ` Armin Wolf
2025-04-03 11:01       ` Ilpo Järvinen
2025-03-17 14:43 ` [PATCH v4 5/6 RESEND] platform/x86: Add Lenovo Other Mode " Derek J. Clark
2025-03-26 19:48   ` Matthew Schwartz
2025-03-27  3:28   ` Armin Wolf
2025-04-02 22:24     ` Derek John Clark
2025-04-03  1:28       ` Armin Wolf
2025-04-03 11:05         ` Ilpo Järvinen
2025-03-27 13:49   ` Ilpo Järvinen
2025-04-02 21:22     ` Derek John Clark
2025-04-03 10:49       ` Ilpo Järvinen
2025-03-17 14:43 ` [PATCH v4 6/6 RESEND] platform/x86: Add Lenovo Gamezone " Derek J. Clark
2025-03-26 19:48   ` Matthew Schwartz
2025-03-27  3:49   ` Armin Wolf
2025-04-02 20:58     ` Derek John Clark
2025-04-03  1:32       ` Armin Wolf
2025-03-27 13:56   ` Ilpo Järvinen
2025-04-02 21:22     ` Derek John Clark
2025-04-03 10:53       ` Ilpo Järvinen
2025-03-27  3:52 ` [PATCH v4 0/6 RESEND] platform/x86: Add Lenovo Gaming Series WMI Drivers Armin Wolf

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).