* [PATCH v6 1/6] platform/x86: Add Lenovo WMI Driver Documentation
2025-04-28 1:18 [PATCH v6 0/6] platform/x86: Add Lenovo WMI Gaming Series Drivers Derek J. Clark
@ 2025-04-28 1:18 ` Derek J. Clark
2025-04-28 1:18 ` [PATCH v6 2/6] platform/x86: Add Lenovo WMI Helpers Derek J. Clark
` (4 subsequent siblings)
5 siblings, 0 replies; 23+ messages in thread
From: Derek J. Clark @ 2025-04-28 1:18 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,
Mario Limonciello
Adds documentation for new lenovo-wmi drivers.
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
Reviewed-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v6:
- Fix typos and requested rewordings from v5 review.
- Rename lenovo-wmi-other-method.rst to lenovo-wmi-other.rst, fixes
warining and aligns documentation with driver name.
v5:
- Fix extra spaces in lenovo-wmi-gamezone.rst.
- Fix monospace for GUID's.
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.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.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..997263e51a7d
--- /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 primary "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
+corresponds to the BIOS Extreme Mode, while the balanced-performance
+platform profile corresponds 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.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
new file mode 100644
index 000000000000..d7928b8dfb4b
--- /dev/null
+++ b/Documentation/wmi/devices/lenovo-wmi-other.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] 23+ messages in thread* [PATCH v6 2/6] platform/x86: Add Lenovo WMI Helpers
2025-04-28 1:18 [PATCH v6 0/6] platform/x86: Add Lenovo WMI Gaming Series Drivers Derek J. Clark
2025-04-28 1:18 ` [PATCH v6 1/6] platform/x86: Add Lenovo WMI Driver Documentation Derek J. Clark
@ 2025-04-28 1:18 ` Derek J. Clark
2025-04-28 19:36 ` ALOK TIWARI
2025-04-28 1:18 ` [PATCH v6 3/6] platform/x86: Add Lenovo WMI Events Driver Derek J. Clark
` (3 subsequent siblings)
5 siblings, 1 reply; 23+ messages in thread
From: Derek J. Clark @ 2025-04-28 1:18 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,
Mario Limonciello
Adds lenovo-wmi-helpers, which provides a common wrapper function for
wmidev_evaluate_method that does data validation and error handling.
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
Reviewed-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v6:
- Fix typos and rewordings from v5 review.
v5:
- Fixes from v4 review.
- Combine all previous methods into a single function that takes a
buffer for the wmi method arguments.
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 | 1 +
drivers/platform/x86/Kconfig | 4 ++
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-helpers.c | 74 +++++++++++++++++++++++
drivers/platform/x86/lenovo-wmi-helpers.h | 20 ++++++
5 files changed, 100 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..aab59a777693 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13164,6 +13164,7 @@ 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.*
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..2df0408e2a9c
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-helpers.c
@@ -0,0 +1,74 @@
+// 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 Mode" interface is a legacy interface for managing and displaying
+ * CPU & GPU power and hwmon settings and readings. The "Other Mode" interface
+ * is a modern interface that replaces or extends the "Custom Mode" interface
+ * methods. The "Gamezone" interface adds advanced features such as fan
+ * profiles and overclocking. The "Lighting" interface adds control of various
+ * status lights related to different hardware components. Each of these
+ * drivers uses a common procedure to get data from the WMI interface,
+ * enumerated here.
+ *
+ * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/wmi.h>
+
+#include "lenovo-wmi-helpers.h"
+
+/**
+ * lwmi_dev_evaluate_int() - Helper function for calling WMI methods that
+ * return an 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.
+ *
+ * Calls wmidev_valuate_method for Lenovo WMI devices that return an ACPI
+ * integer. Validates the return value type and assigns the value to the
+ * retval pointer.
+ *
+ * Return: 0 on success, or an error code.
+ */
+int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
+ unsigned char *buf, size_t size, u32 *retval)
+{
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *ret_obj __free(kfree) = NULL;
+ struct acpi_buffer input = { size, buf };
+ 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_int, "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..b76633603dcc
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-helpers.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_HELPERS_H_
+#define _LENOVO_WMI_HELPERS_H_
+
+#include <linux/types.h>
+
+struct wmi_device;
+
+struct wmi_method_args_32 {
+ u32 arg0;
+ u32 arg1;
+};
+
+int lwmi_dev_evaluate_int(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] 23+ messages in thread* Re: [PATCH v6 2/6] platform/x86: Add Lenovo WMI Helpers
2025-04-28 1:18 ` [PATCH v6 2/6] platform/x86: Add Lenovo WMI Helpers Derek J. Clark
@ 2025-04-28 19:36 ` ALOK TIWARI
2025-04-30 3:17 ` Derek J. Clark
0 siblings, 1 reply; 23+ messages in thread
From: ALOK TIWARI @ 2025-04-28 19:36 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, Mario Limonciello
> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x86/lenovo-wmi-helpers.c
> new file mode 100644
> index 000000000000..2df0408e2a9c
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-helpers.c
> @@ -0,0 +1,74 @@
> +// 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 Mode" interface is a legacy interface for managing and displaying
> + * CPU & GPU power and hwmon settings and readings. The "Other Mode" interface
> + * is a modern interface that replaces or extends the "Custom Mode" interface
> + * methods. The "Gamezone" interface adds advanced features such as fan
> + * profiles and overclocking. The "Lighting" interface adds control of various
> + * status lights related to different hardware components. Each of these
> + * drivers uses a common procedure to get data from the WMI interface,
> + * enumerated here.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
add a space after "Copyright" before "(C)".
Copyright (C) 2025
> +
> +#include <linux/acpi.h>
> +#include <linux/cleanup.h>
> +#include <linux/errno.h>
> +#include <linux/export.h>
> +#include <linux/module.h>
> +#include <linux/wmi.h>
> +
> +#include "lenovo-wmi-helpers.h"
> +
> +/**
> + * lwmi_dev_evaluate_int() - Helper function for calling WMI methods that
> + * return an 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.
> + *
> + * Calls wmidev_valuate_method for Lenovo WMI devices that return an ACPI
> + * integer. Validates the return value type and assigns the value to the
> + * retval pointer.
assuming you meant to refer to wmidev_evaluate_method, the real function
typo wmidev_valuate_method -> wmidev_evaluate_method
> + *
> + * Return: 0 on success, or an error code.
> + */
> +int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
> + unsigned char *buf, size_t size, u32 *retval)
> +{
> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> + union acpi_object *ret_obj __free(kfree) = NULL;
> + struct acpi_buffer input = { size, buf };
> + 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;
> +};
Thanks,
Alok
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v6 2/6] platform/x86: Add Lenovo WMI Helpers
2025-04-28 19:36 ` ALOK TIWARI
@ 2025-04-30 3:17 ` Derek J. Clark
0 siblings, 0 replies; 23+ messages in thread
From: Derek J. Clark @ 2025-04-30 3:17 UTC (permalink / raw)
To: ALOK TIWARI, 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, Mario Limonciello
On April 28, 2025 12:36:03 PM PDT, ALOK TIWARI <alok.a.tiwari@oracle.com> wrote:
>> diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x86/lenovo-wmi-helpers.c
>> new file mode 100644
>> index 000000000000..2df0408e2a9c
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-wmi-helpers.c
>> @@ -0,0 +1,74 @@
>> +// 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 Mode" interface is a legacy interface for managing and displaying
>> + * CPU & GPU power and hwmon settings and readings. The "Other Mode" interface
>> + * is a modern interface that replaces or extends the "Custom Mode" interface
>> + * methods. The "Gamezone" interface adds advanced features such as fan
>> + * profiles and overclocking. The "Lighting" interface adds control of various
>> + * status lights related to different hardware components. Each of these
>> + * drivers uses a common procedure to get data from the WMI interface,
>> + * enumerated here.
>> + *
>> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>> + */
>
>add a space after "Copyright" before "(C)".
>Copyright (C) 2025
>
>> +
>> +#include <linux/acpi.h>
>> +#include <linux/cleanup.h>
>> +#include <linux/errno.h>
>> +#include <linux/export.h>
>> +#include <linux/module.h>
>> +#include <linux/wmi.h>
>> +
>> +#include "lenovo-wmi-helpers.h"
>> +
>> +/**
>> + * lwmi_dev_evaluate_int() - Helper function for calling WMI methods that
>> + * return an 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.
>> + *
>> + * Calls wmidev_valuate_method for Lenovo WMI devices that return an ACPI
>> + * integer. Validates the return value type and assigns the value to the
>> + * retval pointer.
>
>assuming you meant to refer to wmidev_evaluate_method, the real function
>typo wmidev_valuate_method -> wmidev_evaluate_method
>
>> + *
>> + * Return: 0 on success, or an error code.
>> + */
>> +int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
>> + unsigned char *buf, size_t size, u32 *retval)
>> +{
>> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
>> + union acpi_object *ret_obj __free(kfree) = NULL;
>> + struct acpi_buffer input = { size, buf };
>> + 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;
>> +};
>
>Thanks,
>Alok
Ack all. Thanks for taking a look.
- Derek
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v6 3/6] platform/x86: Add Lenovo WMI Events Driver
2025-04-28 1:18 [PATCH v6 0/6] platform/x86: Add Lenovo WMI Gaming Series Drivers Derek J. Clark
2025-04-28 1:18 ` [PATCH v6 1/6] platform/x86: Add Lenovo WMI Driver Documentation Derek J. Clark
2025-04-28 1:18 ` [PATCH v6 2/6] platform/x86: Add Lenovo WMI Helpers Derek J. Clark
@ 2025-04-28 1:18 ` Derek J. Clark
2025-04-28 20:57 ` [External] : " ALOK TIWARI
2025-04-28 1:18 ` [PATCH v6 4/6] platform/x86: Add Lenovo WMI Capability Data 01 Driver Derek J. Clark
` (2 subsequent siblings)
5 siblings, 1 reply; 23+ messages in thread
From: Derek J. Clark @ 2025-04-28 1:18 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,
Mario Limonciello
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>
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
Reviewed-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v6:
- Fix typos and rewordings to ensure consistancy in function description
text.
v5:
- Fixes from v4 review.
v4:
- Remove the Thermal Mode Event GUID from Gamezone and add this driver.
MAINTAINERS | 1 +
drivers/platform/x86/Kconfig | 4 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-events.c | 196 +++++++++++++++++++++++
drivers/platform/x86/lenovo-wmi-events.h | 20 +++
5 files changed, 222 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 aab59a777693..2b4b06e81192 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13164,6 +13164,7 @@ 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.*
F: drivers/platform/x86/lenovo-wmi-helpers.*
LENOVO WMI HOTKEY UTILITIES DRIVER
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..78bcb68b5bc4
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-events.c
@@ -0,0 +1,196 @@
+// 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/acpi.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "lenovo-wmi-events.h"
+#include "lenovo-wmi-gamezone.h"
+
+#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
+
+#define LWMI_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;
+};
+
+/**
+ * lwmi_events_register_notifier() - Add a notifier to the notifier chain.
+ * @nb: The notifier_block struct to register
+ *
+ * Call blocking_notifier_chain_register to register the notifier block to the
+ * lenovo-wmi-events driver blocking notifer chain.
+ *
+ * Return: 0 on success, %-EEXIST on error.
+ */
+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");
+
+/**
+ * lwmi_events_unregister_notifier() - Remove a notifier from the notifier
+ * chain.
+ * @nb: The notifier_block struct to register
+ *
+ * Call blocking_notifier_chain_unregister to unregister the notifier block
+ * from the lenovo-wmi-events driver blocking notifer chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+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");
+
+/**
+ * devm_lwmi_events_unregister_notifier() - Remove a notifier from the notifier
+ * chain.
+ * @data: Void pointer to the notifier_block struct to register.
+ *
+ * Call lwmi_events_unregister_notifier to unregister the notifier block from
+ * the lenovo-wmi-events driver blocking notifer chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+static void devm_lwmi_events_unregister_notifier(void *data)
+{
+ struct notifier_block *nb = data;
+
+ lwmi_events_unregister_notifier(nb);
+}
+
+/**
+ * devm_lwmi_events_register_notifier() - Add a notifier to the notifier chain.
+ * @dev: The parent device of the notifier_block struct.
+ * @nb: The notifier_block struct to register
+ *
+ * Call lwmi_events_register_notifier to register the notifier block to the
+ * lenovo-wmi-events driver blocking notifer chain. Then add, as a device
+ * managed action, unregister_notifier to automatically unregister the
+ * notifier block upon its parent device removal.
+ *
+ * Return: 0 on success, or an error code.
+ */
+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");
+
+/**
+ * lwmi_events_notify() - Call functions for the notifier call chain.
+ * @wdev: The parent WMI device of the driver.
+ * @obj: ACPI object passed by the registered WMI Event.
+ *
+ * Validate WMI event data and notify all registered drivers of the event and
+ * its output.
+ *
+ * Return: 0 on success, or an error code.
+ */
+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 LWMI_EVENT_THERMAL_MODE:
+ if (obj->type != ACPI_TYPE_INTEGER)
+ return;
+
+ sel_prof = obj->integer.value;
+
+ switch (sel_prof) {
+ case LWMI_GZ_THERMAL_MODE_QUIET:
+ case LWMI_GZ_THERMAL_MODE_BALANCED:
+ case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
+ case LWMI_GZ_THERMAL_MODE_EXTREME:
+ case LWMI_GZ_THERMAL_MODE_CUSTOM:
+ ret = blocking_notifier_call_chain(&events_chain_head,
+ LWMI_EVENT_THERMAL_MODE,
+ &sel_prof);
+ if (ret == NOTIFY_BAD)
+ dev_err(&wdev->dev,
+ "Failed to send notification to call chain for WMI Events\n");
+ return;
+ default:
+ dev_err(&wdev->dev, "Got invalid thermal mode: %x",
+ sel_prof);
+ return;
+ }
+ 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[] = {
+ { LWMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, LWMI_EVENT_THERMAL_MODE) },
+ {}
+};
+
+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..e4c5459c2f24
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-events.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_EVENTS_H_
+#define _LENOVO_WMI_EVENTS_H_
+
+struct device;
+struct notifier_block;
+
+enum lwmi_events_type {
+ LWMI_EVENT_THERMAL_MODE = 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] 23+ messages in thread* Re: [External] : [PATCH v6 3/6] platform/x86: Add Lenovo WMI Events Driver
2025-04-28 1:18 ` [PATCH v6 3/6] platform/x86: Add Lenovo WMI Events Driver Derek J. Clark
@ 2025-04-28 20:57 ` ALOK TIWARI
2025-04-30 3:23 ` Derek J. Clark
0 siblings, 1 reply; 23+ messages in thread
From: ALOK TIWARI @ 2025-04-28 20:57 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, Mario Limonciello
On 28-04-2025 06:48, Derek J. Clark wrote:
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-events.c
> @@ -0,0 +1,196 @@
> +// 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/acpi.h>
> +#include <linux/export.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +#include "lenovo-wmi-events.h"
> +#include "lenovo-wmi-gamezone.h"
> +
> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
> +
> +#define LWMI_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;
> +};
> +
> +/**
> + * lwmi_events_register_notifier() - Add a notifier to the notifier chain.
> + * @nb: The notifier_block struct to register
> + *
> + * Call blocking_notifier_chain_register to register the notifier block to the
> + * lenovo-wmi-events driver blocking notifer chain.
typo notifer -> notifier in all register/unregister
> + *
> + * Return: 0 on success, %-EEXIST on error.
> + */
> +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");
> +
> +/**
> + * lwmi_events_unregister_notifier() - Remove a notifier from the notifier
> + * chain.
> + * @nb: The notifier_block struct to register
the @nb parameter is described inconsistently:
@nb: The notifier_block struct to unregister
> + *
> + * Call blocking_notifier_chain_unregister to unregister the notifier block
> + * from the lenovo-wmi-events driver blocking notifer chain.
> + *
> + * Return: 0 on success, %-ENOENT on error.
> + */
> +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");
can be consider lwmi_events_un/register_notifier as static
if they are wrapper func?
> +
> +/**
> + * devm_lwmi_events_unregister_notifier() - Remove a notifier from the notifier
> + * chain.
> + * @data: Void pointer to the notifier_block struct to register.
* @data: Void pointer to the notifier_block struct to unregister.
> + *
> + * Call lwmi_events_unregister_notifier to unregister the notifier block from
> + * the lenovo-wmi-events driver blocking notifer chain.
> + *
> + * Return: 0 on success, %-ENOENT on error.
> + */
> +static void devm_lwmi_events_unregister_notifier(void *data)
> +{
> + struct notifier_block *nb = data;
> +
if (nb) Consider checking NULL pointer
> + lwmi_events_unregister_notifier(nb);
> +}
> +
> +/**
> + * devm_lwmi_events_register_notifier() - Add a notifier to the notifier chain.
> + * @dev: The parent device of the notifier_block struct.
> + * @nb: The notifier_block struct to register
> + *
> + * Call lwmi_events_register_notifier to register the notifier block to the
> + * lenovo-wmi-events driver blocking notifer chain. Then add, as a device
> + * managed action, unregister_notifier to automatically unregister the
> + * notifier block upon its parent device removal.
> + *
> + * Return: 0 on success, or an error code.
> + */
> +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");
> +
> +/**
> + * lwmi_events_notify() - Call functions for the notifier call chain.
> + * @wdev: The parent WMI device of the driver.
> + * @obj: ACPI object passed by the registered WMI Event.
> + *
> + * Validate WMI event data and notify all registered drivers of the event and
> + * its output.
> + *
> + * Return: 0 on success, or an error code.
> + */
> +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 LWMI_EVENT_THERMAL_MODE:
> + if (obj->type != ACPI_TYPE_INTEGER)
> + return;
> +
> + sel_prof = obj->integer.value;
> +
> + switch (sel_prof) {
> + case LWMI_GZ_THERMAL_MODE_QUIET:
> + case LWMI_GZ_THERMAL_MODE_BALANCED:
> + case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
> + case LWMI_GZ_THERMAL_MODE_EXTREME:
> + case LWMI_GZ_THERMAL_MODE_CUSTOM:
> + ret = blocking_notifier_call_chain(&events_chain_head,
> + LWMI_EVENT_THERMAL_MODE,
> + &sel_prof);
> + if (ret == NOTIFY_BAD)
> + dev_err(&wdev->dev,
> + "Failed to send notification to call chain for WMI Events\n");
> + return;
> + default:
> + dev_err(&wdev->dev, "Got invalid thermal mode: %x",
> + sel_prof);
> + return;
> + }
> + 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;
> +
might want to check context before using devm_kzalloc()
to avoid unnecessary memory allocation
> + 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[] = {
> + { LWMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, LWMI_EVENT_THERMAL_MODE) },
> + {}
> +};
> +
> +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..e4c5459c2f24
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-events.h
> @@ -0,0 +1,20 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +/* Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
> +
> +#ifndef _LENOVO_WMI_EVENTS_H_
> +#define _LENOVO_WMI_EVENTS_H_
> +
> +struct device;
> +struct notifier_block;
> +
> +enum lwmi_events_type {
> + LWMI_EVENT_THERMAL_MODE = 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);
why devm_lwmi_events_unregister_notifier not here ?
> +
> +#endif /* !_LENOVO_WMI_EVENTS_H_ */
Thanks,
Alok
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [External] : [PATCH v6 3/6] platform/x86: Add Lenovo WMI Events Driver
2025-04-28 20:57 ` [External] : " ALOK TIWARI
@ 2025-04-30 3:23 ` Derek J. Clark
2025-05-02 2:00 ` Armin Wolf
0 siblings, 1 reply; 23+ messages in thread
From: Derek J. Clark @ 2025-04-30 3:23 UTC (permalink / raw)
To: ALOK TIWARI, 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, Mario Limonciello
On April 28, 2025 1:57:30 PM PDT, ALOK TIWARI <alok.a.tiwari@oracle.com> wrote:
>
>
>On 28-04-2025 06:48, Derek J. Clark wrote:
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-wmi-events.c
>> @@ -0,0 +1,196 @@
>> +// 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/acpi.h>
>> +#include <linux/export.h>
>> +#include <linux/module.h>
>> +#include <linux/notifier.h>
>> +#include <linux/types.h>
>> +#include <linux/wmi.h>
>> +
>> +#include "lenovo-wmi-events.h"
>> +#include "lenovo-wmi-gamezone.h"
>> +
>> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
>> +
>> +#define LWMI_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;
>> +};
>> +
>> +/**
>> + * lwmi_events_register_notifier() - Add a notifier to the notifier chain.
>> + * @nb: The notifier_block struct to register
>> + *
>> + * Call blocking_notifier_chain_register to register the notifier block to the
>> + * lenovo-wmi-events driver blocking notifer chain.
>
>typo notifer -> notifier in all register/unregister
>
>> + *
>> + * Return: 0 on success, %-EEXIST on error.
>> + */
>> +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");
>> +
>> +/**
>> + * lwmi_events_unregister_notifier() - Remove a notifier from the notifier
>> + * chain.
>> + * @nb: The notifier_block struct to register
>
>the @nb parameter is described inconsistently:
>@nb: The notifier_block struct to unregister
>
>> + *
>> + * Call blocking_notifier_chain_unregister to unregister the notifier block
>> + * from the lenovo-wmi-events driver blocking notifer chain.
>> + *
>> + * Return: 0 on success, %-ENOENT on error.
>> + */
>> +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");
>
>can be consider lwmi_events_un/register_notifier as static
>if they are wrapper func?
I do think it's unlikely these would be used externally, but this pattern is consistent with other drivers with this type of function. TBS I'm not tied to one solution if there are objections to it one way or the other.
>> +
>> +/**
>> + * devm_lwmi_events_unregister_notifier() - Remove a notifier from the notifier
>> + * chain.
>> + * @data: Void pointer to the notifier_block struct to register.
>
>* @data: Void pointer to the notifier_block struct to unregister.
>
>> + *
>> + * Call lwmi_events_unregister_notifier to unregister the notifier block from
>> + * the lenovo-wmi-events driver blocking notifer chain.
>> + *
>> + * Return: 0 on success, %-ENOENT on error.
>> + */
>> +static void devm_lwmi_events_unregister_notifier(void *data)
>> +{
>> + struct notifier_block *nb = data;
>> +
>
>if (nb) Consider checking NULL pointer
>
>> + lwmi_events_unregister_notifier(nb);
>> +}
>> +
>> +/**
>> + * devm_lwmi_events_register_notifier() - Add a notifier to the notifier chain.
>> + * @dev: The parent device of the notifier_block struct.
>> + * @nb: The notifier_block struct to register
>> + *
>> + * Call lwmi_events_register_notifier to register the notifier block to the
>> + * lenovo-wmi-events driver blocking notifer chain. Then add, as a device
>> + * managed action, unregister_notifier to automatically unregister the
>> + * notifier block upon its parent device removal.
>> + *
>> + * Return: 0 on success, or an error code.
>> + */
>> +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");
>> +
>> +/**
>> + * lwmi_events_notify() - Call functions for the notifier call chain.
>> + * @wdev: The parent WMI device of the driver.
>> + * @obj: ACPI object passed by the registered WMI Event.
>> + *
>> + * Validate WMI event data and notify all registered drivers of the event and
>> + * its output.
>> + *
>> + * Return: 0 on success, or an error code.
>> + */
>> +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 LWMI_EVENT_THERMAL_MODE:
>> + if (obj->type != ACPI_TYPE_INTEGER)
>> + return;
>> +
>> + sel_prof = obj->integer.value;
>> +
>> + switch (sel_prof) {
>> + case LWMI_GZ_THERMAL_MODE_QUIET:
>> + case LWMI_GZ_THERMAL_MODE_BALANCED:
>> + case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
>> + case LWMI_GZ_THERMAL_MODE_EXTREME:
>> + case LWMI_GZ_THERMAL_MODE_CUSTOM:
>> + ret = blocking_notifier_call_chain(&events_chain_head,
>> + LWMI_EVENT_THERMAL_MODE,
>> + &sel_prof);
>> + if (ret == NOTIFY_BAD)
>> + dev_err(&wdev->dev,
>> + "Failed to send notification to call chain for WMI Events\n");
>> + return;
>> + default:
>> + dev_err(&wdev->dev, "Got invalid thermal mode: %x",
>> + sel_prof);
>> + return;
>> + }
>> + 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;
>> +
>
>might want to check context before using devm_kzalloc()
>to avoid unnecessary memory allocation
>
>> + 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[] = {
>> + { LWMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, LWMI_EVENT_THERMAL_MODE) },
>> + {}
>> +};
>> +
>> +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..e4c5459c2f24
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-wmi-events.h
>> @@ -0,0 +1,20 @@
>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
>> +
>> +/* Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
>> +
>> +#ifndef _LENOVO_WMI_EVENTS_H_
>> +#define _LENOVO_WMI_EVENTS_H_
>> +
>> +struct device;
>> +struct notifier_block;
>> +
>> +enum lwmi_events_type {
>> + LWMI_EVENT_THERMAL_MODE = 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);
>
>why devm_lwmi_events_unregister_notifier not here ?
>
devm_lwmi_events_unregister_notifier is static as it's automatically added as a removal action during registration. It wouldn't be used directly.
>> +
>> +#endif /* !_LENOVO_WMI_EVENTS_H_ */
>
>
>Thanks,
>Alok
Ack remaining, thanks.
- Derek
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [External] : [PATCH v6 3/6] platform/x86: Add Lenovo WMI Events Driver
2025-04-30 3:23 ` Derek J. Clark
@ 2025-05-02 2:00 ` Armin Wolf
0 siblings, 0 replies; 23+ messages in thread
From: Armin Wolf @ 2025-05-02 2:00 UTC (permalink / raw)
To: Derek J. Clark, ALOK TIWARI, 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, Mario Limonciello
Am 30.04.25 um 05:23 schrieb Derek J. Clark:
>
> On April 28, 2025 1:57:30 PM PDT, ALOK TIWARI <alok.a.tiwari@oracle.com> wrote:
>>
>> On 28-04-2025 06:48, Derek J. Clark wrote:
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-events.c
>>> @@ -0,0 +1,196 @@
>>> +// 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/acpi.h>
>>> +#include <linux/export.h>
>>> +#include <linux/module.h>
>>> +#include <linux/notifier.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +
>>> +#include "lenovo-wmi-events.h"
>>> +#include "lenovo-wmi-gamezone.h"
>>> +
>>> +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
>>> +
>>> +#define LWMI_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;
>>> +};
>>> +
>>> +/**
>>> + * lwmi_events_register_notifier() - Add a notifier to the notifier chain.
>>> + * @nb: The notifier_block struct to register
>>> + *
>>> + * Call blocking_notifier_chain_register to register the notifier block to the
>>> + * lenovo-wmi-events driver blocking notifer chain.
>> typo notifer -> notifier in all register/unregister
>>
>>> + *
>>> + * Return: 0 on success, %-EEXIST on error.
>>> + */
>>> +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");
>>> +
>>> +/**
>>> + * lwmi_events_unregister_notifier() - Remove a notifier from the notifier
>>> + * chain.
>>> + * @nb: The notifier_block struct to register
>> the @nb parameter is described inconsistently:
>> @nb: The notifier_block struct to unregister
>>
>>> + *
>>> + * Call blocking_notifier_chain_unregister to unregister the notifier block
>>> + * from the lenovo-wmi-events driver blocking notifer chain.
>>> + *
>>> + * Return: 0 on success, %-ENOENT on error.
>>> + */
>>> +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");
>> can be consider lwmi_events_un/register_notifier as static
>> if they are wrapper func?
> I do think it's unlikely these would be used externally, but this pattern is consistent with other drivers with this type of function. TBS I'm not tied to one solution if there are objections to it one way or the other.
Hi,
please only make private functions static. Public functions should remain non-static.
>>> +
>>> +/**
>>> + * devm_lwmi_events_unregister_notifier() - Remove a notifier from the notifier
>>> + * chain.
>>> + * @data: Void pointer to the notifier_block struct to register.
>> * @data: Void pointer to the notifier_block struct to unregister.
>>
>>> + *
>>> + * Call lwmi_events_unregister_notifier to unregister the notifier block from
>>> + * the lenovo-wmi-events driver blocking notifer chain.
>>> + *
>>> + * Return: 0 on success, %-ENOENT on error.
>>> + */
>>> +static void devm_lwmi_events_unregister_notifier(void *data)
>>> +{
>>> + struct notifier_block *nb = data;
>>> +
>> if (nb) Consider checking NULL pointer
devm_add_action_or_reset() will never call this function with data being NULL, so we do not need that check.
>>
>>> + lwmi_events_unregister_notifier(nb);
>>> +}
>>> +
>>> +/**
>>> + * devm_lwmi_events_register_notifier() - Add a notifier to the notifier chain.
>>> + * @dev: The parent device of the notifier_block struct.
>>> + * @nb: The notifier_block struct to register
>>> + *
>>> + * Call lwmi_events_register_notifier to register the notifier block to the
>>> + * lenovo-wmi-events driver blocking notifer chain. Then add, as a device
>>> + * managed action, unregister_notifier to automatically unregister the
>>> + * notifier block upon its parent device removal.
>>> + *
>>> + * Return: 0 on success, or an error code.
>>> + */
>>> +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");
>>> +
>>> +/**
>>> + * lwmi_events_notify() - Call functions for the notifier call chain.
>>> + * @wdev: The parent WMI device of the driver.
>>> + * @obj: ACPI object passed by the registered WMI Event.
>>> + *
>>> + * Validate WMI event data and notify all registered drivers of the event and
>>> + * its output.
>>> + *
>>> + * Return: 0 on success, or an error code.
>>> + */
>>> +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 LWMI_EVENT_THERMAL_MODE:
>>> + if (obj->type != ACPI_TYPE_INTEGER)
>>> + return;
>>> +
>>> + sel_prof = obj->integer.value;
>>> +
>>> + switch (sel_prof) {
>>> + case LWMI_GZ_THERMAL_MODE_QUIET:
>>> + case LWMI_GZ_THERMAL_MODE_BALANCED:
>>> + case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
>>> + case LWMI_GZ_THERMAL_MODE_EXTREME:
>>> + case LWMI_GZ_THERMAL_MODE_CUSTOM:
>>> + ret = blocking_notifier_call_chain(&events_chain_head,
>>> + LWMI_EVENT_THERMAL_MODE,
>>> + &sel_prof);
>>> + if (ret == NOTIFY_BAD)
>>> + dev_err(&wdev->dev,
>>> + "Failed to send notification to call chain for WMI Events\n");
>>> + return;
>>> + default:
>>> + dev_err(&wdev->dev, "Got invalid thermal mode: %x",
>>> + sel_prof);
>>> + return;
>>> + }
>>> + 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;
>>> +
>> might want to check context before using devm_kzalloc()
>> to avoid unnecessary memory allocation
>>
>>> + 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[] = {
>>> + { LWMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, LWMI_EVENT_THERMAL_MODE) },
>>> + {}
>>> +};
>>> +
>>> +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..e4c5459c2f24
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-events.h
>>> @@ -0,0 +1,20 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
>>> +
>>> +/* Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
>>> +
>>> +#ifndef _LENOVO_WMI_EVENTS_H_
>>> +#define _LENOVO_WMI_EVENTS_H_
>>> +
>>> +struct device;
>>> +struct notifier_block;
>>> +
>>> +enum lwmi_events_type {
>>> + LWMI_EVENT_THERMAL_MODE = 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);
>> why devm_lwmi_events_unregister_notifier not here ?
>>
> devm_lwmi_events_unregister_notifier is static as it's automatically added as a removal action during registration. It wouldn't be used directly.
I agree, most drivers have no need for such a function.
Thanks,
Armin Wolf
>>> +
>>> +#endif /* !_LENOVO_WMI_EVENTS_H_ */
>>
>> Thanks,
>> Alok
> Ack remaining, thanks.
> - Derek
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v6 4/6] platform/x86: Add Lenovo WMI Capability Data 01 Driver
2025-04-28 1:18 [PATCH v6 0/6] platform/x86: Add Lenovo WMI Gaming Series Drivers Derek J. Clark
` (2 preceding siblings ...)
2025-04-28 1:18 ` [PATCH v6 3/6] platform/x86: Add Lenovo WMI Events Driver Derek J. Clark
@ 2025-04-28 1:18 ` Derek J. Clark
2025-05-02 2:18 ` Armin Wolf
2025-04-28 1:18 ` [PATCH v6 5/6] platform/x86: Add Lenovo WMI Gamezone Driver Derek J. Clark
2025-04-28 1:18 ` [PATCH v6 6/6] platform/x86: Add Lenovo WMI Other Mode Driver Derek J. Clark
5 siblings, 1 reply; 23+ messages in thread
From: Derek J. Clark @ 2025-04-28 1:18 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.
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v6:
- Recache capabiltiy data on ACPI AC events to ensure accutare
max_value.
- Fix typos and rewordings from v5 review.
v5:
- Return to cache at device initialization. On component bind, pass a
pointer to lenovo-wmi-other.
- Fixes from v4 review.
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 | 1 +
drivers/platform/x86/Kconfig | 4 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-capdata01.c | 272 ++++++++++++++++++++
drivers/platform/x86/lenovo-wmi-capdata01.h | 29 +++
5 files changed, 307 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 2b4b06e81192..1b22e41cc730 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13164,6 +13164,7 @@ 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.*
F: drivers/platform/x86/lenovo-wmi-events.*
F: drivers/platform/x86/lenovo-wmi-helpers.*
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..841d4a37249b
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Capability Data 01 WMI Data Block driver.
+ *
+ * Lenovo Capability Data 01 provides information on tunable attributes used by
+ * the "Other Mode" WMI interface. The data includes if the attribute is
+ * supported by the hardware, the default_value, max_value, min_value, and step
+ * increment. Each attibute has multiple pages, one for each of the thermal
+ * modes managed by the Gamezone interface.
+ *
+ * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/gfp_types.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/overflow.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "lenovo-wmi-capdata01.h"
+
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+
+#define ACPI_AC_NOTIFY_STATUS 0x80
+
+static DEFINE_MUTEX(list_mutex);
+
+struct lwmi_cd01_priv {
+ struct notifier_block acpi_nb; /* ACPI events */
+ struct wmi_device *wdev;
+ struct cd01_list *list;
+};
+
+/**
+ * lwmi_cd01_component_bind() - Bind component to master device.
+ * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
+ * @data: capdata01_list object pointer used to return the capability data.
+ *
+ * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
+ * list. This is used to call lwmi_cd01_get_data to look up attribute data
+ * from the lenovo-wmi-other driver.
+ *
+:* Return: 0 on success, or an error code.
+ */
+static int lwmi_cd01_component_bind(struct device *cd01_dev,
+ struct device *om_dev, void *data)
+{
+ struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
+ struct cd01_list **cd01_list = data;
+
+ if (!priv->list)
+ return -ENODEV;
+
+ *cd01_list = priv->list;
+
+ return 0;
+}
+
+static const struct component_ops lwmi_cd01_component_ops = {
+ .bind = lwmi_cd01_component_bind,
+};
+
+/**
+ * lwmi_cd01_get_data - Get the data of the specified attribute
+ * @dev: The lenovo-wmi-capdata01 parent device.
+ * @tunable_attr: The attribute to be populated.
+ *
+ * Retrieves the capability data 01 struct pointer for the given
+ * attribute for its specified thermal mode.
+ *
+ * Return: Either a pointer to capability data, or NULL.
+ */
+struct capdata01 *lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id)
+{
+ u8 idx;
+
+ guard(mutex)(&list_mutex);
+ for (idx = 0; idx < list->count; idx++) {
+ if (list->data[idx].id != attribute_id)
+ continue;
+ return &list->data[idx];
+ }
+ return NULL;
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
+
+/**
+ * lwmi_cd01_setup() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Loop through each WMI data block and cache the data.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
+{
+ int idx;
+
+ guard(mutex)(&list_mutex);
+ for (idx = 0; idx < priv->list->count; idx++) {
+ union acpi_object *ret_obj __free(kfree) = NULL;
+
+ ret_obj = wmidev_block_query(priv->wdev, idx);
+ if (!ret_obj)
+ return -ENODEV;
+
+ if (ret_obj->type != ACPI_TYPE_BUFFER ||
+ ret_obj->buffer.length < sizeof(priv->list->data[idx]))
+ continue;
+
+ memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
+ ret_obj->buffer.length);
+ }
+
+ return 0;
+}
+
+/**
+ * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Allocate a cd01_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
+{
+ struct cd01_list *list;
+ size_t list_size;
+ int count;
+
+ count = wmidev_instance_count(priv->wdev);
+ list_size = struct_size(list, data, count);
+
+ guard(mutex)(&list_mutex);
+ list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+ list->count = count;
+ priv->list = list;
+
+ return 0;
+}
+
+/**
+ * lwmi_cd01_setup() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Allocate a cd01_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface. Then loop through each data block and
+ * cache the data.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv)
+{
+ int ret;
+
+ ret = lwmi_cd01_alloc(priv);
+ if (ret)
+ return ret;
+
+ return lwmi_cd01_cache(priv);
+}
+
+/**
+ * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier.
+ * block call chain.
+ * @nb: The notifier_block registered to lenovo-wmi-events driver.
+ * @action: Unused.
+ * @data: The ACPI event.
+ *
+ * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
+ * of a change.
+ *
+ * Return: notifier_block status.
+ */
+static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct acpi_bus_event *event = (struct acpi_bus_event *)data;
+ struct lwmi_cd01_priv *priv;
+ int ret;
+
+
+ priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
+
+ switch (event->type) {
+ case ACPI_AC_NOTIFY_STATUS:
+ ret = lwmi_cd01_cache(priv);
+ if (ret)
+ return NOTIFY_BAD;
+
+ return NOTIFY_OK;
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+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 = lwmi_cd01_setup(priv);
+ if (ret)
+ return ret;
+
+ priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
+ register_acpi_notifier(&priv->acpi_nb);
+
+ return component_add(&wdev->dev, &lwmi_cd01_component_ops);
+}
+
+static void lwmi_cd01_remove(struct wmi_device *wdev)
+{
+ component_del(&wdev->dev, &lwmi_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,
+};
+
+/**
+ * lwmi_cd01_match() - Match rule for the master driver.
+ * @dev: Pointer to the capability data 01 parent device.
+ * @data: Unused void pointer for passing match criteria.
+ *
+ * Return: int.
+ */
+int lwmi_cd01_match(struct device *dev, void *data)
+{
+ return dev->driver == &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..ed4f3d86464d
--- /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/types.h>
+
+struct device;
+
+struct capdata01 {
+ u32 id;
+ u32 supported;
+ u32 default_value;
+ u32 step;
+ u32 min_value;
+ u32 max_value;
+};
+
+struct cd01_list {
+ u8 count;
+ struct capdata01 data[];
+};
+
+struct capdata01 *lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id);
+int lwmi_cd01_match(struct device *dev, void *data);
+
+#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* Re: [PATCH v6 4/6] platform/x86: Add Lenovo WMI Capability Data 01 Driver
2025-04-28 1:18 ` [PATCH v6 4/6] platform/x86: Add Lenovo WMI Capability Data 01 Driver Derek J. Clark
@ 2025-05-02 2:18 ` Armin Wolf
2025-05-02 15:51 ` Derek J. Clark
0 siblings, 1 reply; 23+ messages in thread
From: Armin Wolf @ 2025-05-02 2:18 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 28.04.25 um 03:18 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.
>
> Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v6:
> - Recache capabiltiy data on ACPI AC events to ensure accutare
> max_value.
> - Fix typos and rewordings from v5 review.
> v5:
> - Return to cache at device initialization. On component bind, pass a
> pointer to lenovo-wmi-other.
> - Fixes from v4 review.
> 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 | 1 +
> drivers/platform/x86/Kconfig | 4 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-capdata01.c | 272 ++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi-capdata01.h | 29 +++
> 5 files changed, 307 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 2b4b06e81192..1b22e41cc730 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,7 @@ 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.*
> F: drivers/platform/x86/lenovo-wmi-events.*
> F: drivers/platform/x86/lenovo-wmi-helpers.*
>
> 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..841d4a37249b
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> @@ -0,0 +1,272 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Capability Data 01 WMI Data Block driver.
> + *
> + * Lenovo Capability Data 01 provides information on tunable attributes used by
> + * the "Other Mode" WMI interface. The data includes if the attribute is
> + * supported by the hardware, the default_value, max_value, min_value, and step
> + * increment. Each attibute has multiple pages, one for each of the thermal
> + * modes managed by the Gamezone interface.
> + *
> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/cleanup.h>
> +#include <linux/component.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/export.h>
> +#include <linux/gfp_types.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/overflow.h>
> +#include <linux/types.h>
> +#include <linux/wmi.h>
> +
> +#include "lenovo-wmi-capdata01.h"
> +
> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
> +
> +#define ACPI_AC_NOTIFY_STATUS 0x80
> +
> +static DEFINE_MUTEX(list_mutex);
Hi,
why not moving this mutex inside struct cd01_list?
> +
> +struct lwmi_cd01_priv {
> + struct notifier_block acpi_nb; /* ACPI events */
> + struct wmi_device *wdev;
> + struct cd01_list *list;
> +};
> +
> +/**
> + * lwmi_cd01_component_bind() - Bind component to master device.
> + * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
> + * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
> + * @data: capdata01_list object pointer used to return the capability data.
> + *
> + * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
> + * list. This is used to call lwmi_cd01_get_data to look up attribute data
> + * from the lenovo-wmi-other driver.
> + *
> +:* Return: 0 on success, or an error code.
> + */
> +static int lwmi_cd01_component_bind(struct device *cd01_dev,
> + struct device *om_dev, void *data)
> +{
> + struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
> + struct cd01_list **cd01_list = data;
> +
> + if (!priv->list)
> + return -ENODEV;
> +
> + *cd01_list = priv->list;
> +
> + return 0;
> +}
> +
> +static const struct component_ops lwmi_cd01_component_ops = {
> + .bind = lwmi_cd01_component_bind,
> +};
> +
> +/**
> + * lwmi_cd01_get_data - Get the data of the specified attribute
> + * @dev: The lenovo-wmi-capdata01 parent device.
> + * @tunable_attr: The attribute to be populated.
> + *
> + * Retrieves the capability data 01 struct pointer for the given
> + * attribute for its specified thermal mode.
> + *
> + * Return: Either a pointer to capability data, or NULL.
> + */
> +struct capdata01 *lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id)
> +{
> + u8 idx;
> +
> + guard(mutex)(&list_mutex);
> + for (idx = 0; idx < list->count; idx++) {
> + if (list->data[idx].id != attribute_id)
> + continue;
> + return &list->data[idx];
This might cause issues should lwmi_cd01_cache() be called when the caller of this function
currently accesses the returned data.
Maybe it would make sense to simply copy the struct capdata01? It is rather small, so the overhead
would be negligible.
I envision something like this:
int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output);
In this case the resulting capdata is copied into "output".
> + }
> + return NULL;
> +}
> +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
> +
> +/**
> + * lwmi_cd01_setup() - Cache all WMI data block information
> + * @priv: lenovo-wmi-capdata01 driver data.
> + *
> + * Loop through each WMI data block and cache the data.
> + *
> + * Return: 0 on success, or an error.
> + */
> +static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
> +{
> + int idx;
> +
> + guard(mutex)(&list_mutex);
> + for (idx = 0; idx < priv->list->count; idx++) {
> + union acpi_object *ret_obj __free(kfree) = NULL;
> +
> + ret_obj = wmidev_block_query(priv->wdev, idx);
> + if (!ret_obj)
> + return -ENODEV;
> +
> + if (ret_obj->type != ACPI_TYPE_BUFFER ||
> + ret_obj->buffer.length < sizeof(priv->list->data[idx]))
> + continue;
> +
> + memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
> + ret_obj->buffer.length);
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
> + * @priv: lenovo-wmi-capdata01 driver data.
> + *
> + * Allocate a cd01_list struct large enough to contain data from all WMI data
> + * blocks provided by the interface.
> + *
> + * Return: 0 on success, or an error.
> + */
> +static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
> +{
> + struct cd01_list *list;
> + size_t list_size;
> + int count;
> +
> + count = wmidev_instance_count(priv->wdev);
> + list_size = struct_size(list, data, count);
> +
> + guard(mutex)(&list_mutex);
> + list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
> + if (!list)
> + return -ENOMEM;
> +
> + list->count = count;
> + priv->list = list;
> +
> + return 0;
> +}
> +
> +/**
> + * lwmi_cd01_setup() - Cache all WMI data block information
> + * @priv: lenovo-wmi-capdata01 driver data.
> + *
> + * Allocate a cd01_list struct large enough to contain data from all WMI data
> + * blocks provided by the interface. Then loop through each data block and
> + * cache the data.
> + *
> + * Return: 0 on success, or an error code.
> + */
> +static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv)
> +{
> + int ret;
> +
> + ret = lwmi_cd01_alloc(priv);
> + if (ret)
> + return ret;
> +
> + return lwmi_cd01_cache(priv);
> +}
> +
> +/**
> + * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier.
> + * block call chain.
> + * @nb: The notifier_block registered to lenovo-wmi-events driver.
> + * @action: Unused.
> + * @data: The ACPI event.
> + *
> + * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
> + * of a change.
> + *
> + * Return: notifier_block status.
> + */
> +static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
> + void *data)
> +{
> + struct acpi_bus_event *event = (struct acpi_bus_event *)data;
Unnecessary cast, please drop.
> + struct lwmi_cd01_priv *priv;
> + int ret;
> +
> +
> + priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
> +
> + switch (event->type) {
> + case ACPI_AC_NOTIFY_STATUS:
You should also check the the "device_class" of the ACPI event (search for ACPI_AC_CLASS)
because "type" is not guaranteed to be unique between different events.
> + ret = lwmi_cd01_cache(priv);
> + if (ret)
> + return NOTIFY_BAD;
> +
> + return NOTIFY_OK;
> + default:
> + return NOTIFY_DONE;
> + }
> +}
> +
> +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 = lwmi_cd01_setup(priv);
> + if (ret)
> + return ret;
> +
> + priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
> + register_acpi_notifier(&priv->acpi_nb);
> +
> + return component_add(&wdev->dev, &lwmi_cd01_component_ops);
You need to unregister the ACPI notifier when component_add() fails.
> +}
> +
> +static void lwmi_cd01_remove(struct wmi_device *wdev)
> +{
> + component_del(&wdev->dev, &lwmi_cd01_component_ops);
Same as above. You could use devm_add_action_or_reset() to fix both cases.
> +}
> +
> +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,
> +};
> +
> +/**
> + * lwmi_cd01_match() - Match rule for the master driver.
> + * @dev: Pointer to the capability data 01 parent device.
> + * @data: Unused void pointer for passing match criteria.
> + *
> + * Return: int.
> + */
> +int lwmi_cd01_match(struct device *dev, void *data)
> +{
> + return dev->driver == &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..ed4f3d86464d
> --- /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/types.h>
> +
> +struct device;
> +
> +struct capdata01 {
> + u32 id;
> + u32 supported;
> + u32 default_value;
> + u32 step;
> + u32 min_value;
> + u32 max_value;
> +};
> +
> +struct cd01_list {
> + u8 count;
> + struct capdata01 data[];
> +};
Since client driver are not directly accessing this struct anyway please keep its definition private
and only provide an incomplete definition in this header:
struct cd01_list *;
Thanks,
Armin Wolf
> +
> +struct capdata01 *lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id);
> +int lwmi_cd01_match(struct device *dev, void *data);
> +
> +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v6 4/6] platform/x86: Add Lenovo WMI Capability Data 01 Driver
2025-05-02 2:18 ` Armin Wolf
@ 2025-05-02 15:51 ` Derek J. Clark
2025-05-02 21:10 ` Armin Wolf
0 siblings, 1 reply; 23+ messages in thread
From: Derek J. Clark @ 2025-05-02 15:51 UTC (permalink / raw)
To: Armin Wolf, 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
On May 1, 2025 7:18:40 PM PDT, Armin Wolf <W_Armin@gmx.de> wrote:
>Am 28.04.25 um 03:18 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.
>>
>> Reviewed-by: Armin Wolf <W_Armin@gmx.de>
>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>> ---
>> v6:
>> - Recache capabiltiy data on ACPI AC events to ensure accutare
>> max_value.
>> - Fix typos and rewordings from v5 review.
>> v5:
>> - Return to cache at device initialization. On component bind, pass a
>> pointer to lenovo-wmi-other.
>> - Fixes from v4 review.
>> 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 | 1 +
>> drivers/platform/x86/Kconfig | 4 +
>> drivers/platform/x86/Makefile | 1 +
>> drivers/platform/x86/lenovo-wmi-capdata01.c | 272 ++++++++++++++++++++
>> drivers/platform/x86/lenovo-wmi-capdata01.h | 29 +++
>> 5 files changed, 307 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 2b4b06e81192..1b22e41cc730 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -13164,6 +13164,7 @@ 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.*
>> F: drivers/platform/x86/lenovo-wmi-events.*
>> F: drivers/platform/x86/lenovo-wmi-helpers.*
>> 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..841d4a37249b
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
>> @@ -0,0 +1,272 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Lenovo Capability Data 01 WMI Data Block driver.
>> + *
>> + * Lenovo Capability Data 01 provides information on tunable attributes used by
>> + * the "Other Mode" WMI interface. The data includes if the attribute is
>> + * supported by the hardware, the default_value, max_value, min_value, and step
>> + * increment. Each attibute has multiple pages, one for each of the thermal
>> + * modes managed by the Gamezone interface.
>> + *
>> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>> + */
>> +
>> +#include <linux/acpi.h>
>> +#include <linux/cleanup.h>
>> +#include <linux/component.h>
>> +#include <linux/container_of.h>
>> +#include <linux/device.h>
>> +#include <linux/export.h>
>> +#include <linux/gfp_types.h>
>> +#include <linux/module.h>
>> +#include <linux/notifier.h>
>> +#include <linux/overflow.h>
>> +#include <linux/types.h>
>> +#include <linux/wmi.h>
>> +
>> +#include "lenovo-wmi-capdata01.h"
>> +
>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>> +
>> +#define ACPI_AC_NOTIFY_STATUS 0x80
>> +
>> +static DEFINE_MUTEX(list_mutex);
>
>Hi,
>
>why not moving this mutex inside struct cd01_list?
>
I can do that. Is that preferable over adding it to priv?
>> +
>> +struct lwmi_cd01_priv {
>> + struct notifier_block acpi_nb; /* ACPI events */
>> + struct wmi_device *wdev;
>> + struct cd01_list *list;
>> +};
>> +
>> +/**
>> + * lwmi_cd01_component_bind() - Bind component to master device.
>> + * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
>> + * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
>> + * @data: capdata01_list object pointer used to return the capability data.
>> + *
>> + * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
>> + * list. This is used to call lwmi_cd01_get_data to look up attribute data
>> + * from the lenovo-wmi-other driver.
>> + *
>> +:* Return: 0 on success, or an error code.
>> + */
>> +static int lwmi_cd01_component_bind(struct device *cd01_dev,
>> + struct device *om_dev, void *data)
>> +{
>> + struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
>> + struct cd01_list **cd01_list = data;
>> +
>> + if (!priv->list)
>> + return -ENODEV;
>> +
>> + *cd01_list = priv->list;
>> +
>> + return 0;
>> +}
>> +
>> +static const struct component_ops lwmi_cd01_component_ops = {
>> + .bind = lwmi_cd01_component_bind,
>> +};
>> +
>> +/**
>> + * lwmi_cd01_get_data - Get the data of the specified attribute
>> + * @dev: The lenovo-wmi-capdata01 parent device.
>> + * @tunable_attr: The attribute to be populated.
>> + *
>> + * Retrieves the capability data 01 struct pointer for the given
>> + * attribute for its specified thermal mode.
>> + *
>> + * Return: Either a pointer to capability data, or NULL.
>> + */
>> +struct capdata01 *lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id)
>> +{
>> + u8 idx;
>> +
>> + guard(mutex)(&list_mutex);
>> + for (idx = 0; idx < list->count; idx++) {
>> + if (list->data[idx].id != attribute_id)
>> + continue;
>> + return &list->data[idx];
>
>This might cause issues should lwmi_cd01_cache() be called when the caller of this function
>currently accesses the returned data.
>
>Maybe it would make sense to simply copy the struct capdata01? It is rather small, so the overhead
>would be negligible.
>
>I envision something like this:
>
>int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output);
>
>In this case the resulting capdata is copied into "output".
>
I can make this work. I assume memcpy is preferred. Should I make the source pointer a devm kmalloc before it's sent or use the stack?
>> + }
>> + return NULL;
>> +}
>> +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
>> +
>> +/**
>> + * lwmi_cd01_setup() - Cache all WMI data block information
>> + * @priv: lenovo-wmi-capdata01 driver data.
>> + *
>> + * Loop through each WMI data block and cache the data.
>> + *
>> + * Return: 0 on success, or an error.
>> + */
>> +static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
>> +{
>> + int idx;
>> +
>> + guard(mutex)(&list_mutex);
>> + for (idx = 0; idx < priv->list->count; idx++) {
>> + union acpi_object *ret_obj __free(kfree) = NULL;
>> +
>> + ret_obj = wmidev_block_query(priv->wdev, idx);
>> + if (!ret_obj)
>> + return -ENODEV;
>> +
>> + if (ret_obj->type != ACPI_TYPE_BUFFER ||
>> + ret_obj->buffer.length < sizeof(priv->list->data[idx]))
>> + continue;
>> +
>> + memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
>> + ret_obj->buffer.length);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +/**
>> + * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
>> + * @priv: lenovo-wmi-capdata01 driver data.
>> + *
>> + * Allocate a cd01_list struct large enough to contain data from all WMI data
>> + * blocks provided by the interface.
>> + *
>> + * Return: 0 on success, or an error.
>> + */
>> +static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
>> +{
>> + struct cd01_list *list;
>> + size_t list_size;
>> + int count;
>> +
>> + count = wmidev_instance_count(priv->wdev);
>> + list_size = struct_size(list, data, count);
>> +
>> + guard(mutex)(&list_mutex);
>> + list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
>> + if (!list)
>> + return -ENOMEM;
>> +
>> + list->count = count;
>> + priv->list = list;
>> +
>> + return 0;
>> +}
>> +
>> +/**
>> + * lwmi_cd01_setup() - Cache all WMI data block information
>> + * @priv: lenovo-wmi-capdata01 driver data.
>> + *
>> + * Allocate a cd01_list struct large enough to contain data from all WMI data
>> + * blocks provided by the interface. Then loop through each data block and
>> + * cache the data.
>> + *
>> + * Return: 0 on success, or an error code.
>> + */
>> +static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv)
>> +{
>> + int ret;
>> +
>> + ret = lwmi_cd01_alloc(priv);
>> + if (ret)
>> + return ret;
>> +
>> + return lwmi_cd01_cache(priv);
>> +}
>> +
>> +/**
>> + * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier.
>> + * block call chain.
>> + * @nb: The notifier_block registered to lenovo-wmi-events driver.
>> + * @action: Unused.
>> + * @data: The ACPI event.
>> + *
>> + * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
>> + * of a change.
>> + *
>> + * Return: notifier_block status.
>> + */
>> +static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
>> + void *data)
>> +{
>> + struct acpi_bus_event *event = (struct acpi_bus_event *)data;
>
>Unnecessary cast, please drop.
>
>> + struct lwmi_cd01_priv *priv;
>> + int ret;
>> +
>> +
>> + priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
>> +
>> + switch (event->type) {
>> + case ACPI_AC_NOTIFY_STATUS:
>
>You should also check the the "device_class" of the ACPI event (search for ACPI_AC_CLASS)
>because "type" is not guaranteed to be unique between different events.
>
Good to know, thanks.
Ack all below.
Thanks,
Derek
>> + ret = lwmi_cd01_cache(priv);
>> + if (ret)
>> + return NOTIFY_BAD;
>> +
>> + return NOTIFY_OK;
>> + default:
>> + return NOTIFY_DONE;
>> + }
>> +}
>> +
>> +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 = lwmi_cd01_setup(priv);
>> + if (ret)
>> + return ret;
>> +
>> + priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
>> + register_acpi_notifier(&priv->acpi_nb);
>> +
>> + return component_add(&wdev->dev, &lwmi_cd01_component_ops);
>
>You need to unregister the ACPI notifier when component_add() fails.
>
>> +}
>> +
>> +static void lwmi_cd01_remove(struct wmi_device *wdev)
>> +{
>> + component_del(&wdev->dev, &lwmi_cd01_component_ops);
>
>Same as above. You could use devm_add_action_or_reset() to fix both cases.
>
>> +}
>> +
>> +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,
>> +};
>> +
>> +/**
>> + * lwmi_cd01_match() - Match rule for the master driver.
>> + * @dev: Pointer to the capability data 01 parent device.
>> + * @data: Unused void pointer for passing match criteria.
>> + *
>> + * Return: int.
>> + */
>> +int lwmi_cd01_match(struct device *dev, void *data)
>> +{
>> + return dev->driver == &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..ed4f3d86464d
>> --- /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/types.h>
>> +
>> +struct device;
>> +
>> +struct capdata01 {
>> + u32 id;
>> + u32 supported;
>> + u32 default_value;
>> + u32 step;
>> + u32 min_value;
>> + u32 max_value;
>> +};
>> +
>> +struct cd01_list {
>> + u8 count;
>> + struct capdata01 data[];
>> +};
>
>Since client driver are not directly accessing this struct anyway please keep its definition private
>and only provide an incomplete definition in this header:
>
> struct cd01_list *;
>
>Thanks,
>Armin Wolf
>
>> +
>> +struct capdata01 *lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id);
>> +int lwmi_cd01_match(struct device *dev, void *data);
>> +
>> +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
- Derek
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v6 4/6] platform/x86: Add Lenovo WMI Capability Data 01 Driver
2025-05-02 15:51 ` Derek J. Clark
@ 2025-05-02 21:10 ` Armin Wolf
0 siblings, 0 replies; 23+ messages in thread
From: Armin Wolf @ 2025-05-02 21:10 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 02.05.25 um 17:51 schrieb Derek J. Clark:
>
> On May 1, 2025 7:18:40 PM PDT, Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 28.04.25 um 03:18 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.
>>>
>>> Reviewed-by: Armin Wolf <W_Armin@gmx.de>
>>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>>> ---
>>> v6:
>>> - Recache capabiltiy data on ACPI AC events to ensure accutare
>>> max_value.
>>> - Fix typos and rewordings from v5 review.
>>> v5:
>>> - Return to cache at device initialization. On component bind, pass a
>>> pointer to lenovo-wmi-other.
>>> - Fixes from v4 review.
>>> 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 | 1 +
>>> drivers/platform/x86/Kconfig | 4 +
>>> drivers/platform/x86/Makefile | 1 +
>>> drivers/platform/x86/lenovo-wmi-capdata01.c | 272 ++++++++++++++++++++
>>> drivers/platform/x86/lenovo-wmi-capdata01.h | 29 +++
>>> 5 files changed, 307 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 2b4b06e81192..1b22e41cc730 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -13164,6 +13164,7 @@ 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.*
>>> F: drivers/platform/x86/lenovo-wmi-events.*
>>> F: drivers/platform/x86/lenovo-wmi-helpers.*
>>> 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..841d4a37249b
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
>>> @@ -0,0 +1,272 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * Lenovo Capability Data 01 WMI Data Block driver.
>>> + *
>>> + * Lenovo Capability Data 01 provides information on tunable attributes used by
>>> + * the "Other Mode" WMI interface. The data includes if the attribute is
>>> + * supported by the hardware, the default_value, max_value, min_value, and step
>>> + * increment. Each attibute has multiple pages, one for each of the thermal
>>> + * modes managed by the Gamezone interface.
>>> + *
>>> + * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + */
>>> +
>>> +#include <linux/acpi.h>
>>> +#include <linux/cleanup.h>
>>> +#include <linux/component.h>
>>> +#include <linux/container_of.h>
>>> +#include <linux/device.h>
>>> +#include <linux/export.h>
>>> +#include <linux/gfp_types.h>
>>> +#include <linux/module.h>
>>> +#include <linux/notifier.h>
>>> +#include <linux/overflow.h>
>>> +#include <linux/types.h>
>>> +#include <linux/wmi.h>
>>> +
>>> +#include "lenovo-wmi-capdata01.h"
>>> +
>>> +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
>>> +
>>> +#define ACPI_AC_NOTIFY_STATUS 0x80
>>> +
>>> +static DEFINE_MUTEX(list_mutex);
>> Hi,
>>
>> why not moving this mutex inside struct cd01_list?
>>
> I can do that. Is that preferable over adding it to priv?
You can also add it to priv, but keep in mind that lwmi_cd01_get_data() needs to access it.
>>> +
>>> +struct lwmi_cd01_priv {
>>> + struct notifier_block acpi_nb; /* ACPI events */
>>> + struct wmi_device *wdev;
>>> + struct cd01_list *list;
>>> +};
>>> +
>>> +/**
>>> + * lwmi_cd01_component_bind() - Bind component to master device.
>>> + * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
>>> + * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
>>> + * @data: capdata01_list object pointer used to return the capability data.
>>> + *
>>> + * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
>>> + * list. This is used to call lwmi_cd01_get_data to look up attribute data
>>> + * from the lenovo-wmi-other driver.
>>> + *
>>> +:* Return: 0 on success, or an error code.
>>> + */
>>> +static int lwmi_cd01_component_bind(struct device *cd01_dev,
>>> + struct device *om_dev, void *data)
>>> +{
>>> + struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
>>> + struct cd01_list **cd01_list = data;
>>> +
>>> + if (!priv->list)
>>> + return -ENODEV;
>>> +
>>> + *cd01_list = priv->list;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct component_ops lwmi_cd01_component_ops = {
>>> + .bind = lwmi_cd01_component_bind,
>>> +};
>>> +
>>> +/**
>>> + * lwmi_cd01_get_data - Get the data of the specified attribute
>>> + * @dev: The lenovo-wmi-capdata01 parent device.
>>> + * @tunable_attr: The attribute to be populated.
>>> + *
>>> + * Retrieves the capability data 01 struct pointer for the given
>>> + * attribute for its specified thermal mode.
>>> + *
>>> + * Return: Either a pointer to capability data, or NULL.
>>> + */
>>> +struct capdata01 *lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id)
>>> +{
>>> + u8 idx;
>>> +
>>> + guard(mutex)(&list_mutex);
>>> + for (idx = 0; idx < list->count; idx++) {
>>> + if (list->data[idx].id != attribute_id)
>>> + continue;
>>> + return &list->data[idx];
>> This might cause issues should lwmi_cd01_cache() be called when the caller of this function
>> currently accesses the returned data.
>>
>> Maybe it would make sense to simply copy the struct capdata01? It is rather small, so the overhead
>> would be negligible.
>>
>> I envision something like this:
>>
>> int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output);
>>
>> In this case the resulting capdata is copied into "output".
>>
> I can make this work. I assume memcpy is preferred. Should I make the source pointer a devm kmalloc before it's sent or use the stack?
I suggest that you let the caller decide that. Inside lwmi_cd01_get_data() you simply use memcpy() with "output" being the destination.
The caller will take care of providing the memory referenced by "output".
Thanks,
Armin Wolf
>>> + }
>>> + return NULL;
>>> +}
>>> +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
>>> +
>>> +/**
>>> + * lwmi_cd01_setup() - Cache all WMI data block information
>>> + * @priv: lenovo-wmi-capdata01 driver data.
>>> + *
>>> + * Loop through each WMI data block and cache the data.
>>> + *
>>> + * Return: 0 on success, or an error.
>>> + */
>>> +static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
>>> +{
>>> + int idx;
>>> +
>>> + guard(mutex)(&list_mutex);
>>> + for (idx = 0; idx < priv->list->count; idx++) {
>>> + union acpi_object *ret_obj __free(kfree) = NULL;
>>> +
>>> + ret_obj = wmidev_block_query(priv->wdev, idx);
>>> + if (!ret_obj)
>>> + return -ENODEV;
>>> +
>>> + if (ret_obj->type != ACPI_TYPE_BUFFER ||
>>> + ret_obj->buffer.length < sizeof(priv->list->data[idx]))
>>> + continue;
>>> +
>>> + memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
>>> + ret_obj->buffer.length);
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +/**
>>> + * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
>>> + * @priv: lenovo-wmi-capdata01 driver data.
>>> + *
>>> + * Allocate a cd01_list struct large enough to contain data from all WMI data
>>> + * blocks provided by the interface.
>>> + *
>>> + * Return: 0 on success, or an error.
>>> + */
>>> +static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
>>> +{
>>> + struct cd01_list *list;
>>> + size_t list_size;
>>> + int count;
>>> +
>>> + count = wmidev_instance_count(priv->wdev);
>>> + list_size = struct_size(list, data, count);
>>> +
>>> + guard(mutex)(&list_mutex);
>>> + list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
>>> + if (!list)
>>> + return -ENOMEM;
>>> +
>>> + list->count = count;
>>> + priv->list = list;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +/**
>>> + * lwmi_cd01_setup() - Cache all WMI data block information
>>> + * @priv: lenovo-wmi-capdata01 driver data.
>>> + *
>>> + * Allocate a cd01_list struct large enough to contain data from all WMI data
>>> + * blocks provided by the interface. Then loop through each data block and
>>> + * cache the data.
>>> + *
>>> + * Return: 0 on success, or an error code.
>>> + */
>>> +static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv)
>>> +{
>>> + int ret;
>>> +
>>> + ret = lwmi_cd01_alloc(priv);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return lwmi_cd01_cache(priv);
>>> +}
>>> +
>>> +/**
>>> + * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier.
>>> + * block call chain.
>>> + * @nb: The notifier_block registered to lenovo-wmi-events driver.
>>> + * @action: Unused.
>>> + * @data: The ACPI event.
>>> + *
>>> + * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
>>> + * of a change.
>>> + *
>>> + * Return: notifier_block status.
>>> + */
>>> +static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
>>> + void *data)
>>> +{
>>> + struct acpi_bus_event *event = (struct acpi_bus_event *)data;
>> Unnecessary cast, please drop.
>>
>>> + struct lwmi_cd01_priv *priv;
>>> + int ret;
>>> +
>>> +
>>> + priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
>>> +
>>> + switch (event->type) {
>>> + case ACPI_AC_NOTIFY_STATUS:
>> You should also check the the "device_class" of the ACPI event (search for ACPI_AC_CLASS)
>> because "type" is not guaranteed to be unique between different events.
>>
> Good to know, thanks.
>
>
> Ack all below.
>
> Thanks,
> Derek
>
>>> + ret = lwmi_cd01_cache(priv);
>>> + if (ret)
>>> + return NOTIFY_BAD;
>>> +
>>> + return NOTIFY_OK;
>>> + default:
>>> + return NOTIFY_DONE;
>>> + }
>>> +}
>>> +
>>> +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 = lwmi_cd01_setup(priv);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
>>> + register_acpi_notifier(&priv->acpi_nb);
>>> +
>>> + return component_add(&wdev->dev, &lwmi_cd01_component_ops);
>> You need to unregister the ACPI notifier when component_add() fails.
>>
>>> +}
>>> +
>>> +static void lwmi_cd01_remove(struct wmi_device *wdev)
>>> +{
>>> + component_del(&wdev->dev, &lwmi_cd01_component_ops);
>> Same as above. You could use devm_add_action_or_reset() to fix both cases.
>>
>>> +}
>>> +
>>> +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,
>>> +};
>>> +
>>> +/**
>>> + * lwmi_cd01_match() - Match rule for the master driver.
>>> + * @dev: Pointer to the capability data 01 parent device.
>>> + * @data: Unused void pointer for passing match criteria.
>>> + *
>>> + * Return: int.
>>> + */
>>> +int lwmi_cd01_match(struct device *dev, void *data)
>>> +{
>>> + return dev->driver == &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..ed4f3d86464d
>>> --- /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/types.h>
>>> +
>>> +struct device;
>>> +
>>> +struct capdata01 {
>>> + u32 id;
>>> + u32 supported;
>>> + u32 default_value;
>>> + u32 step;
>>> + u32 min_value;
>>> + u32 max_value;
>>> +};
>>> +
>>> +struct cd01_list {
>>> + u8 count;
>>> + struct capdata01 data[];
>>> +};
>> Since client driver are not directly accessing this struct anyway please keep its definition private
>> and only provide an incomplete definition in this header:
>>
>> struct cd01_list *;
>>
>> Thanks,
>> Armin Wolf
>>
>>> +
>>> +struct capdata01 *lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id);
>>> +int lwmi_cd01_match(struct device *dev, void *data);
>>> +
>>> +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
> - Derek
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v6 5/6] platform/x86: Add Lenovo WMI Gamezone Driver
2025-04-28 1:18 [PATCH v6 0/6] platform/x86: Add Lenovo WMI Gaming Series Drivers Derek J. Clark
` (3 preceding siblings ...)
2025-04-28 1:18 ` [PATCH v6 4/6] platform/x86: Add Lenovo WMI Capability Data 01 Driver Derek J. Clark
@ 2025-04-28 1:18 ` Derek J. Clark
2025-04-29 4:39 ` ALOK TIWARI
2025-05-02 2:26 ` Armin Wolf
2025-04-28 1:18 ` [PATCH v6 6/6] platform/x86: Add Lenovo WMI Other Mode Driver Derek J. Clark
5 siblings, 2 replies; 23+ messages in thread
From: Derek J. Clark @ 2025-04-28 1:18 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>
---
v6:
- Switch to guard/scoped_guard instead of spin_lock/spin_unlock.
- Fix typos and rewordings from v5 review.
v5:
- Move to patch 5 of the series and omit the portions of the driver that
rely on Other Mode.
- Rename most defines to clearly indicate they are defined in this
driver.
- Misc fixes from v4 review.
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 | 1 +
drivers/platform/x86/Kconfig | 14 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
drivers/platform/x86/lenovo-wmi-gamezone.h | 20 ++
5 files changed, 410 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 1b22e41cc730..511d7547fdd9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13167,6 +13167,7 @@ F: Documentation/wmi/devices/lenovo-wmi-other.rst
F: drivers/platform/x86/lenovo-wmi-capdata01.*
F: drivers/platform/x86/lenovo-wmi-events.*
+F: drivers/platform/x86/lenovo-wmi-gamezone.*
F: drivers/platform/x86/lenovo-wmi-helpers.*
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..aaa1a69c10ca 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -467,6 +467,20 @@ config LENOVO_WMI_HELPERS
tristate
depends on ACPI_WMI
+config LENOVO_WMI_GAMEZONE
+ tristate "Lenovo GameZone WMI Driver"
+ depends on ACPI_WMI
+ depends on DMI
+ 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 7a35c77221b7..60058b547de2 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
# Intel
diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
new file mode 100644
index 000000000000..713f7dbbabe2
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
@@ -0,0 +1,374 @@
+// 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/cleanup.h"
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/export.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/spinlock.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"
+
+#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
+
+#define LWMI_GZ_METHOD_ID_SMARTFAN_SUP 43
+#define LWMI_GZ_METHOD_ID_SMARTFAN_SET 44
+#define LWMI_GZ_METHOD_ID_SMARTFAN_GET 45
+
+static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
+static DEFINE_SPINLOCK(gz_mode_lock); /* lwmi_gz_priv.current_mode RW lock */
+
+struct lwmi_gz_priv {
+ enum thermal_mode current_mode;
+ struct notifier_block event_nb;
+ struct notifier_block mode_nb;
+ struct wmi_device *wdev;
+ int extreme_supported;
+ struct device *ppdev;
+};
+
+struct quirk_entry {
+ int extreme_supported;
+};
+
+static struct quirk_entry quirk_no_extreme_bug = {
+ .extreme_supported = false,
+};
+
+/**
+ * lwmi_gz_event_call() - Call method for lenovo-wmi-events driver notifier.
+ * block call chain.
+ * @nb: The notifier_block registered to lenovo-wmi-events driver.
+ * @cmd: The event type.
+ * @data: The data to be updated by the event.
+ *
+ * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
+ * of a change.
+ *
+ * Return: notifier_block status.
+ */
+static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
+ void *data)
+{
+ enum thermal_mode *mode = data;
+ struct lwmi_gz_priv *priv;
+
+ priv = container_of(nb, struct lwmi_gz_priv, event_nb);
+
+ switch (cmd) {
+ case LWMI_EVENT_THERMAL_MODE:
+ scoped_guard(spinlock, &gz_mode_lock) {
+ priv->current_mode = *mode;
+ }
+ platform_profile_notify(priv->ppdev);
+ return NOTIFY_STOP;
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+/**
+ * lwmi_gz_thermal_mode_supported() - Get the version of the WMI
+ * interface to determine the support level.
+ * @wdev: The Gamezone WMI device.
+ * @supported: Pointer to return the support level with.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_thermal_mode_supported(struct wmi_device *wdev,
+ int *supported)
+{
+ return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_SUP,
+ 0, 0, supported);
+}
+
+/**
+ * lwmi_gz_thermal_mode_get() - Get the current thermal mode.
+ * @wdev: The Gamezone interface WMI device.
+ * @mode: Pointer to return the thermal mode with.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
+ enum thermal_mode *mode)
+{
+ return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_GET,
+ 0, 0, mode);
+}
+
+/**
+ * lwmi_gz_profile_get_get() - Get the current platform profile.
+ * @dev: the Gamezone interface parent device.
+ * @profile: Pointer to provide the current platform profile with.
+ *
+ * Call lwmi_gz_thermal_mode_get and convert the thermal mode into a platform
+ * profile based on the support level of the interface.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_profile_get(struct device *dev,
+ enum platform_profile_option *profile)
+{
+ struct lwmi_gz_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 LWMI_GZ_THERMAL_MODE_QUIET:
+ *profile = PLATFORM_PROFILE_LOW_POWER;
+ break;
+ case LWMI_GZ_THERMAL_MODE_BALANCED:
+ *profile = PLATFORM_PROFILE_BALANCED;
+ break;
+ case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
+ if (priv->extreme_supported) {
+ *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+ break;
+ }
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ case LWMI_GZ_THERMAL_MODE_EXTREME:
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ case LWMI_GZ_THERMAL_MODE_CUSTOM:
+ *profile = PLATFORM_PROFILE_CUSTOM;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(spinlock)(&gz_mode_lock);
+ priv->current_mode = mode;
+
+ return 0;
+}
+
+/**
+ * lwmi_gz_profile_get_get() - Set the current platform profile.
+ * @dev: The Gamezone interface parent device.
+ * @profile: Pointer to the desired platform profile.
+ *
+ * Convert the given platform profile into a thermal mode based on the support
+ * level of the interface, then call the WMI method to set the thermal mode.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_profile_set(struct device *dev,
+ enum platform_profile_option profile)
+{
+ struct lwmi_gz_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 = LWMI_GZ_THERMAL_MODE_QUIET;
+ break;
+ case PLATFORM_PROFILE_BALANCED:
+ mode = LWMI_GZ_THERMAL_MODE_BALANCED;
+ break;
+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
+ mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE;
+ break;
+ case PLATFORM_PROFILE_PERFORMANCE:
+ if (priv->extreme_supported) {
+ mode = LWMI_GZ_THERMAL_MODE_EXTREME;
+ break;
+ }
+ mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE;
+ break;
+ case PLATFORM_PROFILE_CUSTOM:
+ mode = LWMI_GZ_THERMAL_MODE_CUSTOM;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ args.arg0 = mode;
+
+ ret = lwmi_dev_evaluate_int(priv->wdev, 0x0,
+ LWMI_GZ_METHOD_ID_SMARTFAN_SET,
+ (u8 *)&args, sizeof(args), NULL);
+ if (ret)
+ return ret;
+
+ guard(spinlock)(&gz_mode_lock);
+ 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 8APU1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 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,
+ },
+ {},
+
+};
+
+/**
+ * lwmi_gz_extreme_supported() - Evaluate if a device supports extreme thermal mode.
+ * @profile_support_ver: Version of the WMI interface.
+ *
+ * Determine if the extreme thermal mode is supported by the hardware.
+ * Anything version 5 or lower does not. For devices wuth a version 6 or
+ * greater do a DMI check, as some devices report a version that supports
+ * extreme mode but have an incomplete entry in the BIOS. To ensure this
+ * cannot be set, quirk them to prevent assignment.
+ *
+ * Return: int.
+ */
+static int lwmi_gz_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;
+}
+
+/**
+ * lwmi_gz_platform_profile_probe - Enable and set up the platform profile
+ * device.
+ * @drvdata: Driver data for the interface.
+ * @choices: Container for enabled platform profiles.
+ *
+ * Determine if thermal mode is supported, and if so to what feature level.
+ * Then enable all supported platform profiles.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_platform_profile_probe(void *drvdata, unsigned long *choices)
+{
+ struct lwmi_gz_priv *priv = drvdata;
+ int profile_support_ver;
+ int ret;
+
+ ret = lwmi_gz_thermal_mode_supported(priv->wdev, &profile_support_ver);
+ if (ret)
+ return ret;
+
+ if (profile_support_ver < 1)
+ return -ENODEV;
+
+ priv->extreme_supported = lwmi_gz_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_gz_platform_profile_probe,
+ .profile_get = lwmi_gz_profile_get,
+ .profile_set = lwmi_gz_profile_set,
+};
+
+static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
+{
+ struct lwmi_gz_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);
+
+ 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;
+
+ priv->event_nb.notifier_call = lwmi_gz_event_call;
+ ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
+ 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_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..75ef16bf0798
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-gamezone.h
@@ -0,0 +1,20 @@
+/* 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 gamezone_events_type {
+ LWMI_GZ_GET_THERMAL_MODE = 1,
+};
+
+enum thermal_mode {
+ LWMI_GZ_THERMAL_MODE_QUIET = 0x01,
+ LWMI_GZ_THERMAL_MODE_BALANCED = 0x02,
+ LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03,
+ LWMI_GZ_THERMAL_MODE_EXTREME = 0xE0, /* Ver 6+ */
+ LWMI_GZ_THERMAL_MODE_CUSTOM = 0xFF,
+};
+
+#endif /* !_LENOVO_WMI_GAMEZONE_H_ */
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* Re: [PATCH v6 5/6] platform/x86: Add Lenovo WMI Gamezone Driver
2025-04-28 1:18 ` [PATCH v6 5/6] platform/x86: Add Lenovo WMI Gamezone Driver Derek J. Clark
@ 2025-04-29 4:39 ` ALOK TIWARI
2025-04-30 3:26 ` Derek J. Clark
2025-05-02 2:26 ` Armin Wolf
1 sibling, 1 reply; 23+ messages in thread
From: ALOK TIWARI @ 2025-04-29 4:39 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 28-04-2025 06:48, Derek J. Clark wrote:
> + * Determine if the extreme thermal mode is supported by the hardware.
> + * Anything version 5 or lower does not. For devices wuth a version 6 or
typo wuth
> + * greater do a DMI check, as some devices report a version that supports
> + * extreme mode but have an incomplete entry in the BIOS. To ensure this
> + * cannot be set, quirk them to prevent assignment.
> + *
> + * Return: int.
The function returns int.
But logically it's returning boolean false, true
> + */
> +static int lwmi_gz_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;
> +}
Thank,
Alok
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v6 5/6] platform/x86: Add Lenovo WMI Gamezone Driver
2025-04-29 4:39 ` ALOK TIWARI
@ 2025-04-30 3:26 ` Derek J. Clark
2025-04-30 7:58 ` Ilpo Järvinen
0 siblings, 1 reply; 23+ messages in thread
From: Derek J. Clark @ 2025-04-30 3:26 UTC (permalink / raw)
To: ALOK TIWARI, 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 April 28, 2025 9:39:55 PM PDT, ALOK TIWARI <alok.a.tiwari@oracle.com> wrote:
>
>
>On 28-04-2025 06:48, Derek J. Clark wrote:
>> + * Determine if the extreme thermal mode is supported by the hardware.
>> + * Anything version 5 or lower does not. For devices wuth a version 6 or
>
>typo wuth
>
>> + * greater do a DMI check, as some devices report a version that supports
>> + * extreme mode but have an incomplete entry in the BIOS. To ensure this
>> + * cannot be set, quirk them to prevent assignment.
>> + *
>> + * Return: int.
>
>The function returns int.
>But logically it's returning boolean false, true
I may have overdone it by removing all bools after the v5 review as I interpreted Ilpo's comment to mean I shouldn't return any bool c types. I'll wait for them to weigh in before changing this back.
>> + */
>> +static int lwmi_gz_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;
>> +}
>
>Thank,
>Alok
Thanks,
- Derek
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v6 5/6] platform/x86: Add Lenovo WMI Gamezone Driver
2025-04-30 3:26 ` Derek J. Clark
@ 2025-04-30 7:58 ` Ilpo Järvinen
2025-04-30 10:10 ` Derek J. Clark
0 siblings, 1 reply; 23+ messages in thread
From: Ilpo Järvinen @ 2025-04-30 7:58 UTC (permalink / raw)
To: Derek J. Clark
Cc: ALOK TIWARI, 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 Tue, 29 Apr 2025, Derek J. Clark wrote:
>
>
> On April 28, 2025 9:39:55 PM PDT, ALOK TIWARI <alok.a.tiwari@oracle.com> wrote:
> >
> >
> >On 28-04-2025 06:48, Derek J. Clark wrote:
> >> + * Determine if the extreme thermal mode is supported by the hardware.
> >> + * Anything version 5 or lower does not. For devices wuth a version 6 or
> >
> >typo wuth
> >
> >> + * greater do a DMI check, as some devices report a version that supports
> >> + * extreme mode but have an incomplete entry in the BIOS. To ensure this
> >> + * cannot be set, quirk them to prevent assignment.
> >> + *
> >> + * Return: int.
> >
> >The function returns int.
> >But logically it's returning boolean false, true
>
> I may have overdone it by removing all bools after the v5 review as I
> interpreted Ilpo's comment to mean I shouldn't return any bool c types.
> I'll wait for them to weigh in before changing this back.
Hi Derek,
That is certainly a misinterpretation.
It's perfectly fine to return bool from a function. If there's no good
reason e.g. because of some API that requires int return, booleans should
be returned as bool.
I was trying to say your kerneldoc said "Return: bool" for a function that
returns int. Both "bool" and "int" are C types so there was a contradition
in that, which is what I tried to point out. Please write "boolean" if you
refer to a boolean which is not "bool" typed (but consider what was said
above and if the type too can be changed to bool in that case).
--
i.
> >> + */
> >> +static int lwmi_gz_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;
> >> +}
> >
> >Thank,
> >Alok
>
> Thanks,
> - Derek
>
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v6 5/6] platform/x86: Add Lenovo WMI Gamezone Driver
2025-04-30 7:58 ` Ilpo Järvinen
@ 2025-04-30 10:10 ` Derek J. Clark
0 siblings, 0 replies; 23+ messages in thread
From: Derek J. Clark @ 2025-04-30 10:10 UTC (permalink / raw)
To: Ilpo Järvinen
Cc: ALOK TIWARI, 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 April 30, 2025 12:58:35 AM PDT, "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com> wrote:
>On Tue, 29 Apr 2025, Derek J. Clark wrote:
>
>>
>>
>> On April 28, 2025 9:39:55 PM PDT, ALOK TIWARI <alok.a.tiwari@oracle.com> wrote:
>> >
>> >
>> >On 28-04-2025 06:48, Derek J. Clark wrote:
>> >> + * Determine if the extreme thermal mode is supported by the hardware.
>> >> + * Anything version 5 or lower does not. For devices wuth a version 6 or
>> >
>> >typo wuth
>> >
>> >> + * greater do a DMI check, as some devices report a version that supports
>> >> + * extreme mode but have an incomplete entry in the BIOS. To ensure this
>> >> + * cannot be set, quirk them to prevent assignment.
>> >> + *
>> >> + * Return: int.
>> >
>> >The function returns int.
>> >But logically it's returning boolean false, true
>>
>> I may have overdone it by removing all bools after the v5 review as I
>> interpreted Ilpo's comment to mean I shouldn't return any bool c types.
>> I'll wait for them to weigh in before changing this back.
>
>Hi Derek,
>
>That is certainly a misinterpretation.
>
>It's perfectly fine to return bool from a function. If there's no good
>reason e.g. because of some API that requires int return, booleans should
>be returned as bool.
>
>I was trying to say your kerneldoc said "Return: bool" for a function that
>returns int. Both "bool" and "int" are C types so there was a contradition
>in that, which is what I tried to point out. Please write "boolean" if you
>refer to a boolean which is not "bool" typed (but consider what was said
>above and if the type too can be changed to bool in that case).
>
Okay, that makes sense. Thanks for the clarification.
- Derek
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 5/6] platform/x86: Add Lenovo WMI Gamezone Driver
2025-04-28 1:18 ` [PATCH v6 5/6] platform/x86: Add Lenovo WMI Gamezone Driver Derek J. Clark
2025-04-29 4:39 ` ALOK TIWARI
@ 2025-05-02 2:26 ` Armin Wolf
1 sibling, 0 replies; 23+ messages in thread
From: Armin Wolf @ 2025-05-02 2: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 28.04.25 um 03:18 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>
> ---
> v6:
> - Switch to guard/scoped_guard instead of spin_lock/spin_unlock.
> - Fix typos and rewordings from v5 review.
> v5:
> - Move to patch 5 of the series and omit the portions of the driver that
> rely on Other Mode.
> - Rename most defines to clearly indicate they are defined in this
> driver.
> - Misc fixes from v4 review.
> 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 | 1 +
> drivers/platform/x86/Kconfig | 14 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-gamezone.c | 374 +++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi-gamezone.h | 20 ++
> 5 files changed, 410 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 1b22e41cc730..511d7547fdd9 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13167,6 +13167,7 @@ F: Documentation/wmi/devices/lenovo-wmi-other.rst
> F: drivers/platform/x86/lenovo-wmi-capdata01.*
> F: drivers/platform/x86/lenovo-wmi-events.*
> +F: drivers/platform/x86/lenovo-wmi-gamezone.*
> F: drivers/platform/x86/lenovo-wmi-helpers.*
>
> 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..aaa1a69c10ca 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -467,6 +467,20 @@ config LENOVO_WMI_HELPERS
> tristate
> depends on ACPI_WMI
>
> +config LENOVO_WMI_GAMEZONE
> + tristate "Lenovo GameZone WMI Driver"
> + depends on ACPI_WMI
> + depends on DMI
> + 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 7a35c77221b7..60058b547de2 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
>
> # Intel
> diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> new file mode 100644
> index 000000000000..713f7dbbabe2
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> @@ -0,0 +1,374 @@
> +// 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/cleanup.h"
> +#include <linux/acpi.h>
> +#include <linux/dmi.h>
> +#include <linux/export.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/spinlock.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"
> +
> +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
> +
> +#define LWMI_GZ_METHOD_ID_SMARTFAN_SUP 43
> +#define LWMI_GZ_METHOD_ID_SMARTFAN_SET 44
> +#define LWMI_GZ_METHOD_ID_SMARTFAN_GET 45
> +
> +static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
> +static DEFINE_SPINLOCK(gz_mode_lock); /* lwmi_gz_priv.current_mode RW lock */
Hi,
please move this mutex into the driver struct.
> +
> +struct lwmi_gz_priv {
> + enum thermal_mode current_mode;
> + struct notifier_block event_nb;
> + struct notifier_block mode_nb;
> + struct wmi_device *wdev;
> + int extreme_supported;
> + struct device *ppdev;
> +};
> +
> +struct quirk_entry {
> + int extreme_supported;
> +};
> +
> +static struct quirk_entry quirk_no_extreme_bug = {
> + .extreme_supported = false,
> +};
> +
> +/**
> + * lwmi_gz_event_call() - Call method for lenovo-wmi-events driver notifier.
> + * block call chain.
> + * @nb: The notifier_block registered to lenovo-wmi-events driver.
> + * @cmd: The event type.
> + * @data: The data to be updated by the event.
> + *
> + * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
> + * of a change.
> + *
> + * Return: notifier_block status.
> + */
> +static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
> + void *data)
> +{
> + enum thermal_mode *mode = data;
> + struct lwmi_gz_priv *priv;
> +
> + priv = container_of(nb, struct lwmi_gz_priv, event_nb);
> +
> + switch (cmd) {
> + case LWMI_EVENT_THERMAL_MODE:
> + scoped_guard(spinlock, &gz_mode_lock) {
> + priv->current_mode = *mode;
> + }
> + platform_profile_notify(priv->ppdev);
> + return NOTIFY_STOP;
Hi,
since this is the event call chain, i suggest you instead return NOTIFY_OK so other
event consumers are still being called.
> + default:
> + return NOTIFY_DONE;
> + }
> +}
> +
> +/**
> + * lwmi_gz_thermal_mode_supported() - Get the version of the WMI
> + * interface to determine the support level.
> + * @wdev: The Gamezone WMI device.
> + * @supported: Pointer to return the support level with.
> + *
> + * Return: 0 on success, or an error code.
> + */
> +static int lwmi_gz_thermal_mode_supported(struct wmi_device *wdev,
> + int *supported)
> +{
> + return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_SUP,
> + 0, 0, supported);
> +}
> +
> +/**
> + * lwmi_gz_thermal_mode_get() - Get the current thermal mode.
> + * @wdev: The Gamezone interface WMI device.
> + * @mode: Pointer to return the thermal mode with.
> + *
> + * Return: 0 on success, or an error code.
> + */
> +static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
> + enum thermal_mode *mode)
> +{
> + return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_GET,
> + 0, 0, mode);
> +}
> +
> +/**
> + * lwmi_gz_profile_get_get() - Get the current platform profile.
> + * @dev: the Gamezone interface parent device.
> + * @profile: Pointer to provide the current platform profile with.
> + *
> + * Call lwmi_gz_thermal_mode_get and convert the thermal mode into a platform
> + * profile based on the support level of the interface.
> + *
> + * Return: 0 on success, or an error code.
> + */
> +static int lwmi_gz_profile_get(struct device *dev,
> + enum platform_profile_option *profile)
> +{
> + struct lwmi_gz_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 LWMI_GZ_THERMAL_MODE_QUIET:
> + *profile = PLATFORM_PROFILE_LOW_POWER;
> + break;
> + case LWMI_GZ_THERMAL_MODE_BALANCED:
> + *profile = PLATFORM_PROFILE_BALANCED;
> + break;
> + case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
> + if (priv->extreme_supported) {
> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> + break;
> + }
> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> + break;
> + case LWMI_GZ_THERMAL_MODE_EXTREME:
> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> + break;
> + case LWMI_GZ_THERMAL_MODE_CUSTOM:
> + *profile = PLATFORM_PROFILE_CUSTOM;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + guard(spinlock)(&gz_mode_lock);
> + priv->current_mode = mode;
> +
> + return 0;
> +}
> +
> +/**
> + * lwmi_gz_profile_get_get() - Set the current platform profile.
> + * @dev: The Gamezone interface parent device.
> + * @profile: Pointer to the desired platform profile.
> + *
> + * Convert the given platform profile into a thermal mode based on the support
> + * level of the interface, then call the WMI method to set the thermal mode.
> + *
> + * Return: 0 on success, or an error code.
> + */
> +static int lwmi_gz_profile_set(struct device *dev,
> + enum platform_profile_option profile)
> +{
> + struct lwmi_gz_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 = LWMI_GZ_THERMAL_MODE_QUIET;
> + break;
> + case PLATFORM_PROFILE_BALANCED:
> + mode = LWMI_GZ_THERMAL_MODE_BALANCED;
> + break;
> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> + mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE;
> + break;
> + case PLATFORM_PROFILE_PERFORMANCE:
> + if (priv->extreme_supported) {
> + mode = LWMI_GZ_THERMAL_MODE_EXTREME;
> + break;
> + }
> + mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE;
> + break;
> + case PLATFORM_PROFILE_CUSTOM:
> + mode = LWMI_GZ_THERMAL_MODE_CUSTOM;
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + args.arg0 = mode;
> +
> + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0,
> + LWMI_GZ_METHOD_ID_SMARTFAN_SET,
> + (u8 *)&args, sizeof(args), NULL);
> + if (ret)
> + return ret;
> +
> + guard(spinlock)(&gz_mode_lock);
> + 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 8APU1",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 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,
> + },
> + {},
> +
> +};
> +
> +/**
> + * lwmi_gz_extreme_supported() - Evaluate if a device supports extreme thermal mode.
> + * @profile_support_ver: Version of the WMI interface.
> + *
> + * Determine if the extreme thermal mode is supported by the hardware.
> + * Anything version 5 or lower does not. For devices wuth a version 6 or
> + * greater do a DMI check, as some devices report a version that supports
> + * extreme mode but have an incomplete entry in the BIOS. To ensure this
> + * cannot be set, quirk them to prevent assignment.
> + *
> + * Return: int.
> + */
> +static int lwmi_gz_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;
> +}
> +
> +/**
> + * lwmi_gz_platform_profile_probe - Enable and set up the platform profile
> + * device.
> + * @drvdata: Driver data for the interface.
> + * @choices: Container for enabled platform profiles.
> + *
> + * Determine if thermal mode is supported, and if so to what feature level.
> + * Then enable all supported platform profiles.
> + *
> + * Return: 0 on success, or an error code.
> + */
> +static int lwmi_gz_platform_profile_probe(void *drvdata, unsigned long *choices)
> +{
> + struct lwmi_gz_priv *priv = drvdata;
> + int profile_support_ver;
> + int ret;
> +
> + ret = lwmi_gz_thermal_mode_supported(priv->wdev, &profile_support_ver);
> + if (ret)
> + return ret;
> +
> + if (profile_support_ver < 1)
> + return -ENODEV;
> +
> + priv->extreme_supported = lwmi_gz_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_gz_platform_profile_probe,
> + .profile_get = lwmi_gz_profile_get,
> + .profile_set = lwmi_gz_profile_set,
> +};
> +
> +static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
> +{
> + struct lwmi_gz_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);
> +
> + priv->ppdev = platform_profile_register(&wdev->dev,
> + "lenovo-wmi-gamezone", priv,
> + &lwmi_gz_platform_profile_ops);
Please use devm_platform_profile_register() instead. Otherwise you will leak it in case of an error
in the remaining part of the probe function.
> +
> + if (IS_ERR(priv->ppdev))
> + return -ENODEV;
> +
> + ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode);
> + if (ret)
> + return ret;
> +
> + priv->event_nb.notifier_call = lwmi_gz_event_call;
> + ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
> + if (ret)
> + return ret;
Please return the result of devm_lwmi_events_register_notifier() directly.
Thanks,
Armin Wolf
> +
> + 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_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..75ef16bf0798
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.h
> @@ -0,0 +1,20 @@
> +/* 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 gamezone_events_type {
> + LWMI_GZ_GET_THERMAL_MODE = 1,
> +};
> +
> +enum thermal_mode {
> + LWMI_GZ_THERMAL_MODE_QUIET = 0x01,
> + LWMI_GZ_THERMAL_MODE_BALANCED = 0x02,
> + LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03,
> + LWMI_GZ_THERMAL_MODE_EXTREME = 0xE0, /* Ver 6+ */
> + LWMI_GZ_THERMAL_MODE_CUSTOM = 0xFF,
> +};
> +
> +#endif /* !_LENOVO_WMI_GAMEZONE_H_ */
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v6 6/6] platform/x86: Add Lenovo WMI Other Mode Driver
2025-04-28 1:18 [PATCH v6 0/6] platform/x86: Add Lenovo WMI Gaming Series Drivers Derek J. Clark
` (4 preceding siblings ...)
2025-04-28 1:18 ` [PATCH v6 5/6] platform/x86: Add Lenovo WMI Gamezone Driver Derek J. Clark
@ 2025-04-28 1:18 ` Derek J. Clark
2025-04-29 20:13 ` ALOK TIWARI
2025-05-02 2:32 ` Armin Wolf
5 siblings, 2 replies; 23+ messages in thread
From: Derek J. Clark @ 2025-04-28 1:18 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>
---
v6:
- Pass locally stored pointer to cd01_list back to lenovo-wmi-capdata01
instead of itterating over the list locally. Due to ACPI event handing
the list is now mutexed.
- Fix typos and rewordings from v5 review.
v5:
- Switch from locally storing capability data list to storing a pointer
from the capability data interface.
- Add portion of Gamezone driver that relies on the exported functions
of this driver.
- Properly initialize IDA and use it for naming the firmware-attributes
path.
- Rename most defines to clearly indicate they are from this driver.
- Misc fixes from v4 review.
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 | 3 +-
drivers/platform/x86/Kconfig | 15 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-gamezone.c | 35 ++
drivers/platform/x86/lenovo-wmi-other.c | 655 +++++++++++++++++++++
drivers/platform/x86/lenovo-wmi-other.h | 16 +
6 files changed, 724 insertions(+), 1 deletion(-)
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 511d7547fdd9..764eadee48ac 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13166,8 +13166,9 @@ F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
F: Documentation/wmi/devices/lenovo-wmi-other.rst
F: drivers/platform/x86/lenovo-wmi-capdata01.*
F: drivers/platform/x86/lenovo-wmi-events.*
F: drivers/platform/x86/lenovo-wmi-gamezone.*
F: drivers/platform/x86/lenovo-wmi-helpers.*
+F: drivers/platform/x86/lenovo-wmi-other.*
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 aaa1a69c10ca..be5ab24960b5 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -485,6 +485,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 60058b547de2..f3e64926a96b 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -73,6 +73,7 @@ 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
# Intel
obj-y += intel/
diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
index 713f7dbbabe2..5f092790956b 100644
--- a/drivers/platform/x86/lenovo-wmi-gamezone.c
+++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
@@ -23,6 +23,7 @@
#include "lenovo-wmi-events.h"
#include "lenovo-wmi-gamezone.h"
#include "lenovo-wmi-helpers.h"
+#include "lenovo-wmi-other.h"
#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
@@ -50,6 +51,34 @@ static struct quirk_entry quirk_no_extreme_bug = {
.extreme_supported = false,
};
+/**
+ * lwmi_gz_mode_call() - Call method for lenovo-wmi-other driver notifier.
+ *
+ * @nb: The notifier_block registered to lenovo-wmi-other driver.
+ * @cmd: The event type.
+ * @data: Thermal mode enum pointer pointer for returning the thermal mode.
+ *
+ * For LWMI_GZ_GET_THERMAL_MODE, retrieve the current thermal mode.
+ *
+ * Return: Notifier_block status.
+ */
+static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
+ void *data)
+{
+ enum thermal_mode **mode = data;
+ struct lwmi_gz_priv *priv;
+
+ priv = container_of(nb, struct lwmi_gz_priv, mode_nb);
+
+ switch (cmd) {
+ case LWMI_GZ_GET_THERMAL_MODE:
+ **mode = priv->current_mode;
+ return NOTIFY_STOP;
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
/**
* lwmi_gz_event_call() - Call method for lenovo-wmi-events driver notifier.
* block call chain.
@@ -346,6 +375,11 @@ static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
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;
+
return 0;
}
@@ -368,6 +402,7 @@ 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");
diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
new file mode 100644
index 000000000000..18a48f9103d6
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-other.c
@@ -0,0 +1,655 @@
+// 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 Capability Data struct that
+ * allows the driver to probe details about the attribute such as if it is
+ * supported by the hardware, the default_value, max_value, min_value, and step
+ * increment.
+ *
+ * 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>
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/gfp_types.h>
+#include <linux/idr.h>
+#include <linux/kobject.h>
+#include <linux/module.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"
+
+#define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
+
+#define LWMI_DEVICE_ID_CPU 0x01
+
+#define LWMI_FEATURE_ID_CPU_SPPT 0x01
+#define LWMI_FEATURE_ID_CPU_SPL 0x02
+#define LWMI_FEATURE_ID_CPU_FPPT 0x03
+
+#define LWMI_TYPE_ID_NONE 0x00
+
+#define LWMI_FEATURE_VALUE_GET 17
+#define LWMI_FEATURE_VALUE_SET 18
+
+#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
+#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
+#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
+#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
+
+#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
+
+static BLOCKING_NOTIFIER_HEAD(om_chain_head);
+static DEFINE_IDA(lwmi_om_ida);
+
+enum attribute_property {
+ DEFAULT_VAL,
+ MAX_VAL,
+ MIN_VAL,
+ STEP_VAL,
+ SUPPORTED,
+};
+
+struct lwmi_om_priv {
+ struct component_master_ops *ops;
+ /* *cd01_list is only valid after master bind and while capdata01 exists */
+ struct cd01_list *cd01_list;
+ struct device *fw_attr_dev;
+ struct kset *fw_attr_kset;
+ struct notifier_block nb;
+ struct wmi_device *wdev;
+ int ida_id;
+};
+
+struct tunable_attr_01 {
+ struct capdata01 *capdata;
+ struct device *dev;
+ u32 feature_id;
+ u32 device_id;
+ u32 type_id;
+};
+
+struct tunable_attr_01 ppt_pl1_spl = { .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_SPL,
+ .type_id = LWMI_TYPE_ID_NONE };
+struct tunable_attr_01 ppt_pl2_sppt = { .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_SPPT,
+ .type_id = LWMI_TYPE_ID_NONE };
+struct tunable_attr_01 ppt_pl3_fppt = { .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_FPPT,
+ .type_id = LWMI_TYPE_ID_NONE };
+
+struct capdata01_attr_group {
+ const struct attribute_group *attr_group;
+ struct tunable_attr_01 *tunable_attr;
+};
+
+/**
+ * lwmi_om_register_notifier() - Add a notifier to the blocking notifier chain
+ * @nb: The notifier_block struct to register
+ *
+ * Call blocking_notifier_chain_register to register the notifier block to the
+ * lenovo-wmi-other driver notifer chain.
+ *
+ * Return: 0 on success, %-EEXIST on error.
+ */
+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");
+
+/**
+ * lwmi_om_unregister_notifier() - Remove a notifier from the blocking notifier
+ * chain.
+ * @nb: The notifier_block struct to register
+ *
+ * Call blocking_notifier_chain_unregister to unregister the notifier block from the
+ * lenovo-wmi-other driver notifer chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+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");
+
+/**
+ * devm_lwmi_om_unregister_notifier() - Remove a notifier from the blocking
+ * notifier chain.
+ * @data: Void pointer to the notifier_block struct to register.
+ *
+ * Call lwmi_om_unregister_notifier to unregister the notifier block from the
+ * lenovo-wmi-other driver notifer chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+static void devm_lwmi_om_unregister_notifier(void *data)
+{
+ struct notifier_block *nb = data;
+
+ lwmi_om_unregister_notifier(nb);
+}
+
+/**
+ * devm_lwmi_om_register_notifier() - Add a notifier to the blocking notifier
+ * chain.
+ * @dev: The parent device of the notifier_block struct.
+ * @nb: The notifier_block struct to register
+ *
+ * Call lwmi_om_register_notifier to register the notifier block to the
+ * lenovo-wmi-other driver notifer chain. Then add devm_lwmi_om_unregister_notifier
+ * as a device managed ation to automatically unregister the notifier block
+ * upon parent device removal.
+ *
+ * Return: 0 on success, or on error.
+ */
+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");
+
+/**
+ * lwmi_om_notifier_call() - Call functions for the notifier call chain.
+ * @mode: Pointer to a thermal mode enum to retrieve the data from.
+ *
+ * Call blocking_notifier_call_chain to retrieve the thermal mode from the
+ * lenovo-wmi-gamezone driver.
+ *
+ * Return: 0 on success, or on error.
+ */
+static int lwmi_om_notifier_call(enum thermal_mode *mode)
+{
+ int ret;
+
+ ret = blocking_notifier_call_chain(&om_chain_head, LWMI_GZ_GET_THERMAL_MODE, &mode);
+ if ((ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK)
+ 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.
+ *
+ * Return: 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_show() - Get the value of the specified attribute property
+ *
+ * @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.
+ *
+ * Retrieves the given property from the capability data 01 struct for the
+ * specified attribute's "custom" thermal mode. This function is intended
+ * to be generic so it can be called from any integer attributes "_show"
+ * function.
+ *
+ * If the WMI is success the sysfs attribute is notified.
+ *
+ * Return: Either number of characters written to buf, or an error code.
+ */
+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;
+ u32 attribute_id;
+ int value;
+
+ attribute_id =
+ FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+ FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, LWMI_GZ_THERMAL_MODE_CUSTOM) |
+ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+ capdata = lwmi_cd01_get_data(priv->cd01_list, attribute_id);
+
+ 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);
+}
+
+/**
+ * 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.
+ *
+ * Sets the value of the given attribute when operating under the "custom"
+ * smartfan profile. The current smartfan profile is retrieved from the
+ * lenovo-wmi-gamezone driver and error is returned if the result is not
+ * "custom". This function is intended to be generic so it can be called from
+ * any integer attribute's "_store" function. The integer to be sent to the WMI
+ * method is range checked and an error code is returned if out of range.
+ *
+ * If the value is valid and WMI is success, then the sysfs attribute is
+ * notified.
+ *
+ * Return: Either count, or an error code.
+ */
+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;
+
+ err = lwmi_om_notifier_call(&mode);
+ if (err)
+ return err;
+
+ if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
+ return -EBUSY;
+
+ attribute_id =
+ FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+ FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
+ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+ capdata = lwmi_cd01_get_data(priv->cd01_list, attribute_id);
+
+ if (!capdata)
+ return -ENODEV;
+
+ 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_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
+ (unsigned char *)&args, sizeof(args), NULL);
+
+ if (err)
+ return err;
+
+ 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.
+ *
+ * Retrieves the value of the given attribute for the current smartfan profile.
+ * The current smartfan profile is retrieved from the lenovo-wmi-gamezone driver.
+ * This function is intended to be generic so it can be called from any integer
+ * attribute's "_show" function.
+ *
+ * If the WMI is success the sysfs attribute is notified.
+ *
+ * Return: Either number of characters written to buf, or an error code.
+ */
+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;
+
+ err = lwmi_om_notifier_call(&mode);
+ if (err)
+ return err;
+
+ attribute_id =
+ FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+ FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
+ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+ args.arg0 = attribute_id;
+
+ err = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
+ (unsigned char *)&args, sizeof(args),
+ &retval);
+
+ if (err)
+ return err;
+
+ return sysfs_emit(buf, "%d\n", retval);
+}
+
+/* Lenovo WMI Other Mode Attribute macros */
+#define __LWMI_ATTR_RO(_func, _name) \
+ { \
+ .attr = { .name = __stringify(_name), .mode = 0444 }, \
+ .show = _func##_##_name##_show, \
+ }
+
+#define __LWMI_ATTR_RO_AS(_name, _show) \
+ { \
+ .attr = { .name = __stringify(_name), .mode = 0444 }, \
+ .show = _show, \
+ }
+
+#define __LWMI_ATTR_RW(_func, _name) \
+ __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
+
+/* Shows a formatted static variable */
+#define __LWMI_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 = \
+ __LWMI_ATTR_RO(_attrname, _prop)
+
+/* Attribute current value read/write */
+#define __LWMI_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 = \
+ __LWMI_ATTR_RW(_attrname, current_value)
+
+/* Attribute property read only */
+#define __LWMI_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 = \
+ __LWMI_ATTR_RO(_attrname, _prop)
+
+#define LWMI_ATTR_GROUP_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
+ __LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname); \
+ __LWMI_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
+ __LWMI_ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+ __LWMI_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
+ __LWMI_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
+ __LWMI_TUNABLE_RO_CAP01(scalar_increment, _attrname, STEP_VAL); \
+ static struct kobj_attribute attr_##_attrname##_type = \
+ __LWMI_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 \
+ }
+
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
+ "Set the CPU sustained power limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
+ "Set the CPU slow package power tracking limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
+ "Set the CPU fast package power tracking limit");
+
+static struct capdata01_attr_group cd01_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() - Register all firmware_attributes_class members
+ * @priv: The Other Mode driver data.
+ *
+ * Return: Either 0, or an error code.
+ */
+static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
+{
+ unsigned int i;
+ int err;
+
+ priv->ida_id = ida_alloc(&lwmi_om_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-%u",
+ LWMI_OM_FW_ATTR_BASE_PATH,
+ priv->ida_id);
+ if (IS_ERR(priv->fw_attr_dev)) {
+ err = PTR_ERR(priv->fw_attr_dev);
+ goto err_free_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(cd01_attr_groups) - 1; i++) {
+ err = sysfs_create_group(&priv->fw_attr_kset->kobj, cd01_attr_groups[i].attr_group);
+ if (err)
+ goto err_remove_groups;
+
+ cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
+ }
+ return 0;
+
+err_remove_groups:
+ while (i--)
+ sysfs_remove_group(&priv->fw_attr_kset->kobj, cd01_attr_groups[i].attr_group);
+
+ kset_unregister(priv->fw_attr_kset);
+
+err_destroy_classdev:
+ device_unregister(priv->fw_attr_dev);
+
+err_free_ida:
+ ida_free(&lwmi_om_ida, priv->ida_id);
+ return err;
+}
+
+/**
+ * lwmi_om_fw_attr_remove() - Unregister all capability data attribute groups
+ * @priv: the lenovo-wmi-other driver data.
+ */
+static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
+{
+ for (unsigned int i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++)
+ sysfs_remove_group(&priv->fw_attr_kset->kobj, cd01_attr_groups[i].attr_group);
+
+ kset_unregister(priv->fw_attr_kset);
+ device_unregister(priv->fw_attr_dev);
+}
+
+/**
+ * lwmi_om_master_bind() - Bind all components of the other mode driver
+ * @dev: The lenovo-wmi-other driver basic device.
+ *
+ * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the
+ * lenovo-wmi-other master driver. On success, assign the capability data 01
+ * list pointer to the driver data struct for later access. This pointer
+ * is only valid while the capdata01 interface exists. Finally, register all
+ * firmware attribute groups.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_master_bind(struct device *dev)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+ struct cd01_list *tmp_list;
+ int ret;
+
+ ret = component_bind_all(dev, &tmp_list);
+ if (ret)
+ return ret;
+
+ priv->cd01_list = tmp_list;
+
+ if (!priv->cd01_list)
+ return -ENODEV;
+
+ return lwmi_om_fw_attr_add(priv);
+}
+
+/**
+ * lwmi_om_master_unbind() - Unbind all components of the other mode driver
+ * @dev: The lenovo-wmi-other driver basic device
+ *
+ * Unregister all capability data attribute groups. Then call
+ * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the
+ * lenovo-wmi-other master driver. Finally, free the IDA for this device.
+ */
+static void lwmi_om_master_unbind(struct device *dev)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+
+ lwmi_om_fw_attr_remove(priv);
+ 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);
+ ida_free(&lwmi_om_ida, priv->ida_id);
+}
+
+static const struct wmi_device_id lwmi_other_id_table[] = {
+ { LENOVO_OTHER_MODE_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..49bc3521e184
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-other.h
@@ -0,0 +1,16 @@
+/* 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_
+
+struct device;
+struct notifier_block;
+
+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_OTHER_H_ */
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* Re: [PATCH v6 6/6] platform/x86: Add Lenovo WMI Other Mode Driver
2025-04-28 1:18 ` [PATCH v6 6/6] platform/x86: Add Lenovo WMI Other Mode Driver Derek J. Clark
@ 2025-04-29 20:13 ` ALOK TIWARI
2025-04-30 3:26 ` Derek J. Clark
2025-05-02 2:32 ` Armin Wolf
1 sibling, 1 reply; 23+ messages in thread
From: ALOK TIWARI @ 2025-04-29 20:13 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 28-04-2025 06:48, 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>
> ---
> +++ b/drivers/platform/x86/lenovo-wmi-other.c
> @@ -0,0 +1,655 @@
> +// 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 Capability Data struct that
typo "Other Mode"" -> "Other Mode"
> + * allows the driver to probe details about the attribute such as if it is
> + * supported by the hardware, the default_value, max_value, min_value, and step
> + * increment.
> + *
> + * 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>
> + */
> +
[clip]
> +
> +/**
> + * lwmi_om_register_notifier() - Add a notifier to the blocking notifier chain
> + * @nb: The notifier_block struct to register
> + *
> + * Call blocking_notifier_chain_register to register the notifier block to the
> + * lenovo-wmi-other driver notifer chain.
typo notifer -> notifier
> + *
> + * Return: 0 on success, %-EEXIST on error.
> + */
> +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");
> +
> +/**
> + * lwmi_om_unregister_notifier() - Remove a notifier from the blocking notifier
> + * chain.
> + * @nb: The notifier_block struct to register
> + *
> + * Call blocking_notifier_chain_unregister to unregister the notifier block from the
> + * lenovo-wmi-other driver notifer chain.
> + *
> + * Return: 0 on success, %-ENOENT on error.
> + */
> +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");
> +
> +/**
> + * devm_lwmi_om_unregister_notifier() - Remove a notifier from the blocking
> + * notifier chain.
> + * @data: Void pointer to the notifier_block struct to register.
> + *
> + * Call lwmi_om_unregister_notifier to unregister the notifier block from the
> + * lenovo-wmi-other driver notifer chain.
typo notifer -> notifier
> + *
> + * Return: 0 on success, %-ENOENT on error.
> + */
> +static void devm_lwmi_om_unregister_notifier(void *data)
> +{
> + struct notifier_block *nb = data;
> +
> + lwmi_om_unregister_notifier(nb);
> +}
> +
> +/**
> + * devm_lwmi_om_register_notifier() - Add a notifier to the blocking notifier
> + * chain.
> + * @dev: The parent device of the notifier_block struct.
> + * @nb: The notifier_block struct to register
> + *
> + * Call lwmi_om_register_notifier to register the notifier block to the
> + * lenovo-wmi-other driver notifer chain. Then add devm_lwmi_om_unregister_notifier
typo notifer -> notifier
> + * as a device managed ation to automatically unregister the notifier block
typo ation
> + * upon parent device removal.
> + *
> + * Return: 0 on success, or on error.
> + */
> +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;
> +
Thanks,
Alok
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v6 6/6] platform/x86: Add Lenovo WMI Other Mode Driver
2025-04-29 20:13 ` ALOK TIWARI
@ 2025-04-30 3:26 ` Derek J. Clark
0 siblings, 0 replies; 23+ messages in thread
From: Derek J. Clark @ 2025-04-30 3:26 UTC (permalink / raw)
To: ALOK TIWARI, 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 April 29, 2025 1:13:10 PM PDT, ALOK TIWARI <alok.a.tiwari@oracle.com> wrote:
>
>
>On 28-04-2025 06:48, 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>
>> ---
>> +++ b/drivers/platform/x86/lenovo-wmi-other.c
>> @@ -0,0 +1,655 @@
>> +// 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 Capability Data struct that
>
>typo "Other Mode"" -> "Other Mode"
>
>> + * allows the driver to probe details about the attribute such as if it is
>> + * supported by the hardware, the default_value, max_value, min_value, and step
>> + * increment.
>> + *
>> + * 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>
>> + */
>> +
>[clip]
>> +
>> +/**
>> + * lwmi_om_register_notifier() - Add a notifier to the blocking notifier chain
>> + * @nb: The notifier_block struct to register
>> + *
>> + * Call blocking_notifier_chain_register to register the notifier block to the
>> + * lenovo-wmi-other driver notifer chain.
>
>typo notifer -> notifier
>
>> + *
>> + * Return: 0 on success, %-EEXIST on error.
>> + */
>> +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");
>> +
>> +/**
>> + * lwmi_om_unregister_notifier() - Remove a notifier from the blocking notifier
>> + * chain.
>> + * @nb: The notifier_block struct to register
>> + *
>> + * Call blocking_notifier_chain_unregister to unregister the notifier block from the
>> + * lenovo-wmi-other driver notifer chain.
>> + *
>> + * Return: 0 on success, %-ENOENT on error.
>> + */
>> +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");
>> +
>> +/**
>> + * devm_lwmi_om_unregister_notifier() - Remove a notifier from the blocking
>> + * notifier chain.
>> + * @data: Void pointer to the notifier_block struct to register.
>> + *
>> + * Call lwmi_om_unregister_notifier to unregister the notifier block from the
>> + * lenovo-wmi-other driver notifer chain.
>
>typo notifer -> notifier
>
>> + *
>> + * Return: 0 on success, %-ENOENT on error.
>> + */
>> +static void devm_lwmi_om_unregister_notifier(void *data)
>> +{
>> + struct notifier_block *nb = data;
>> +
>> + lwmi_om_unregister_notifier(nb);
>> +}
>> +
>> +/**
>> + * devm_lwmi_om_register_notifier() - Add a notifier to the blocking notifier
>> + * chain.
>> + * @dev: The parent device of the notifier_block struct.
>> + * @nb: The notifier_block struct to register
>> + *
>> + * Call lwmi_om_register_notifier to register the notifier block to the
>> + * lenovo-wmi-other driver notifer chain. Then add devm_lwmi_om_unregister_notifier
>
>typo notifer -> notifier
>
>> + * as a device managed ation to automatically unregister the notifier block
>
>typo ation
>
>> + * upon parent device removal.
>> + *
>> + * Return: 0 on success, or on error.
>> + */
>> +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;
>> +
>
>
>Thanks,
>Alok
>
>
>
>
Ack all, thanks.
- Derek
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 6/6] platform/x86: Add Lenovo WMI Other Mode Driver
2025-04-28 1:18 ` [PATCH v6 6/6] platform/x86: Add Lenovo WMI Other Mode Driver Derek J. Clark
2025-04-29 20:13 ` ALOK TIWARI
@ 2025-05-02 2:32 ` Armin Wolf
1 sibling, 0 replies; 23+ messages in thread
From: Armin Wolf @ 2025-05-02 2:32 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 28.04.25 um 03:18 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>
> ---
> v6:
> - Pass locally stored pointer to cd01_list back to lenovo-wmi-capdata01
> instead of itterating over the list locally. Due to ACPI event handing
> the list is now mutexed.
> - Fix typos and rewordings from v5 review.
> v5:
> - Switch from locally storing capability data list to storing a pointer
> from the capability data interface.
> - Add portion of Gamezone driver that relies on the exported functions
> of this driver.
> - Properly initialize IDA and use it for naming the firmware-attributes
> path.
> - Rename most defines to clearly indicate they are from this driver.
> - Misc fixes from v4 review.
> 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 | 3 +-
> drivers/platform/x86/Kconfig | 15 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-gamezone.c | 35 ++
> drivers/platform/x86/lenovo-wmi-other.c | 655 +++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi-other.h | 16 +
> 6 files changed, 724 insertions(+), 1 deletion(-)
> 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 511d7547fdd9..764eadee48ac 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13166,8 +13166,9 @@ F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> F: Documentation/wmi/devices/lenovo-wmi-other.rst
> F: drivers/platform/x86/lenovo-wmi-capdata01.*
> F: drivers/platform/x86/lenovo-wmi-events.*
> F: drivers/platform/x86/lenovo-wmi-gamezone.*
> F: drivers/platform/x86/lenovo-wmi-helpers.*
> +F: drivers/platform/x86/lenovo-wmi-other.*
>
> 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 aaa1a69c10ca..be5ab24960b5 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -485,6 +485,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 60058b547de2..f3e64926a96b 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -73,6 +73,7 @@ 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
>
> # Intel
> obj-y += intel/
> diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
> index 713f7dbbabe2..5f092790956b 100644
> --- a/drivers/platform/x86/lenovo-wmi-gamezone.c
> +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
> @@ -23,6 +23,7 @@
> #include "lenovo-wmi-events.h"
> #include "lenovo-wmi-gamezone.h"
> #include "lenovo-wmi-helpers.h"
> +#include "lenovo-wmi-other.h"
>
> #define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
>
> @@ -50,6 +51,34 @@ static struct quirk_entry quirk_no_extreme_bug = {
> .extreme_supported = false,
> };
>
> +/**
> + * lwmi_gz_mode_call() - Call method for lenovo-wmi-other driver notifier.
> + *
> + * @nb: The notifier_block registered to lenovo-wmi-other driver.
> + * @cmd: The event type.
> + * @data: Thermal mode enum pointer pointer for returning the thermal mode.
> + *
> + * For LWMI_GZ_GET_THERMAL_MODE, retrieve the current thermal mode.
> + *
> + * Return: Notifier_block status.
> + */
> +static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
> + void *data)
> +{
> + enum thermal_mode **mode = data;
> + struct lwmi_gz_priv *priv;
> +
> + priv = container_of(nb, struct lwmi_gz_priv, mode_nb);
> +
> + switch (cmd) {
> + case LWMI_GZ_GET_THERMAL_MODE:
> + **mode = priv->current_mode;
Hi,
please use the spinlock here.
> + return NOTIFY_STOP;
> + default:
> + return NOTIFY_DONE;
> + }
> +}
> +
> /**
> * lwmi_gz_event_call() - Call method for lenovo-wmi-events driver notifier.
> * block call chain.
> @@ -346,6 +375,11 @@ static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
> 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;
Again, please return the result directly.
Other than that:
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> +
> return 0;
> }
>
> @@ -368,6 +402,7 @@ 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");
> diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
> new file mode 100644
> index 000000000000..18a48f9103d6
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.c
> @@ -0,0 +1,655 @@
> +// 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 Capability Data struct that
> + * allows the driver to probe details about the attribute such as if it is
> + * supported by the hardware, the default_value, max_value, min_value, and step
> + * increment.
> + *
> + * 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>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/bitfield.h>
> +#include <linux/cleanup.h>
> +#include <linux/component.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/export.h>
> +#include <linux/gfp_types.h>
> +#include <linux/idr.h>
> +#include <linux/kobject.h>
> +#include <linux/module.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"
> +
> +#define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> +
> +#define LWMI_DEVICE_ID_CPU 0x01
> +
> +#define LWMI_FEATURE_ID_CPU_SPPT 0x01
> +#define LWMI_FEATURE_ID_CPU_SPL 0x02
> +#define LWMI_FEATURE_ID_CPU_FPPT 0x03
> +
> +#define LWMI_TYPE_ID_NONE 0x00
> +
> +#define LWMI_FEATURE_VALUE_GET 17
> +#define LWMI_FEATURE_VALUE_SET 18
> +
> +#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
> +#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
> +#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
> +#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
> +
> +#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
> +
> +static BLOCKING_NOTIFIER_HEAD(om_chain_head);
> +static DEFINE_IDA(lwmi_om_ida);
> +
> +enum attribute_property {
> + DEFAULT_VAL,
> + MAX_VAL,
> + MIN_VAL,
> + STEP_VAL,
> + SUPPORTED,
> +};
> +
> +struct lwmi_om_priv {
> + struct component_master_ops *ops;
> + /* *cd01_list is only valid after master bind and while capdata01 exists */
> + struct cd01_list *cd01_list;
> + struct device *fw_attr_dev;
> + struct kset *fw_attr_kset;
> + struct notifier_block nb;
> + struct wmi_device *wdev;
> + int ida_id;
> +};
> +
> +struct tunable_attr_01 {
> + struct capdata01 *capdata;
> + struct device *dev;
> + u32 feature_id;
> + u32 device_id;
> + u32 type_id;
> +};
> +
> +struct tunable_attr_01 ppt_pl1_spl = { .device_id = LWMI_DEVICE_ID_CPU,
> + .feature_id = LWMI_FEATURE_ID_CPU_SPL,
> + .type_id = LWMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl2_sppt = { .device_id = LWMI_DEVICE_ID_CPU,
> + .feature_id = LWMI_FEATURE_ID_CPU_SPPT,
> + .type_id = LWMI_TYPE_ID_NONE };
> +struct tunable_attr_01 ppt_pl3_fppt = { .device_id = LWMI_DEVICE_ID_CPU,
> + .feature_id = LWMI_FEATURE_ID_CPU_FPPT,
> + .type_id = LWMI_TYPE_ID_NONE };
> +
> +struct capdata01_attr_group {
> + const struct attribute_group *attr_group;
> + struct tunable_attr_01 *tunable_attr;
> +};
> +
> +/**
> + * lwmi_om_register_notifier() - Add a notifier to the blocking notifier chain
> + * @nb: The notifier_block struct to register
> + *
> + * Call blocking_notifier_chain_register to register the notifier block to the
> + * lenovo-wmi-other driver notifer chain.
> + *
> + * Return: 0 on success, %-EEXIST on error.
> + */
> +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");
> +
> +/**
> + * lwmi_om_unregister_notifier() - Remove a notifier from the blocking notifier
> + * chain.
> + * @nb: The notifier_block struct to register
> + *
> + * Call blocking_notifier_chain_unregister to unregister the notifier block from the
> + * lenovo-wmi-other driver notifer chain.
> + *
> + * Return: 0 on success, %-ENOENT on error.
> + */
> +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");
> +
> +/**
> + * devm_lwmi_om_unregister_notifier() - Remove a notifier from the blocking
> + * notifier chain.
> + * @data: Void pointer to the notifier_block struct to register.
> + *
> + * Call lwmi_om_unregister_notifier to unregister the notifier block from the
> + * lenovo-wmi-other driver notifer chain.
> + *
> + * Return: 0 on success, %-ENOENT on error.
> + */
> +static void devm_lwmi_om_unregister_notifier(void *data)
> +{
> + struct notifier_block *nb = data;
> +
> + lwmi_om_unregister_notifier(nb);
> +}
> +
> +/**
> + * devm_lwmi_om_register_notifier() - Add a notifier to the blocking notifier
> + * chain.
> + * @dev: The parent device of the notifier_block struct.
> + * @nb: The notifier_block struct to register
> + *
> + * Call lwmi_om_register_notifier to register the notifier block to the
> + * lenovo-wmi-other driver notifer chain. Then add devm_lwmi_om_unregister_notifier
> + * as a device managed ation to automatically unregister the notifier block
> + * upon parent device removal.
> + *
> + * Return: 0 on success, or on error.
> + */
> +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");
> +
> +/**
> + * lwmi_om_notifier_call() - Call functions for the notifier call chain.
> + * @mode: Pointer to a thermal mode enum to retrieve the data from.
> + *
> + * Call blocking_notifier_call_chain to retrieve the thermal mode from the
> + * lenovo-wmi-gamezone driver.
> + *
> + * Return: 0 on success, or on error.
> + */
> +static int lwmi_om_notifier_call(enum thermal_mode *mode)
> +{
> + int ret;
> +
> + ret = blocking_notifier_call_chain(&om_chain_head, LWMI_GZ_GET_THERMAL_MODE, &mode);
> + if ((ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK)
> + 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.
> + *
> + * Return: 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_show() - Get the value of the specified attribute property
> + *
> + * @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.
> + *
> + * Retrieves the given property from the capability data 01 struct for the
> + * specified attribute's "custom" thermal mode. This function is intended
> + * to be generic so it can be called from any integer attributes "_show"
> + * function.
> + *
> + * If the WMI is success the sysfs attribute is notified.
> + *
> + * Return: Either number of characters written to buf, or an error code.
> + */
> +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;
> + u32 attribute_id;
> + int value;
> +
> + attribute_id =
> + FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, LWMI_GZ_THERMAL_MODE_CUSTOM) |
> + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> + capdata = lwmi_cd01_get_data(priv->cd01_list, attribute_id);
> +
> + 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);
> +}
> +
> +/**
> + * 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.
> + *
> + * Sets the value of the given attribute when operating under the "custom"
> + * smartfan profile. The current smartfan profile is retrieved from the
> + * lenovo-wmi-gamezone driver and error is returned if the result is not
> + * "custom". This function is intended to be generic so it can be called from
> + * any integer attribute's "_store" function. The integer to be sent to the WMI
> + * method is range checked and an error code is returned if out of range.
> + *
> + * If the value is valid and WMI is success, then the sysfs attribute is
> + * notified.
> + *
> + * Return: Either count, or an error code.
> + */
> +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;
> +
> + err = lwmi_om_notifier_call(&mode);
> + if (err)
> + return err;
> +
> + if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
> + return -EBUSY;
> +
> + attribute_id =
> + FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
> + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> + capdata = lwmi_cd01_get_data(priv->cd01_list, attribute_id);
> +
> + if (!capdata)
> + return -ENODEV;
> +
> + 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_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
> + (unsigned char *)&args, sizeof(args), NULL);
> +
> + if (err)
> + return err;
> +
> + 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.
> + *
> + * Retrieves the value of the given attribute for the current smartfan profile.
> + * The current smartfan profile is retrieved from the lenovo-wmi-gamezone driver.
> + * This function is intended to be generic so it can be called from any integer
> + * attribute's "_show" function.
> + *
> + * If the WMI is success the sysfs attribute is notified.
> + *
> + * Return: Either number of characters written to buf, or an error code.
> + */
> +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;
> +
> + err = lwmi_om_notifier_call(&mode);
> + if (err)
> + return err;
> +
> + attribute_id =
> + FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
> + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> +
> + args.arg0 = attribute_id;
> +
> + err = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
> + (unsigned char *)&args, sizeof(args),
> + &retval);
> +
> + if (err)
> + return err;
> +
> + return sysfs_emit(buf, "%d\n", retval);
> +}
> +
> +/* Lenovo WMI Other Mode Attribute macros */
> +#define __LWMI_ATTR_RO(_func, _name) \
> + { \
> + .attr = { .name = __stringify(_name), .mode = 0444 }, \
> + .show = _func##_##_name##_show, \
> + }
> +
> +#define __LWMI_ATTR_RO_AS(_name, _show) \
> + { \
> + .attr = { .name = __stringify(_name), .mode = 0444 }, \
> + .show = _show, \
> + }
> +
> +#define __LWMI_ATTR_RW(_func, _name) \
> + __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
> +
> +/* Shows a formatted static variable */
> +#define __LWMI_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 = \
> + __LWMI_ATTR_RO(_attrname, _prop)
> +
> +/* Attribute current value read/write */
> +#define __LWMI_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 = \
> + __LWMI_ATTR_RW(_attrname, current_value)
> +
> +/* Attribute property read only */
> +#define __LWMI_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 = \
> + __LWMI_ATTR_RO(_attrname, _prop)
> +
> +#define LWMI_ATTR_GROUP_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
> + __LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname); \
> + __LWMI_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
> + __LWMI_ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
> + __LWMI_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
> + __LWMI_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
> + __LWMI_TUNABLE_RO_CAP01(scalar_increment, _attrname, STEP_VAL); \
> + static struct kobj_attribute attr_##_attrname##_type = \
> + __LWMI_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 \
> + }
> +
> +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
> + "Set the CPU sustained power limit");
> +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
> + "Set the CPU slow package power tracking limit");
> +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
> + "Set the CPU fast package power tracking limit");
> +
> +static struct capdata01_attr_group cd01_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() - Register all firmware_attributes_class members
> + * @priv: The Other Mode driver data.
> + *
> + * Return: Either 0, or an error code.
> + */
> +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
> +{
> + unsigned int i;
> + int err;
> +
> + priv->ida_id = ida_alloc(&lwmi_om_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-%u",
> + LWMI_OM_FW_ATTR_BASE_PATH,
> + priv->ida_id);
> + if (IS_ERR(priv->fw_attr_dev)) {
> + err = PTR_ERR(priv->fw_attr_dev);
> + goto err_free_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(cd01_attr_groups) - 1; i++) {
> + err = sysfs_create_group(&priv->fw_attr_kset->kobj, cd01_attr_groups[i].attr_group);
> + if (err)
> + goto err_remove_groups;
> +
> + cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
> + }
> + return 0;
> +
> +err_remove_groups:
> + while (i--)
> + sysfs_remove_group(&priv->fw_attr_kset->kobj, cd01_attr_groups[i].attr_group);
> +
> + kset_unregister(priv->fw_attr_kset);
> +
> +err_destroy_classdev:
> + device_unregister(priv->fw_attr_dev);
> +
> +err_free_ida:
> + ida_free(&lwmi_om_ida, priv->ida_id);
> + return err;
> +}
> +
> +/**
> + * lwmi_om_fw_attr_remove() - Unregister all capability data attribute groups
> + * @priv: the lenovo-wmi-other driver data.
> + */
> +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
> +{
> + for (unsigned int i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++)
> + sysfs_remove_group(&priv->fw_attr_kset->kobj, cd01_attr_groups[i].attr_group);
> +
> + kset_unregister(priv->fw_attr_kset);
> + device_unregister(priv->fw_attr_dev);
> +}
> +
> +/**
> + * lwmi_om_master_bind() - Bind all components of the other mode driver
> + * @dev: The lenovo-wmi-other driver basic device.
> + *
> + * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the
> + * lenovo-wmi-other master driver. On success, assign the capability data 01
> + * list pointer to the driver data struct for later access. This pointer
> + * is only valid while the capdata01 interface exists. Finally, register all
> + * firmware attribute groups.
> + *
> + * Return: 0 on success, or an error code.
> + */
> +static int lwmi_om_master_bind(struct device *dev)
> +{
> + struct lwmi_om_priv *priv = dev_get_drvdata(dev);
> + struct cd01_list *tmp_list;
> + int ret;
> +
> + ret = component_bind_all(dev, &tmp_list);
> + if (ret)
> + return ret;
> +
> + priv->cd01_list = tmp_list;
> +
> + if (!priv->cd01_list)
> + return -ENODEV;
> +
> + return lwmi_om_fw_attr_add(priv);
> +}
> +
> +/**
> + * lwmi_om_master_unbind() - Unbind all components of the other mode driver
> + * @dev: The lenovo-wmi-other driver basic device
> + *
> + * Unregister all capability data attribute groups. Then call
> + * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the
> + * lenovo-wmi-other master driver. Finally, free the IDA for this device.
> + */
> +static void lwmi_om_master_unbind(struct device *dev)
> +{
> + struct lwmi_om_priv *priv = dev_get_drvdata(dev);
> +
> + lwmi_om_fw_attr_remove(priv);
> + 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);
> + ida_free(&lwmi_om_ida, priv->ida_id);
> +}
> +
> +static const struct wmi_device_id lwmi_other_id_table[] = {
> + { LENOVO_OTHER_MODE_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..49bc3521e184
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-other.h
> @@ -0,0 +1,16 @@
> +/* 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_
> +
> +struct device;
> +struct notifier_block;
> +
> +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_OTHER_H_ */
^ permalink raw reply [flat|nested] 23+ messages in thread