History

Merge tag 'platform-drivers-x86-v7.0-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86

Pull x86 platform driver updates from Ilpo Järvinen: "Highlights: - amd/pmf: - Avoid overwriting BIOS input values when events occur rapidly - Fix PMF driver issues related to S4 (in part on crypto/ccp side) - Add NPU metrics API (for accel side consumers) - Allow disabling Smart PC function through a module parameter - asus-wmi & HID/asus: - Unification of backlight control (replaces quirks) - Support multiple interfaces for controlling keyboard/RGB brightness - Simplify init sequence - hp-wmi: - Add manual fan control for Victus S models - Add fan mode keep-alive - Fix platform profile values for Omen 16-wf1xxx - Add EC offset to get the thermal profile - intel/pmc: Show substate residencies also for non-primary PMCs - intel/ISST: - Store and restore data for all domains - Write interface improvements - lenovo-wmi: - Support multiple Capability Data - Add HWMON reporting and tuning support - mellanox/mlx-platform: Add HI173 & HI174 support - surface/aggregator_registry: Add Surface Pro 11 (QCOM) - thinkpad_acpi: Add support for HW damage detection capability - uniwill: Implement cTGP setting - wmi: - Introduce marshalling support - Convert a few drivers to use the new buffer-based WMI API - tools/power/x86/intel-speed-select: Allow read operations for non-root - Miscellaneous cleanups / refactoring / improvements" * tag 'platform-drivers-x86-v7.0-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (68 commits) platform/x86: lenovo-wmi-{capdata,other}: Fix HWMON channel visibility platform/x86: hp-wmi: Add EC offsets to read Victus S thermal profile platform: mellanox: mlx-platform: Add support DGX flavor of next-generation 800GB/s ethernet switch. platform: mellanox: mlx-platform: Add support for new Nvidia DGX system based on class VMOD0010 HID: asus: add support for the asus-wmi brightness handler platform/x86: asus-wmi: add keyboard brightness event handler platform/x86: asus-wmi: remove unused keyboard backlight quirk HID: asus: listen to the asus-wmi brightness device instead of creating one platform/x86: asus-wmi: Add support for multiple kbd led handlers HID: asus: early return for ROG devices HID: asus: move vendor initialization to probe HID: asus: fortify keyboard handshake HID: asus: use same report_id in response HID: asus: initialize additional endpoints only for certain devices HID: asus: simplify RGB init sequence platform/wmi: string-kunit: Add missing oversized string test case platform/x86/amd/pmf: Added a module parameter to disable the Smart PC function platform/x86/uniwill: Implement cTGP setting platform/x86: uniwill-laptop: Introduce device descriptor system platform/x86/amd: Use scope-based cleanup for wbrf_record() ...

Linus Torvalds committed 9 months ago

Commit f50822f2 parents 1b49e36 5a5203a61 files · +5390 −953Browse files

Unified · Split

61 files
  1. Documentation/admin-guide/laptops/thinkpad-acpi.rst +37 −0
  2. Documentation/driver-api/wmi.rst +3 −0
  3. Documentation/wmi/acpi-interface.rst +68 −0
  4. Documentation/wmi/devices/lenovo-wmi-other.rst +43 −3
  5. Documentation/wmi/driver-development-guide.rst +53 −23
  6. drivers/crypto/ccp/psp-dev.c +11 −0
  7. drivers/crypto/ccp/sp-dev.c +12 −0
  8. drivers/crypto/ccp/sp-dev.h +3 −0
  9. drivers/crypto/ccp/sp-pci.c +15 −1
  10. drivers/crypto/ccp/tee-dev.c +44 −12
  11. drivers/crypto/ccp/tee-dev.h +1 −0
  12. drivers/hid/hid-asus.c +104 −117
  13. drivers/platform/mellanox/mlx-platform.c +484 −0
  14. drivers/platform/surface/surface_aggregator_registry.c +18 −0
  15. drivers/platform/surface/surfacepro3_button.c +5 −6
  16. drivers/platform/wmi/Kconfig +3 −0
  17. drivers/platform/wmi/Makefile +4 −1
  18. drivers/platform/wmi/core.c +156 −4
  19. drivers/platform/wmi/internal.h +17 −0
  20. drivers/platform/wmi/marshalling.c +247 −0
  21. drivers/platform/wmi/string.c +92 −0
  22. drivers/platform/wmi/tests/Kconfig +27 −0
  23. drivers/platform/wmi/tests/Makefile +11 −0
  24. drivers/platform/wmi/tests/marshalling_kunit.c +452 −0
  25. drivers/platform/wmi/tests/string_kunit.c +296 −0
  26. drivers/platform/x86/amd/pmf/acpi.c +40 −0
  27. drivers/platform/x86/amd/pmf/core.c +155 −6
  28. drivers/platform/x86/amd/pmf/pmf.h +33 −0
  29. drivers/platform/x86/amd/pmf/spc.c +19 −14
  30. drivers/platform/x86/amd/pmf/tee-if.c +5 −9
  31. drivers/platform/x86/amd/wbrf.c +8 −17
  32. drivers/platform/x86/asus-wmi.c +196 −27
  33. drivers/platform/x86/hp/hp-wmi.c +489 −114
  34. drivers/platform/x86/intel/pmc/core.c +56 −29
  35. drivers/platform/x86/intel/pmc/core.h +7 −8
  36. drivers/platform/x86/intel/pmt/class.c +3 −3
  37. drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +56 −23
  38. drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c +7 −6
  39. drivers/platform/x86/intel/wmi/sbl-fw-update.c +18 −25
  40. drivers/platform/x86/intel/wmi/thunderbolt.c +11 −15
  41. drivers/platform/x86/lenovo/Kconfig +3 −2
  42. drivers/platform/x86/lenovo/Makefile +1 −1
  43. drivers/platform/x86/lenovo/ideapad-laptop.c +16 −23
  44. drivers/platform/x86/lenovo/thinkpad_acpi.c +206 −0
  45. drivers/platform/x86/lenovo/wmi-capdata.c +829 −0
  46. drivers/platform/x86/lenovo/wmi-capdata.h +65 −0
  47. drivers/platform/x86/lenovo/wmi-capdata01.c +0 −302
  48. drivers/platform/x86/lenovo/wmi-capdata01.h +0 −25
  49. drivers/platform/x86/lenovo/wmi-helpers.c +18 −3
  50. drivers/platform/x86/lenovo/wmi-other.c +507 −21
  51. drivers/platform/x86/lenovo/yogabook.c +7 −5
  52. drivers/platform/x86/uniwill/uniwill-acpi.c +272 −30
  53. drivers/platform/x86/wmi-bmof.c +15 −19
  54. drivers/platform/x86/xiaomi-wmi.c +2 −3
  55. include/linux/amd-pmf-io.h +21 −0
  56. include/linux/platform_data/x86/asus-wmi-leds-ids.h +0 −50
  57. include/linux/platform_data/x86/asus-wmi.h +28 −0
  58. include/linux/psp.h +1 −0
  59. include/linux/wmi.h +43 −2
  60. tools/power/x86/intel-speed-select/Makefile +7 −1
  61. tools/power/x86/intel-speed-select/isst-config.c +40 −3
Documentation/admin-guide/laptops/thinkpad-acpi.rst+37−0
@@ -54,6 +54,7 @@ detailed description): · 53 unmodified lines
5454 - Setting keyboard language
5555 - WWAN Antenna type
5656 - Auxmac
57+ - Hardware damage detection capability
5758
5859 A compatibility table by model and feature is maintained on the web
5960 site, http://ibm-acpi.sf.net/. I appreciate any success or failure
@@ -1576,6 +1577,42 @@ percentage level, above which charging will stop. · 1516 unmodified lines
15761577 The exact semantics of the attributes may be found in
15771578 Documentation/ABI/testing/sysfs-class-power.
15781579
1580+Hardware damage detection capability
1581+------------------------------------
1582+
1583+sysfs attributes: hwdd_status, hwdd_detail
1584+
1585+Thinkpads are adding the ability to detect and report hardware damage.
1586+Add new sysfs interface to identify the damaged device status.
1587+Initial support is available for the USB-C replaceable connector.
1588+
1589+The command to check device damaged status is::
1590+
1591+ cat /sys/devices/platform/thinkpad_acpi/hwdd_status
1592+
1593+This value displays status of device damaged.
1594+
1595+- 0 = Not Damaged
1596+- 1 = Damaged
1597+
1598+The command to check location of damaged device is::
1599+
1600+ cat /sys/devices/platform/thinkpad_acpi/hwdd_detail
1601+
1602+This value displays location of damaged device having 1 line per damaged "item".
1603+For example:
1604+
1605+if no damage is detected:
1606+
1607+- No damage detected
1608+
1609+if damage detected:
1610+
1611+- TYPE-C: Base, Right side, Center port
1612+
1613+The property is read-only. If feature is not supported then sysfs
1614+attribute is not created.
1615+
15791616 Multiple Commands, Module Parameters
15801617 ------------------------------------
15811618
Documentation/driver-api/wmi.rst+3−0
@@ -16,5 +16,8 @@ which will be bound to compatible WMI devices by the driver core. · 15 unmodified lines
1616 .. kernel-doc:: include/linux/wmi.h
1717 :internal:
1818
19+.. kernel-doc:: drivers/platform/wmi/string.c
20+ :export:
21+
1922 .. kernel-doc:: drivers/platform/wmi/core.c
2023 :export:
Documentation/wmi/acpi-interface.rst+68−0
@@ -104,3 +104,71 @@ holding the notification ID of the event. This method should be evaluated every · 103 unmodified lines
104104 time an ACPI notification is received, since some ACPI implementations use a
105105 queue to store WMI event data items. This queue will overflow after a couple
106106 of WMI events are received without retrieving the associated WMI event data.
107+
108+Conversion rules for ACPI data types
109+------------------------------------
110+
111+Consumers of the ACPI-WMI interface use binary buffers to exchange data with the WMI driver core,
112+with the internal structure of the buffer being only know to the consumers. The WMI driver core is
113+thus responsible for converting the data inside the buffer into an appropriate ACPI data type for
114+consumption by the ACPI firmware. Additionally, any data returned by the various ACPI methods needs
115+to be converted back into a binary buffer.
116+
117+The layout of said buffers is defined by the MOF description of the WMI method or data block in
118+question [1]_:
119+
120+=============== ======================================================================= =========
121+Data Type Layout Alignment
122+=============== ======================================================================= =========
123+``string`` Starts with an unsigned 16-bit little endian integer specifying 2 bytes
124+ the length of the string data in bytes, followed by the string data
125+ encoded as UTF-16LE with **optional** NULL termination and padding.
126+ Keep in mind that some firmware implementations might depend on the
127+ terminating NULL character to be present. Also the padding should
128+ always be performed with NULL characters.
129+``boolean`` Single byte where 0 means ``false`` and nonzero means ``true``. 1 byte
130+``sint8`` Signed 8-bit integer. 1 byte
131+``uint8`` Unsigned 8-bit integer. 1 byte
132+``sint16`` Signed 16-bit little endian integer. 2 bytes
133+``uint16`` Unsigned 16-bit little endian integer. 2 bytes
134+``sint32`` Signed 32-bit little endian integer. 4 bytes
135+``uint32`` Unsigned 32-bit little endian integer. 4 bytes
136+``sint64`` Signed 64-bit little endian integer. 8 bytes
137+``uint64`` Unsigned 64-bit little endian integer. 8 bytes
138+``datetime`` A fixed-length 25-character UTF-16LE string with the format 2 bytes
139+ *yyyymmddhhmmss.mmmmmmsutc* where *yyyy* is the 4-digit year, *mm* is
140+ the 2-digit month, *dd* is the 2-digit day, *hh* is the 2-digit hour
141+ based on a 24-hour clock, *mm* is the 2-digit minute, *ss* is the
142+ 2-digit second, *mmmmmm* is the 6-digit microsecond, *s* is a plus or
143+ minus character depending on whether *utc* is a positive or negative
144+ offset from UTC (or a colon if the date is an interval). Unpopulated
145+ fields should be filled with asterisks.
146+=============== ======================================================================= =========
147+
148+Arrays should be aligned based on the alignment of their base type, while objects should be
149+aligned based on the largest alignment of an element inside them.
150+
151+All buffers returned by the WMI driver core are 8-byte aligned. When converting ACPI data types
152+into such buffers the following conversion rules apply:
153+
154+=============== ============================================================
155+ACPI Data Type Converted into
156+=============== ============================================================
157+Buffer Copied as-is.
158+Integer Converted into a ``uint32``.
159+String Converted into a ``string`` with a terminating NULL character
160+ to match the behavior the of the Windows driver.
161+Package Each element inside the package is converted with alignment
162+ of the resulting data types being respected. Nested packages
163+ are not allowed.
164+=============== ============================================================
165+
166+The Windows driver does attempt to handle nested packages, but this results in internal data
167+structures (``_ACPI_METHOD_ARGUMENT_V1``) erroneously being copied into the resulting buffer.
168+ACPI firmware implementations should thus not return nested packages from ACPI methods
169+associated with the ACPI-WMI interface.
170+
171+References
172+==========
173+
174+.. [1] https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items
Documentation/wmi/devices/lenovo-wmi-other.rst+43−3
@@ -31,13 +31,32 @@ under the following path: · 30 unmodified lines
3131
3232 /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
3333
34+Additionally, this driver also exports attributes to HWMON.
35+
36+LENOVO_CAPABILITY_DATA_00
37+-------------------------
38+
39+WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E``
40+
41+The LENOVO_CAPABILITY_DATA_00 interface provides various information that
42+does not rely on the gamezone thermal mode.
43+
44+The following HWMON attributes are implemented:
45+ - fanX_div: internal RPM divisor
46+ - fanX_input: current RPM
47+ - fanX_target: target RPM (tunable, 0=auto)
48+
49+Due to the internal RPM divisor, the current/target RPMs are rounded down to
50+its nearest multiple. The divisor itself is not necessary to be a power of two.
51+
3452 LENOVO_CAPABILITY_DATA_01
3553 -------------------------
3654
3755 WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154``
3856
39−The LENOVO_CAPABILITY_DATA_01 interface provides information on various
40−power limits of integrated CPU and GPU components.
57+The LENOVO_CAPABILITY_DATA_01 interface provides various information that
58+relies on the gamezone thermal mode, including power limits of integrated
59+CPU and GPU components.
4160
4261 Each attribute has the following properties:
4362 - current_value
@@ -48,11 +67,22 @@ Each attribute has the following properties: · 4 unmodified lines
4867 - scalar_increment
4968 - type
5069
51−The following attributes are implemented:
70+The following firmware-attributes are implemented:
5271 - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
5372 - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
5473 - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
5574
75+LENOVO_FAN_TEST_DATA
76+-------------------------
77+
78+WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21``
79+
80+The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of
81+cooling fans.
82+
83+The following HWMON attributes are implemented:
84+ - fanX_max: maximum RPM
85+ - fanX_min: minimum RPM
5686
5787 WMI interface description
5888 =========================
@@ -106,3 +136,13 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility: · 47 unmodified lines
106136 [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize;
107137 [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[];
108138 };
139+
140+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of Fan Test Data"), guid("{B642801B-3D21-45DE-90AE-6E86F164FB21}")]
141+ class LENOVO_FAN_TEST_DATA {
142+ [key, read] string InstanceName;
143+ [read] boolean Active;
144+ [WmiDataId(1), read, Description("Mode.")] uint32 NumOfFans;
145+ [WmiDataId(2), read, Description("Fan ID."), WmiSizeIs("NumOfFans")] uint32 FanId[];
146+ [WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMaxSpeed[];
147+ [WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[];
148+ };
Documentation/wmi/driver-development-guide.rst+53−23
@@ -70,7 +70,7 @@ to matching WMI devices using a struct wmi_device_id table: · 69 unmodified lines
7070 .probe = foo_probe,
7171 .remove = foo_remove, /* optional, devres is preferred */
7272 .shutdown = foo_shutdown, /* optional, called during shutdown */
73− .notify = foo_notify, /* optional, for event handling */
73+ .notify_new = foo_notify, /* optional, for event handling */
7474 .no_notify_data = true, /* optional, enables events containing no additional data */
7575 .no_singleton = true, /* required for new WMI drivers */
7676 };
@@ -90,9 +90,9 @@ the WMI device and put it in a well-known state for the WMI driver to pick up la · 13 unmodified lines
9090 or kexec. Most WMI drivers need no special shutdown handling and can thus omit this callback.
9191
9292 Please note that new WMI drivers are required to be able to be instantiated multiple times,
93−and are forbidden from using any deprecated GUID-based WMI functions. This means that the
94−WMI driver should be prepared for the scenario that multiple matching WMI devices are present
95−on a given machine.
93+and are forbidden from using any deprecated GUID-based or ACPI-based WMI functions. This means
94+that the WMI driver should be prepared for the scenario that multiple matching WMI devices are
95+present on a given machine.
9696
9797 Because of this, WMI drivers should use the state container design pattern as described in
9898 Documentation/driver-api/driver-model/design-patterns.rst.
@@ -104,38 +104,37 @@ Documentation/driver-api/driver-model/design-patterns.rst. · 5 unmodified lines
104104 WMI method drivers
105105 ------------------
106106
107−WMI drivers can call WMI device methods using wmidev_evaluate_method(), the
108−structure of the ACPI buffer passed to this function is device-specific and usually
109−needs some tinkering to get right. Looking at the ACPI tables containing the WMI
110−device usually helps here. The method id and instance number passed to this function
111−are also device-specific, looking at the decoded Binary MOF is usually enough to
112−find the right values.
107+WMI drivers can call WMI device methods using wmidev_invoke_method(). For each WMI method
108+invocation the WMI driver needs to provide the instance number and the method ID, as well as
109+a buffer with the method arguments and optionally a buffer for the results.
113110
114−The maximum instance number can be retrieved during runtime using wmidev_instance_count().
111+The layout of said buffers is device-specific and described by the Binary MOF data associated
112+with a given WMI device. Said Binary MOF data also describes the method ID of a given WMI method
113+with the ``WmiMethodId`` qualifier. WMI devices exposing WMI methods usually expose only a single
114+instance (instance number 0), but in theory can expose multiple instances as well. In such a case
115+the number of instances can be retrieved using wmidev_instance_count().
115116
116−Take a look at drivers/platform/x86/inspur_platform_profile.c for an example WMI method driver.
117+Take a look at drivers/platform/x86/intel/wmi/thunderbolt.c for an example WMI method driver.
117118
118119 WMI data block drivers
119120 ----------------------
120121
121−WMI drivers can query WMI device data blocks using wmidev_block_query(), the
122−structure of the returned ACPI object is again device-specific. Some WMI devices
123−also allow for setting data blocks using wmidev_block_set().
122+WMI drivers can query WMI data blocks using wmidev_query_block(), the layout of the returned
123+buffer is again device-specific and described by the Binary MOF data. Some WMI data blocks are
124+also writeable and can be set using wmidev_set_block(). The number of data block instances can
125+again be retrieved using wmidev_instance_count().
124126
125−The maximum instance number can also be retrieved using wmidev_instance_count().
126−
127−Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example
128−WMI data block driver.
127+Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example WMI data block driver.
129128
130129 WMI event drivers
131130 -----------------
132131
133−WMI drivers can receive WMI events via the notify() callback inside the struct wmi_driver.
132+WMI drivers can receive WMI events via the notify_new() callback inside the struct wmi_driver.
134133 The WMI subsystem will then take care of setting up the WMI event accordingly. Please note that
135−the structure of the ACPI object passed to this callback is device-specific, and freeing the
136−ACPI object is being done by the WMI subsystem, not the driver.
134+the layout of the buffer passed to this callback is device-specific, and freeing of the buffer
135+is done by the WMI subsystem itself, not the driver.
137136
138−The WMI driver core will take care that the notify() callback will only be called after
137+The WMI driver core will take care that the notify_new() callback will only be called after
139138 the probe() callback has been called, and that no events are being received by the driver
140139 right before and after calling its remove() or shutdown() callback.
141140
@@ -147,6 +146,36 @@ the ``no_notify_data`` flag inside struct wmi_driver should be set to ``true``. · 5 unmodified lines
147146
148147 Take a look at drivers/platform/x86/xiaomi-wmi.c for an example WMI event driver.
149148
149+Exchanging data with the WMI driver core
150+----------------------------------------
151+
152+WMI drivers can exchange data with the WMI driver core using struct wmi_buffer. The internal
153+structure of those buffers is device-specific and only known by the WMI driver. Because of this
154+the WMI driver itself is responsible for parsing and validating the data received from its
155+WMI device.
156+
157+The structure of said buffers is described by the MOF data associated with the WMI device in
158+question. When such a buffer contains multiple data items it usually makes sense to define a
159+C structure and use it during parsing. Since the WMI driver core guarantees that all buffers
160+received from a WMI device are aligned on an 8-byte boundary, WMI drivers can simply perform
161+a cast between the WMI buffer data and this C structure.
162+
163+This however should only be done after the size of the buffer was verified to be large enough
164+to hold the whole C structure. WMI drivers should reject undersized buffers as they are usually
165+sent by the WMI device to signal an internal error. Oversized buffers however should be accepted
166+to emulate the behavior of the Windows WMI implementation.
167+
168+When defining a C structure for parsing WMI buffers the alignment of the data items should be
169+respected. This is especially important for 64-bit integers as those have different alignments
170+on 64-bit (8-byte alignment) and 32-bit (4-byte alignment) architectures. It is thus a good idea
171+to manually specify the alignment of such data items or mark the whole structure as packed when
172+appropriate. Integer data items in general are little-endian integers and should be marked as
173+such using ``__le64`` and friends. When parsing WMI string data items the struct wmi_string should
174+be used as WMI strings have a different layout than C strings.
175+
176+See Documentation/wmi/acpi-interface.rst for more information regarding the binary format
177+of WMI data items.
178+
150179 Handling multiple WMI devices at once
151180 -------------------------------------
152181
@@ -171,6 +200,7 @@ Things to avoid · 18 unmodified lines
171200 When developing WMI drivers, there are a couple of things which should be avoided:
172201
173202 - usage of the deprecated GUID-based WMI interface which uses GUIDs instead of WMI device structs
203+- usage of the deprecated ACPI-based WMI interface which uses ACPI objects instead of plain buffers
174204 - bypassing of the WMI subsystem when talking to WMI devices
175205 - WMI drivers which cannot be instantiated multiple times.
176206
drivers/crypto/ccp/psp-dev.c+11−0
@@ -351,6 +351,17 @@ struct psp_device *psp_get_master_device(void) · 350 unmodified lines
351351 return sp ? sp->psp_data : NULL;
352352 }
353353
354+int psp_restore(struct sp_device *sp)
355+{
356+ struct psp_device *psp = sp->psp_data;
357+ int ret = 0;
358+
359+ if (psp->tee_data)
360+ ret = tee_restore(psp);
361+
362+ return ret;
363+}
364+
354365 void psp_pci_init(void)
355366 {
356367 psp_master = psp_get_master_device();
drivers/crypto/ccp/sp-dev.c+12−0
@@ -230,6 +230,18 @@ int sp_resume(struct sp_device *sp) · 229 unmodified lines
230230 return 0;
231231 }
232232
233+int sp_restore(struct sp_device *sp)
234+{
235+ if (sp->psp_data) {
236+ int ret = psp_restore(sp);
237+
238+ if (ret)
239+ return ret;
240+ }
241+
242+ return sp_resume(sp);
243+}
244+
233245 struct sp_device *sp_get_psp_master_device(void)
234246 {
235247 struct sp_device *i, *ret = NULL;
drivers/crypto/ccp/sp-dev.h+3−0
@@ -141,6 +141,7 @@ void sp_destroy(struct sp_device *sp); · 140 unmodified lines
141141
142142 int sp_suspend(struct sp_device *sp);
143143 int sp_resume(struct sp_device *sp);
144+int sp_restore(struct sp_device *sp);
144145 int sp_request_ccp_irq(struct sp_device *sp, irq_handler_t handler,
145146 const char *name, void *data);
146147 void sp_free_ccp_irq(struct sp_device *sp, void *data);
@@ -174,6 +175,7 @@ int psp_dev_init(struct sp_device *sp); · 27 unmodified lines
174175 void psp_pci_init(void);
175176 void psp_dev_destroy(struct sp_device *sp);
176177 void psp_pci_exit(void);
178+int psp_restore(struct sp_device *sp);
177179
178180 #else /* !CONFIG_CRYPTO_DEV_SP_PSP */
179181
@@ -181,6 +183,7 @@ static inline int psp_dev_init(struct sp_device *sp) { return 0; } · 1 unmodified line
181183 static inline void psp_pci_init(void) { }
182184 static inline void psp_dev_destroy(struct sp_device *sp) { }
183185 static inline void psp_pci_exit(void) { }
186+static inline int psp_restore(struct sp_device *sp) { return 0; }
184187
185188 #endif /* CONFIG_CRYPTO_DEV_SP_PSP */
186189
drivers/crypto/ccp/sp-pci.c+15−1
@@ -353,6 +353,13 @@ static int __maybe_unused sp_pci_resume(struct device *dev) · 352 unmodified lines
353353 return sp_resume(sp);
354354 }
355355
356+static int __maybe_unused sp_pci_restore(struct device *dev)
357+{
358+ struct sp_device *sp = dev_get_drvdata(dev);
359+
360+ return sp_restore(sp);
361+}
362+
356363 #ifdef CONFIG_CRYPTO_DEV_SP_PSP
357364 static const struct sev_vdata sevv1 = {
358365 .cmdresp_reg = 0x10580, /* C2PMSG_32 */
@@ -563,7 +570,14 @@ static const struct pci_device_id sp_pci_table[] = { · 204 unmodified lines
563570 };
564571 MODULE_DEVICE_TABLE(pci, sp_pci_table);
565572
566−static SIMPLE_DEV_PM_OPS(sp_pci_pm_ops, sp_pci_suspend, sp_pci_resume);
573+static const struct dev_pm_ops sp_pci_pm_ops = {
574+ .suspend = pm_sleep_ptr(sp_pci_suspend),
575+ .resume = pm_sleep_ptr(sp_pci_resume),
576+ .freeze = pm_sleep_ptr(sp_pci_suspend),
577+ .thaw = pm_sleep_ptr(sp_pci_resume),
578+ .poweroff = pm_sleep_ptr(sp_pci_suspend),
579+ .restore_early = pm_sleep_ptr(sp_pci_restore),
580+};
567581
568582 static struct pci_driver sp_pci_driver = {
569583 .name = "ccp",
drivers/crypto/ccp/tee-dev.c+44−12
@@ -86,10 +86,34 @@ static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd) · 85 unmodified lines
8686 kfree(cmd);
8787 }
8888
89+static bool tee_send_destroy_cmd(struct psp_tee_device *tee)
90+{
91+ unsigned int reg;
92+ int ret;
93+
94+ ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL,
95+ TEE_DEFAULT_CMD_TIMEOUT, &reg);
96+ if (ret) {
97+ dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n");
98+ psp_dead = true;
99+ return false;
100+ }
101+
102+ if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
103+ dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n",
104+ FIELD_GET(PSP_CMDRESP_STS, reg));
105+ psp_dead = true;
106+ return false;
107+ }
108+
109+ return true;
110+}
111+
89112 static int tee_init_ring(struct psp_tee_device *tee)
90113 {
91114 int ring_size = MAX_RING_BUFFER_ENTRIES * sizeof(struct tee_ring_cmd);
92115 struct tee_init_ring_cmd *cmd;
116+ bool retry = false;
93117 unsigned int reg;
94118 int ret;
95119
@@ -112,6 +136,7 @@ static int tee_init_ring(struct psp_tee_device *tee) · 16 unmodified lines
112136 /* Send command buffer details to Trusted OS by writing to
113137 * CPU-PSP message registers
114138 */
139+retry_init:
115140 ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_INIT, cmd,
116141 TEE_DEFAULT_CMD_TIMEOUT, &reg);
117142 if (ret) {
@@ -122,9 +147,22 @@ static int tee_init_ring(struct psp_tee_device *tee) · 4 unmodified lines
122147 }
123148
124149 if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
150+ /*
151+ * During the hibernate resume sequence driver may have gotten loaded
152+ * but the ring not properly destroyed. If the ring doesn't work, try
153+ * to destroy and re-init once.
154+ */
155+ if (!retry && FIELD_GET(PSP_CMDRESP_STS, reg) == PSP_TEE_STS_RING_BUSY) {
156+ dev_info(tee->dev, "tee: ring init command failed with busy status, retrying\n");
157+ if (tee_send_destroy_cmd(tee)) {
158+ retry = true;
159+ goto retry_init;
160+ }
161+ }
125162 dev_err(tee->dev, "tee: ring init command failed (%#010lx)\n",
126163 FIELD_GET(PSP_CMDRESP_STS, reg));
127164 tee_free_ring(tee);
165+ psp_dead = true;
128166 ret = -EIO;
129167 }
130168
@@ -136,24 +174,13 @@ free_buf: · 5 unmodified lines
136174
137175 static void tee_destroy_ring(struct psp_tee_device *tee)
138176 {
139− unsigned int reg;
140− int ret;
141−
142177 if (!tee->rb_mgr.ring_start)
143178 return;
144179
145180 if (psp_dead)
146181 goto free_ring;
147182
148− ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL,
149− TEE_DEFAULT_CMD_TIMEOUT, &reg);
150− if (ret) {
151− dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n");
152− psp_dead = true;
153− } else if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
154− dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n",
155− FIELD_GET(PSP_CMDRESP_STS, reg));
156− }
183+ tee_send_destroy_cmd(tee);
157184
158185 free_ring:
159186 tee_free_ring(tee);
@@ -365,3 +392,8 @@ int psp_check_tee_status(void) · 205 unmodified lines
365392 return 0;
366393 }
367394 EXPORT_SYMBOL(psp_check_tee_status);
395+
396+int tee_restore(struct psp_device *psp)
397+{
398+ return tee_init_ring(psp->tee_data);
399+}
drivers/crypto/ccp/tee-dev.h+1−0
@@ -111,5 +111,6 @@ struct tee_ring_cmd { · 110 unmodified lines
111111
112112 int tee_dev_init(struct psp_device *psp);
113113 void tee_dev_destroy(struct psp_device *psp);
114+int tee_restore(struct psp_device *psp);
114115
115116 #endif /* __TEE_DEV_H__ */
drivers/hid/hid-asus.c+104−117
@@ -28,7 +28,6 @@ · 27 unmodified lines
2828 #include <linux/hid.h>
2929 #include <linux/module.h>
3030 #include <linux/platform_data/x86/asus-wmi.h>
31−#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
3231 #include <linux/input/mt.h>
3332 #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
3433 #include <linux/power_supply.h>
@@ -50,7 +49,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); · 15 unmodified lines
5049 #define FEATURE_REPORT_ID 0x0d
5150 #define INPUT_REPORT_ID 0x5d
5251 #define FEATURE_KBD_REPORT_ID 0x5a
53−#define FEATURE_KBD_REPORT_SIZE 16
52+#define FEATURE_KBD_REPORT_SIZE 64
5453 #define FEATURE_KBD_LED_REPORT_ID1 0x5d
5554 #define FEATURE_KBD_LED_REPORT_ID2 0x5e
5655
@@ -99,9 +98,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); · 42 unmodified lines
9998 #define QUIRK_T90CHI BIT(9)
10099 #define QUIRK_MEDION_E1239T BIT(10)
101100 #define QUIRK_ROG_NKEY_KEYBOARD BIT(11)
102−#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
101+#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
103102 #define QUIRK_ROG_ALLY_XPAD BIT(13)
104103 #define QUIRK_HID_FN_LOCK BIT(14)
104+#define QUIRK_ROG_NKEY_ID1ID2_INIT BIT(15)
105105
106106 #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
107107 QUIRK_NO_INIT_REPORTS | \
@@ -113,7 +113,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); · 5 unmodified lines
113113 #define TRKID_SGN ((TRKID_MAX + 1) >> 1)
114114
115115 struct asus_kbd_leds {
116− struct led_classdev cdev;
116+ struct asus_hid_listener listener;
117117 struct hid_device *hdev;
118118 struct work_struct work;
119119 unsigned int brightness;
@@ -138,7 +138,6 @@ struct asus_drvdata { · 18 unmodified lines
138138 struct input_dev *tp_kbd_input;
139139 struct asus_kbd_leds *kbd_backlight;
140140 const struct asus_touchpad_info *tp;
141− bool enable_backlight;
142141 struct power_supply *battery;
143142 struct power_supply_desc battery_desc;
144143 int battery_capacity;
@@ -363,10 +362,21 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field, · 218 unmodified lines
363362 usage->hid & HID_USAGE);
364363 }
365364
366− if (drvdata->quirks & QUIRK_HID_FN_LOCK &&
367− usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) {
368− drvdata->fn_lock = !drvdata->fn_lock;
369− schedule_work(&drvdata->fn_lock_sync_work);
365+ if (usage->type == EV_KEY && value) {
366+ switch (usage->code) {
367+ case KEY_KBDILLUMUP:
368+ return !asus_hid_event(ASUS_EV_BRTUP);
369+ case KEY_KBDILLUMDOWN:
370+ return !asus_hid_event(ASUS_EV_BRTDOWN);
371+ case KEY_KBDILLUMTOGGLE:
372+ return !asus_hid_event(ASUS_EV_BRTTOGGLE);
373+ case KEY_FN_ESC:
374+ if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
375+ drvdata->fn_lock = !drvdata->fn_lock;
376+ schedule_work(&drvdata->fn_lock_sync_work);
377+ }
378+ break;
379+ }
370380 }
371381
372382 return 0;
@@ -476,15 +486,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu · 103 unmodified lines
476486
477487 static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
478488 {
489+ /*
490+ * The handshake is first sent as a set_report, then retrieved
491+ * from a get_report. They should be equal.
492+ */
479493 const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
480494 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
481495 int ret;
482496
483497 ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
484− if (ret < 0)
485− hid_err(hdev, "Asus failed to send init command: %d\n", ret);
498+ if (ret < 0) {
499+ hid_err(hdev, "Asus handshake %02x failed to send: %d\n",
500+ report_id, ret);
501+ return ret;
502+ }
486503
487− return ret;
504+ u8 *readbuf __free(kfree) = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
505+ if (!readbuf)
506+ return -ENOMEM;
507+
508+ ret = hid_hw_raw_request(hdev, report_id, readbuf,
509+ FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
510+ HID_REQ_GET_REPORT);
511+ if (ret < 0) {
512+ hid_warn(hdev, "Asus handshake %02x failed to receive ack: %d\n",
513+ report_id, ret);
514+ } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
515+ hid_warn(hdev, "Asus handshake %02x returned invalid response: %*ph\n",
516+ report_id, FEATURE_KBD_REPORT_SIZE, readbuf);
517+ }
518+
519+ /*
520+ * Do not return error if handshake is wrong until this is
521+ * verified to work for all devices.
522+ */
523+ return 0;
488524 }
489525
490526 static int asus_kbd_get_functions(struct hid_device *hdev,
@@ -505,7 +541,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev, · 14 unmodified lines
505541 if (!readbuf)
506542 return -ENOMEM;
507543
508− ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
544+ ret = hid_hw_raw_request(hdev, report_id, readbuf,
509545 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
510546 HID_REQ_GET_REPORT);
511547 if (ret < 0) {
@@ -565,11 +601,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led) · 53 unmodified lines
565601 spin_unlock_irqrestore(&led->lock, flags);
566602 }
567603
568−static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
569− enum led_brightness brightness)
604+static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
605+ int brightness)
570606 {
571− struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
572− cdev);
607+ struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
608+ listener);
573609 unsigned long flags;
574610
575611 spin_lock_irqsave(&led->lock, flags);
@@ -579,20 +615,6 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev, · 3 unmodified lines
579615 asus_schedule_work(led);
580616 }
581617
582−static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
583−{
584− struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
585− cdev);
586− enum led_brightness brightness;
587− unsigned long flags;
588−
589− spin_lock_irqsave(&led->lock, flags);
590− brightness = led->brightness;
591− spin_unlock_irqrestore(&led->lock, flags);
592−
593− return brightness;
594−}
595−
596618 static void asus_kbd_backlight_work(struct work_struct *work)
597619 {
598620 struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
@@ -609,34 +631,6 @@ static void asus_kbd_backlight_work(struct work_struct *work) · 10 unmodified lines
609631 hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
610632 }
611633
612−/* WMI-based keyboard backlight LED control (via asus-wmi driver) takes
613− * precedence. We only activate HID-based backlight control when the
614− * WMI control is not available.
615− */
616−static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
617−{
618− struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
619− u32 value;
620− int ret;
621−
622− if (!IS_ENABLED(CONFIG_ASUS_WMI))
623− return false;
624−
625− if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD &&
626− dmi_check_system(asus_use_hid_led_dmi_ids)) {
627− hid_info(hdev, "using HID for asus::kbd_backlight\n");
628− return false;
629− }
630−
631− ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS,
632− ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value);
633− hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value);
634− if (ret)
635− return false;
636−
637− return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT);
638−}
639−
640634 /*
641635 * We don't care about any other part of the string except the version section.
642636 * Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01
@@ -736,48 +730,35 @@ static int asus_kbd_register_leds(struct hid_device *hdev) · 93 unmodified lines
736730 unsigned char kbd_func;
737731 int ret;
738732
739− if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
740− /* Initialize keyboard */
741− ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
742− if (ret < 0)
743− return ret;
744−
745− /* The LED endpoint is initialised in two HID */
746− ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
747− if (ret < 0)
748− return ret;
749−
750− ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
751− if (ret < 0)
752− return ret;
733+ ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
734+ if (ret < 0)
735+ return ret;
753736
754− if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
755− ret = asus_kbd_disable_oobe(hdev);
756− if (ret < 0)
757− return ret;
758− }
737+ /* Get keyboard functions */
738+ ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
739+ if (ret < 0)
740+ return ret;
759741
760− if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
761− intf = to_usb_interface(hdev->dev.parent);
762− udev = interface_to_usbdev(intf);
763− validate_mcu_fw_version(hdev,
764− le16_to_cpu(udev->descriptor.idProduct));
765− }
742+ /* Check for backlight support */
743+ if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
744+ return -ENODEV;
766745
767− } else {
768− /* Initialize keyboard */
769− ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
770− if (ret < 0)
771− return ret;
746+ if (drvdata->quirks & QUIRK_ROG_NKEY_ID1ID2_INIT) {
747+ asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
748+ asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
749+ }
772750
773− /* Get keyboard functions */
774− ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
751+ if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
752+ ret = asus_kbd_disable_oobe(hdev);
775753 if (ret < 0)
776754 return ret;
755+ }
777756
778− /* Check for backlight support */
779− if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
780− return -ENODEV;
757+ if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
758+ intf = to_usb_interface(hdev->dev.parent);
759+ udev = interface_to_usbdev(intf);
760+ validate_mcu_fw_version(hdev,
761+ le16_to_cpu(udev->descriptor.idProduct));
781762 }
782763
783764 drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
@@ -789,14 +770,11 @@ static int asus_kbd_register_leds(struct hid_device *hdev) · 5 unmodified lines
789770 drvdata->kbd_backlight->removed = false;
790771 drvdata->kbd_backlight->brightness = 0;
791772 drvdata->kbd_backlight->hdev = hdev;
792− drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight";
793− drvdata->kbd_backlight->cdev.max_brightness = 3;
794− drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
795− drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
773+ drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
796774 INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
797775 spin_lock_init(&drvdata->kbd_backlight->lock);
798776
799− ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
777+ ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
800778 if (ret < 0) {
801779 /* No need to have this still around */
802780 devm_kfree(&hdev->dev, drvdata->kbd_backlight);
@@ -1021,11 +999,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) · 218 unmodified lines
1021999
10221000 drvdata->input = input;
10231001
1024− if (drvdata->enable_backlight &&
1025− !asus_kbd_wmi_led_control_present(hdev) &&
1026− asus_kbd_register_leds(hdev))
1027− hid_warn(hdev, "Failed to initialize backlight.\n");
1028−
10291002 if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
10301003 drvdata->fn_lock = true;
10311004 INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock);
@@ -1104,15 +1077,6 @@ static int asus_input_mapping(struct hid_device *hdev, · 72 unmodified lines
11041077 return -1;
11051078 }
11061079
1107− /*
1108− * Check and enable backlight only on devices with UsagePage ==
1109− * 0xff31 to avoid initializing the keyboard firmware multiple
1110− * times on devices with multiple HID descriptors but same
1111− * PID/VID.
1112− */
1113− if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
1114− drvdata->enable_backlight = true;
1115−
11161080 set_bit(EV_REP, hi->input->evbit);
11171081 return 1;
11181082 }
@@ -1205,7 +1169,7 @@ static int __maybe_unused asus_resume(struct hid_device *hdev) { · 86 unmodified lines
12051169
12061170 if (drvdata->kbd_backlight) {
12071171 const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4,
1208− drvdata->kbd_backlight->cdev.brightness };
1172+ drvdata->kbd_backlight->brightness };
12091173 ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
12101174 if (ret < 0) {
12111175 hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
@@ -1229,8 +1193,11 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev) · 17 unmodified lines
12291193
12301194 static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
12311195 {
1232− int ret;
1196+ struct hid_report_enum *rep_enum;
12331197 struct asus_drvdata *drvdata;
1198+ struct hid_report *rep;
1199+ bool is_vendor = false;
1200+ int ret;
12341201
12351202 drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
12361203 if (drvdata == NULL) {
@@ -1314,12 +1281,30 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) · 77 unmodified lines
13141281 return ret;
13151282 }
13161283
1284+ /* Check for vendor for RGB init and handle generic devices properly. */
1285+ rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
1286+ list_for_each_entry(rep, &rep_enum->report_list, list) {
1287+ if ((rep->application & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR)
1288+ is_vendor = true;
1289+ }
1290+
13171291 ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
13181292 if (ret) {
13191293 hid_err(hdev, "Asus hw start failed: %d\n", ret);
13201294 return ret;
13211295 }
13221296
1297+ if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
1298+ asus_kbd_register_leds(hdev))
1299+ hid_warn(hdev, "Failed to initialize backlight.\n");
1300+
1301+ /*
1302+ * For ROG keyboards, skip rename for consistency and ->input check as
1303+ * some devices do not have inputs.
1304+ */
1305+ if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
1306+ return 0;
1307+
13231308 /*
13241309 * Check that input registration succeeded. Checking that
13251310 * HID_CLAIMED_INPUT is set prevents a UAF when all input devices
@@ -1356,6 +1341,8 @@ static void asus_remove(struct hid_device *hdev) · 30 unmodified lines
13561341 unsigned long flags;
13571342
13581343 if (drvdata->kbd_backlight) {
1344+ asus_hid_unregister_listener(&drvdata->kbd_backlight->listener);
1345+
13591346 spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
13601347 drvdata->kbd_backlight->removed = true;
13611348 spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
@@ -1490,10 +1477,10 @@ static const struct hid_device_id asus_devices[] = { · 128 unmodified lines
14901477 QUIRK_USE_KBD_BACKLIGHT },
14911478 { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
14921479 USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
1493− QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
1480+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT },
14941481 { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
14951482 USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
1496− QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK },
1483+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK | QUIRK_ROG_NKEY_ID1ID2_INIT },
14971484 { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
14981485 USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
14991486 QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
drivers/platform/mellanox/mlx-platform.c+484−0
@@ -6,6 +6,8 @@ · 5 unmodified lines
66 * Copyright (C) 2016-2018 Vadim Pasternak <vadimp@mellanox.com>
77 */
88
9+#include <linux/array_size.h>
10+#include <linux/bits.h>
911 #include <linux/device.h>
1012 #include <linux/dmi.h>
1113 #include <linux/i2c.h>
@@ -727,6 +729,16 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = { · 715 unmodified lines
727729 },
728730 };
729731
732+/* Platform hotplug dgx data */
733+static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pdb_items_data[] = {
734+ {
735+ .label = "pdb1",
736+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
737+ .mask = BIT(0),
738+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE,
739+ },
740+};
741+
730742 static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_items_data[] = {
731743 {
732744 .label = "pwr1",
@@ -776,6 +788,15 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_ng800_items_data[] = · 43 unmodified lines
776788 },
777789 };
778790
791+static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pwr_items_data[] = {
792+ {
793+ .label = "pwr1",
794+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
795+ .mask = BIT(0),
796+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE,
797+ },
798+};
799+
779800 static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_items_data[] = {
780801 {
781802 .label = "fan1",
@@ -1399,6 +1420,45 @@ static struct mlxreg_core_item mlxplat_mlxcpld_ext_items[] = { · 617 unmodified lines
13991420 }
14001421 };
14011422
1423+static struct mlxreg_core_item mlxplat_mlxcpld_ext_dgx_items[] = {
1424+ {
1425+ .data = mlxplat_mlxcpld_dgx_pdb_items_data,
1426+ .aggr_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF,
1427+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
1428+ .mask = MLXPLAT_CPLD_PSU_MASK,
1429+ .count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pdb_items_data),
1430+ .inversed = 1,
1431+ .health = false,
1432+ },
1433+ {
1434+ .data = mlxplat_mlxcpld_dgx_pwr_items_data,
1435+ .aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF,
1436+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
1437+ .mask = MLXPLAT_CPLD_PWR_MASK,
1438+ .count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pwr_items_data),
1439+ .inversed = 0,
1440+ .health = false,
1441+ },
1442+ {
1443+ .data = mlxplat_mlxcpld_default_ng_fan_items_data,
1444+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF,
1445+ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET,
1446+ .mask = MLXPLAT_CPLD_FAN_NG_MASK,
1447+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_fan_items_data),
1448+ .inversed = 1,
1449+ .health = false,
1450+ },
1451+ {
1452+ .data = mlxplat_mlxcpld_default_asic_items_data,
1453+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF,
1454+ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET,
1455+ .mask = MLXPLAT_CPLD_ASIC_MASK,
1456+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_asic_items_data),
1457+ .inversed = 0,
1458+ .health = true,
1459+ },
1460+};
1461+
14021462 static struct mlxreg_core_item mlxplat_mlxcpld_ng800_items[] = {
14031463 {
14041464 .data = mlxplat_mlxcpld_default_ng_psu_items_data,
@@ -1450,6 +1510,16 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ext_data = { · 45 unmodified lines
14501510 .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2,
14511511 };
14521512
1513+static
1514+struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_dgx_ext_data = {
1515+ .items = mlxplat_mlxcpld_ext_dgx_items,
1516+ .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_dgx_items),
1517+ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET,
1518+ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX,
1519+ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET,
1520+ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2,
1521+};
1522+
14531523 static
14541524 struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_data = {
14551525 .items = mlxplat_mlxcpld_ng800_items,
@@ -4625,6 +4695,359 @@ static struct mlxreg_core_platform_data mlxplat_default_ng_regs_io_data = { · 3169 unmodified lines
46254695 .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_regs_io_data),
46264696 };
46274697
4698+/* Platform register access for next generation systems families data */
4699+static struct mlxreg_core_data mlxplat_mlxcpld_dgx_ng_regs_io_data[] = {
4700+ {
4701+ .label = "cpld1_version",
4702+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET,
4703+ .bit = GENMASK(7, 0),
4704+ .mode = 0444,
4705+ },
4706+ {
4707+ .label = "cpld2_version",
4708+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET,
4709+ .bit = GENMASK(7, 0),
4710+ .mode = 0444,
4711+ },
4712+ {
4713+ .label = "cpld3_version",
4714+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET,
4715+ .bit = GENMASK(7, 0),
4716+ .mode = 0444,
4717+ },
4718+ {
4719+ .label = "cpld4_version",
4720+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET,
4721+ .bit = GENMASK(7, 0),
4722+ .mode = 0444,
4723+ },
4724+ {
4725+ .label = "cpld1_pn",
4726+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET,
4727+ .bit = GENMASK(15, 0),
4728+ .mode = 0444,
4729+ .regnum = 2,
4730+ },
4731+ {
4732+ .label = "cpld2_pn",
4733+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET,
4734+ .bit = GENMASK(15, 0),
4735+ .mode = 0444,
4736+ .regnum = 2,
4737+ },
4738+ {
4739+ .label = "cpld3_pn",
4740+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET,
4741+ .bit = GENMASK(15, 0),
4742+ .mode = 0444,
4743+ .regnum = 2,
4744+ },
4745+ {
4746+ .label = "cpld4_pn",
4747+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET,
4748+ .bit = GENMASK(15, 0),
4749+ .mode = 0444,
4750+ .regnum = 2,
4751+ },
4752+ {
4753+ .label = "cpld1_version_min",
4754+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET,
4755+ .bit = GENMASK(7, 0),
4756+ .mode = 0444,
4757+ },
4758+ {
4759+ .label = "cpld2_version_min",
4760+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET,
4761+ .bit = GENMASK(7, 0),
4762+ .mode = 0444,
4763+ },
4764+ {
4765+ .label = "cpld3_version_min",
4766+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET,
4767+ .bit = GENMASK(7, 0),
4768+ .mode = 0444,
4769+ },
4770+ {
4771+ .label = "cpld4_version_min",
4772+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET,
4773+ .bit = GENMASK(7, 0),
4774+ .mode = 0444,
4775+ },
4776+ {
4777+ .label = "asic_reset",
4778+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET,
4779+ .mask = GENMASK(7, 0) & ~BIT(3),
4780+ .mode = 0200,
4781+ },
4782+ {
4783+ .label = "reset_long_pb",
4784+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
4785+ .mask = GENMASK(7, 0) & ~BIT(0),
4786+ .mode = 0444,
4787+ },
4788+ {
4789+ .label = "reset_short_pb",
4790+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
4791+ .mask = GENMASK(7, 0) & ~BIT(1),
4792+ .mode = 0444,
4793+ },
4794+ {
4795+ .label = "reset_aux_pwr_or_ref",
4796+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
4797+ .mask = GENMASK(7, 0) & ~BIT(2),
4798+ .mode = 0444,
4799+ },
4800+ {
4801+ .label = "reset_swb_dc_dc_pwr_fail",
4802+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
4803+ .mask = GENMASK(7, 0) & ~BIT(3),
4804+ .mode = 0444,
4805+ },
4806+ {
4807+ .label = "reset_from_asic",
4808+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
4809+ .mask = GENMASK(7, 0) & ~BIT(5),
4810+ .mode = 0444,
4811+ },
4812+ {
4813+ .label = "reset_swb_wd",
4814+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
4815+ .mask = GENMASK(7, 0) & ~BIT(6),
4816+ .mode = 0444,
4817+ },
4818+ {
4819+ .label = "reset_asic_thermal",
4820+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
4821+ .mask = GENMASK(7, 0) & ~BIT(7),
4822+ .mode = 0444,
4823+ },
4824+ {
4825+ .label = "reset_sw_reset",
4826+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
4827+ .mask = GENMASK(7, 0) & ~BIT(0),
4828+ .mode = 0444,
4829+ },
4830+ {
4831+ .label = "reset_comex_pwr_fail",
4832+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
4833+ .mask = GENMASK(7, 0) & ~BIT(3),
4834+ .mode = 0444,
4835+ },
4836+ {
4837+ .label = "reset_platform",
4838+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
4839+ .mask = GENMASK(7, 0) & ~BIT(4),
4840+ .mode = 0444,
4841+ },
4842+ {
4843+ .label = "reset_soc",
4844+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
4845+ .mask = GENMASK(7, 0) & ~BIT(5),
4846+ .mode = 0444,
4847+ },
4848+ {
4849+ .label = "reset_comex_wd",
4850+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
4851+ .mask = GENMASK(7, 0) & ~BIT(6),
4852+ .mode = 0444,
4853+ },
4854+ {
4855+ .label = "reset_system",
4856+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
4857+ .mask = GENMASK(7, 0) & ~BIT(1),
4858+ .mode = 0444,
4859+ },
4860+ {
4861+ .label = "reset_sw_pwr_off",
4862+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
4863+ .mask = GENMASK(7, 0) & ~BIT(2),
4864+ .mode = 0444,
4865+ },
4866+ {
4867+ .label = "reset_comex_thermal",
4868+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
4869+ .mask = GENMASK(7, 0) & ~BIT(3),
4870+ .mode = 0444,
4871+ },
4872+ {
4873+ .label = "reset_reload_bios",
4874+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
4875+ .mask = GENMASK(7, 0) & ~BIT(5),
4876+ .mode = 0444,
4877+ },
4878+ {
4879+ .label = "reset_pdb_pwr_fail",
4880+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
4881+ .mask = GENMASK(7, 0) & ~BIT(6),
4882+ .mode = 0444,
4883+ },
4884+ {
4885+ .label = "pdb_reset_stby",
4886+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
4887+ .mask = GENMASK(7, 0) & ~BIT(0),
4888+ .mode = 0200,
4889+ },
4890+ {
4891+ .label = "pwr_cycle",
4892+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
4893+ .mask = GENMASK(7, 0) & ~BIT(2),
4894+ .mode = 0200,
4895+ },
4896+ {
4897+ .label = "pwr_down",
4898+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
4899+ .mask = GENMASK(7, 0) & ~BIT(3),
4900+ .mode = 0200,
4901+ },
4902+ {
4903+ .label = "deep_pwr_cycle",
4904+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
4905+ .mask = GENMASK(7, 0) & ~BIT(5),
4906+ .mode = 0200,
4907+ },
4908+ {
4909+ .label = "latch_reset",
4910+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
4911+ .mask = GENMASK(7, 0) & ~BIT(6),
4912+ .mode = 0200,
4913+ },
4914+ {
4915+ .label = "jtag_cap",
4916+ .reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET,
4917+ .mask = MLXPLAT_CPLD_FU_CAP_MASK,
4918+ .bit = 1,
4919+ .mode = 0444,
4920+ },
4921+ {
4922+ .label = "jtag_enable",
4923+ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET,
4924+ .mask = GENMASK(7, 0) & ~BIT(4),
4925+ .mode = 0644,
4926+ },
4927+ {
4928+ .label = "dbg1",
4929+ .reg = MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET,
4930+ .bit = GENMASK(7, 0),
4931+ .mode = 0644,
4932+ },
4933+ {
4934+ .label = "dbg2",
4935+ .reg = MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET,
4936+ .bit = GENMASK(7, 0),
4937+ .mode = 0644,
4938+ },
4939+ {
4940+ .label = "dbg3",
4941+ .reg = MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET,
4942+ .bit = GENMASK(7, 0),
4943+ .mode = 0644,
4944+ },
4945+ {
4946+ .label = "dbg4",
4947+ .reg = MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET,
4948+ .bit = GENMASK(7, 0),
4949+ .mode = 0644,
4950+ },
4951+ {
4952+ .label = "asic_health",
4953+ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET,
4954+ .mask = MLXPLAT_CPLD_ASIC_MASK,
4955+ .bit = 1,
4956+ .mode = 0444,
4957+ },
4958+ {
4959+ .label = "fan_dir",
4960+ .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION,
4961+ .bit = GENMASK(7, 0),
4962+ .mode = 0444,
4963+ },
4964+ {
4965+ .label = "bios_safe_mode",
4966+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
4967+ .mask = GENMASK(7, 0) & ~BIT(4),
4968+ .mode = 0444,
4969+ },
4970+ {
4971+ .label = "bios_active_image",
4972+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
4973+ .mask = GENMASK(7, 0) & ~BIT(5),
4974+ .mode = 0444,
4975+ },
4976+ {
4977+ .label = "bios_auth_fail",
4978+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
4979+ .mask = GENMASK(7, 0) & ~BIT(6),
4980+ .mode = 0444,
4981+ },
4982+ {
4983+ .label = "bios_upgrade_fail",
4984+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
4985+ .mask = GENMASK(7, 0) & ~BIT(7),
4986+ .mode = 0444,
4987+ },
4988+ {
4989+ .label = "voltreg_update_status",
4990+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET,
4991+ .mask = MLXPLAT_CPLD_VOLTREG_UPD_MASK,
4992+ .bit = 5,
4993+ .mode = 0444,
4994+ },
4995+ {
4996+ .label = "pwr_converter_prog_en",
4997+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
4998+ .mask = GENMASK(7, 0) & ~BIT(0),
4999+ .mode = 0644,
5000+ .secured = 1,
5001+ },
5002+ {
5003+ .label = "vpd_wp",
5004+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
5005+ .mask = GENMASK(7, 0) & ~BIT(3),
5006+ .mode = 0644,
5007+ },
5008+ {
5009+ .label = "pcie_asic_reset_dis",
5010+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
5011+ .mask = GENMASK(7, 0) & ~BIT(4),
5012+ .mode = 0644,
5013+ },
5014+ {
5015+ .label = "shutdown_unlock",
5016+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
5017+ .mask = GENMASK(7, 0) & ~BIT(5),
5018+ .mode = 0644,
5019+ },
5020+ {
5021+ .label = "config1",
5022+ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET,
5023+ .bit = GENMASK(7, 0),
5024+ .mode = 0444,
5025+ },
5026+ {
5027+ .label = "config2",
5028+ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET,
5029+ .bit = GENMASK(7, 0),
5030+ .mode = 0444,
5031+ },
5032+ {
5033+ .label = "config3",
5034+ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET,
5035+ .bit = GENMASK(7, 0),
5036+ .mode = 0444,
5037+ },
5038+ {
5039+ .label = "ufm_version",
5040+ .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET,
5041+ .bit = GENMASK(7, 0),
5042+ .mode = 0444,
5043+ },
5044+};
5045+
5046+static struct mlxreg_core_platform_data mlxplat_dgx_ng_regs_io_data = {
5047+ .data = mlxplat_mlxcpld_dgx_ng_regs_io_data,
5048+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_dgx_ng_regs_io_data),
5049+};
5050+
46285051 /* Platform register access for modular systems families data */
46295052 static struct mlxreg_core_data mlxplat_mlxcpld_modular_regs_io_data[] = {
46305053 {
@@ -7239,6 +7662,32 @@ static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi) · 2608 unmodified lines
72397662 return mlxplat_register_platform_device();
72407663 }
72417664
7665+static int __init mlxplat_dmi_ng400_dgx_matched(const struct dmi_system_id *dmi)
7666+{
7667+ int i;
7668+
7669+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM;
7670+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data);
7671+ mlxplat_mux_data = mlxplat_default_mux_data;
7672+ for (i = 0; i < mlxplat_mux_num; i++) {
7673+ mlxplat_mux_data[i].values = mlxplat_msn21xx_channels;
7674+ mlxplat_mux_data[i].n_values =
7675+ ARRAY_SIZE(mlxplat_msn21xx_channels);
7676+ }
7677+ mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data;
7678+ mlxplat_hotplug->deferred_nr =
7679+ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
7680+ mlxplat_led = &mlxplat_default_ng_led_data;
7681+ mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data;
7682+ mlxplat_fan = &mlxplat_default_fan_data;
7683+ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++)
7684+ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i];
7685+ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
7686+ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
7687+
7688+ return mlxplat_register_platform_device();
7689+}
7690+
72427691 static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi)
72437692 {
72447693 int i;
@@ -7323,6 +7772,27 @@ static int __init mlxplat_dmi_ng800_matched(const struct dmi_system_id *dmi) · 78 unmodified lines
73237772 return mlxplat_register_platform_device();
73247773 }
73257774
7775+static int __init mlxplat_dmi_ng800_dgx_matched(const struct dmi_system_id *dmi)
7776+{
7777+ int i;
7778+
7779+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM;
7780+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_ng800_mux_data);
7781+ mlxplat_mux_data = mlxplat_ng800_mux_data;
7782+ mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data;
7783+ mlxplat_hotplug->deferred_nr =
7784+ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
7785+ mlxplat_led = &mlxplat_default_ng_led_data;
7786+ mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data;
7787+ mlxplat_fan = &mlxplat_default_fan_data;
7788+ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++)
7789+ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i];
7790+ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
7791+ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
7792+
7793+ return mlxplat_register_platform_device();
7794+}
7795+
73267796 static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi)
73277797 {
73287798 int i;
@@ -7458,6 +7928,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { · 129 unmodified lines
74587928 DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI142"),
74597929 },
74607930 },
7931+ {
7932+ .callback = mlxplat_dmi_ng400_dgx_matched,
7933+ .matches = {
7934+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0010"),
7935+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI173"),
7936+ },
7937+ },
74617938 {
74627939 .callback = mlxplat_dmi_ng400_matched,
74637940 .matches = {
@@ -7470,6 +7947,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { · 6 unmodified lines
74707947 DMI_MATCH(DMI_BOARD_NAME, "VMOD0011"),
74717948 },
74727949 },
7950+ {
7951+ .callback = mlxplat_dmi_ng800_dgx_matched,
7952+ .matches = {
7953+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0013"),
7954+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI174"),
7955+ },
7956+ },
74737957 {
74747958 .callback = mlxplat_dmi_ng800_matched,
74757959 .matches = {
drivers/platform/surface/surface_aggregator_registry.c+18−0
@@ -406,6 +406,22 @@ static const struct software_node *ssam_node_group_sp9_5g[] = { · 405 unmodified lines
406406 NULL,
407407 };
408408
409+/* Devices for Surface Pro 11 (ARM/QCOM) */
410+static const struct software_node *ssam_node_group_sp11[] = {
411+ &ssam_node_root,
412+ &ssam_node_hub_kip,
413+ &ssam_node_bat_ac,
414+ &ssam_node_bat_main,
415+ &ssam_node_tmp_sensors,
416+ &ssam_node_hid_kip_keyboard,
417+ &ssam_node_hid_kip_penstash,
418+ &ssam_node_hid_kip_touchpad,
419+ &ssam_node_hid_kip_fwupd,
420+ &ssam_node_hid_sam_sensors,
421+ &ssam_node_kip_tablet_switch,
422+ NULL,
423+};
424+
409425 /* -- SSAM platform/meta-hub driver. ---------------------------------------- */
410426
411427 static const struct acpi_device_id ssam_platform_hub_acpi_match[] = {
@@ -482,6 +498,8 @@ MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_acpi_match); · 70 unmodified lines
482498 static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
483499 /* Surface Pro 9 5G (ARM/QCOM) */
484500 { .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g },
501+ /* Surface Pro 11 (ARM/QCOM) */
502+ { .compatible = "microsoft,denali", (void *)ssam_node_group_sp11 },
485503 /* Surface Laptop 7 */
486504 { .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
487505 { .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },
drivers/platform/surface/surfacepro3_button.c+5−6
@@ -10,6 +10,7 @@ · 9 unmodified lines
1010 #include <linux/kernel.h>
1111 #include <linux/module.h>
1212 #include <linux/init.h>
13+#include <linux/string.h>
1314 #include <linux/types.h>
1415 #include <linux/input.h>
1516 #include <linux/acpi.h>
@@ -189,7 +190,6 @@ static int surface_button_add(struct acpi_device *device) · 173 unmodified lines
189190 struct surface_button *button;
190191 struct input_dev *input;
191192 const char *hid = acpi_device_hid(device);
192− char *name;
193193 int error;
194194
195195 if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME,
@@ -210,11 +210,10 @@ static int surface_button_add(struct acpi_device *device) · 14 unmodified lines
210210 goto err_free_button;
211211 }
212212
213− name = acpi_device_name(device);
214− strcpy(name, SURFACE_BUTTON_DEVICE_NAME);
213+ strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME);
215214 snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid);
216215
217− input->name = name;
216+ input->name = acpi_device_name(device);
218217 input->phys = button->phys;
219218 input->id.bustype = BUS_HOST;
220219 input->dev.parent = &device->dev;
@@ -228,8 +227,8 @@ static int surface_button_add(struct acpi_device *device) · 7 unmodified lines
228227 goto err_free_input;
229228
230229 device_init_wakeup(&device->dev, true);
231− dev_info(&device->dev,
232− "%s [%s]\n", name, acpi_device_bid(device));
230+ dev_info(&device->dev, "%s [%s]\n", acpi_device_name(device),
231+ acpi_device_bid(device));
233232 return 0;
234233
235234 err_free_input:
drivers/platform/wmi/Kconfig+3−0
@@ -6,6 +6,7 @@ · 5 unmodified lines
66 menuconfig ACPI_WMI
77 tristate "ACPI-WMI support"
88 depends on ACPI && X86
9+ select NLS
910 help
1011 This option enables support for the ACPI-WMI driver core.
1112
@@ -31,4 +32,6 @@ config ACPI_WMI_LEGACY_DEVICE_NAMES · 19 unmodified lines
3132 userspace applications but will cause the registration of WMI devices with
3233 the same GUID to fail in some corner cases.
3334
35+source "drivers/platform/wmi/tests/Kconfig"
36+
3437 endif # ACPI_WMI
drivers/platform/wmi/Makefile+4−1
@@ -4,5 +4,8 @@ · 3 unmodified lines
44 # ACPI WMI core
55 #
66
7−wmi-y := core.o
7+wmi-y := core.o marshalling.o string.o
88 obj-$(CONFIG_ACPI_WMI) += wmi.o
9+
10+# Unit tests
11+obj-y += tests/
drivers/platform/wmi/core.c+156−4
@@ -23,6 +23,7 @@ · 22 unmodified lines
2323 #include <linux/idr.h>
2424 #include <linux/init.h>
2525 #include <linux/kernel.h>
26+#include <linux/limits.h>
2627 #include <linux/module.h>
2728 #include <linux/platform_device.h>
2829 #include <linux/rwsem.h>
@@ -33,6 +34,8 @@ · 4 unmodified lines
3334 #include <linux/wmi.h>
3435 #include <linux/fs.h>
3536
37+#include "internal.h"
38+
3639 MODULE_AUTHOR("Carlos Corbacho");
3740 MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
3841 MODULE_LICENSE("GPL");
@@ -302,7 +305,7 @@ acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method · 263 unmodified lines
302305 EXPORT_SYMBOL_GPL(wmi_evaluate_method);
303306
304307 /**
305− * wmidev_evaluate_method - Evaluate a WMI method
308+ * wmidev_evaluate_method - Evaluate a WMI method (deprecated)
306309 * @wdev: A wmi bus device from a driver
307310 * @instance: Instance index
308311 * @method_id: Method ID to call
@@ -360,6 +363,70 @@ acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 met · 51 unmodified lines
360363 }
361364 EXPORT_SYMBOL_GPL(wmidev_evaluate_method);
362365
366+/**
367+ * wmidev_invoke_method - Invoke a WMI method
368+ * @wdev: A wmi bus device from a driver
369+ * @instance: Instance index
370+ * @method_id: Method ID to call
371+ * @in: Mandatory WMI buffer containing input for the method call
372+ * @out: Optional WMI buffer to return the method results
373+ *
374+ * Invoke a WMI method, the caller must free the resulting data inside @out.
375+ * Said data is guaranteed to be aligned on a 8-byte boundary.
376+ *
377+ * Return: 0 on success or negative error code on failure.
378+ */
379+int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id,
380+ const struct wmi_buffer *in, struct wmi_buffer *out)
381+{
382+ struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
383+ struct acpi_buffer aout = { ACPI_ALLOCATE_BUFFER, NULL };
384+ struct acpi_buffer ain;
385+ union acpi_object *obj;
386+ acpi_status status;
387+ int ret;
388+
389+ if (wblock->gblock.flags & ACPI_WMI_STRING) {
390+ ret = wmi_marshal_string(in, &ain);
391+ if (ret < 0)
392+ return ret;
393+ } else {
394+ if (in->length > U32_MAX)
395+ return -E2BIG;
396+
397+ ain.length = in->length;
398+ ain.pointer = in->data;
399+ }
400+
401+ if (out)
402+ status = wmidev_evaluate_method(wdev, instance, method_id, &ain, &aout);
403+ else
404+ status = wmidev_evaluate_method(wdev, instance, method_id, &ain, NULL);
405+
406+ if (wblock->gblock.flags & ACPI_WMI_STRING)
407+ kfree(ain.pointer);
408+
409+ if (ACPI_FAILURE(status))
410+ return -EIO;
411+
412+ if (!out)
413+ return 0;
414+
415+ obj = aout.pointer;
416+ if (!obj) {
417+ out->length = 0;
418+ out->data = ZERO_SIZE_PTR;
419+
420+ return 0;
421+ }
422+
423+ ret = wmi_unmarshal_acpi_object(obj, out);
424+ kfree(obj);
425+
426+ return ret;
427+}
428+EXPORT_SYMBOL_GPL(wmidev_invoke_method);
429+
363430 static acpi_status __query_block(struct wmi_block *wblock, u8 instance,
364431 struct acpi_buffer *out)
365432 {
@@ -432,7 +499,7 @@ acpi_status wmi_query_block(const char *guid_string, u8 instance, · 66 unmodified lines
432499 EXPORT_SYMBOL_GPL(wmi_query_block);
433500
434501 /**
435− * wmidev_block_query - Return contents of a WMI block
502+ * wmidev_block_query - Return contents of a WMI block (deprectated)
436503 * @wdev: A wmi bus device from a driver
437504 * @instance: Instance index
438505 *
@@ -452,6 +519,33 @@ union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance) · 13 unmodified lines
452519 }
453520 EXPORT_SYMBOL_GPL(wmidev_block_query);
454521
522+/**
523+ * wmidev_query_block - Return contents of a WMI data block
524+ * @wdev: A wmi bus device from a driver
525+ * @instance: Instance index
526+ * @out: WMI buffer to fill
527+ *
528+ * Query a WMI data block, the caller must free the resulting data inside @out.
529+ * Said data is guaranteed to be aligned on a 8-byte boundary.
530+ *
531+ * Return: 0 on success or a negative error code on failure.
532+ */
533+int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out)
534+{
535+ union acpi_object *obj;
536+ int ret;
537+
538+ obj = wmidev_block_query(wdev, instance);
539+ if (!obj)
540+ return -EIO;
541+
542+ ret = wmi_unmarshal_acpi_object(obj, out);
543+ kfree(obj);
544+
545+ return ret;
546+}
547+EXPORT_SYMBOL_GPL(wmidev_query_block);
548+
455549 /**
456550 * wmi_set_block - Write to a WMI block (deprecated)
457551 * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
@@ -486,7 +580,7 @@ acpi_status wmi_set_block(const char *guid_string, u8 instance, const struct acp · 28 unmodified lines
486580 EXPORT_SYMBOL_GPL(wmi_set_block);
487581
488582 /**
489− * wmidev_block_set - Write to a WMI block
583+ * wmidev_block_set - Write to a WMI block (deprecated)
490584 * @wdev: A wmi bus device from a driver
491585 * @instance: Instance index
492586 * @in: Buffer containing new values for the data block
@@ -535,6 +629,46 @@ acpi_status wmidev_block_set(struct wmi_device *wdev, u8 instance, const struct · 42 unmodified lines
535629 }
536630 EXPORT_SYMBOL_GPL(wmidev_block_set);
537631
632+/**
633+ * wmidev_set_block - Write to a WMI data block
634+ * @wdev: A wmi bus device from a driver
635+ * @instance: Instance index
636+ * @in: WMI buffer containing new values for the data block
637+ *
638+ * Write the content of @in into a WMI data block.
639+ *
640+ * Return: 0 on success or negative error code on failure.
641+ */
642+int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in)
643+{
644+ struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
645+ struct acpi_buffer buffer;
646+ acpi_status status;
647+ int ret;
648+
649+ if (wblock->gblock.flags & ACPI_WMI_STRING) {
650+ ret = wmi_marshal_string(in, &buffer);
651+ if (ret < 0)
652+ return ret;
653+ } else {
654+ if (in->length > U32_MAX)
655+ return -E2BIG;
656+
657+ buffer.length = in->length;
658+ buffer.pointer = in->data;
659+ }
660+
661+ status = wmidev_block_set(wdev, instance, &buffer);
662+ if (wblock->gblock.flags & ACPI_WMI_STRING)
663+ kfree(buffer.pointer);
664+
665+ if (ACPI_FAILURE(status))
666+ return -EIO;
667+
668+ return 0;
669+}
670+EXPORT_SYMBOL_GPL(wmidev_set_block);
671+
538672 /**
539673 * wmi_install_notify_handler - Register handler for WMI events (deprecated)
540674 * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
@@ -862,7 +996,7 @@ static int wmi_dev_probe(struct device *dev) · 321 unmodified lines
862996 return -ENODEV;
863997 }
864998
865− if (wdriver->notify) {
999+ if (wdriver->notify || wdriver->notify_new) {
8661000 if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data)
8671001 return -ENODEV;
8681002 }
@@ -1221,6 +1355,8 @@ static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj · 352 unmodified lines
12211355 static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj)
12221356 {
12231357 struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver);
1358+ struct wmi_buffer buffer;
1359+ int ret;
12241360
12251361 if (!obj && !driver->no_notify_data) {
12261362 dev_warn(&wblock->dev.dev, "Event contains no event data\n");
@@ -1229,6 +1365,22 @@ static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj) · 2 unmodified lines
12291365
12301366 if (driver->notify)
12311367 driver->notify(&wblock->dev, obj);
1368+
1369+ if (driver->notify_new) {
1370+ if (!obj) {
1371+ driver->notify_new(&wblock->dev, NULL);
1372+ return;
1373+ }
1374+
1375+ ret = wmi_unmarshal_acpi_object(obj, &buffer);
1376+ if (ret < 0) {
1377+ dev_warn(&wblock->dev.dev, "Failed to unmarshal event data: %d\n", ret);
1378+ return;
1379+ }
1380+
1381+ driver->notify_new(&wblock->dev, &buffer);
1382+ kfree(buffer.data);
1383+ }
12321384 }
12331385
12341386 static int wmi_notify_device(struct device *dev, void *data)
drivers/platform/wmi/internal.h+17−0
@@ -0,0 +1,17 @@
1+/* SPDX-License-Identifier: GPL-2.0-or-later */
2+/*
3+ * Internal interfaces used by the WMI core.
4+ *
5+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
6+ */
7+
8+#ifndef _WMI_INTERNAL_H_
9+#define _WMI_INTERNAL_H_
10+
11+union acpi_object;
12+struct wmi_buffer;
13+
14+int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer);
15+int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out);
16+
17+#endif /* _WMI_INTERNAL_H_ */
drivers/platform/wmi/marshalling.c+247−0
@@ -0,0 +1,247 @@
1+// SPDX-License-Identifier: GPL-2.0-or-later
2+/*
3+ * ACPI-WMI buffer marshalling.
4+ *
5+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
6+ */
7+
8+#include <linux/acpi.h>
9+#include <linux/align.h>
10+#include <linux/math.h>
11+#include <linux/overflow.h>
12+#include <linux/slab.h>
13+#include <linux/unaligned.h>
14+#include <linux/wmi.h>
15+
16+#include <kunit/visibility.h>
17+
18+#include "internal.h"
19+
20+static int wmi_adjust_buffer_length(size_t *length, const union acpi_object *obj)
21+{
22+ size_t alignment, size;
23+
24+ switch (obj->type) {
25+ case ACPI_TYPE_INTEGER:
26+ /*
27+ * Integers are threated as 32 bit even if the ACPI DSDT
28+ * declares 64 bit integer width.
29+ */
30+ alignment = 4;
31+ size = sizeof(u32);
32+ break;
33+ case ACPI_TYPE_STRING:
34+ /*
35+ * Strings begin with a single little-endian 16-bit field containing
36+ * the string length in bytes and are encoded as UTF-16LE with a terminating
37+ * nul character.
38+ */
39+ if (obj->string.length + 1 > U16_MAX / 2)
40+ return -EOVERFLOW;
41+
42+ alignment = 2;
43+ size = struct_size_t(struct wmi_string, chars, obj->string.length + 1);
44+ break;
45+ case ACPI_TYPE_BUFFER:
46+ /*
47+ * Buffers are copied as-is.
48+ */
49+ alignment = 1;
50+ size = obj->buffer.length;
51+ break;
52+ default:
53+ return -EPROTO;
54+ }
55+
56+ *length = size_add(ALIGN(*length, alignment), size);
57+
58+ return 0;
59+}
60+
61+static int wmi_obj_get_buffer_length(const union acpi_object *obj, size_t *length)
62+{
63+ size_t total = 0;
64+ int ret;
65+
66+ if (obj->type == ACPI_TYPE_PACKAGE) {
67+ for (int i = 0; i < obj->package.count; i++) {
68+ ret = wmi_adjust_buffer_length(&total, &obj->package.elements[i]);
69+ if (ret < 0)
70+ return ret;
71+ }
72+ } else {
73+ ret = wmi_adjust_buffer_length(&total, obj);
74+ if (ret < 0)
75+ return ret;
76+ }
77+
78+ *length = total;
79+
80+ return 0;
81+}
82+
83+static int wmi_obj_transform_simple(const union acpi_object *obj, u8 *buffer, size_t *consumed)
84+{
85+ struct wmi_string *string;
86+ size_t length;
87+ __le32 value;
88+ u8 *aligned;
89+
90+ switch (obj->type) {
91+ case ACPI_TYPE_INTEGER:
92+ aligned = PTR_ALIGN(buffer, 4);
93+ length = sizeof(value);
94+
95+ value = cpu_to_le32(obj->integer.value);
96+ memcpy(aligned, &value, length);
97+ break;
98+ case ACPI_TYPE_STRING:
99+ aligned = PTR_ALIGN(buffer, 2);
100+ string = (struct wmi_string *)aligned;
101+ length = struct_size(string, chars, obj->string.length + 1);
102+
103+ /* We do not have to worry about unaligned accesses here as the WMI
104+ * string will already be aligned on a two-byte boundary.
105+ */
106+ string->length = cpu_to_le16((obj->string.length + 1) * 2);
107+ for (int i = 0; i < obj->string.length; i++)
108+ string->chars[i] = cpu_to_le16(obj->string.pointer[i]);
109+
110+ /*
111+ * The Windows WMI-ACPI driver always emits a terminating nul character,
112+ * so we emulate this behavior here as well.
113+ */
114+ string->chars[obj->string.length] = '\0';
115+ break;
116+ case ACPI_TYPE_BUFFER:
117+ aligned = buffer;
118+ length = obj->buffer.length;
119+
120+ memcpy(aligned, obj->buffer.pointer, length);
121+ break;
122+ default:
123+ return -EPROTO;
124+ }
125+
126+ *consumed = (aligned - buffer) + length;
127+
128+ return 0;
129+}
130+
131+static int wmi_obj_transform(const union acpi_object *obj, u8 *buffer)
132+{
133+ size_t consumed;
134+ int ret;
135+
136+ if (obj->type == ACPI_TYPE_PACKAGE) {
137+ for (int i = 0; i < obj->package.count; i++) {
138+ ret = wmi_obj_transform_simple(&obj->package.elements[i], buffer,
139+ &consumed);
140+ if (ret < 0)
141+ return ret;
142+
143+ buffer += consumed;
144+ }
145+ } else {
146+ ret = wmi_obj_transform_simple(obj, buffer, &consumed);
147+ if (ret < 0)
148+ return ret;
149+ }
150+
151+ return 0;
152+}
153+
154+int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer)
155+{
156+ size_t length, alloc_length;
157+ u8 *data;
158+ int ret;
159+
160+ ret = wmi_obj_get_buffer_length(obj, &length);
161+ if (ret < 0)
162+ return ret;
163+
164+ if (ARCH_KMALLOC_MINALIGN < 8) {
165+ /*
166+ * kmalloc() guarantees that the alignment of the resulting memory allocation is at
167+ * least the largest power-of-two divisor of the allocation size. The WMI buffer
168+ * data needs to be aligned on a 8 byte boundary to properly support 64-bit WMI
169+ * integers, so we have to round the allocation size to the next multiple of 8.
170+ */
171+ alloc_length = round_up(length, 8);
172+ } else {
173+ alloc_length = length;
174+ }
175+
176+ data = kzalloc(alloc_length, GFP_KERNEL);
177+ if (!data)
178+ return -ENOMEM;
179+
180+ ret = wmi_obj_transform(obj, data);
181+ if (ret < 0) {
182+ kfree(data);
183+ return ret;
184+ }
185+
186+ buffer->length = length;
187+ buffer->data = data;
188+
189+ return 0;
190+}
191+EXPORT_SYMBOL_IF_KUNIT(wmi_unmarshal_acpi_object);
192+
193+int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out)
194+{
195+ const struct wmi_string *string;
196+ u16 length, value;
197+ size_t chars;
198+ char *str;
199+
200+ if (buffer->length < sizeof(*string))
201+ return -ENODATA;
202+
203+ string = buffer->data;
204+ length = get_unaligned_le16(&string->length);
205+ if (buffer->length < sizeof(*string) + length)
206+ return -ENODATA;
207+
208+ /* Each character needs to be 16 bits long */
209+ if (length % 2)
210+ return -EINVAL;
211+
212+ chars = length / 2;
213+ str = kmalloc(chars + 1, GFP_KERNEL);
214+ if (!str)
215+ return -ENOMEM;
216+
217+ for (int i = 0; i < chars; i++) {
218+ value = get_unaligned_le16(&string->chars[i]);
219+
220+ /* ACPI only accepts ASCII strings */
221+ if (value > 0x7F) {
222+ kfree(str);
223+ return -EINVAL;
224+ }
225+
226+ str[i] = value & 0xFF;
227+
228+ /*
229+ * ACPI strings should only contain a single nul character at the end.
230+ * Because of this we must not copy any padding from the WMI string.
231+ */
232+ if (!value) {
233+ /* ACPICA wants the length of the string without the nul character */
234+ out->length = i;
235+ out->pointer = str;
236+ return 0;
237+ }
238+ }
239+
240+ str[chars] = '\0';
241+
242+ out->length = chars;
243+ out->pointer = str;
244+
245+ return 0;
246+}
247+EXPORT_SYMBOL_IF_KUNIT(wmi_marshal_string);
drivers/platform/wmi/string.c+92−0
@@ -0,0 +1,92 @@
1+// SPDX-License-Identifier: GPL-2.0-or-later
2+/*
3+ * WMI string utility functions.
4+ *
5+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
6+ */
7+
8+#include <linux/build_bug.h>
9+#include <linux/compiler_types.h>
10+#include <linux/err.h>
11+#include <linux/export.h>
12+#include <linux/nls.h>
13+#include <linux/limits.h>
14+#include <linux/types.h>
15+#include <linux/wmi.h>
16+
17+#include <asm/byteorder.h>
18+
19+static_assert(sizeof(__le16) == sizeof(wchar_t));
20+
21+/**
22+ * wmi_string_to_utf8s - Convert a WMI string into a UTF8 string.
23+ * @str: WMI string representation
24+ * @dst: Buffer to fill with UTF8 characters
25+ * @length: Length of the destination buffer
26+ *
27+ * Convert as WMI string into a standard UTF8 string. The conversion will stop
28+ * once a NUL character is detected or when the buffer is full. Any invalid UTF16
29+ * characters will be ignored. The resulting UTF8 string will always be NUL-terminated
30+ * when this function returns successfully.
31+ *
32+ * Return: Length of the resulting UTF8 string or negative errno code on failure.
33+ */
34+ssize_t wmi_string_to_utf8s(const struct wmi_string *str, u8 *dst, size_t length)
35+{
36+ /* Contains the maximum number of UTF16 code points to read */
37+ int inlen = le16_to_cpu(str->length) / 2;
38+ int ret;
39+
40+ if (length < 1)
41+ return -EINVAL;
42+
43+ /* We must leave room for the NUL character at the end of the destination buffer */
44+ ret = utf16s_to_utf8s((__force const wchar_t *)str->chars, inlen, UTF16_LITTLE_ENDIAN, dst,
45+ length - 1);
46+ if (ret < 0)
47+ return ret;
48+
49+ dst[ret] = '\0';
50+
51+ return ret;
52+}
53+EXPORT_SYMBOL_GPL(wmi_string_to_utf8s);
54+
55+/**
56+ * wmi_string_from_utf8s - Convert a UTF8 string into a WMI string.
57+ * @str: WMI string representation
58+ * @max_chars: Maximum number of UTF16 code points to store inside the WMI string
59+ * @src: UTF8 string to convert
60+ * @src_length: Length of the source string without any trailing NUL-characters
61+ *
62+ * Convert a UTF8 string into a WMI string. The conversion will stop when the WMI string is
63+ * full. The resulting WMI string will always be NUL-terminated and have its length field set
64+ * to and appropriate value when this function returns successfully.
65+ *
66+ * Return: Number of UTF16 code points inside the WMI string or negative errno code on failure.
67+ */
68+ssize_t wmi_string_from_utf8s(struct wmi_string *str, size_t max_chars, const u8 *src,
69+ size_t src_length)
70+{
71+ size_t str_length;
72+ int ret;
73+
74+ if (max_chars < 1)
75+ return -EINVAL;
76+
77+ /* We must leave room for the NUL character at the end of the WMI string */
78+ ret = utf8s_to_utf16s(src, src_length, UTF16_LITTLE_ENDIAN, (__force wchar_t *)str->chars,
79+ max_chars - 1);
80+ if (ret < 0)
81+ return ret;
82+
83+ str_length = (ret + 1) * sizeof(u16);
84+ if (str_length > U16_MAX)
85+ return -EOVERFLOW;
86+
87+ str->length = cpu_to_le16(str_length);
88+ str->chars[ret] = '\0';
89+
90+ return ret;
91+}
92+EXPORT_SYMBOL_GPL(wmi_string_from_utf8s);
drivers/platform/wmi/tests/Kconfig+27−0
@@ -0,0 +1,27 @@
1+# SPDX-License-Identifier: GPL-2.0-or-later
2+#
3+# ACPI WMI KUnit tests
4+#
5+
6+config ACPI_WMI_MARSHALLING_KUNIT_TEST
7+ tristate "KUnit Test for ACPI-WMI marshalling" if !KUNIT_ALL_TESTS
8+ depends on KUNIT
9+ default KUNIT_ALL_TESTS
10+ help
11+ This builds unit tests for the ACPI-WMI marshalling code.
12+
13+ For more information on KUnit and unit tests in general, please refer
14+ to the KUnit documentation in Documentation/dev-tools/kunit/.
15+
16+ If unsure, say N.
17+
18+config ACPI_WMI_STRING_KUNIT_TEST
19+ tristate "KUnit Test for ACPI-WMI string conversion" if !KUNIT_ALL_TESTS
20+ depends on KUNIT
21+ default KUNIT_ALL_TESTS
22+ help
23+ This builds unit tests for the ACPI-WMI string conversion code.
24+ For more information on KUnit and unit tests in general, please refer
25+ to the KUnit documentation in Documentation/dev-tools/kunit/.
26+
27+ If unsure, say N.
drivers/platform/wmi/tests/Makefile+11−0
@@ -0,0 +1,11 @@
1+# SPDX-License-Identifier: GPL-2.0-or-later
2+#
3+# Makefile for linux/drivers/platform/x86/wmi/tests
4+# ACPI WMI KUnit tests
5+#
6+
7+wmi_marshalling_kunit-y := marshalling_kunit.o
8+obj-$(CONFIG_ACPI_WMI_MARSHALLING_KUNIT_TEST) += wmi_marshalling_kunit.o
9+
10+wmi_string_kunit-y := string_kunit.o
11+obj-$(CONFIG_ACPI_WMI_STRING_KUNIT_TEST) += wmi_string_kunit.o
drivers/platform/wmi/tests/marshalling_kunit.c+452−0
@@ -0,0 +1,452 @@
1+// SPDX-License-Identifier: GPL-2.0-or-later
2+/*
3+ * KUnit test for the ACPI-WMI marshalling code.
4+ *
5+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
6+ */
7+
8+#include <linux/acpi.h>
9+#include <linux/align.h>
10+#include <linux/module.h>
11+#include <linux/slab.h>
12+#include <linux/string.h>
13+#include <linux/types.h>
14+#include <linux/wmi.h>
15+
16+#include <kunit/resource.h>
17+#include <kunit/test.h>
18+
19+#include "../internal.h"
20+
21+struct wmi_acpi_param {
22+ const char *name;
23+ const union acpi_object obj;
24+ const struct wmi_buffer buffer;
25+};
26+
27+struct wmi_string_param {
28+ const char *name;
29+ const char *string;
30+ const struct wmi_buffer buffer;
31+};
32+
33+struct wmi_invalid_acpi_param {
34+ const char *name;
35+ const union acpi_object obj;
36+};
37+
38+struct wmi_invalid_string_param {
39+ const char *name;
40+ const struct wmi_buffer buffer;
41+};
42+
43+/* 0xdeadbeef */
44+static u8 expected_single_integer[] = {
45+ 0xef, 0xbe, 0xad, 0xde,
46+};
47+
48+/* "TEST" */
49+static u8 expected_single_string[] = {
50+ 0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00,
51+};
52+
53+static u8 test_buffer[] = {
54+ 0xab, 0xcd,
55+};
56+
57+static u8 expected_single_buffer[] = {
58+ 0xab, 0xcd,
59+};
60+
61+static union acpi_object simple_package_elements[] = {
62+ {
63+ .buffer = {
64+ .type = ACPI_TYPE_BUFFER,
65+ .length = sizeof(test_buffer),
66+ .pointer = test_buffer,
67+ },
68+ },
69+ {
70+ .integer = {
71+ .type = ACPI_TYPE_INTEGER,
72+ .value = 0x01020304,
73+ },
74+ },
75+};
76+
77+static u8 expected_simple_package[] = {
78+ 0xab, 0xcd,
79+ 0x00, 0x00,
80+ 0x04, 0x03, 0x02, 0x01,
81+};
82+
83+static u8 test_small_buffer[] = {
84+ 0xde,
85+};
86+
87+static union acpi_object complex_package_elements[] = {
88+ {
89+ .integer = {
90+ .type = ACPI_TYPE_INTEGER,
91+ .value = 0xdeadbeef,
92+ },
93+ },
94+ {
95+ .buffer = {
96+ .type = ACPI_TYPE_BUFFER,
97+ .length = sizeof(test_small_buffer),
98+ .pointer = test_small_buffer,
99+ },
100+ },
101+ {
102+ .string = {
103+ .type = ACPI_TYPE_STRING,
104+ .length = sizeof("TEST") - 1,
105+ .pointer = "TEST",
106+ },
107+ },
108+ {
109+ .buffer = {
110+ .type = ACPI_TYPE_BUFFER,
111+ .length = sizeof(test_small_buffer),
112+ .pointer = test_small_buffer,
113+ },
114+ },
115+ {
116+ .integer = {
117+ .type = ACPI_TYPE_INTEGER,
118+ .value = 0x01020304,
119+ },
120+ }
121+};
122+
123+static u8 expected_complex_package[] = {
124+ 0xef, 0xbe, 0xad, 0xde,
125+ 0xde,
126+ 0x00,
127+ 0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00,
128+ 0xde,
129+ 0x00,
130+ 0x04, 0x03, 0x02, 0x01,
131+};
132+
133+static const struct wmi_acpi_param wmi_acpi_params_array[] = {
134+ {
135+ .name = "single_integer",
136+ .obj = {
137+ .integer = {
138+ .type = ACPI_TYPE_INTEGER,
139+ .value = 0xdeadbeef,
140+ },
141+ },
142+ .buffer = {
143+ .data = expected_single_integer,
144+ .length = sizeof(expected_single_integer),
145+ },
146+ },
147+ {
148+ .name = "single_string",
149+ .obj = {
150+ .string = {
151+ .type = ACPI_TYPE_STRING,
152+ .length = sizeof("TEST") - 1,
153+ .pointer = "TEST",
154+ },
155+ },
156+ .buffer = {
157+ .data = expected_single_string,
158+ .length = sizeof(expected_single_string),
159+ },
160+ },
161+ {
162+ .name = "single_buffer",
163+ .obj = {
164+ .buffer = {
165+ .type = ACPI_TYPE_BUFFER,
166+ .length = sizeof(test_buffer),
167+ .pointer = test_buffer,
168+ },
169+ },
170+ .buffer = {
171+ .data = expected_single_buffer,
172+ .length = sizeof(expected_single_buffer),
173+ },
174+ },
175+ {
176+ .name = "simple_package",
177+ .obj = {
178+ .package = {
179+ .type = ACPI_TYPE_PACKAGE,
180+ .count = ARRAY_SIZE(simple_package_elements),
181+ .elements = simple_package_elements,
182+ },
183+ },
184+ .buffer = {
185+ .data = expected_simple_package,
186+ .length = sizeof(expected_simple_package),
187+ },
188+ },
189+ {
190+ .name = "complex_package",
191+ .obj = {
192+ .package = {
193+ .type = ACPI_TYPE_PACKAGE,
194+ .count = ARRAY_SIZE(complex_package_elements),
195+ .elements = complex_package_elements,
196+ },
197+ },
198+ .buffer = {
199+ .data = expected_complex_package,
200+ .length = sizeof(expected_complex_package),
201+ },
202+ },
203+};
204+
205+static void wmi_acpi_param_get_desc(const struct wmi_acpi_param *param, char *desc)
206+{
207+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
208+}
209+
210+KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object, wmi_acpi_params_array, wmi_acpi_param_get_desc);
211+
212+/* "WMI\0" */
213+static u8 padded_wmi_string[] = {
214+ 0x0a, 0x00,
215+ 0x57, 0x00,
216+ 0x4D, 0x00,
217+ 0x49, 0x00,
218+ 0x00, 0x00,
219+ 0x00, 0x00,
220+};
221+
222+static const struct wmi_string_param wmi_string_params_array[] = {
223+ {
224+ .name = "test",
225+ .string = "TEST",
226+ .buffer = {
227+ .length = sizeof(expected_single_string),
228+ .data = expected_single_string,
229+ },
230+ },
231+ {
232+ .name = "padded",
233+ .string = "WMI",
234+ .buffer = {
235+ .length = sizeof(padded_wmi_string),
236+ .data = padded_wmi_string,
237+ },
238+ },
239+};
240+
241+static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc)
242+{
243+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
244+}
245+
246+KUNIT_ARRAY_PARAM(wmi_marshal_string, wmi_string_params_array, wmi_string_param_get_desc);
247+
248+static union acpi_object nested_package_elements[] = {
249+ {
250+ .package = {
251+ .type = ACPI_TYPE_PACKAGE,
252+ .count = ARRAY_SIZE(simple_package_elements),
253+ .elements = simple_package_elements,
254+ },
255+ }
256+};
257+
258+static const struct wmi_invalid_acpi_param wmi_invalid_acpi_params_array[] = {
259+ {
260+ .name = "nested_package",
261+ .obj = {
262+ .package = {
263+ .type = ACPI_TYPE_PACKAGE,
264+ .count = ARRAY_SIZE(nested_package_elements),
265+ .elements = nested_package_elements,
266+ },
267+ },
268+ },
269+ {
270+ .name = "reference",
271+ .obj = {
272+ .reference = {
273+ .type = ACPI_TYPE_LOCAL_REFERENCE,
274+ .actual_type = ACPI_TYPE_ANY,
275+ .handle = NULL,
276+ },
277+ },
278+ },
279+ {
280+ .name = "processor",
281+ .obj = {
282+ .processor = {
283+ .type = ACPI_TYPE_PROCESSOR,
284+ .proc_id = 0,
285+ .pblk_address = 0,
286+ .pblk_length = 0,
287+ },
288+ },
289+ },
290+ {
291+ .name = "power_resource",
292+ .obj = {
293+ .power_resource = {
294+ .type = ACPI_TYPE_POWER,
295+ .system_level = 0,
296+ .resource_order = 0,
297+ },
298+ },
299+ },
300+};
301+
302+static void wmi_invalid_acpi_param_get_desc(const struct wmi_invalid_acpi_param *param, char *desc)
303+{
304+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
305+}
306+
307+KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object_failure, wmi_invalid_acpi_params_array,
308+ wmi_invalid_acpi_param_get_desc);
309+
310+static u8 oversized_wmi_string[] = {
311+ 0x04, 0x00, 0x00, 0x00,
312+};
313+
314+/*
315+ * The error is that 3 bytes can not hold UTF-16 characters
316+ * without cutting of the last one.
317+ */
318+static u8 undersized_wmi_string[] = {
319+ 0x03, 0x00, 0x00, 0x00, 0x00,
320+};
321+
322+static u8 non_ascii_wmi_string[] = {
323+ 0x04, 0x00, 0xC4, 0x00, 0x00, 0x00,
324+};
325+
326+static const struct wmi_invalid_string_param wmi_invalid_string_params_array[] = {
327+ {
328+ .name = "empty_buffer",
329+ .buffer = {
330+ .length = 0,
331+ .data = ZERO_SIZE_PTR,
332+ },
333+
334+ },
335+ {
336+ .name = "oversized",
337+ .buffer = {
338+ .length = sizeof(oversized_wmi_string),
339+ .data = oversized_wmi_string,
340+ },
341+ },
342+ {
343+ .name = "undersized",
344+ .buffer = {
345+ .length = sizeof(undersized_wmi_string),
346+ .data = undersized_wmi_string,
347+ },
348+ },
349+ {
350+ .name = "non_ascii",
351+ .buffer = {
352+ .length = sizeof(non_ascii_wmi_string),
353+ .data = non_ascii_wmi_string,
354+ },
355+ },
356+};
357+
358+static void wmi_invalid_string_param_get_desc(const struct wmi_invalid_string_param *param,
359+ char *desc)
360+{
361+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
362+}
363+
364+KUNIT_ARRAY_PARAM(wmi_marshal_string_failure, wmi_invalid_string_params_array,
365+ wmi_invalid_string_param_get_desc);
366+
367+KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *);
368+
369+static void wmi_unmarshal_acpi_object_test(struct kunit *test)
370+{
371+ const struct wmi_acpi_param *param = test->param_value;
372+ struct wmi_buffer result;
373+ int ret;
374+
375+ ret = wmi_unmarshal_acpi_object(&param->obj, &result);
376+ if (ret < 0)
377+ KUNIT_FAIL_AND_ABORT(test, "Unmarshalling of ACPI object failed\n");
378+
379+ kunit_add_action(test, kfree_wrapper, result.data);
380+
381+ KUNIT_EXPECT_TRUE(test, IS_ALIGNED((uintptr_t)result.data, 8));
382+ KUNIT_EXPECT_EQ(test, result.length, param->buffer.length);
383+ KUNIT_EXPECT_MEMEQ(test, result.data, param->buffer.data, result.length);
384+}
385+
386+static void wmi_unmarshal_acpi_object_failure_test(struct kunit *test)
387+{
388+ const struct wmi_invalid_acpi_param *param = test->param_value;
389+ struct wmi_buffer result;
390+ int ret;
391+
392+ ret = wmi_unmarshal_acpi_object(&param->obj, &result);
393+ if (ret < 0)
394+ return;
395+
396+ kfree(result.data);
397+ KUNIT_FAIL(test, "Invalid ACPI object was not rejected\n");
398+}
399+
400+static void wmi_marshal_string_test(struct kunit *test)
401+{
402+ const struct wmi_string_param *param = test->param_value;
403+ struct acpi_buffer result;
404+ int ret;
405+
406+ ret = wmi_marshal_string(&param->buffer, &result);
407+ if (ret < 0)
408+ KUNIT_FAIL_AND_ABORT(test, "Marshalling of WMI string failed\n");
409+
410+ kunit_add_action(test, kfree_wrapper, result.pointer);
411+
412+ KUNIT_EXPECT_EQ(test, result.length, strlen(param->string));
413+ KUNIT_EXPECT_STREQ(test, result.pointer, param->string);
414+}
415+
416+static void wmi_marshal_string_failure_test(struct kunit *test)
417+{
418+ const struct wmi_invalid_string_param *param = test->param_value;
419+ struct acpi_buffer result;
420+ int ret;
421+
422+ ret = wmi_marshal_string(&param->buffer, &result);
423+ if (ret < 0)
424+ return;
425+
426+ kfree(result.pointer);
427+ KUNIT_FAIL(test, "Invalid string was not rejected\n");
428+}
429+
430+static struct kunit_case wmi_marshalling_test_cases[] = {
431+ KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_test,
432+ wmi_unmarshal_acpi_object_gen_params),
433+ KUNIT_CASE_PARAM(wmi_marshal_string_test,
434+ wmi_marshal_string_gen_params),
435+ KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_failure_test,
436+ wmi_unmarshal_acpi_object_failure_gen_params),
437+ KUNIT_CASE_PARAM(wmi_marshal_string_failure_test,
438+ wmi_marshal_string_failure_gen_params),
439+ {}
440+};
441+
442+static struct kunit_suite wmi_marshalling_test_suite = {
443+ .name = "wmi_marshalling",
444+ .test_cases = wmi_marshalling_test_cases,
445+};
446+
447+kunit_test_suite(wmi_marshalling_test_suite);
448+
449+MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
450+MODULE_DESCRIPTION("KUnit test for the ACPI-WMI marshalling code");
451+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
452+MODULE_LICENSE("GPL");
drivers/platform/wmi/tests/string_kunit.c+296−0
@@ -0,0 +1,296 @@
1+// SPDX-License-Identifier: GPL-2.0-or-later
2+/*
3+ * KUnit test for the ACPI-WMI string conversion code.
4+ *
5+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
6+ */
7+
8+#include <linux/module.h>
9+#include <linux/slab.h>
10+#include <linux/string.h>
11+#include <linux/wmi.h>
12+
13+#include <kunit/resource.h>
14+#include <kunit/test.h>
15+
16+#include <asm/byteorder.h>
17+
18+struct wmi_string_param {
19+ const char *name;
20+ const struct wmi_string *wmi_string;
21+ /*
22+ * Remember that using sizeof() on a struct wmi_string will
23+ * always return a size of two bytes due to the flexible
24+ * array member!
25+ */
26+ size_t wmi_string_length;
27+ const u8 *utf8_string;
28+ size_t utf8_string_length;
29+};
30+
31+#define TEST_WMI_STRING_LENGTH 12
32+
33+static const struct wmi_string test_wmi_string = {
34+ .length = cpu_to_le16(10),
35+ .chars = {
36+ cpu_to_le16(u'T'),
37+ cpu_to_le16(u'E'),
38+ cpu_to_le16(u'S'),
39+ cpu_to_le16(u'T'),
40+ cpu_to_le16(u'\0'),
41+ },
42+};
43+
44+static const u8 test_utf8_string[] = "TEST";
45+
46+#define SPECIAL_WMI_STRING_LENGTH 14
47+
48+static const struct wmi_string special_wmi_string = {
49+ .length = cpu_to_le16(12),
50+ .chars = {
51+ cpu_to_le16(u'Ä'),
52+ cpu_to_le16(u'Ö'),
53+ cpu_to_le16(u'Ü'),
54+ cpu_to_le16(u'ß'),
55+ cpu_to_le16(u'€'),
56+ cpu_to_le16(u'\0'),
57+ },
58+};
59+
60+static const u8 special_utf8_string[] = "ÄÖÜ߀";
61+
62+#define MULTI_POINT_WMI_STRING_LENGTH 12
63+
64+static const struct wmi_string multi_point_wmi_string = {
65+ .length = cpu_to_le16(10),
66+ .chars = {
67+ cpu_to_le16(u'K'),
68+ /* 🐧 */
69+ cpu_to_le16(0xD83D),
70+ cpu_to_le16(0xDC27),
71+ cpu_to_le16(u'!'),
72+ cpu_to_le16(u'\0'),
73+ },
74+};
75+
76+static const u8 multi_point_utf8_string[] = "K🐧!";
77+
78+#define PADDED_TEST_WMI_STRING_LENGTH 14
79+
80+static const struct wmi_string padded_test_wmi_string = {
81+ .length = cpu_to_le16(12),
82+ .chars = {
83+ cpu_to_le16(u'T'),
84+ cpu_to_le16(u'E'),
85+ cpu_to_le16(u'S'),
86+ cpu_to_le16(u'T'),
87+ cpu_to_le16(u'\0'),
88+ cpu_to_le16(u'\0'),
89+ },
90+};
91+
92+static const u8 padded_test_utf8_string[] = "TEST\0";
93+
94+#define OVERSIZED_TEST_WMI_STRING_LENGTH 14
95+
96+static const struct wmi_string oversized_test_wmi_string = {
97+ .length = cpu_to_le16(8),
98+ .chars = {
99+ cpu_to_le16(u'T'),
100+ cpu_to_le16(u'E'),
101+ cpu_to_le16(u'S'),
102+ cpu_to_le16(u'T'),
103+ cpu_to_le16(u'!'),
104+ cpu_to_le16(u'\0'),
105+ },
106+};
107+
108+static const u8 oversized_test_utf8_string[] = "TEST!";
109+
110+#define INVALID_TEST_WMI_STRING_LENGTH 14
111+
112+static const struct wmi_string invalid_test_wmi_string = {
113+ .length = cpu_to_le16(12),
114+ .chars = {
115+ cpu_to_le16(u'T'),
116+ /* 🐧, with low surrogate missing */
117+ cpu_to_le16(0xD83D),
118+ cpu_to_le16(u'E'),
119+ cpu_to_le16(u'S'),
120+ cpu_to_le16(u'T'),
121+ cpu_to_le16(u'\0'),
122+ },
123+};
124+
125+/* We have to split the string here to end the hex escape sequence */
126+static const u8 invalid_test_utf8_string[] = "T" "\xF0\x9F" "EST";
127+
128+static const struct wmi_string_param wmi_string_params_array[] = {
129+ {
130+ .name = "ascii_string",
131+ .wmi_string = &test_wmi_string,
132+ .wmi_string_length = TEST_WMI_STRING_LENGTH,
133+ .utf8_string = test_utf8_string,
134+ .utf8_string_length = sizeof(test_utf8_string),
135+ },
136+ {
137+ .name = "special_string",
138+ .wmi_string = &special_wmi_string,
139+ .wmi_string_length = SPECIAL_WMI_STRING_LENGTH,
140+ .utf8_string = special_utf8_string,
141+ .utf8_string_length = sizeof(special_utf8_string),
142+ },
143+ {
144+ .name = "multi_point_string",
145+ .wmi_string = &multi_point_wmi_string,
146+ .wmi_string_length = MULTI_POINT_WMI_STRING_LENGTH,
147+ .utf8_string = multi_point_utf8_string,
148+ .utf8_string_length = sizeof(multi_point_utf8_string),
149+ },
150+};
151+
152+static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc)
153+{
154+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
155+}
156+
157+KUNIT_ARRAY_PARAM(wmi_string, wmi_string_params_array, wmi_string_param_get_desc);
158+
159+static void wmi_string_to_utf8s_test(struct kunit *test)
160+{
161+ const struct wmi_string_param *param = test->param_value;
162+ ssize_t ret;
163+ u8 *result;
164+
165+ result = kunit_kzalloc(test, param->utf8_string_length, GFP_KERNEL);
166+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
167+
168+ ret = wmi_string_to_utf8s(param->wmi_string, result, param->utf8_string_length);
169+
170+ KUNIT_EXPECT_EQ(test, ret, param->utf8_string_length - 1);
171+ KUNIT_EXPECT_MEMEQ(test, result, param->utf8_string, param->utf8_string_length);
172+}
173+
174+static void wmi_string_from_utf8s_test(struct kunit *test)
175+{
176+ const struct wmi_string_param *param = test->param_value;
177+ struct wmi_string *result;
178+ size_t max_chars;
179+ ssize_t ret;
180+
181+ max_chars = (param->wmi_string_length - sizeof(*result)) / 2;
182+ result = kunit_kzalloc(test, param->wmi_string_length, GFP_KERNEL);
183+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
184+
185+ ret = wmi_string_from_utf8s(result, max_chars, param->utf8_string,
186+ param->utf8_string_length);
187+
188+ KUNIT_EXPECT_EQ(test, ret, max_chars - 1);
189+ KUNIT_EXPECT_MEMEQ(test, result, param->wmi_string, param->wmi_string_length);
190+}
191+
192+static void wmi_string_to_utf8s_padded_test(struct kunit *test)
193+{
194+ u8 result[sizeof(padded_test_utf8_string)];
195+ ssize_t ret;
196+
197+ ret = wmi_string_to_utf8s(&padded_test_wmi_string, result, sizeof(result));
198+
199+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
200+ KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
201+}
202+
203+static void wmi_string_from_utf8s_padded_test(struct kunit *test)
204+{
205+ struct wmi_string *result;
206+ size_t max_chars;
207+ ssize_t ret;
208+
209+ max_chars = (PADDED_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
210+ result = kunit_kzalloc(test, PADDED_TEST_WMI_STRING_LENGTH, GFP_KERNEL);
211+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
212+
213+ ret = wmi_string_from_utf8s(result, max_chars, padded_test_utf8_string,
214+ sizeof(padded_test_utf8_string));
215+
216+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
217+ KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string));
218+}
219+
220+static void wmi_string_to_utf8s_oversized_test(struct kunit *test)
221+{
222+ u8 result[sizeof(oversized_test_utf8_string)];
223+ ssize_t ret;
224+
225+ ret = wmi_string_to_utf8s(&oversized_test_wmi_string, result, sizeof(result));
226+
227+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
228+ KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
229+}
230+
231+static void wmi_string_from_utf8s_oversized_test(struct kunit *test)
232+{
233+ struct wmi_string *result;
234+ size_t max_chars;
235+ ssize_t ret;
236+
237+ max_chars = (TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
238+ result = kunit_kzalloc(test, TEST_WMI_STRING_LENGTH, GFP_KERNEL);
239+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
240+
241+ ret = wmi_string_from_utf8s(result, max_chars, oversized_test_utf8_string,
242+ sizeof(oversized_test_utf8_string));
243+
244+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
245+ KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string));
246+}
247+
248+static void wmi_string_to_utf8s_invalid_test(struct kunit *test)
249+{
250+ u8 result[sizeof(invalid_test_utf8_string)];
251+ ssize_t ret;
252+
253+ ret = wmi_string_to_utf8s(&invalid_test_wmi_string, result, sizeof(result));
254+
255+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
256+ KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
257+}
258+
259+static void wmi_string_from_utf8s_invalid_test(struct kunit *test)
260+{
261+ struct wmi_string *result;
262+ size_t max_chars;
263+ ssize_t ret;
264+
265+ max_chars = (INVALID_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
266+ result = kunit_kzalloc(test, INVALID_TEST_WMI_STRING_LENGTH, GFP_KERNEL);
267+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
268+
269+ ret = wmi_string_from_utf8s(result, max_chars, invalid_test_utf8_string,
270+ sizeof(invalid_test_utf8_string));
271+
272+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
273+}
274+
275+static struct kunit_case wmi_string_test_cases[] = {
276+ KUNIT_CASE_PARAM(wmi_string_to_utf8s_test, wmi_string_gen_params),
277+ KUNIT_CASE_PARAM(wmi_string_from_utf8s_test, wmi_string_gen_params),
278+ KUNIT_CASE(wmi_string_to_utf8s_padded_test),
279+ KUNIT_CASE(wmi_string_from_utf8s_padded_test),
280+ KUNIT_CASE(wmi_string_to_utf8s_oversized_test),
281+ KUNIT_CASE(wmi_string_from_utf8s_oversized_test),
282+ KUNIT_CASE(wmi_string_to_utf8s_invalid_test),
283+ KUNIT_CASE(wmi_string_from_utf8s_invalid_test),
284+ {}
285+};
286+
287+static struct kunit_suite wmi_string_test_suite = {
288+ .name = "wmi_string",
289+ .test_cases = wmi_string_test_cases,
290+};
291+
292+kunit_test_suite(wmi_string_test_suite);
293+
294+MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
295+MODULE_DESCRIPTION("KUnit test for the ACPI-WMI string conversion code");
296+MODULE_LICENSE("GPL");
drivers/platform/x86/amd/pmf/acpi.c+40−0
@@ -9,6 +9,9 @@ · 8 unmodified lines
99 */
1010
1111 #include <linux/acpi.h>
12+#include <linux/array_size.h>
13+#include <linux/cleanup.h>
14+#include <linux/dev_printk.h>
1215 #include "pmf.h"
1316
1417 #define APMF_CQL_NOTIFICATION 2
@@ -331,6 +334,39 @@ int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req · 316 unmodified lines
331334 req, sizeof(*req));
332335 }
333336
337+/* Store custom BIOS inputs data in ring buffer */
338+static void amd_pmf_custom_bios_inputs_rb(struct amd_pmf_dev *pmf_dev)
339+{
340+ struct pmf_cbi_ring_buffer *rb = &pmf_dev->cbi_buf;
341+ int i;
342+
343+ guard(mutex)(&pmf_dev->cbi_mutex);
344+
345+ switch (pmf_dev->cpu_id) {
346+ case AMD_CPU_ID_PS:
347+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs_v1); i++)
348+ rb->data[rb->head].val[i] = pmf_dev->req1.custom_policy[i];
349+ rb->data[rb->head].preq = pmf_dev->req1.pending_req;
350+ break;
351+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
352+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
353+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
354+ rb->data[rb->head].val[i] = pmf_dev->req.custom_policy[i];
355+ rb->data[rb->head].preq = pmf_dev->req.pending_req;
356+ break;
357+ default:
358+ return;
359+ }
360+
361+ if (CIRC_SPACE(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0) {
362+ /* Rare case: ensures the newest BIOS input value is kept */
363+ dev_warn(pmf_dev->dev, "Overwriting BIOS input value, data may be lost\n");
364+ rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
365+ }
366+
367+ rb->head = (rb->head + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
368+}
369+
334370 static void amd_pmf_handle_early_preq(struct amd_pmf_dev *pdev)
335371 {
336372 if (!pdev->cb_flag)
@@ -356,6 +392,8 @@ static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data) · 19 unmodified lines
356392 dev_dbg(pmf_dev->dev, "Pending request (preq): 0x%x\n", pmf_dev->req.pending_req);
357393
358394 amd_pmf_handle_early_preq(pmf_dev);
395+
396+ amd_pmf_custom_bios_inputs_rb(pmf_dev);
359397 }
360398
361399 static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data)
@@ -374,6 +412,8 @@ static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data) · 12 unmodified lines
374412 dev_dbg(pmf_dev->dev, "Pending request (preq1): 0x%x\n", pmf_dev->req1.pending_req);
375413
376414 amd_pmf_handle_early_preq(pmf_dev);
415+
416+ amd_pmf_custom_bios_inputs_rb(pmf_dev);
377417 }
378418
379419 static void apmf_event_handler(acpi_handle handle, u32 event, void *data)
drivers/platform/x86/amd/pmf/core.c+155−6
@@ -8,12 +8,16 @@ · 7 unmodified lines
88 * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
99 */
1010
11+#include <linux/array_size.h>
12+#include <linux/cleanup.h>
1113 #include <linux/debugfs.h>
1214 #include <linux/iopoll.h>
1315 #include <linux/module.h>
16+#include <linux/mutex.h>
1417 #include <linux/pci.h>
1518 #include <linux/platform_device.h>
1619 #include <linux/power_supply.h>
20+#include <linux/string.h>
1721 #include <asm/amd/node.h>
1822 #include "pmf.h"
1923
@@ -53,6 +57,12 @@ static bool force_load; · 33 unmodified lines
5357 module_param(force_load, bool, 0444);
5458 MODULE_PARM_DESC(force_load, "Force load this driver on supported older platforms (experimental)");
5559
60+static bool smart_pc_support = true;
61+module_param(smart_pc_support, bool, 0444);
62+MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)");
63+
64+static struct device *pmf_device;
65+
5666 static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data)
5767 {
5868 struct amd_pmf_dev *pmf = container_of(nb, struct amd_pmf_dev, pwr_src_notifier);
@@ -314,6 +324,126 @@ int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) · 255 unmodified lines
314324 return 0;
315325 }
316326
327+static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
328+{
329+ switch (pdev->cpu_id) {
330+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
331+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
332+ return 0;
333+ default:
334+ return -EOPNOTSUPP;
335+ }
336+}
337+
338+static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
339+{
340+ int ret, i;
341+
342+ guard(mutex)(&dev->metrics_mutex);
343+
344+ ret = is_npu_metrics_supported(dev);
345+ if (ret)
346+ return ret;
347+
348+ ret = amd_pmf_set_dram_addr(dev, true);
349+ if (ret)
350+ return ret;
351+
352+ memset(dev->buf, 0, dev->mtable_size);
353+
354+ /* Send SMU command to get NPU metrics */
355+ ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
356+ if (ret) {
357+ dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
358+ return ret;
359+ }
360+
361+ memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
362+
363+ data->npuclk_freq = dev->m_table_v2.npuclk_freq;
364+ for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
365+ data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
366+ data->npu_power = dev->m_table_v2.npu_power;
367+ data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
368+ data->npu_reads = dev->m_table_v2.npu_reads;
369+ data->npu_writes = dev->m_table_v2.npu_writes;
370+
371+ return 0;
372+}
373+
374+int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
375+{
376+ struct amd_pmf_dev *pdev;
377+
378+ if (!info)
379+ return -EINVAL;
380+
381+ if (!pmf_device)
382+ return -ENODEV;
383+
384+ pdev = dev_get_drvdata(pmf_device);
385+ if (!pdev)
386+ return -ENODEV;
387+
388+ return amd_pmf_get_smu_metrics(pdev, info);
389+}
390+EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
391+
392+static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev)
393+{
394+ bool status;
395+ int ret, i;
396+
397+ for (i = 0; i < ARRAY_SIZE(amd_pmf_ta_uuid); i++) {
398+ ret = amd_pmf_tee_init(pdev, &amd_pmf_ta_uuid[i]);
399+ if (ret) {
400+ dev_err(pdev->dev, "TEE init failed for UUID[%d] ret: %d\n", i, ret);
401+ return ret;
402+ }
403+
404+ ret = amd_pmf_start_policy_engine(pdev);
405+ dev_dbg(pdev->dev, "start policy engine ret: %d (UUID idx: %d)\n", ret, i);
406+ status = ret == TA_PMF_TYPE_SUCCESS;
407+ if (status)
408+ break;
409+ amd_pmf_tee_deinit(pdev);
410+ }
411+
412+ return 0;
413+}
414+
415+static int amd_pmf_restore_handler(struct device *dev)
416+{
417+ struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
418+ int ret;
419+
420+ if (pdev->buf) {
421+ ret = amd_pmf_set_dram_addr(pdev, false);
422+ if (ret)
423+ return ret;
424+ }
425+
426+ if (pdev->smart_pc_enabled)
427+ amd_pmf_reinit_ta(pdev);
428+
429+ return 0;
430+}
431+
432+static int amd_pmf_freeze_handler(struct device *dev)
433+{
434+ struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
435+
436+ if (!pdev->smart_pc_enabled)
437+ return 0;
438+
439+ cancel_delayed_work_sync(&pdev->pb_work);
440+ /* Clear all TEE resources */
441+ amd_pmf_tee_deinit(pdev);
442+ pdev->session_id = 0;
443+
444+ return 0;
445+}
446+
317447 static int amd_pmf_suspend_handler(struct device *dev)
318448 {
319449 struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
@@ -347,7 +477,12 @@ static int amd_pmf_resume_handler(struct device *dev) · 27 unmodified lines
347477 return 0;
348478 }
349479
350−static DEFINE_SIMPLE_DEV_PM_OPS(amd_pmf_pm, amd_pmf_suspend_handler, amd_pmf_resume_handler);
480+static const struct dev_pm_ops amd_pmf_pm = {
481+ .suspend = amd_pmf_suspend_handler,
482+ .resume = amd_pmf_resume_handler,
483+ .freeze = amd_pmf_freeze_handler,
484+ .restore = amd_pmf_restore_handler,
485+};
351486
352487 static void amd_pmf_init_features(struct amd_pmf_dev *dev)
353488 {
@@ -362,11 +497,15 @@ static void amd_pmf_init_features(struct amd_pmf_dev *dev) · 8 unmodified lines
362497 dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n");
363498 }
364499
365− amd_pmf_init_smart_pc(dev);
366− if (dev->smart_pc_enabled) {
367− dev_dbg(dev->dev, "Smart PC Solution Enabled\n");
368− /* If Smart PC is enabled, no need to check for other features */
369− return;
500+ if (smart_pc_support) {
501+ amd_pmf_init_smart_pc(dev);
502+ if (dev->smart_pc_enabled) {
503+ dev_dbg(dev->dev, "Smart PC Solution Enabled\n");
504+ /* If Smart PC is enabled, no need to check for other features */
505+ return;
506+ }
507+ } else {
508+ dev->smart_pc_enabled = false;
370509 }
371510
372511 if (is_apmf_func_supported(dev, APMF_FUNC_AUTO_MODE)) {
@@ -477,6 +616,14 @@ static int amd_pmf_probe(struct platform_device *pdev) · 104 unmodified lines
477616 if (err)
478617 return err;
479618
619+ err = devm_mutex_init(dev->dev, &dev->cbi_mutex);
620+ if (err)
621+ return err;
622+
623+ err = devm_mutex_init(dev->dev, &dev->metrics_mutex);
624+ if (err)
625+ return err;
626+
480627 apmf_acpi_init(dev);
481628 platform_set_drvdata(pdev, dev);
482629 amd_pmf_dbgfs_register(dev);
@@ -485,6 +632,8 @@ static int amd_pmf_probe(struct platform_device *pdev) · 2 unmodified lines
485632 if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
486633 amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD);
487634
635+ pmf_device = dev->dev;
636+
488637 dev_info(dev->dev, "registered PMF device successfully\n");
489638
490639 return 0;
drivers/platform/x86/amd/pmf/pmf.h+33−0
@@ -12,7 +12,10 @@ · 11 unmodified lines
1212 #define PMF_H
1313
1414 #include <linux/acpi.h>
15+#include <linux/amd-pmf-io.h>
16+#include <linux/circ_buf.h>
1517 #include <linux/input.h>
18+#include <linux/mutex_types.h>
1619 #include <linux/platform_device.h>
1720 #include <linux/platform_profile.h>
1821
@@ -120,6 +123,7 @@ struct cookie_header { · 101 unmodified lines
120123 #define APTS_MAX_STATES 16
121124 #define CUSTOM_BIOS_INPUT_BITS GENMASK(16, 7)
122125 #define BIOS_INPUTS_MAX 10
126+#define CUSTOM_BIOS_INPUT_RING_ENTRIES 64 /* Must be power of two for CIRC_* macros */
123127
124128 /* amd_pmf_send_cmd() set/get */
125129 #define SET_CMD false
@@ -129,6 +133,12 @@ struct cookie_header { · 3 unmodified lines
129133
130134 typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data);
131135
136+static const uuid_t amd_pmf_ta_uuid[] __used = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8,
137+ 0x8a, 0xcc, 0x2b, 0x2b, 0x60, 0xd6),
138+ UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d,
139+ 0xc5, 0x29, 0xb1, 0x3d, 0x85, 0x43),
140+ };
141+
132142 /* APTS PMF BIOS Interface */
133143 struct amd_pmf_apts_output {
134144 u16 table_version;
@@ -365,6 +375,22 @@ struct pmf_bios_inputs_prev { · 230 unmodified lines
365375 u32 custom_bios_inputs[BIOS_INPUTS_MAX];
366376 };
367377
378+/**
379+ * struct pmf_bios_input_entry - Snapshot of custom BIOS input event
380+ * @val: Array of custom BIOS input values
381+ * @preq: Pending request value associated with this event
382+ */
383+struct pmf_bios_input_entry {
384+ u32 val[BIOS_INPUTS_MAX];
385+ u32 preq;
386+};
387+
388+struct pmf_cbi_ring_buffer {
389+ struct pmf_bios_input_entry data[CUSTOM_BIOS_INPUT_RING_ENTRIES];
390+ int head;
391+ int tail;
392+};
393+
368394 struct amd_pmf_dev {
369395 void __iomem *regbase;
370396 void __iomem *smu_virt_addr;
@@ -413,6 +439,9 @@ struct amd_pmf_dev { · 42 unmodified lines
413439 struct apmf_sbios_req_v1 req1;
414440 struct pmf_bios_inputs_prev cb_prev; /* To preserve custom BIOS inputs */
415441 bool cb_flag; /* To handle first custom BIOS input */
442+ struct pmf_cbi_ring_buffer cbi_buf;
443+ struct mutex cbi_mutex; /* Protects ring buffer access */
444+ struct mutex metrics_mutex;
416445 };
417446
418447 struct apmf_sps_prop_granular_v2 {
@@ -895,4 +924,8 @@ void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_tab · 476 unmodified lines
895924 void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
896925 int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev);
897926
927+int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid);
928+void amd_pmf_tee_deinit(struct amd_pmf_dev *dev);
929+int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev);
930+
898931 #endif /* PMF_H */
drivers/platform/x86/amd/pmf/spc.c+19−14
@@ -11,6 +11,7 @@ · 10 unmodified lines
1111
1212 #include <acpi/button.h>
1313 #include <linux/amd-pmf-io.h>
14+#include <linux/cleanup.h>
1415 #include <linux/power_supply.h>
1516 #include <linux/units.h>
1617 #include "pmf.h"
@@ -132,32 +133,39 @@ static void amd_pmf_set_ta_custom_bios_input(struct ta_pmf_enact_table *in, int · 115 unmodified lines
132133 }
133134 }
134135
135−static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, u32 pending_req,
136+static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, struct pmf_bios_input_entry *data,
136137 const struct amd_pmf_pb_bitmap *inputs,
137− const u32 *custom_policy, struct ta_pmf_enact_table *in)
138+ struct ta_pmf_enact_table *in)
138139 {
139140 unsigned int i;
140141
141142 for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) {
142− if (!(pending_req & inputs[i].bit_mask))
143+ if (!(data->preq & inputs[i].bit_mask))
143144 continue;
144− amd_pmf_set_ta_custom_bios_input(in, i, custom_policy[i]);
145− pdev->cb_prev.custom_bios_inputs[i] = custom_policy[i];
146− dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, custom_policy[i]);
145+ amd_pmf_set_ta_custom_bios_input(in, i, data->val[i]);
146+ pdev->cb_prev.custom_bios_inputs[i] = data->val[i];
147+ dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, data->val[i]);
147148 }
148149 }
149150
150151 static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
151152 struct ta_pmf_enact_table *in)
152153 {
154+ struct pmf_cbi_ring_buffer *rb = &pdev->cbi_buf;
153155 unsigned int i;
154156
157+ guard(mutex)(&pdev->cbi_mutex);
158+
155159 for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
156160 amd_pmf_set_ta_custom_bios_input(in, i, pdev->cb_prev.custom_bios_inputs[i]);
157161
158− if (!(pdev->req.pending_req || pdev->req1.pending_req))
162+ if (CIRC_CNT(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0)
159163 return;
160164
165+ /* If no active custom BIOS input pending request, do not consume further work */
166+ if (!rb->data[rb->tail].preq)
167+ goto out_rbadvance;
168+
161169 if (!pdev->smart_pc_enabled)
162170 return;
163171
@@ -165,20 +173,17 @@ static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev, · 1 unmodified line
165173 case PMF_IF_V1:
166174 if (!is_apmf_bios_input_notifications_supported(pdev))
167175 return;
168− amd_pmf_update_bios_inputs(pdev, pdev->req1.pending_req, custom_bios_inputs_v1,
169− pdev->req1.custom_policy, in);
176+ amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs_v1, in);
170177 break;
171178 case PMF_IF_V2:
172− amd_pmf_update_bios_inputs(pdev, pdev->req.pending_req, custom_bios_inputs,
173− pdev->req.custom_policy, in);
179+ amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs, in);
174180 break;
175181 default:
176182 break;
177183 }
178184
179− /* Clear pending requests after handling */
180− memset(&pdev->req, 0, sizeof(pdev->req));
181− memset(&pdev->req1, 0, sizeof(pdev->req1));
185+out_rbadvance:
186+ rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
182187 }
183188
184189 static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in)
drivers/platform/x86/amd/pmf/tee-if.c+5−9
@@ -27,12 +27,6 @@ module_param(pb_side_load, bool, 0444); · 26 unmodified lines
2727 MODULE_PARM_DESC(pb_side_load, "Sideload policy binaries debug policy failures");
2828 #endif
2929
30−static const uuid_t amd_pmf_ta_uuid[] = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, 0x8a,
31− 0xcc, 0x2b, 0x2b, 0x60, 0xd6),
32− UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d, 0xc5,
33− 0x29, 0xb1, 0x3d, 0x85, 0x43),
34− };
35−
3630 static const char *amd_pmf_uevent_as_str(unsigned int state)
3731 {
3832 switch (state) {
@@ -324,7 +318,7 @@ static void amd_pmf_invoke_cmd(struct work_struct *work) · 285 unmodified lines
324318 schedule_delayed_work(&dev->pb_work, msecs_to_jiffies(pb_actions_ms));
325319 }
326320
327−static int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
321+int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
328322 {
329323 struct cookie_header *header;
330324 int res;
@@ -480,7 +474,7 @@ static int amd_pmf_register_input_device(struct amd_pmf_dev *dev) · 149 unmodified lines
480474 return 0;
481475 }
482476
483−static int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
477+int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
484478 {
485479 u32 size;
486480 int ret;
@@ -528,7 +522,7 @@ out_ctx: · 41 unmodified lines
528522 return ret;
529523 }
530524
531−static void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
525+void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
532526 {
533527 if (!dev->tee_ctx)
534528 return;
@@ -591,6 +585,8 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev) · 56 unmodified lines
591585 status = ret == TA_PMF_TYPE_SUCCESS;
592586 if (status) {
593587 dev->cb_flag = true;
588+ dev->cbi_buf.head = 0;
589+ dev->cbi_buf.tail = 0;
594590 break;
595591 }
596592 amd_pmf_tee_deinit(dev);
drivers/platform/x86/amd/wbrf.c+8−17
@@ -42,8 +42,6 @@ static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head); · 41 unmodified lines
4242 static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ranges_in_out *in)
4343 {
4444 union acpi_object argv4;
45− union acpi_object *tmp;
46− union acpi_object *obj;
4745 u32 num_of_ranges = 0;
4846 u32 num_of_elements;
4947 u32 arg_idx = 0;
@@ -74,7 +72,7 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran · 24 unmodified lines
7472 */
7573 num_of_elements = 2 * num_of_ranges + 2;
7674
77− tmp = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL);
75+ union acpi_object *tmp __free(kfree) = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL);
7876 if (!tmp)
7977 return -ENOMEM;
8078
@@ -101,26 +99,19 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran · 20 unmodified lines
10199 tmp[arg_idx++].integer.value = in->band_list[i].end;
102100 }
103101
104− obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
105− WBRF_REVISION, WBRF_RECORD, &argv4);
102+ union acpi_object *obj __free(kfree) =
103+ acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
104+ WBRF_REVISION, WBRF_RECORD, &argv4);
106105
107− if (!obj) {
108− kfree(tmp);
106+ if (!obj)
109107 return -EINVAL;
110− }
111108
112− if (obj->type != ACPI_TYPE_INTEGER) {
113− ret = -EINVAL;
114− goto out;
115− }
109+ if (obj->type != ACPI_TYPE_INTEGER)
110+ return -EINVAL;
116111
117112 ret = obj->integer.value;
118113 if (ret)
119− ret = -EINVAL;
120−
121−out:
122− ACPI_FREE(obj);
123− kfree(tmp);
114+ return -EINVAL;
124115
125116 return ret;
126117 }
drivers/platform/x86/asus-wmi.c+196−27
@@ -31,13 +31,13 @@ · 30 unmodified lines
3131 #include <linux/pci.h>
3232 #include <linux/pci_hotplug.h>
3333 #include <linux/platform_data/x86/asus-wmi.h>
34−#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
3534 #include <linux/platform_device.h>
3635 #include <linux/platform_profile.h>
3736 #include <linux/power_supply.h>
3837 #include <linux/rfkill.h>
3938 #include <linux/seq_file.h>
4039 #include <linux/slab.h>
40+#include <linux/spinlock.h>
4141 #include <linux/types.h>
4242 #include <linux/units.h>
4343
@@ -256,6 +256,9 @@ struct asus_wmi { · 212 unmodified lines
256256 int tpd_led_wk;
257257 struct led_classdev kbd_led;
258258 int kbd_led_wk;
259+ bool kbd_led_notify;
260+ bool kbd_led_avail;
261+ bool kbd_led_registered;
259262 struct led_classdev lightbar_led;
260263 int lightbar_led_wk;
261264 struct led_classdev micmute_led;
@@ -264,6 +267,7 @@ struct asus_wmi { · 2 unmodified lines
264267 struct work_struct tpd_led_work;
265268 struct work_struct wlan_led_work;
266269 struct work_struct lightbar_led_work;
270+ struct work_struct kbd_led_work;
267271
268272 struct asus_rfkill wlan;
269273 struct asus_rfkill bluetooth;
@@ -1615,6 +1619,144 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus) · 1345 unmodified lines
16151619
16161620 /* LEDs ***********************************************************************/
16171621
1622+struct asus_hid_ref {
1623+ struct list_head listeners;
1624+ struct asus_wmi *asus;
1625+ /* Protects concurrent access from hid-asus and asus-wmi to leds */
1626+ spinlock_t lock;
1627+};
1628+
1629+static struct asus_hid_ref asus_ref = {
1630+ .listeners = LIST_HEAD_INIT(asus_ref.listeners),
1631+ .asus = NULL,
1632+ /*
1633+ * Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other
1634+ * asus variables are read-only after .asus is set.
1635+ *
1636+ * The led cdev device is not protected because it calls backlight_get
1637+ * during initialization, which would result in a nested lock attempt.
1638+ *
1639+ * The led cdev is safe to access without a lock because if
1640+ * kbd_led_avail is true it is initialized before .asus is set and never
1641+ * changed until .asus is dropped. If kbd_led_avail is false, the led
1642+ * cdev is registered by the workqueue, which is single-threaded and
1643+ * cancelled before asus-wmi would access the led cdev to unregister it.
1644+ *
1645+ * A spinlock is used, because the protected variables can be accessed
1646+ * from an IRQ context from asus-hid.
1647+ */
1648+ .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
1649+};
1650+
1651+/*
1652+ * Allows registering hid-asus listeners that want to be notified of
1653+ * keyboard backlight changes.
1654+ */
1655+int asus_hid_register_listener(struct asus_hid_listener *bdev)
1656+{
1657+ struct asus_wmi *asus;
1658+
1659+ guard(spinlock_irqsave)(&asus_ref.lock);
1660+ list_add_tail(&bdev->list, &asus_ref.listeners);
1661+ asus = asus_ref.asus;
1662+ if (asus)
1663+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
1664+ return 0;
1665+}
1666+EXPORT_SYMBOL_GPL(asus_hid_register_listener);
1667+
1668+/*
1669+ * Allows unregistering hid-asus listeners that were added with
1670+ * asus_hid_register_listener().
1671+ */
1672+void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
1673+{
1674+ guard(spinlock_irqsave)(&asus_ref.lock);
1675+ list_del(&bdev->list);
1676+}
1677+EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
1678+
1679+static void do_kbd_led_set(struct led_classdev *led_cdev, int value);
1680+
1681+static void kbd_led_update_all(struct work_struct *work)
1682+{
1683+ struct asus_wmi *asus;
1684+ bool registered, notify;
1685+ int ret, value;
1686+
1687+ asus = container_of(work, struct asus_wmi, kbd_led_work);
1688+
1689+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
1690+ registered = asus->kbd_led_registered;
1691+ value = asus->kbd_led_wk;
1692+ notify = asus->kbd_led_notify;
1693+ }
1694+
1695+ if (!registered) {
1696+ /*
1697+ * This workqueue runs under asus-wmi, which means probe has
1698+ * completed and asus-wmi will keep running until it finishes.
1699+ * Therefore, we can safely register the LED without holding
1700+ * a spinlock.
1701+ */
1702+ ret = devm_led_classdev_register(&asus->platform_device->dev,
1703+ &asus->kbd_led);
1704+ if (!ret) {
1705+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
1706+ asus->kbd_led_registered = true;
1707+ } else {
1708+ pr_warn("Failed to register keyboard backlight LED: %d\n", ret);
1709+ return;
1710+ }
1711+ }
1712+
1713+ if (value >= 0)
1714+ do_kbd_led_set(&asus->kbd_led, value);
1715+ if (notify) {
1716+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
1717+ asus->kbd_led_notify = false;
1718+ led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value);
1719+ }
1720+}
1721+
1722+/*
1723+ * This function is called from hid-asus to inform asus-wmi of brightness
1724+ * changes initiated by the keyboard backlight keys.
1725+ */
1726+int asus_hid_event(enum asus_hid_event event)
1727+{
1728+ struct asus_wmi *asus;
1729+ int brightness;
1730+
1731+ guard(spinlock_irqsave)(&asus_ref.lock);
1732+ asus = asus_ref.asus;
1733+ if (!asus || !asus->kbd_led_registered)
1734+ return -EBUSY;
1735+
1736+ brightness = asus->kbd_led_wk;
1737+
1738+ switch (event) {
1739+ case ASUS_EV_BRTUP:
1740+ brightness += 1;
1741+ break;
1742+ case ASUS_EV_BRTDOWN:
1743+ brightness -= 1;
1744+ break;
1745+ case ASUS_EV_BRTTOGGLE:
1746+ if (brightness >= ASUS_EV_MAX_BRIGHTNESS)
1747+ brightness = 0;
1748+ else
1749+ brightness += 1;
1750+ break;
1751+ }
1752+
1753+ asus->kbd_led_wk = clamp_val(brightness, 0, ASUS_EV_MAX_BRIGHTNESS);
1754+ asus->kbd_led_notify = true;
1755+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
1756+ return 0;
1757+}
1758+EXPORT_SYMBOL_GPL(asus_hid_event);
1759+
16181760 /*
16191761 * These functions actually update the LED's, and are called from a
16201762 * workqueue. By doing this as separate work rather than when the LED
@@ -1661,7 +1803,8 @@ static void kbd_led_update(struct asus_wmi *asus) · 40 unmodified lines
16611803 {
16621804 int ctrl_param = 0;
16631805
1664− ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
1806+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
1807+ ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
16651808 asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
16661809 }
16671810
@@ -1694,14 +1837,21 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env) · 26 unmodified lines
16941837
16951838 static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
16961839 {
1840+ struct asus_hid_listener *listener;
16971841 struct asus_wmi *asus;
1698− int max_level;
16991842
17001843 asus = container_of(led_cdev, struct asus_wmi, kbd_led);
1701− max_level = asus->kbd_led.max_brightness;
17021844
1703− asus->kbd_led_wk = clamp_val(value, 0, max_level);
1704− kbd_led_update(asus);
1845+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
1846+ asus->kbd_led_wk = clamp_val(value, 0, ASUS_EV_MAX_BRIGHTNESS);
1847+
1848+ if (asus->kbd_led_avail)
1849+ kbd_led_update(asus);
1850+
1851+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
1852+ list_for_each_entry(listener, &asus_ref.listeners, list)
1853+ listener->brightness_set(listener, asus->kbd_led_wk);
1854+ }
17051855 }
17061856
17071857 static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
@@ -1716,10 +1866,11 @@ static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) · 8 unmodified lines
17161866
17171867 static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
17181868 {
1719− struct led_classdev *led_cdev = &asus->kbd_led;
1720−
1721− do_kbd_led_set(led_cdev, value);
1722− led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
1869+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
1870+ asus->kbd_led_wk = value;
1871+ asus->kbd_led_notify = true;
1872+ }
1873+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
17231874 }
17241875
17251876 static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
@@ -1729,10 +1880,18 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) · 3 unmodified lines
17291880
17301881 asus = container_of(led_cdev, struct asus_wmi, kbd_led);
17311882
1883+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
1884+ if (!asus->kbd_led_avail)
1885+ return asus->kbd_led_wk;
1886+ }
1887+
17321888 retval = kbd_led_read(asus, &value, NULL);
17331889 if (retval < 0)
17341890 return retval;
17351891
1892+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
1893+ asus->kbd_led_wk = value;
1894+
17361895 return value;
17371896 }
17381897
@@ -1844,7 +2003,9 @@ static int camera_led_set(struct led_classdev *led_cdev, · 105 unmodified lines
18442003
18452004 static void asus_wmi_led_exit(struct asus_wmi *asus)
18462005 {
1847− led_classdev_unregister(&asus->kbd_led);
2006+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
2007+ asus_ref.asus = NULL;
2008+
18482009 led_classdev_unregister(&asus->tpd_led);
18492010 led_classdev_unregister(&asus->wlan_led);
18502011 led_classdev_unregister(&asus->lightbar_led);
@@ -1882,22 +2043,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus) · 31 unmodified lines
18822043 goto error;
18832044 }
18842045
1885− if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
1886− pr_info("using asus-wmi for asus::kbd_backlight\n");
1887− asus->kbd_led_wk = led_val;
1888− asus->kbd_led.name = "asus::kbd_backlight";
1889− asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
1890− asus->kbd_led.brightness_set_blocking = kbd_led_set;
1891− asus->kbd_led.brightness_get = kbd_led_get;
1892− asus->kbd_led.max_brightness = 3;
2046+ asus->kbd_led.name = "asus::kbd_backlight";
2047+ asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
2048+ asus->kbd_led.brightness_set_blocking = kbd_led_set;
2049+ asus->kbd_led.brightness_get = kbd_led_get;
2050+ asus->kbd_led.max_brightness = ASUS_EV_MAX_BRIGHTNESS;
2051+ asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
2052+ INIT_WORK(&asus->kbd_led_work, kbd_led_update_all);
18932053
2054+ if (asus->kbd_led_avail) {
2055+ asus->kbd_led_wk = led_val;
18942056 if (num_rgb_groups != 0)
18952057 asus->kbd_led.groups = kbd_rgb_mode_groups;
2058+ } else {
2059+ asus->kbd_led_wk = -1;
2060+ }
18962061
1897− rv = led_classdev_register(&asus->platform_device->dev,
1898− &asus->kbd_led);
1899− if (rv)
1900− goto error;
2062+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
2063+ asus_ref.asus = asus;
2064+ if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners))
2065+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
19012066 }
19022067
19032068 if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
@@ -4372,6 +4537,7 @@ static int asus_wmi_get_event_code(union acpi_object *obj) · 2468 unmodified lines
43724537
43734538 static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
43744539 {
4540+ enum led_brightness led_value;
43754541 unsigned int key_value = 1;
43764542 bool autorelease = 1;
43774543
@@ -4388,19 +4554,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) · 10 unmodified lines
43884554 return;
43894555 }
43904556
4557+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
4558+ led_value = asus->kbd_led_wk;
4559+
43914560 if (code == NOTIFY_KBD_BRTUP) {
4392− kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
4561+ kbd_led_set_by_kbd(asus, led_value + 1);
43934562 return;
43944563 }
43954564 if (code == NOTIFY_KBD_BRTDWN) {
4396− kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1);
4565+ kbd_led_set_by_kbd(asus, led_value - 1);
43974566 return;
43984567 }
43994568 if (code == NOTIFY_KBD_BRTTOGGLE) {
4400− if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
4569+ if (led_value >= ASUS_EV_MAX_BRIGHTNESS)
44014570 kbd_led_set_by_kbd(asus, 0);
44024571 else
4403− kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
4572+ kbd_led_set_by_kbd(asus, led_value + 1);
44044573 return;
44054574 }
44064575
drivers/platform/x86/hp/hp-wmi.c+489−114
@@ -13,23 +13,28 @@ · 12 unmodified lines
1313
1414 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1515
16−#include <linux/kernel.h>
17−#include <linux/module.h>
16+#include <linux/acpi.h>
17+#include <linux/cleanup.h>
18+#include <linux/compiler_attributes.h>
19+#include <linux/dmi.h>
20+#include <linux/fixp-arith.h>
21+#include <linux/hwmon.h>
1822 #include <linux/init.h>
19−#include <linux/slab.h>
20−#include <linux/types.h>
2123 #include <linux/input.h>
2224 #include <linux/input/sparse-keymap.h>
25+#include <linux/kernel.h>
26+#include <linux/limits.h>
27+#include <linux/minmax.h>
28+#include <linux/module.h>
29+#include <linux/mutex.h>
2330 #include <linux/platform_device.h>
2431 #include <linux/platform_profile.h>
25−#include <linux/hwmon.h>
26−#include <linux/acpi.h>
27−#include <linux/mutex.h>
28−#include <linux/cleanup.h>
2932 #include <linux/power_supply.h>
3033 #include <linux/rfkill.h>
34+#include <linux/slab.h>
3135 #include <linux/string.h>
32−#include <linux/dmi.h>
36+#include <linux/types.h>
37+#include <linux/workqueue.h>
3338
3439 MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
3540 MODULE_DESCRIPTION("HP laptop WMI driver");
@@ -41,9 +46,13 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4"); · 5 unmodified lines
4146 #define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
4247 #define HPWMI_BIOS_GUID "5FB7F034-2C63-45E9-BE91-3D44E2C707E4"
4348
44−#define HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET 0x62
45−#define HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET 0x63
46−#define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95
49+enum hp_ec_offsets {
50+ HP_EC_OFFSET_UNKNOWN = 0x00,
51+ HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET = 0x59,
52+ HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET = 0x62,
53+ HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET = 0x63,
54+ HP_OMEN_EC_THERMAL_PROFILE_OFFSET = 0x95,
55+};
4756
4857 #define HP_FAN_SPEED_AUTOMATIC 0x00
4958 #define HP_POWER_LIMIT_DEFAULT 0x00
@@ -53,6 +62,70 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4"); · 3 unmodified lines
5362
5463 #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
5564
65+enum hp_thermal_profile_omen_v0 {
66+ HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00,
67+ HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01,
68+ HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02,
69+};
70+
71+enum hp_thermal_profile_omen_v1 {
72+ HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30,
73+ HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31,
74+ HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
75+};
76+
77+enum hp_thermal_profile_omen_flags {
78+ HP_OMEN_EC_FLAGS_TURBO = 0x04,
79+ HP_OMEN_EC_FLAGS_NOTIMER = 0x02,
80+ HP_OMEN_EC_FLAGS_JUSTSET = 0x01,
81+};
82+
83+enum hp_thermal_profile_victus {
84+ HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00,
85+ HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01,
86+ HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03,
87+};
88+
89+enum hp_thermal_profile_victus_s {
90+ HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00,
91+ HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01,
92+};
93+
94+enum hp_thermal_profile {
95+ HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
96+ HP_THERMAL_PROFILE_DEFAULT = 0x01,
97+ HP_THERMAL_PROFILE_COOL = 0x02,
98+ HP_THERMAL_PROFILE_QUIET = 0x03,
99+};
100+
101+
102+struct thermal_profile_params {
103+ u8 performance;
104+ u8 balanced;
105+ u8 low_power;
106+ u8 ec_tp_offset;
107+};
108+
109+static const struct thermal_profile_params victus_s_thermal_params = {
110+ .performance = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE,
111+ .balanced = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT,
112+ .low_power = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT,
113+ .ec_tp_offset = HP_EC_OFFSET_UNKNOWN,
114+};
115+
116+static const struct thermal_profile_params omen_v1_thermal_params = {
117+ .performance = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE,
118+ .balanced = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
119+ .low_power = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
120+ .ec_tp_offset = HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET,
121+};
122+
123+/*
124+ * A generic pointer for the currently-active board's thermal profile
125+ * parameters.
126+ */
127+static struct thermal_profile_params *active_thermal_profile_params;
128+
56129 /* DMI board names of devices that should use the omen specific path for
57130 * thermal profiles.
58131 * This was obtained by taking a look in the windows omen command center
@@ -99,12 +172,40 @@ static const char * const victus_thermal_profile_boards[] = { · 40 unmodified lines
99172 };
100173
101174 /* DMI Board names of Victus 16-r and Victus 16-s laptops */
102−static const char * const victus_s_thermal_profile_boards[] = {
103− "8BBE", "8BD4", "8BD5",
104− "8C78", "8C99", "8C9C",
105− "8D41",
175+static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = {
176+ {
177+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") },
178+ .driver_data = (void *)&victus_s_thermal_params,
179+ },
180+ {
181+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") },
182+ .driver_data = (void *)&victus_s_thermal_params,
183+ },
184+ {
185+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") },
186+ .driver_data = (void *)&victus_s_thermal_params,
187+ },
188+ {
189+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") },
190+ .driver_data = (void *)&omen_v1_thermal_params,
191+ },
192+ {
193+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") },
194+ .driver_data = (void *)&victus_s_thermal_params,
195+ },
196+ {
197+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") },
198+ .driver_data = (void *)&victus_s_thermal_params,
199+ },
200+ {
201+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") },
202+ .driver_data = (void *)&victus_s_thermal_params,
203+ },
204+ {},
106205 };
107206
207+static bool is_victus_s_board;
208+
108209 enum hp_wmi_radio {
109210 HPWMI_WIFI = 0x0,
110211 HPWMI_BLUETOOTH = 0x1,
@@ -190,7 +291,8 @@ enum hp_wmi_gm_commandtype { · 79 unmodified lines
190291 HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22,
191292 HPWMI_SET_POWER_LIMITS_QUERY = 0x29,
192293 HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY = 0x2D,
193− HPWMI_FAN_SPEED_SET_QUERY = 0x2E,
294+ HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY = 0x2E,
295+ HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY = 0x2F,
194296 };
195297
196298 enum hp_wmi_command {
@@ -225,42 +327,6 @@ enum hp_wireless2_bits { · 28 unmodified lines
225327 HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD,
226328 };
227329
228−enum hp_thermal_profile_omen_v0 {
229− HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00,
230− HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01,
231− HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02,
232−};
233−
234−enum hp_thermal_profile_omen_v1 {
235− HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30,
236− HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31,
237− HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
238−};
239−
240−enum hp_thermal_profile_omen_flags {
241− HP_OMEN_EC_FLAGS_TURBO = 0x04,
242− HP_OMEN_EC_FLAGS_NOTIMER = 0x02,
243− HP_OMEN_EC_FLAGS_JUSTSET = 0x01,
244−};
245−
246−enum hp_thermal_profile_victus {
247− HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00,
248− HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01,
249− HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03,
250−};
251−
252−enum hp_thermal_profile_victus_s {
253− HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00,
254− HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01,
255−};
256−
257−enum hp_thermal_profile {
258− HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
259− HP_THERMAL_PROFILE_DEFAULT = 0x01,
260− HP_THERMAL_PROFILE_COOL = 0x02,
261− HP_THERMAL_PROFILE_QUIET = 0x03,
262−};
263−
264330 #define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW)
265331 #define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
266332
@@ -348,6 +414,58 @@ static const char * const tablet_chassis_types[] = { · 81 unmodified lines
348414
349415 #define DEVICE_MODE_TABLET 0x06
350416
417+#define CPU_FAN 0
418+#define GPU_FAN 1
419+
420+enum pwm_modes {
421+ PWM_MODE_MAX = 0,
422+ PWM_MODE_MANUAL = 1,
423+ PWM_MODE_AUTO = 2,
424+};
425+
426+struct hp_wmi_hwmon_priv {
427+ u8 min_rpm;
428+ u8 max_rpm;
429+ u8 gpu_delta;
430+ u8 mode;
431+ u8 pwm;
432+ struct delayed_work keep_alive_dwork;
433+};
434+
435+struct victus_s_fan_table_header {
436+ u8 unknown;
437+ u8 num_entries;
438+} __packed;
439+
440+struct victus_s_fan_table_entry {
441+ u8 cpu_rpm;
442+ u8 gpu_rpm;
443+ u8 unknown;
444+} __packed;
445+
446+struct victus_s_fan_table {
447+ struct victus_s_fan_table_header header;
448+ struct victus_s_fan_table_entry entries[];
449+} __packed;
450+
451+/*
452+ * 90s delay to prevent the firmware from resetting fan mode after fixed
453+ * 120s timeout
454+ */
455+#define KEEP_ALIVE_DELAY_SECS 90
456+
457+static inline u8 rpm_to_pwm(u8 rpm, struct hp_wmi_hwmon_priv *priv)
458+{
459+ return fixp_linear_interpolate(0, 0, priv->max_rpm, U8_MAX,
460+ clamp_val(rpm, 0, priv->max_rpm));
461+}
462+
463+static inline u8 pwm_to_rpm(u8 pwm, struct hp_wmi_hwmon_priv *priv)
464+{
465+ return fixp_linear_interpolate(0, 0, U8_MAX, priv->max_rpm,
466+ clamp_val(pwm, 0, U8_MAX));
467+}
468+
351469 /* map output size to the corresponding WMI method id */
352470 static inline int encode_outsize_for_pvsz(int outsize)
353471 {
@@ -637,41 +755,53 @@ static int hp_wmi_fan_speed_max_set(int enabled) · 283 unmodified lines
637755 return enabled;
638756 }
639757
640−static int hp_wmi_fan_speed_reset(void)
758+static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed)
641759 {
642− u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC };
643− int ret;
760+ u8 fan_speed[2];
761+ int gpu_speed, ret;
644762
645− ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM,
646− &fan_speed, sizeof(fan_speed), 0);
763+ fan_speed[CPU_FAN] = speed;
764+ fan_speed[GPU_FAN] = speed;
647765
648− return ret;
649−}
650−
651−static int hp_wmi_fan_speed_max_reset(void)
652−{
653− int ret;
766+ /*
767+ * GPU fan speed is always a little higher than CPU fan speed, we fetch
768+ * this delta value from the fan table during hwmon init.
769+ * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to
770+ * automatic mode.
771+ */
772+ if (speed != HP_FAN_SPEED_AUTOMATIC) {
773+ gpu_speed = speed + priv->gpu_delta;
774+ fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX);
775+ }
654776
777+ ret = hp_wmi_get_fan_count_userdefine_trigger();
778+ if (ret < 0)
779+ return ret;
780+ /* Max fans need to be explicitly disabled */
655781 ret = hp_wmi_fan_speed_max_set(0);
656− if (ret)
782+ if (ret < 0)
657783 return ret;
784+ ret = hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY, HPWMI_GM,
785+ &fan_speed, sizeof(fan_speed), 0);
658786
659− /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */
660− ret = hp_wmi_fan_speed_reset();
661787 return ret;
662788 }
663789
664−static int hp_wmi_fan_speed_max_get(void)
790+static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv)
665791 {
666− int val = 0, ret;
792+ return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC);
793+}
667794
668− ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM,
669− &val, zero_if_sup(val), sizeof(val));
795+static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv)
796+{
797+ int ret;
670798
799+ ret = hp_wmi_fan_speed_max_set(0);
671800 if (ret)
672− return ret < 0 ? ret : -EINVAL;
801+ return ret;
673802
674− return val;
803+ /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */
804+ return hp_wmi_fan_speed_reset(priv);
675805 }
676806
677807 static int __init hp_wmi_bios_2008_later(void)
@@ -1581,15 +1711,8 @@ static int platform_profile_victus_set_ec(enum platform_profile_option profile) · 903 unmodified lines
15811711
15821712 static bool is_victus_s_thermal_profile(void)
15831713 {
1584− const char *board_name;
1585−
1586− board_name = dmi_get_system_info(DMI_BOARD_NAME);
1587− if (!board_name)
1588− return false;
1589−
1590− return match_string(victus_s_thermal_profile_boards,
1591− ARRAY_SIZE(victus_s_thermal_profile_boards),
1592− board_name) >= 0;
1714+ /* Initialised in driver init, hence safe to use here */
1715+ return is_victus_s_board;
15931716 }
15941717
15951718 static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable,
@@ -1670,27 +1793,86 @@ static int victus_s_set_cpu_pl1_pl2(u8 pl1, u8 pl2) · 74 unmodified lines
16701793 return ret;
16711794 }
16721795
1796+static int platform_profile_victus_s_get_ec(enum platform_profile_option *profile)
1797+{
1798+ int ret = 0;
1799+ bool current_ctgp_state, current_ppab_state;
1800+ u8 current_dstate, current_gpu_slowdown_temp, tp;
1801+ const struct thermal_profile_params *params;
1802+
1803+ params = active_thermal_profile_params;
1804+ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
1805+ *profile = active_platform_profile;
1806+ return 0;
1807+ }
1808+
1809+ ret = ec_read(params->ec_tp_offset, &tp);
1810+ if (ret)
1811+ return ret;
1812+
1813+ /*
1814+ * We cannot use active_thermal_profile_params here, because boards
1815+ * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically
1816+ * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen
1817+ * Gaming Hub on windows. Hence accept both of these values.
1818+ */
1819+ if (tp == victus_s_thermal_params.performance ||
1820+ tp == omen_v1_thermal_params.performance) {
1821+ *profile = PLATFORM_PROFILE_PERFORMANCE;
1822+ } else if (tp == victus_s_thermal_params.balanced ||
1823+ tp == omen_v1_thermal_params.balanced) {
1824+ /*
1825+ * Since both PLATFORM_PROFILE_LOW_POWER and
1826+ * PLATFORM_PROFILE_BALANCED share the same thermal profile
1827+ * parameter value, hence to differentiate between them, we
1828+ * query the GPU CTGP and PPAB states and compare based off of
1829+ * that.
1830+ */
1831+ ret = victus_s_gpu_thermal_profile_get(&current_ctgp_state,
1832+ &current_ppab_state,
1833+ &current_dstate,
1834+ &current_gpu_slowdown_temp);
1835+ if (ret < 0)
1836+ return ret;
1837+ if (current_ctgp_state == 0 && current_ppab_state == 0)
1838+ *profile = PLATFORM_PROFILE_LOW_POWER;
1839+ else if (current_ctgp_state == 0 && current_ppab_state == 1)
1840+ *profile = PLATFORM_PROFILE_BALANCED;
1841+ else
1842+ return -EINVAL;
1843+ } else {
1844+ return -EINVAL;
1845+ }
1846+
1847+ return 0;
1848+}
1849+
16731850 static int platform_profile_victus_s_set_ec(enum platform_profile_option profile)
16741851 {
1852+ struct thermal_profile_params *params;
16751853 bool gpu_ctgp_enable, gpu_ppab_enable;
16761854 u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */
16771855 int err, tp;
16781856
1857+ params = active_thermal_profile_params;
1858+ if (!params)
1859+ return -ENODEV;
1860+
16791861 switch (profile) {
16801862 case PLATFORM_PROFILE_PERFORMANCE:
1681− tp = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE;
1863+ tp = params->performance;
16821864 gpu_ctgp_enable = true;
16831865 gpu_ppab_enable = true;
16841866 gpu_dstate = 1;
16851867 break;
16861868 case PLATFORM_PROFILE_BALANCED:
1687− tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
1869+ tp = params->balanced;
16881870 gpu_ctgp_enable = false;
16891871 gpu_ppab_enable = true;
16901872 gpu_dstate = 1;
16911873 break;
16921874 case PLATFORM_PROFILE_LOW_POWER:
1693− tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
1875+ tp = params->low_power;
16941876 gpu_ctgp_enable = false;
16951877 gpu_ppab_enable = false;
16961878 gpu_dstate = 1;
@@ -1832,6 +2014,7 @@ static int victus_s_powersource_event(struct notifier_block *nb, · 135 unmodified lines
18322014 void *data)
18332015 {
18342016 struct acpi_bus_event *event_entry = data;
2017+ enum platform_profile_option actual_profile;
18352018 int err;
18362019
18372020 if (strcmp(event_entry->device_class, ACPI_AC_CLASS) != 0)
@@ -1839,6 +2022,17 @@ static int victus_s_powersource_event(struct notifier_block *nb, · 1 unmodified line
18392022
18402023 pr_debug("Received power source device event\n");
18412024
2025+ guard(mutex)(&active_platform_profile_lock);
2026+ err = platform_profile_victus_s_get_ec(&actual_profile);
2027+ if (err < 0) {
2028+ /*
2029+ * Although we failed to get the current platform profile, we
2030+ * still want the other event consumers to process it.
2031+ */
2032+ pr_warn("Failed to read current platform profile (%d)\n", err);
2033+ return NOTIFY_DONE;
2034+ }
2035+
18422036 /*
18432037 * Switching to battery power source while Performance mode is active
18442038 * needs manual triggering of CPU power limits. Same goes when switching
@@ -1847,7 +2041,7 @@ static int victus_s_powersource_event(struct notifier_block *nb, · 2 unmodified lines
18472041 * Seen on HP 16-s1034nf (board 8C9C) with F.11 and F.13 BIOS versions.
18482042 */
18492043
1850− if (active_platform_profile == PLATFORM_PROFILE_PERFORMANCE) {
2044+ if (actual_profile == PLATFORM_PROFILE_PERFORMANCE) {
18512045 pr_debug("Triggering CPU PL1/PL2 actualization\n");
18522046 err = victus_s_set_cpu_pl1_pl2(HP_POWER_LIMIT_DEFAULT,
18532047 HP_POWER_LIMIT_DEFAULT);
@@ -1958,11 +2152,22 @@ static int thermal_profile_setup(struct platform_device *device) · 104 unmodified lines
19582152 ops = &platform_profile_victus_ops;
19592153 } else if (is_victus_s_thermal_profile()) {
19602154 /*
1961− * Being unable to retrieve laptop's current thermal profile,
1962− * during this setup, we set it to Balanced by default.
2155+ * For an unknown EC layout board, platform_profile_victus_s_get_ec(),
2156+ * behaves like a wrapper around active_platform_profile, to avoid using
2157+ * uninitialized data, we default to PLATFORM_PROFILE_BALANCED.
19632158 */
1964− active_platform_profile = PLATFORM_PROFILE_BALANCED;
2159+ if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
2160+ active_platform_profile = PLATFORM_PROFILE_BALANCED;
2161+ } else {
2162+ err = platform_profile_victus_s_get_ec(&active_platform_profile);
2163+ if (err < 0)
2164+ return err;
2165+ }
19652166
2167+ /*
2168+ * call thermal profile write command to ensure that the
2169+ * firmware correctly sets the OEM variables
2170+ */
19662171 err = platform_profile_victus_s_set_ec(active_platform_profile);
19672172 if (err < 0)
19682173 return err;
@@ -2031,6 +2236,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) · 62 unmodified lines
20312236 static void __exit hp_wmi_bios_remove(struct platform_device *device)
20322237 {
20332238 int i;
2239+ struct hp_wmi_hwmon_priv *priv;
20342240
20352241 for (i = 0; i < rfkill2_count; i++) {
20362242 rfkill_unregister(rfkill2[i].rfkill);
@@ -2049,6 +2255,10 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device) · 12 unmodified lines
20492255 rfkill_unregister(wwan_rfkill);
20502256 rfkill_destroy(wwan_rfkill);
20512257 }
2258+
2259+ priv = platform_get_drvdata(device);
2260+ if (priv)
2261+ cancel_delayed_work_sync(&priv->keep_alive_dwork);
20522262 }
20532263
20542264 static int hp_wmi_resume_handler(struct device *device)
@@ -2108,12 +2318,56 @@ static struct platform_driver hp_wmi_driver __refdata = { · 53 unmodified lines
21082318 .remove = __exit_p(hp_wmi_bios_remove),
21092319 };
21102320
2321+static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
2322+{
2323+ int ret;
2324+
2325+ switch (priv->mode) {
2326+ case PWM_MODE_MAX:
2327+ if (is_victus_s_thermal_profile())
2328+ hp_wmi_get_fan_count_userdefine_trigger();
2329+ ret = hp_wmi_fan_speed_max_set(1);
2330+ if (ret < 0)
2331+ return ret;
2332+ schedule_delayed_work(&priv->keep_alive_dwork,
2333+ secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
2334+ return 0;
2335+ case PWM_MODE_MANUAL:
2336+ if (!is_victus_s_thermal_profile())
2337+ return -EOPNOTSUPP;
2338+ ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv));
2339+ if (ret < 0)
2340+ return ret;
2341+ schedule_delayed_work(&priv->keep_alive_dwork,
2342+ secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
2343+ return 0;
2344+ case PWM_MODE_AUTO:
2345+ if (is_victus_s_thermal_profile()) {
2346+ hp_wmi_get_fan_count_userdefine_trigger();
2347+ ret = hp_wmi_fan_speed_max_reset(priv);
2348+ } else {
2349+ ret = hp_wmi_fan_speed_max_set(0);
2350+ }
2351+ if (ret < 0)
2352+ return ret;
2353+ cancel_delayed_work_sync(&priv->keep_alive_dwork);
2354+ return 0;
2355+ default:
2356+ /* shouldn't happen */
2357+ return -EINVAL;
2358+ }
2359+
2360+ return 0;
2361+}
2362+
21112363 static umode_t hp_wmi_hwmon_is_visible(const void *data,
21122364 enum hwmon_sensor_types type,
21132365 u32 attr, int channel)
21142366 {
21152367 switch (type) {
21162368 case hwmon_pwm:
2369+ if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile())
2370+ return 0;
21172371 return 0644;
21182372 case hwmon_fan:
21192373 if (is_victus_s_thermal_profile()) {
@@ -2134,8 +2388,10 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, · 14 unmodified lines
21342388 static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
21352389 u32 attr, int channel, long *val)
21362390 {
2137− int ret;
2391+ struct hp_wmi_hwmon_priv *priv;
2392+ int rpm, ret;
21382393
2394+ priv = dev_get_drvdata(dev);
21392395 switch (type) {
21402396 case hwmon_fan:
21412397 if (is_victus_s_thermal_profile())
@@ -2147,16 +2403,21 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, · 5 unmodified lines
21472403 *val = ret;
21482404 return 0;
21492405 case hwmon_pwm:
2150− switch (hp_wmi_fan_speed_max_get()) {
2151− case 0:
2152− /* 0 is automatic fan, which is 2 for hwmon */
2153− *val = 2;
2406+ if (attr == hwmon_pwm_input) {
2407+ if (!is_victus_s_thermal_profile())
2408+ return -EOPNOTSUPP;
2409+
2410+ rpm = hp_wmi_get_fan_speed_victus_s(channel);
2411+ if (rpm < 0)
2412+ return rpm;
2413+ *val = rpm_to_pwm(rpm / 100, priv);
21542414 return 0;
2155− case 1:
2156− /* 1 is max fan, which is 0
2157− * (no fan speed control) for hwmon
2158− */
2159− *val = 0;
2415+ }
2416+ switch (priv->mode) {
2417+ case PWM_MODE_MAX:
2418+ case PWM_MODE_MANUAL:
2419+ case PWM_MODE_AUTO:
2420+ *val = priv->mode;
21602421 return 0;
21612422 default:
21622423 /* shouldn't happen */
@@ -2170,23 +2431,46 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, · 7 unmodified lines
21702431 static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
21712432 u32 attr, int channel, long val)
21722433 {
2434+ struct hp_wmi_hwmon_priv *priv;
2435+ int rpm;
2436+
2437+ priv = dev_get_drvdata(dev);
21732438 switch (type) {
21742439 case hwmon_pwm:
2440+ if (attr == hwmon_pwm_input) {
2441+ if (!is_victus_s_thermal_profile())
2442+ return -EOPNOTSUPP;
2443+ /* PWM input is invalid when not in manual mode */
2444+ if (priv->mode != PWM_MODE_MANUAL)
2445+ return -EINVAL;
2446+
2447+ /* ensure PWM input is within valid fan speeds */
2448+ rpm = pwm_to_rpm(val, priv);
2449+ rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm);
2450+ priv->pwm = rpm_to_pwm(rpm, priv);
2451+ return hp_wmi_apply_fan_settings(priv);
2452+ }
21752453 switch (val) {
2176− case 0:
2177− if (is_victus_s_thermal_profile())
2178− hp_wmi_get_fan_count_userdefine_trigger();
2179− /* 0 is no fan speed control (max), which is 1 for us */
2180− return hp_wmi_fan_speed_max_set(1);
2181− case 2:
2182− /* 2 is automatic speed control, which is 0 for us */
2183− if (is_victus_s_thermal_profile()) {
2184− hp_wmi_get_fan_count_userdefine_trigger();
2185− return hp_wmi_fan_speed_max_reset();
2186− } else
2187− return hp_wmi_fan_speed_max_set(0);
2454+ case PWM_MODE_MAX:
2455+ priv->mode = PWM_MODE_MAX;
2456+ return hp_wmi_apply_fan_settings(priv);
2457+ case PWM_MODE_MANUAL:
2458+ if (!is_victus_s_thermal_profile())
2459+ return -EOPNOTSUPP;
2460+ /*
2461+ * When switching to manual mode, set fan speed to
2462+ * current RPM values to ensure a smooth transition.
2463+ */
2464+ rpm = hp_wmi_get_fan_speed_victus_s(channel);
2465+ if (rpm < 0)
2466+ return rpm;
2467+ priv->pwm = rpm_to_pwm(rpm / 100, priv);
2468+ priv->mode = PWM_MODE_MANUAL;
2469+ return hp_wmi_apply_fan_settings(priv);
2470+ case PWM_MODE_AUTO:
2471+ priv->mode = PWM_MODE_AUTO;
2472+ return hp_wmi_apply_fan_settings(priv);
21882473 default:
2189− /* we don't support manual fan speed control */
21902474 return -EINVAL;
21912475 }
21922476 default:
@@ -2196,7 +2480,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, · 3 unmodified lines
21962480
21972481 static const struct hwmon_channel_info * const info[] = {
21982482 HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT),
2199− HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE),
2483+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT),
22002484 NULL
22012485 };
22022486
@@ -2211,12 +2495,70 @@ static const struct hwmon_chip_info chip_info = { · 8 unmodified lines
22112495 .info = info,
22122496 };
22132497
2498+static void hp_wmi_hwmon_keep_alive_handler(struct work_struct *work)
2499+{
2500+ struct delayed_work *dwork;
2501+ struct hp_wmi_hwmon_priv *priv;
2502+
2503+ dwork = to_delayed_work(work);
2504+ priv = container_of(dwork, struct hp_wmi_hwmon_priv, keep_alive_dwork);
2505+ /*
2506+ * Re-apply the current hwmon context settings.
2507+ * NOTE: hp_wmi_apply_fan_settings will handle the re-scheduling.
2508+ */
2509+ hp_wmi_apply_fan_settings(priv);
2510+}
2511+
2512+static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
2513+{
2514+ u8 fan_data[128] = { 0 };
2515+ struct victus_s_fan_table *fan_table;
2516+ u8 min_rpm, max_rpm, gpu_delta;
2517+ int ret;
2518+
2519+ /* Default behaviour on hwmon init is automatic mode */
2520+ priv->mode = PWM_MODE_AUTO;
2521+
2522+ /* Bypass all non-Victus S devices */
2523+ if (!is_victus_s_thermal_profile())
2524+ return 0;
2525+
2526+ ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY,
2527+ HPWMI_GM, &fan_data, 4, sizeof(fan_data));
2528+ if (ret)
2529+ return ret;
2530+
2531+ fan_table = (struct victus_s_fan_table *)fan_data;
2532+ if (fan_table->header.num_entries == 0 ||
2533+ sizeof(struct victus_s_fan_table_header) +
2534+ sizeof(struct victus_s_fan_table_entry) * fan_table->header.num_entries > sizeof(fan_data))
2535+ return -EINVAL;
2536+
2537+ min_rpm = fan_table->entries[0].cpu_rpm;
2538+ max_rpm = fan_table->entries[fan_table->header.num_entries - 1].cpu_rpm;
2539+ gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm;
2540+ priv->min_rpm = min_rpm;
2541+ priv->max_rpm = max_rpm;
2542+ priv->gpu_delta = gpu_delta;
2543+
2544+ return 0;
2545+}
2546+
22142547 static int hp_wmi_hwmon_init(void)
22152548 {
22162549 struct device *dev = &hp_wmi_platform_dev->dev;
2550+ struct hp_wmi_hwmon_priv *priv;
22172551 struct device *hwmon;
2552+ int ret;
2553+
2554+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
2555+ if (!priv)
2556+ return -ENOMEM;
22182557
2219− hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver,
2558+ ret = hp_wmi_setup_fan_settings(priv);
2559+ if (ret)
2560+ return ret;
2561+ hwmon = devm_hwmon_device_register_with_info(dev, "hp", priv,
22202562 &chip_info, NULL);
22212563
22222564 if (IS_ERR(hwmon)) {
@@ -2224,9 +2566,37 @@ static int hp_wmi_hwmon_init(void) · 1 unmodified line
22242566 return PTR_ERR(hwmon);
22252567 }
22262568
2569+ INIT_DELAYED_WORK(&priv->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler);
2570+ platform_set_drvdata(hp_wmi_platform_dev, priv);
2571+ hp_wmi_apply_fan_settings(priv);
2572+
22272573 return 0;
22282574 }
22292575
2576+static void __init setup_active_thermal_profile_params(void)
2577+{
2578+ const struct dmi_system_id *id;
2579+
2580+ /*
2581+ * Currently only victus_s devices use the
2582+ * active_thermal_profile_params
2583+ */
2584+ id = dmi_first_match(victus_s_thermal_profile_boards);
2585+ if (id) {
2586+ /*
2587+ * Marking this boolean is required to ensure that
2588+ * is_victus_s_thermal_profile() behaves like a valid
2589+ * wrapper.
2590+ */
2591+ is_victus_s_board = true;
2592+ active_thermal_profile_params = id->driver_data;
2593+ if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
2594+ pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n",
2595+ dmi_get_system_info(DMI_BOARD_NAME));
2596+ }
2597+ }
2598+}
2599+
22302600 static int __init hp_wmi_init(void)
22312601 {
22322602 int event_capable = wmi_has_guid(HPWMI_EVENT_GUID);
@@ -2254,6 +2624,11 @@ static int __init hp_wmi_init(void) · 21 unmodified lines
22542624 goto err_destroy_input;
22552625 }
22562626
2627+ /*
2628+ * Setup active board's thermal profile parameters before
2629+ * starting platform driver probe.
2630+ */
2631+ setup_active_thermal_profile_params();
22572632 err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup);
22582633 if (err)
22592634 goto err_unregister_device;
drivers/platform/x86/intel/pmc/core.c+56−29
@@ -776,16 +776,26 @@ static inline u64 adjust_lpm_residency(struct pmc *pmc, u32 offset, · 775 unmodified lines
776776 static int pmc_core_substate_res_show(struct seq_file *s, void *unused)
777777 {
778778 struct pmc_dev *pmcdev = s->private;
779− struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
780− const int lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
781− u32 offset = pmc->map->lpm_residency_offset;
782− int mode;
779+ unsigned int pmc_idx;
780+
781+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
782+ int lpm_adj_x2;
783+ struct pmc *pmc;
784+ u32 offset;
785+ u8 mode;
783786
784− seq_printf(s, "%-10s %-15s\n", "Substate", "Residency");
787+ pmc = pmcdev->pmcs[pmc_idx];
788+ if (!pmc)
789+ continue;
790+
791+ lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
792+ offset = pmc->map->lpm_residency_offset;
785793
786− pmc_for_each_mode(mode, pmcdev) {
787− seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode],
788− adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2));
794+ seq_printf(s, "pmc%u %10s %15s\n", pmc_idx, "Substate", "Residency");
795+ pmc_for_each_mode(mode, pmc) {
796+ seq_printf(s, "%15s %15llu\n", pmc_lpm_modes[mode],
797+ adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2));
798+ }
789799 }
790800
791801 return 0;
@@ -838,10 +848,11 @@ static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index, · 46 unmodified lines
838848 enum header_type type)
839849 {
840850 struct pmc_dev *pmcdev = s->private;
841− int mode;
851+ struct pmc *pmc = pmcdev->pmcs[pmc_index];
852+ u8 mode;
842853
843854 seq_printf(s, "%40s |", "Element");
844− pmc_for_each_mode(mode, pmcdev)
855+ pmc_for_each_mode(mode, pmc)
845856 seq_printf(s, " %9s |", pmc_lpm_modes[mode]);
846857
847858 if (type == HEADER_STATUS) {
@@ -880,14 +891,14 @@ static int pmc_core_substate_blk_req_show(struct seq_file *s, void *unused) · 32 unmodified lines
880891 const struct pmc_bit_map *map;
881892
882893 for (map = maps[r_idx]; map->name; map++) {
883− int mode;
894+ u8 mode;
884895
885896 if (!map->blk)
886897 continue;
887898
888899 counter = pmc_core_reg_read(pmc, offset);
889900 seq_printf(s, "pmc%u: %34s |", pmc_idx, map->name);
890− pmc_for_each_mode(mode, pmcdev) {
901+ pmc_for_each_mode(mode, pmc) {
891902 bool required = *lpm_req_regs & BIT(mode);
892903
893904 seq_printf(s, " %9s |", required ? "Required" : " ");
@@ -953,14 +964,15 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) · 59 unmodified lines
953964 u32 lpm_status;
954965 u32 lpm_status_live;
955966 const struct pmc_bit_map *map;
956− int mode, i, len = 32;
967+ int i, len = 32;
968+ u8 mode;
957969
958970 /*
959971 * Capture the requirements and create a mask so that we only
960972 * show an element if it's required for at least one of the
961973 * enabled low power modes
962974 */
963− pmc_for_each_mode(mode, pmcdev)
975+ pmc_for_each_mode(mode, pmc)
964976 req_mask |= lpm_req_regs[mp + (mode * num_maps)];
965977
966978 /* Get the last latched status for this map */
@@ -986,7 +998,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) · 19 unmodified lines
986998 seq_printf(s, "pmc%d: %34s |", pmc_idx, map[i].name);
987999
9881000 /* Loop over the enabled states and display if required */
989− pmc_for_each_mode(mode, pmcdev) {
1001+ pmc_for_each_mode(mode, pmc) {
9901002 bool required = lpm_req_regs[mp + (mode * num_maps)] &
9911003 bit_mask;
9921004 seq_printf(s, " %9s |", required ? "Required" : " ");
@@ -1065,7 +1077,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused) · 72 unmodified lines
10651077 struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
10661078 bool c10;
10671079 u32 reg;
1068− int mode;
1080+ u8 mode;
10691081
10701082 reg = pmc_core_reg_read(pmc, pmc->map->lpm_sts_latch_en_offset);
10711083 if (reg & LPM_STS_LATCH_MODE) {
@@ -1076,7 +1088,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused) · 4 unmodified lines
10761088 c10 = true;
10771089 }
10781090
1079− pmc_for_each_mode(mode, pmcdev) {
1091+ pmc_for_each_mode(mode, pmc) {
10801092 if ((BIT(mode) & reg) && !c10)
10811093 seq_printf(s, " [%s]", pmc_lpm_modes[mode]);
10821094 else
@@ -1097,8 +1109,9 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, · 14 unmodified lines
10971109 struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
10981110 bool clear = false, c10 = false;
10991111 unsigned char buf[8];
1100− int m, mode;
1112+ int mode;
11011113 u32 reg;
1114+ u8 m;
11021115
11031116 if (count > sizeof(buf) - 1)
11041117 return -EINVAL;
@@ -1115,7 +1128,7 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, · 10 unmodified lines
11151128 mode = sysfs_match_string(pmc_lpm_modes, buf);
11161129
11171130 /* Check string matches enabled mode */
1118− pmc_for_each_mode(m, pmcdev)
1131+ pmc_for_each_mode(m, pmc)
11191132 if (mode == m)
11201133 break;
11211134
@@ -1211,15 +1224,15 @@ static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order) · 89 unmodified lines
12111224 return true;
12121225 }
12131226
1214−void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
1227+static void pmc_core_pmc_get_low_power_modes(struct pmc_dev *pmcdev, struct pmc *pmc)
12151228 {
1216− struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
12171229 u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI;
12181230 u8 mode_order[LPM_MAX_NUM_MODES];
12191231 u32 lpm_pri;
12201232 u32 lpm_en;
1233+ u8 mode;
12211234 unsigned int i;
1222− int mode, p;
1235+ int p;
12231236
12241237 /* Use LPM Maps to indicate support for substates */
12251238 if (!pmc->map->lpm_num_maps)
@@ -1230,12 +1243,11 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev) · 4 unmodified lines
12301243 * Lower byte is enough to cover the number of lpm modes for all
12311244 * platforms and hence mask the upper 3 bytes.
12321245 */
1233− pmcdev->num_lpm_modes = hweight32(lpm_en & 0xFF);
1246+ pmc->num_lpm_modes = hweight32(lpm_en & 0xFF);
12341247
12351248 /* Read 32 bit LPM_PRI register */
12361249 lpm_pri = pmc_core_reg_read(pmc, pmc->map->lpm_priority_offset);
12371250
1238−
12391251 /*
12401252 * If lpm_pri value passes verification, then override the default
12411253 * modes here. Otherwise stick with the default.
@@ -1254,12 +1266,27 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev) · 12 unmodified lines
12541266 */
12551267 i = 0;
12561268 for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) {
1257− int mode = pri_order[p];
1269+ u8 mode = pri_order[p];
12581270
12591271 if (!(BIT(mode) & lpm_en))
12601272 continue;
12611273
1262− pmcdev->lpm_en_modes[i++] = mode;
1274+ pmc->lpm_en_modes[i++] = mode;
1275+ }
1276+}
1277+
1278+static void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
1279+{
1280+ unsigned int pmc_idx;
1281+
1282+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) {
1283+ struct pmc *pmc;
1284+
1285+ pmc = pmcdev->pmcs[pmc_idx];
1286+ if (!pmc)
1287+ continue;
1288+
1289+ pmc_core_pmc_get_low_power_modes(pmcdev, pmc);
12631290 }
12641291 }
12651292
@@ -1490,8 +1517,8 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel · 224 unmodified lines
14901517 {
14911518 const u8 *lpm_indices;
14921519 int num_maps, mode_offset = 0;
1493− int ret, mode;
1494− int lpm_size;
1520+ int ret, lpm_size;
1521+ u8 mode;
14951522
14961523 lpm_indices = pmc->map->lpm_reg_index;
14971524 num_maps = pmc->map->lpm_num_maps;
@@ -1504,7 +1531,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel · 6 unmodified lines
15041531 return -ENOMEM;
15051532
15061533 mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET;
1507− pmc_for_each_mode(mode, pmcdev) {
1534+ pmc_for_each_mode(mode, pmc) {
15081535 u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps);
15091536 int m;
15101537
drivers/platform/x86/intel/pmc/core.h+7−8
@@ -423,6 +423,8 @@ struct pmc_info { · 422 unmodified lines
423423 * specific attributes
424424 * @lpm_req_regs: List of substate requirements
425425 * @ltr_ign: Holds LTR ignore data while suspended
426+ * @num_lpm_modes: Count of enabled modes
427+ * @lpm_en_modes: Array of enabled modes from lowest to highest priority
426428 *
427429 * pmc contains info about one power management controller device.
428430 */
@@ -432,6 +434,8 @@ struct pmc { · 3 unmodified lines
432434 const struct pmc_reg_map *map;
433435 u32 *lpm_req_regs;
434436 u32 ltr_ign;
437+ u8 num_lpm_modes;
438+ u8 lpm_en_modes[LPM_MAX_NUM_MODES];
435439 };
436440
437441 /**
@@ -446,8 +450,6 @@ struct pmc { · 8 unmodified lines
446450 * @pkgc_res_cnt: Array of PKGC residency counters
447451 * @num_of_pkgc: Number of PKGC
448452 * @s0ix_counter: S0ix residency (step adjusted)
449− * @num_lpm_modes: Count of enabled modes
450− * @lpm_en_modes: Array of enabled modes from lowest to highest priority
451453 * @suspend: Function to perform platform specific suspend
452454 * @resume: Function to perform platform specific resume
453455 *
@@ -462,8 +464,6 @@ struct pmc_dev { · 8 unmodified lines
462464 struct mutex lock; /* generic mutex lock for PMC Core */
463465
464466 u64 s0ix_counter;
465− int num_lpm_modes;
466− int lpm_en_modes[LPM_MAX_NUM_MODES];
467467 void (*suspend)(struct pmc_dev *pmcdev);
468468 int (*resume)(struct pmc_dev *pmcdev);
469469
@@ -535,7 +535,6 @@ int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore); · 65 unmodified lines
535535
536536 int pmc_core_resume_common(struct pmc_dev *pmcdev);
537537 int get_primary_reg_base(struct pmc *pmc);
538−void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev);
539538 void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids);
540539 void pmc_core_set_device_d3(unsigned int device);
541540
@@ -563,10 +562,10 @@ int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc, · 21 unmodified lines
563562 extern const struct file_operations pmc_core_substate_req_regs_fops;
564563 extern const struct file_operations pmc_core_substate_blk_req_fops;
565564
566−#define pmc_for_each_mode(mode, pmcdev) \
565+#define pmc_for_each_mode(mode, pmc) \
567566 for (unsigned int __i = 0, __cond; \
568− __cond = __i < (pmcdev)->num_lpm_modes, \
569− __cond && ((mode) = (pmcdev)->lpm_en_modes[__i]), \
567+ __cond = __i < (pmc)->num_lpm_modes, \
568+ __cond && ((mode) = (pmc)->lpm_en_modes[__i]), \
570569 __cond; \
571570 __i++)
572571
drivers/platform/x86/intel/pmt/class.c+3−3
@@ -140,7 +140,7 @@ guid_show(struct device *dev, struct device_attribute *attr, char *buf) · 139 unmodified lines
140140 {
141141 struct intel_pmt_entry *entry = dev_get_drvdata(dev);
142142
143− return sprintf(buf, "0x%x\n", entry->guid);
143+ return sysfs_emit(buf, "0x%x\n", entry->guid);
144144 }
145145 static DEVICE_ATTR_RO(guid);
146146
@@ -149,7 +149,7 @@ static ssize_t size_show(struct device *dev, struct device_attribute *attr, · 2 unmodified lines
149149 {
150150 struct intel_pmt_entry *entry = dev_get_drvdata(dev);
151151
152− return sprintf(buf, "%zu\n", entry->size);
152+ return sysfs_emit(buf, "%zu\n", entry->size);
153153 }
154154 static DEVICE_ATTR_RO(size);
155155
@@ -158,7 +158,7 @@ offset_show(struct device *dev, struct device_attribute *attr, char *buf) · 2 unmodified lines
158158 {
159159 struct intel_pmt_entry *entry = dev_get_drvdata(dev);
160160
161− return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr));
161+ return sysfs_emit(buf, "%lu\n", offset_in_page(entry->base_addr));
162162 }
163163 static DEVICE_ATTR_RO(offset);
164164
drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c+56−23
@@ -612,6 +612,9 @@ static long isst_if_core_power_state(void __user *argp) · 611 unmodified lines
612612 return -EINVAL;
613613
614614 if (core_power.get_set) {
615+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
616+ return -EPERM;
617+
615618 _write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET,
616619 SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE)
617620 _write_cp_info("cp_prio_type", core_power.priority_type, SST_CP_CONTROL_OFFSET,
@@ -656,7 +659,7 @@ static long isst_if_clos_param(void __user *argp) · 38 unmodified lines
656659 return -EINVAL;
657660
658661 if (clos_param.get_set) {
659− if (power_domain_info->write_blocked)
662+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
660663 return -EPERM;
661664
662665 _write_cp_info("clos.min_freq", clos_param.min_freq_mhz,
@@ -748,7 +751,8 @@ static long isst_if_clos_assoc(void __user *argp) · 85 unmodified lines
748751
749752 power_domain_info = &sst_inst->power_domain_info[part][punit_id];
750753
751− if (assoc_cmds.get_set && power_domain_info->write_blocked)
754+ if (assoc_cmds.get_set && (power_domain_info->write_blocked ||
755+ !capable(CAP_SYS_ADMIN)))
752756 return -EPERM;
753757
754758 offset = SST_CLOS_ASSOC_0_OFFSET +
@@ -925,7 +929,7 @@ static int isst_if_set_perf_level(void __user *argp) · 170 unmodified lines
925929 if (!power_domain_info)
926930 return -EINVAL;
927931
928− if (power_domain_info->write_blocked)
932+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
929933 return -EPERM;
930934
931935 if (!(power_domain_info->pp_header.allowed_level_mask & BIT(perf_level.level)))
@@ -985,7 +989,7 @@ static int isst_if_set_perf_feature(void __user *argp) · 53 unmodified lines
985989 if (!power_domain_info)
986990 return -EINVAL;
987991
988− if (power_domain_info->write_blocked)
992+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
989993 return -EPERM;
990994
991995 _write_pp_info("perf_feature", perf_feature.feature, SST_PP_CONTROL_OFFSET,
@@ -1717,58 +1721,87 @@ void tpmi_sst_dev_remove(struct auxiliary_device *auxdev) · 725 unmodified lines
17171721 }
17181722 EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_remove, "INTEL_TPMI_SST");
17191723
1724+#define SST_PP_CAP_CP_ENABLE BIT(0)
1725+#define SST_PP_CAP_PP_ENABLE BIT(1)
1726+
17201727 void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev)
17211728 {
17221729 struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
1723− struct tpmi_per_power_domain_info *power_domain_info;
1730+ struct tpmi_per_power_domain_info *power_domain_info, *pd_info;
17241731 struct oobmsm_plat_info *plat_info;
17251732 void __iomem *cp_base;
1733+ int num_resources, i;
17261734
17271735 plat_info = tpmi_get_platform_data(auxdev);
17281736 if (!plat_info)
17291737 return;
17301738
17311739 power_domain_info = tpmi_sst->power_domain_info[plat_info->partition];
1740+ num_resources = tpmi_sst->number_of_power_domains[plat_info->partition];
17321741
1733− cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
1734− power_domain_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET);
1742+ for (i = 0; i < num_resources; i++) {
1743+ pd_info = &power_domain_info[i];
1744+ if (!pd_info || !pd_info->sst_base)
1745+ continue;
1746+
1747+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE))
1748+ goto process_pp_suspend;
17351749
1736− memcpy_fromio(power_domain_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET,
1737− sizeof(power_domain_info->saved_clos_configs));
1750+ cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset;
1751+ pd_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET);
1752+ memcpy_fromio(pd_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET,
1753+ sizeof(pd_info->saved_clos_configs));
1754+ memcpy_fromio(pd_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET,
1755+ sizeof(pd_info->saved_clos_assocs));
17381756
1739− memcpy_fromio(power_domain_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET,
1740− sizeof(power_domain_info->saved_clos_assocs));
1757+process_pp_suspend:
1758+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE))
1759+ continue;
17411760
1742− power_domain_info->saved_pp_control = readq(power_domain_info->sst_base +
1743− power_domain_info->sst_header.pp_offset +
1744− SST_PP_CONTROL_OFFSET);
1761+ pd_info->saved_pp_control = readq(pd_info->sst_base +
1762+ pd_info->sst_header.pp_offset +
1763+ SST_PP_CONTROL_OFFSET);
1764+ }
17451765 }
17461766 EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_suspend, "INTEL_TPMI_SST");
17471767
17481768 void tpmi_sst_dev_resume(struct auxiliary_device *auxdev)
17491769 {
17501770 struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
1751− struct tpmi_per_power_domain_info *power_domain_info;
1771+ struct tpmi_per_power_domain_info *power_domain_info, *pd_info;
17521772 struct oobmsm_plat_info *plat_info;
17531773 void __iomem *cp_base;
1774+ int num_resources, i;
17541775
17551776 plat_info = tpmi_get_platform_data(auxdev);
17561777 if (!plat_info)
17571778 return;
17581779
17591780 power_domain_info = tpmi_sst->power_domain_info[plat_info->partition];
1781+ num_resources = tpmi_sst->number_of_power_domains[plat_info->partition];
1782+
1783+ for (i = 0; i < num_resources; i++) {
1784+ pd_info = &power_domain_info[i];
1785+ if (!pd_info || !pd_info->sst_base)
1786+ continue;
17601787
1761− cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
1762− writeq(power_domain_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET);
1788+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE))
1789+ goto process_pp_resume;
17631790
1764− memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, power_domain_info->saved_clos_configs,
1765− sizeof(power_domain_info->saved_clos_configs));
1791+ cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset;
1792+ writeq(pd_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET);
1793+ memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, pd_info->saved_clos_configs,
1794+ sizeof(pd_info->saved_clos_configs));
1795+ memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, pd_info->saved_clos_assocs,
1796+ sizeof(pd_info->saved_clos_assocs));
17661797
1767− memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, power_domain_info->saved_clos_assocs,
1768− sizeof(power_domain_info->saved_clos_assocs));
1798+process_pp_resume:
1799+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE))
1800+ continue;
17691801
1770− writeq(power_domain_info->saved_pp_control, power_domain_info->sst_base +
1771− power_domain_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
1802+ writeq(pd_info->saved_pp_control, power_domain_info->sst_base +
1803+ pd_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
1804+ }
17721805 }
17731806 EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_resume, "INTEL_TPMI_SST");
17741807
drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c+7−6
@@ -26,21 +26,21 @@ static ssize_t show_domain_id(struct kobject *kobj, struct kobj_attribute *attr, · 25 unmodified lines
2626 {
2727 struct uncore_data *data = container_of(attr, struct uncore_data, domain_id_kobj_attr);
2828
29− return sprintf(buf, "%u\n", data->domain_id);
29+ return sysfs_emit(buf, "%u\n", data->domain_id);
3030 }
3131
3232 static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
3333 {
3434 struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_kobj_attr);
3535
36− return sprintf(buf, "%u\n", data->cluster_id);
36+ return sysfs_emit(buf, "%u\n", data->cluster_id);
3737 }
3838
3939 static ssize_t show_package_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
4040 {
4141 struct uncore_data *data = container_of(attr, struct uncore_data, package_id_kobj_attr);
4242
43− return sprintf(buf, "%u\n", data->package_id);
43+ return sysfs_emit(buf, "%u\n", data->package_id);
4444 }
4545
4646 #define MAX_UNCORE_AGENT_TYPES 4
@@ -77,7 +77,7 @@ static ssize_t show_attr(struct uncore_data *data, char *buf, enum uncore_index · 30 unmodified lines
7777 if (ret)
7878 return ret;
7979
80− return sprintf(buf, "%u\n", value);
80+ return sysfs_emit(buf, "%u\n", value);
8181 }
8282
8383 static ssize_t store_attr(struct uncore_data *data, const char *buf, ssize_t count,
@@ -269,9 +269,10 @@ int uncore_freq_add_entry(struct uncore_data *data, int cpu) · 185 unmodified lines
269269 goto uncore_unlock;
270270
271271 data->instance_id = ret;
272− sprintf(data->name, "uncore%02d", ret);
272+ scnprintf(data->name, sizeof(data->name), "uncore%02d", ret);
273273 } else {
274− sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id);
274+ scnprintf(data->name, sizeof(data->name), "package_%02d_die_%02d",
275+ data->package_id, data->die_id);
275276 }
276277
277278 uncore_read(data, &data->initial_min_freq_khz, UNCORE_INDEX_MIN_FREQ);
drivers/platform/x86/intel/wmi/sbl-fw-update.c+18−25
@@ -14,7 +14,6 @@ · 13 unmodified lines
1414 * https://slimbootloader.github.io/security/firmware-update.html
1515 */
1616
17−#include <linux/acpi.h>
1817 #include <linux/device.h>
1918 #include <linux/module.h>
2019 #include <linux/slab.h>
@@ -25,41 +24,35 @@ · 4 unmodified lines
2524
2625 static int get_fwu_request(struct device *dev, u32 *out)
2726 {
28− union acpi_object *obj;
27+ struct wmi_buffer buffer;
28+ __le32 *result;
29+ int ret;
2930
30− obj = wmidev_block_query(to_wmi_device(dev), 0);
31− if (!obj)
32− return -ENODEV;
31+ ret = wmidev_query_block(to_wmi_device(dev), 0, &buffer);
32+ if (ret < 0)
33+ return ret;
3334
34− if (obj->type != ACPI_TYPE_INTEGER) {
35− dev_warn(dev, "wmidev_block_query returned invalid value\n");
36− kfree(obj);
37− return -EINVAL;
35+ if (buffer.length < sizeof(*result)) {
36+ kfree(buffer.data);
37+ return -ENODATA;
3838 }
3939
40− *out = obj->integer.value;
41− kfree(obj);
40+ result = buffer.data;
41+ *out = le32_to_cpu(*result);
42+ kfree(result);
4243
4344 return 0;
4445 }
4546
4647 static int set_fwu_request(struct device *dev, u32 in)
4748 {
48− struct acpi_buffer input;
49− acpi_status status;
50− u32 value;
51−
52− value = in;
53− input.length = sizeof(u32);
54− input.pointer = &value;
55−
56− status = wmidev_block_set(to_wmi_device(dev), 0, &input);
57− if (ACPI_FAILURE(status)) {
58− dev_err(dev, "wmidev_block_set failed\n");
59− return -ENODEV;
60− }
49+ __le32 value = cpu_to_le32(in);
50+ struct wmi_buffer buffer = {
51+ .length = sizeof(value),
52+ .data = &value,
53+ };
6154
62− return 0;
55+ return wmidev_set_block(to_wmi_device(dev), 0, &buffer);
6356 }
6457
6558 static ssize_t firmware_update_request_show(struct device *dev,
drivers/platform/x86/intel/wmi/thunderbolt.c+11−15
@@ -7,7 +7,6 @@ · 6 unmodified lines
77
88 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
99
10−#include <linux/acpi.h>
1110 #include <linux/device.h>
1211 #include <linux/fs.h>
1312 #include <linux/hex.h>
@@ -24,24 +23,21 @@ static ssize_t force_power_store(struct device *dev, · 10 unmodified lines
2423 struct device_attribute *attr,
2524 const char *buf, size_t count)
2625 {
27− struct acpi_buffer input;
28− acpi_status status;
26+ struct wmi_buffer buffer;
27+ int ret;
2928 u8 mode;
3029
31− input.length = sizeof(u8);
32− input.pointer = &mode;
30+ buffer.length = sizeof(mode);
31+ buffer.data = &mode;
32+
3333 mode = hex_to_bin(buf[0]);
34− dev_dbg(dev, "force_power: storing %#x\n", mode);
35− if (mode == 0 || mode == 1) {
36− status = wmidev_evaluate_method(to_wmi_device(dev), 0, 1, &input, NULL);
37− if (ACPI_FAILURE(status)) {
38− dev_dbg(dev, "force_power: failed to evaluate ACPI method\n");
39− return -ENODEV;
40− }
41− } else {
42− dev_dbg(dev, "force_power: unsupported mode\n");
34+ if (mode > 1)
4335 return -EINVAL;
44− }
36+
37+ ret = wmidev_invoke_method(to_wmi_device(dev), 0, 1, &buffer, NULL);
38+ if (ret < 0)
39+ return ret;
40+
4541 return count;
4642 }
4743
drivers/platform/x86/lenovo/Kconfig+3−2
@@ -233,7 +233,7 @@ config YT2_1380 · 232 unmodified lines
233233 To compile this driver as a module, choose M here: the module will
234234 be called lenovo-yogabook.
235235
236−config LENOVO_WMI_DATA01
236+config LENOVO_WMI_CAPDATA
237237 tristate
238238 depends on ACPI_WMI
239239
@@ -263,8 +263,9 @@ config LENOVO_WMI_GAMEZONE · 23 unmodified lines
263263 config LENOVO_WMI_TUNING
264264 tristate "Lenovo Other Mode WMI Driver"
265265 depends on ACPI_WMI
266+ select HWMON
266267 select FW_ATTR_CLASS
267− select LENOVO_WMI_DATA01
268+ select LENOVO_WMI_CAPDATA
268269 select LENOVO_WMI_EVENTS
269270 select LENOVO_WMI_HELPERS
270271 help
drivers/platform/x86/lenovo/Makefile+1−1
@@ -12,7 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o · 11 unmodified lines
1212 lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
1313 lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
1414 lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o
15−lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) += wmi-capdata01.o
15+lenovo-target-$(CONFIG_LENOVO_WMI_CAPDATA) += wmi-capdata.o
1616 lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o
1717 lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o
1818 lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += wmi-gamezone.o
drivers/platform/x86/lenovo/ideapad-laptop.c+16−23
@@ -219,38 +219,32 @@ MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); · 218 unmodified lines
219219 static bool allow_v4_dytc;
220220 module_param(allow_v4_dytc, bool, 0444);
221221 MODULE_PARM_DESC(allow_v4_dytc,
222− "Enable DYTC version 4 platform-profile support. "
223− "If you need this please report this to: platform-driver-x86@vger.kernel.org");
222+ "Enable DYTC version 4 platform-profile support. If you need this please report this to: platform-driver-x86@vger.kernel.org");
224223
225224 static bool hw_rfkill_switch;
226225 module_param(hw_rfkill_switch, bool, 0444);
227226 MODULE_PARM_DESC(hw_rfkill_switch,
228− "Enable rfkill support for laptops with a hw on/off wifi switch/slider. "
229− "If you need this please report this to: platform-driver-x86@vger.kernel.org");
227+ "Enable rfkill support for laptops with a hw on/off wifi switch/slider. If you need this please report this to: platform-driver-x86@vger.kernel.org");
230228
231229 static bool set_fn_lock_led;
232230 module_param(set_fn_lock_led, bool, 0444);
233231 MODULE_PARM_DESC(set_fn_lock_led,
234− "Enable driver based updates of the fn-lock LED on fn-lock changes. "
235− "If you need this please report this to: platform-driver-x86@vger.kernel.org");
232+ "Enable driver based updates of the fn-lock LED on fn-lock changes. If you need this please report this to: platform-driver-x86@vger.kernel.org");
236233
237234 static bool ctrl_ps2_aux_port;
238235 module_param(ctrl_ps2_aux_port, bool, 0444);
239236 MODULE_PARM_DESC(ctrl_ps2_aux_port,
240− "Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. "
241− "If you need this please report this to: platform-driver-x86@vger.kernel.org");
237+ "Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. If you need this please report this to: platform-driver-x86@vger.kernel.org");
242238
243239 static bool touchpad_ctrl_via_ec;
244240 module_param(touchpad_ctrl_via_ec, bool, 0444);
245241 MODULE_PARM_DESC(touchpad_ctrl_via_ec,
246− "Enable registering a 'touchpad' sysfs-attribute which can be used to manually "
247− "tell the EC to enable/disable the touchpad. This may not work on all models.");
242+ "Enable registering a 'touchpad' sysfs-attribute which can be used to manually tell the EC to enable/disable the touchpad. This may not work on all models.");
248243
249244 static bool ymc_ec_trigger __read_mostly;
250245 module_param(ymc_ec_trigger, bool, 0444);
251246 MODULE_PARM_DESC(ymc_ec_trigger,
252− "Enable EC triggering work-around to force emitting tablet mode events. "
253− "If you need this please report this to: platform-driver-x86@vger.kernel.org");
247+ "Enable EC triggering work-around to force emitting tablet mode events. If you need this please report this to: platform-driver-x86@vger.kernel.org");
254248
255249 /*
256250 * shared data
@@ -1446,7 +1440,7 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv) · 1189 unmodified lines
14461440 if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value))
14471441 return;
14481442
1449− for_each_set_bit (bit, &value, 16) {
1443+ for_each_set_bit(bit, &value, 16) {
14501444 switch (bit) {
14511445 case 6: /* Z570 */
14521446 case 0: /* Z580 */
@@ -1706,11 +1700,10 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv) · 253 unmodified lines
17061700 if (WARN_ON(priv->kbd_bl.initialized))
17071701 return -EEXIST;
17081702
1709− if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) {
1703+ if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type))
17101704 priv->kbd_bl.led.max_brightness = 2;
1711− } else {
1705+ else
17121706 priv->kbd_bl.led.max_brightness = 1;
1713− }
17141707
17151708 brightness = ideapad_kbd_bl_brightness_get(priv);
17161709 if (brightness < 0)
@@ -1752,7 +1745,7 @@ static enum led_brightness ideapad_fn_lock_led_cdev_get(struct led_classdev *led · 35 unmodified lines
17521745 }
17531746
17541747 static int ideapad_fn_lock_led_cdev_set(struct led_classdev *led_cdev,
1755− enum led_brightness brightness)
1748+ enum led_brightness brightness)
17561749 {
17571750 struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, fn_lock.led);
17581751
@@ -1928,7 +1921,7 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) · 169 unmodified lines
19281921
19291922 vpc1 = (vpc2 << 8) | vpc1;
19301923
1931− for_each_set_bit (bit, &vpc1, 16) {
1924+ for_each_set_bit(bit, &vpc1, 16) {
19321925 switch (bit) {
19331926 case 13:
19341927 case 11:
@@ -2142,14 +2135,14 @@ static const enum power_supply_property ideapad_power_supply_props[] = { · 207 unmodified lines
21422135 }
21432136
21442137 DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v1,
2145− (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
2146− BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
2138+ (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
2139+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
21472140 );
21482141
21492142 DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v2,
2150− (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
2151− BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
2152− BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
2143+ (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
2144+ BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
2145+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
21532146 );
21542147
21552148 static int ideapad_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
drivers/platform/x86/lenovo/thinkpad_acpi.c+206−0
@@ -36,6 +36,7 @@ · 35 unmodified lines
3636
3737 #include <linux/acpi.h>
3838 #include <linux/backlight.h>
39+#include <linux/bitfield.h>
3940 #include <linux/bitops.h>
4041 #include <linux/delay.h>
4142 #include <linux/dmi.h>
@@ -11080,6 +11081,206 @@ static const struct attribute_group auxmac_attr_group = { · 11038 unmodified lines
1108011081 .attrs = auxmac_attributes,
1108111082 };
1108211083
11084+/*************************************************************************
11085+ * HWDD subdriver, for the Lenovo Hardware Damage Detection feature.
11086+ */
11087+
11088+#define HWDD_GET_DMG_USBC 0x80000001
11089+#define HWDD_GET_CAP 0
11090+#define HWDD_NOT_SUPPORTED BIT(31)
11091+#define HWDD_SUPPORT_USBC BIT(0)
11092+
11093+#define PORT_STATUS GENMASK(7, 4)
11094+#define LID_STATUS GENMASK(11, 8)
11095+#define BASE_STATUS GENMASK(15, 12)
11096+#define POS_STATUS GENMASK(3, 2)
11097+#define PANEL_STATUS GENMASK(1, 0)
11098+
11099+#define PORT_DETAIL_OFFSET 16
11100+
11101+#define PANEL_TOP 0
11102+#define PANEL_BASE 1
11103+#define PANEL_LEFT 2
11104+#define PANEL_RIGHT 3
11105+
11106+#define POS_LEFT 0
11107+#define POS_CENTER 1
11108+#define POS_RIGHT 2
11109+
11110+#define NUM_PORTS 4
11111+
11112+static bool hwdd_support_available;
11113+static bool ucdd_supported;
11114+
11115+static int hwdd_command(int command, int *output)
11116+{
11117+ acpi_handle hwdd_handle;
11118+
11119+ if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "HWDD", &hwdd_handle)))
11120+ return -ENODEV;
11121+
11122+ if (!acpi_evalf(hwdd_handle, output, NULL, "dd", command))
11123+ return -EIO;
11124+
11125+ return 0;
11126+}
11127+
11128+static bool display_damage(char *buf, int *count, char *type, unsigned int dmg_status)
11129+{
11130+ unsigned char lid_status, base_status, port_status;
11131+ unsigned char loc_status, pos_status, panel_status;
11132+ bool damage_detected = false;
11133+ int i;
11134+
11135+ port_status = FIELD_GET(PORT_STATUS, dmg_status);
11136+ lid_status = FIELD_GET(LID_STATUS, dmg_status);
11137+ base_status = FIELD_GET(BASE_STATUS, dmg_status);
11138+ for (i = 0; i < NUM_PORTS; i++) {
11139+ if (!(dmg_status & BIT(i)) || !(port_status & BIT(i)))
11140+ continue;
11141+
11142+ *count += sysfs_emit_at(buf, *count, "%s: ", type);
11143+ loc_status = (dmg_status >> (PORT_DETAIL_OFFSET + (4 * i))) & 0xF;
11144+ pos_status = FIELD_GET(POS_STATUS, loc_status);
11145+ panel_status = FIELD_GET(PANEL_STATUS, loc_status);
11146+
11147+ if (lid_status & BIT(i))
11148+ *count += sysfs_emit_at(buf, *count, "Lid, ");
11149+ if (base_status & BIT(i))
11150+ *count += sysfs_emit_at(buf, *count, "Base, ");
11151+
11152+ switch (pos_status) {
11153+ case PANEL_TOP:
11154+ *count += sysfs_emit_at(buf, *count, "Top, ");
11155+ break;
11156+ case PANEL_BASE:
11157+ *count += sysfs_emit_at(buf, *count, "Bottom, ");
11158+ break;
11159+ case PANEL_LEFT:
11160+ *count += sysfs_emit_at(buf, *count, "Left, ");
11161+ break;
11162+ case PANEL_RIGHT:
11163+ *count += sysfs_emit_at(buf, *count, "Right, ");
11164+ break;
11165+ default:
11166+ pr_err("Unexpected value %d in switch statement\n", pos_status);
11167+ }
11168+
11169+ switch (panel_status) {
11170+ case POS_LEFT:
11171+ *count += sysfs_emit_at(buf, *count, "Left port\n");
11172+ break;
11173+ case POS_CENTER:
11174+ *count += sysfs_emit_at(buf, *count, "Center port\n");
11175+ break;
11176+ case POS_RIGHT:
11177+ *count += sysfs_emit_at(buf, *count, "Right port\n");
11178+ break;
11179+ default:
11180+ *count += sysfs_emit_at(buf, *count, "Undefined\n");
11181+ break;
11182+ }
11183+ damage_detected = true;
11184+ }
11185+ return damage_detected;
11186+}
11187+
11188+/* sysfs type-c damage detection detail */
11189+static ssize_t hwdd_detail_show(struct device *dev,
11190+ struct device_attribute *attr,
11191+ char *buf)
11192+{
11193+ unsigned int damage_status;
11194+ int err, count = 0;
11195+
11196+ if (!ucdd_supported)
11197+ return -ENODEV;
11198+
11199+ /* Get USB TYPE-C damage status */
11200+ err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status);
11201+ if (err)
11202+ return err;
11203+
11204+ if (!display_damage(buf, &count, "Type-C", damage_status))
11205+ count += sysfs_emit_at(buf, count, "No damage detected\n");
11206+
11207+ return count;
11208+}
11209+
11210+/* sysfs type-c damage detection capability */
11211+static ssize_t hwdd_status_show(struct device *dev,
11212+ struct device_attribute *attr,
11213+ char *buf)
11214+{
11215+ unsigned int damage_status, port_status;
11216+ int err, i;
11217+
11218+ if (!ucdd_supported)
11219+ return -ENODEV;
11220+
11221+ /* Get USB TYPE-C damage status */
11222+ err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status);
11223+ if (err)
11224+ return err;
11225+
11226+ port_status = FIELD_GET(PORT_STATUS, damage_status);
11227+ for (i = 0; i < NUM_PORTS; i++) {
11228+ if (!(damage_status & BIT(i)))
11229+ continue;
11230+ if (port_status & BIT(i))
11231+ return sysfs_emit(buf, "1\n");
11232+ }
11233+
11234+ return sysfs_emit(buf, "0\n");
11235+}
11236+static DEVICE_ATTR_RO(hwdd_status);
11237+static DEVICE_ATTR_RO(hwdd_detail);
11238+
11239+static struct attribute *hwdd_attributes[] = {
11240+ &dev_attr_hwdd_status.attr,
11241+ &dev_attr_hwdd_detail.attr,
11242+ NULL
11243+};
11244+
11245+static umode_t hwdd_attr_is_visible(struct kobject *kobj,
11246+ struct attribute *attr, int n)
11247+{
11248+ return hwdd_support_available ? attr->mode : 0;
11249+}
11250+
11251+static const struct attribute_group hwdd_attr_group = {
11252+ .is_visible = hwdd_attr_is_visible,
11253+ .attrs = hwdd_attributes,
11254+};
11255+
11256+static int tpacpi_hwdd_init(struct ibm_init_struct *iibm)
11257+{
11258+ int err, output;
11259+
11260+ /* Below command checks the HWDD damage capability */
11261+ err = hwdd_command(HWDD_GET_CAP, &output);
11262+ if (err)
11263+ return err;
11264+
11265+ if (!(output & HWDD_NOT_SUPPORTED))
11266+ return -ENODEV;
11267+
11268+ hwdd_support_available = true;
11269+
11270+ /*
11271+ * BIT(0) is assigned to check capability of damage detection is
11272+ * supported for USB Type-C port or not.
11273+ */
11274+ if (output & HWDD_SUPPORT_USBC)
11275+ ucdd_supported = true;
11276+
11277+ return err;
11278+}
11279+
11280+static struct ibm_struct hwdd_driver_data = {
11281+ .name = "hwdd",
11282+};
11283+
1108311284 /* --------------------------------------------------------------------- */
1108411285
1108511286 static struct attribute *tpacpi_driver_attributes[] = {
@@ -11139,6 +11340,7 @@ static const struct attribute_group *tpacpi_groups[] = { · 53 unmodified lines
1113911340 &kbdlang_attr_group,
1114011341 &dprc_attr_group,
1114111342 &auxmac_attr_group,
11343+ &hwdd_attr_group,
1114211344 NULL,
1114311345 };
1114411346
@@ -11752,6 +11954,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { · 607 unmodified lines
1175211954 .init = auxmac_init,
1175311955 .data = &auxmac_data,
1175411956 },
11957+ {
11958+ .init = tpacpi_hwdd_init,
11959+ .data = &hwdd_driver_data,
11960+ },
1175511961 };
1175611962
1175711963 static int __init set_ibm_param(const char *val, const struct kernel_param *kp)
drivers/platform/x86/lenovo/wmi-capdata.c+829−0
@@ -0,0 +1,829 @@
1+// SPDX-License-Identifier: GPL-2.0-or-later
2+/*
3+ * Lenovo Capability Data WMI Data Block driver.
4+ *
5+ * Lenovo Capability Data provides information on tunable attributes used by
6+ * the "Other Mode" WMI interface.
7+ *
8+ * Capability Data 00 includes if the attribute is supported by the hardware,
9+ * and the default_value. All attributes are independent of thermal modes.
10+ *
11+ * Capability Data 01 includes if the attribute is supported by the hardware,
12+ * and the default_value, max_value, min_value, and step increment. Each
13+ * attribute has multiple pages, one for each of the thermal modes managed by
14+ * the Gamezone interface.
15+ *
16+ * Fan Test Data includes the max/min fan speed RPM for each fan. This is
17+ * reference data for self-test. If the fan is in good condition, it is capable
18+ * to spin faster than max RPM or slower than min RPM.
19+ *
20+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
21+ * - Initial implementation (formerly named lenovo-wmi-capdata01)
22+ *
23+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
24+ * - Unified implementation
25+ */
26+
27+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
28+
29+#include <linux/acpi.h>
30+#include <linux/bitfield.h>
31+#include <linux/bug.h>
32+#include <linux/cleanup.h>
33+#include <linux/component.h>
34+#include <linux/container_of.h>
35+#include <linux/device.h>
36+#include <linux/dev_printk.h>
37+#include <linux/err.h>
38+#include <linux/export.h>
39+#include <linux/gfp_types.h>
40+#include <linux/limits.h>
41+#include <linux/module.h>
42+#include <linux/mutex.h>
43+#include <linux/mutex_types.h>
44+#include <linux/notifier.h>
45+#include <linux/overflow.h>
46+#include <linux/stddef.h>
47+#include <linux/types.h>
48+#include <linux/wmi.h>
49+
50+#include "wmi-capdata.h"
51+
52+#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E"
53+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
54+#define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21"
55+
56+#define ACPI_AC_CLASS "ac_adapter"
57+#define ACPI_AC_NOTIFY_STATUS 0x80
58+
59+#define LWMI_FEATURE_ID_FAN_TEST 0x05
60+
61+#define LWMI_ATTR_ID_FAN_TEST \
62+ (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
63+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST))
64+
65+enum lwmi_cd_type {
66+ LENOVO_CAPABILITY_DATA_00,
67+ LENOVO_CAPABILITY_DATA_01,
68+ LENOVO_FAN_TEST_DATA,
69+ CD_TYPE_NONE = -1,
70+};
71+
72+#define LWMI_CD_TABLE_ITEM(_type) \
73+ [_type] = { \
74+ .name = #_type, \
75+ .type = _type, \
76+ }
77+
78+static const struct lwmi_cd_info {
79+ const char *name;
80+ enum lwmi_cd_type type;
81+} lwmi_cd_table[] = {
82+ LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00),
83+ LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01),
84+ LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA),
85+};
86+
87+struct lwmi_cd_priv {
88+ struct notifier_block acpi_nb; /* ACPI events */
89+ struct wmi_device *wdev;
90+ struct cd_list *list;
91+
92+ /*
93+ * A capdata device may be a component master of another capdata device.
94+ * E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan
95+ * |- master |- component
96+ * |- sub-master
97+ * |- sub-component
98+ */
99+ struct lwmi_cd_sub_master_priv {
100+ struct device *master_dev;
101+ cd_list_cb_t master_cb;
102+ struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */
103+ bool registered; /* Has the sub-master been registered? */
104+ } *sub_master;
105+};
106+
107+struct cd_list {
108+ struct mutex list_mutex; /* list R/W mutex */
109+ enum lwmi_cd_type type;
110+ u8 count;
111+
112+ union {
113+ DECLARE_FLEX_ARRAY(struct capdata00, cd00);
114+ DECLARE_FLEX_ARRAY(struct capdata01, cd01);
115+ DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan);
116+ };
117+};
118+
119+static struct wmi_driver lwmi_cd_driver;
120+
121+/**
122+ * lwmi_cd_match() - Match rule for the master driver.
123+ * @dev: Pointer to the capability data parent device.
124+ * @type: Pointer to capability data type (enum lwmi_cd_type *) to match.
125+ *
126+ * Return: int.
127+ */
128+static int lwmi_cd_match(struct device *dev, void *type)
129+{
130+ struct lwmi_cd_priv *priv;
131+
132+ if (dev->driver != &lwmi_cd_driver.driver)
133+ return false;
134+
135+ priv = dev_get_drvdata(dev);
136+ return priv->list->type == *(enum lwmi_cd_type *)type;
137+}
138+
139+/**
140+ * lwmi_cd_match_add_all() - Add all match rule for the master driver.
141+ * @master: Pointer to the master device.
142+ * @matchptr: Pointer to the returned component_match pointer.
143+ *
144+ * Adds all component matches to the list stored in @matchptr for the @master
145+ * device. @matchptr must be initialized to NULL.
146+ */
147+void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr)
148+{
149+ int i;
150+
151+ if (WARN_ON(*matchptr))
152+ return;
153+
154+ for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) {
155+ /* Skip sub-components. */
156+ if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA)
157+ continue;
158+
159+ component_match_add(master, matchptr, lwmi_cd_match,
160+ (void *)&lwmi_cd_table[i].type);
161+ if (IS_ERR(*matchptr))
162+ return;
163+ }
164+}
165+EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA");
166+
167+/**
168+ * lwmi_cd_call_master_cb() - Call the master callback for the sub-component.
169+ * @priv: Pointer to the capability data private data.
170+ *
171+ * Call the master callback and pass the sub-component list to it if the
172+ * dependency chain (master <-> sub-master <-> sub-component) is complete.
173+ */
174+static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv)
175+{
176+ struct cd_list *sub_component_list = priv->sub_master->sub_component_list;
177+
178+ /*
179+ * Call the callback only if the dependency chain is ready:
180+ * - Binding between master and sub-master: fills master_dev and master_cb
181+ * - Binding between sub-master and sub-component: fills sub_component_list
182+ *
183+ * If a binding has been unbound before the other binding is bound, the
184+ * corresponding members filled by the former are guaranteed to be cleared.
185+ *
186+ * This function is only called in bind callbacks, and the component
187+ * framework guarantees bind/unbind callbacks may never execute
188+ * simultaneously, which implies that it's impossible to have a race
189+ * condition.
190+ *
191+ * Hence, this check is sufficient to ensure that the callback is called
192+ * at most once and with the correct state, without relying on a specific
193+ * sequence of binding establishment.
194+ */
195+ if (!sub_component_list ||
196+ !priv->sub_master->master_dev ||
197+ !priv->sub_master->master_cb)
198+ return;
199+
200+ if (PTR_ERR(sub_component_list) == -ENODEV)
201+ sub_component_list = NULL;
202+ else if (WARN_ON(IS_ERR(sub_component_list)))
203+ return;
204+
205+ priv->sub_master->master_cb(priv->sub_master->master_dev,
206+ sub_component_list);
207+
208+ /*
209+ * Userspace may unbind a device from its driver and bind it again
210+ * through sysfs. Let's call this operation "reprobe" to distinguish it
211+ * from component "rebind".
212+ *
213+ * When reprobing capdata00/01 or the master device, the master device
214+ * is unbound from us with appropriate cleanup before we bind to it and
215+ * call master_cb. Everything is fine in this case.
216+ *
217+ * When reprobing capdata_fan, the master device has never been unbound
218+ * from us (hence no cleanup is done)[1], but we call master_cb the
219+ * second time. To solve this issue, we clear master_cb and master_dev
220+ * so we won't call master_cb twice while a binding is still complete.
221+ *
222+ * Note that we can't clear sub_component_list, otherwise reprobing
223+ * capdata01 or the master device causes master_cb to be never called
224+ * after we rebind to the master device.
225+ *
226+ * [1]: The master device does not need capdata_fan in run time, so
227+ * losing capdata_fan will not break the binding to the master device.
228+ */
229+ priv->sub_master->master_cb = NULL;
230+ priv->sub_master->master_dev = NULL;
231+}
232+
233+/**
234+ * lwmi_cd_component_bind() - Bind component to master device.
235+ * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
236+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
237+ * @data: lwmi_cd_binder object pointer used to return the capability data.
238+ *
239+ * On lenovo-wmi-other's master bind, provide a pointer to the local capdata
240+ * list. This is used to call lwmi_cd*_get_data to look up attribute data
241+ * from the lenovo-wmi-other driver.
242+ *
243+ * If cd_dev is a sub-master, try to call the master callback.
244+ *
245+ * Return: 0
246+ */
247+static int lwmi_cd_component_bind(struct device *cd_dev,
248+ struct device *om_dev, void *data)
249+{
250+ struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
251+ struct lwmi_cd_binder *binder = data;
252+
253+ switch (priv->list->type) {
254+ case LENOVO_CAPABILITY_DATA_00:
255+ binder->cd00_list = priv->list;
256+
257+ priv->sub_master->master_dev = om_dev;
258+ priv->sub_master->master_cb = binder->cd_fan_list_cb;
259+ lwmi_cd_call_master_cb(priv);
260+
261+ break;
262+ case LENOVO_CAPABILITY_DATA_01:
263+ binder->cd01_list = priv->list;
264+ break;
265+ default:
266+ return -EINVAL;
267+ }
268+
269+ return 0;
270+}
271+
272+/**
273+ * lwmi_cd_component_unbind() - Unbind component to master device.
274+ * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
275+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
276+ * @data: Unused.
277+ *
278+ * If cd_dev is a sub-master, clear the collected data from the master device to
279+ * prevent the binding establishment between the sub-master and the sub-
280+ * component (if it's about to happen) from calling the master callback.
281+ */
282+static void lwmi_cd_component_unbind(struct device *cd_dev,
283+ struct device *om_dev, void *data)
284+{
285+ struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
286+
287+ switch (priv->list->type) {
288+ case LENOVO_CAPABILITY_DATA_00:
289+ priv->sub_master->master_dev = NULL;
290+ priv->sub_master->master_cb = NULL;
291+ return;
292+ default:
293+ return;
294+ }
295+}
296+
297+static const struct component_ops lwmi_cd_component_ops = {
298+ .bind = lwmi_cd_component_bind,
299+ .unbind = lwmi_cd_component_unbind,
300+};
301+
302+/**
303+ * lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device
304+ * @dev: The sub-master capdata basic device.
305+ *
306+ * Call component_bind_all to bind the sub-component device to the sub-master
307+ * device. On success, collect the pointer to the sub-component list and try
308+ * to call the master callback.
309+ *
310+ * Return: 0 on success, or an error code.
311+ */
312+static int lwmi_cd_sub_master_bind(struct device *dev)
313+{
314+ struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
315+ struct cd_list *sub_component_list;
316+ int ret;
317+
318+ ret = component_bind_all(dev, &sub_component_list);
319+ if (ret)
320+ return ret;
321+
322+ priv->sub_master->sub_component_list = sub_component_list;
323+ lwmi_cd_call_master_cb(priv);
324+
325+ return 0;
326+}
327+
328+/**
329+ * lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device
330+ * @dev: The sub-master capdata basic device
331+ *
332+ * Clear the collected pointer to the sub-component list to prevent the binding
333+ * establishment between the sub-master and the sub-component (if it's about to
334+ * happen) from calling the master callback. Then, call component_unbind_all to
335+ * unbind the sub-component device from the sub-master device.
336+ */
337+static void lwmi_cd_sub_master_unbind(struct device *dev)
338+{
339+ struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
340+
341+ priv->sub_master->sub_component_list = NULL;
342+
343+ component_unbind_all(dev, NULL);
344+}
345+
346+static const struct component_master_ops lwmi_cd_sub_master_ops = {
347+ .bind = lwmi_cd_sub_master_bind,
348+ .unbind = lwmi_cd_sub_master_unbind,
349+};
350+
351+/**
352+ * lwmi_cd_sub_master_add() - Register a sub-master with its sub-component
353+ * @priv: Pointer to the sub-master capdata device private data.
354+ * @sub_component_type: Type of the sub-component.
355+ *
356+ * Match the sub-component type and register the current capdata device as a
357+ * sub-master. If the given sub-component type is CD_TYPE_NONE, mark the sub-
358+ * component as non-existent without registering sub-master.
359+ *
360+ * Return: 0 on success, or an error code.
361+ */
362+static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv,
363+ enum lwmi_cd_type sub_component_type)
364+{
365+ struct component_match *master_match = NULL;
366+ int ret;
367+
368+ priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL);
369+ if (!priv->sub_master)
370+ return -ENOMEM;
371+
372+ if (sub_component_type == CD_TYPE_NONE) {
373+ /* The master callback will be called with NULL on bind. */
374+ priv->sub_master->sub_component_list = ERR_PTR(-ENODEV);
375+ priv->sub_master->registered = false;
376+ return 0;
377+ }
378+
379+ /*
380+ * lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack
381+ * data cannot be used here. Steal one from lwmi_cd_table.
382+ */
383+ component_match_add(&priv->wdev->dev, &master_match, lwmi_cd_match,
384+ (void *)&lwmi_cd_table[sub_component_type].type);
385+ if (IS_ERR(master_match))
386+ return PTR_ERR(master_match);
387+
388+ ret = component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_master_ops,
389+ master_match);
390+ if (ret)
391+ return ret;
392+
393+ priv->sub_master->registered = true;
394+ return 0;
395+}
396+
397+/**
398+ * lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered
399+ * @priv: Pointer to the sub-master capdata device private data.
400+ */
401+static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv)
402+{
403+ if (!priv->sub_master->registered)
404+ return;
405+
406+ component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops);
407+ priv->sub_master->registered = false;
408+}
409+
410+/**
411+ * lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device.
412+ * @sc_dev: Pointer to the sub-component capdata parent device.
413+ * @sm_dev: Pointer to the sub-master capdata parent device.
414+ * @data: Pointer used to return the capability data list pointer.
415+ *
416+ * On sub-master's bind, provide a pointer to the local capdata list.
417+ * This is used by the sub-master to call the master callback.
418+ *
419+ * Return: 0
420+ */
421+static int lwmi_cd_sub_component_bind(struct device *sc_dev,
422+ struct device *sm_dev, void *data)
423+{
424+ struct lwmi_cd_priv *priv = dev_get_drvdata(sc_dev);
425+ struct cd_list **listp = data;
426+
427+ *listp = priv->list;
428+
429+ return 0;
430+}
431+
432+static const struct component_ops lwmi_cd_sub_component_ops = {
433+ .bind = lwmi_cd_sub_component_bind,
434+};
435+
436+/*
437+ * lwmi_cd*_get_data - Get the data of the specified attribute
438+ * @list: The lenovo-wmi-capdata pointer to its cd_list struct.
439+ * @attribute_id: The capdata attribute ID to be found.
440+ * @output: Pointer to a capdata* struct to return the data.
441+ *
442+ * Retrieves the capability data struct pointer for the given
443+ * attribute.
444+ *
445+ * Return: 0 on success, or -EINVAL.
446+ */
447+#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \
448+ int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output) \
449+ { \
450+ u8 idx; \
451+ \
452+ if (WARN_ON(list->type != _cd_type)) \
453+ return -EINVAL; \
454+ \
455+ guard(mutex)(&list->list_mutex); \
456+ for (idx = 0; idx < list->count; idx++) { \
457+ if (list->_cdxx[idx].id != attribute_id) \
458+ continue; \
459+ memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx])); \
460+ return 0; \
461+ } \
462+ return -EINVAL; \
463+ }
464+
465+DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00);
466+EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA");
467+
468+DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
469+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA");
470+
471+DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan);
472+EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA");
473+
474+/**
475+ * lwmi_cd_cache() - Cache all WMI data block information
476+ * @priv: lenovo-wmi-capdata driver data.
477+ *
478+ * Loop through each WMI data block and cache the data.
479+ *
480+ * Return: 0 on success, or an error.
481+ */
482+static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
483+{
484+ size_t size;
485+ int idx;
486+ void *p;
487+
488+ switch (priv->list->type) {
489+ case LENOVO_CAPABILITY_DATA_00:
490+ p = &priv->list->cd00[0];
491+ size = sizeof(priv->list->cd00[0]);
492+ break;
493+ case LENOVO_CAPABILITY_DATA_01:
494+ p = &priv->list->cd01[0];
495+ size = sizeof(priv->list->cd01[0]);
496+ break;
497+ case LENOVO_FAN_TEST_DATA:
498+ /* Done by lwmi_cd_alloc() => lwmi_cd_fan_list_alloc_cache(). */
499+ return 0;
500+ default:
501+ return -EINVAL;
502+ }
503+
504+ guard(mutex)(&priv->list->list_mutex);
505+ for (idx = 0; idx < priv->list->count; idx++, p += size) {
506+ union acpi_object *ret_obj __free(kfree) = NULL;
507+
508+ ret_obj = wmidev_block_query(priv->wdev, idx);
509+ if (!ret_obj)
510+ return -ENODEV;
511+
512+ if (ret_obj->type != ACPI_TYPE_BUFFER ||
513+ ret_obj->buffer.length < size)
514+ continue;
515+
516+ memcpy(p, ret_obj->buffer.pointer, size);
517+ }
518+
519+ return 0;
520+}
521+
522+/**
523+ * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list
524+ * @priv: lenovo-wmi-capdata driver data.
525+ * @listptr: Pointer to returned cd_list pointer.
526+ *
527+ * Return: count of fans found, or an error.
528+ */
529+static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr)
530+{
531+ struct cd_list *list;
532+ size_t size;
533+ u32 count;
534+ int idx;
535+
536+ /* Emit unaligned access to u8 buffer with __packed. */
537+ struct cd_fan_block {
538+ u32 nr;
539+ u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */
540+ } __packed * block;
541+
542+ union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0);
543+ if (!ret_obj)
544+ return -ENODEV;
545+
546+ if (ret_obj->type == ACPI_TYPE_BUFFER) {
547+ block = (struct cd_fan_block *)ret_obj->buffer.pointer;
548+ size = ret_obj->buffer.length;
549+
550+ count = size >= sizeof(*block) ? block->nr : 0;
551+ if (size < struct_size(block, data, count * 3)) {
552+ dev_warn(&priv->wdev->dev,
553+ "incomplete fan test data block: %zu < %zu, ignoring\n",
554+ size, struct_size(block, data, count * 3));
555+ count = 0;
556+ } else if (count > U8_MAX) {
557+ dev_warn(&priv->wdev->dev,
558+ "too many fans reported: %u > %u, truncating\n",
559+ count, U8_MAX);
560+ count = U8_MAX;
561+ }
562+ } else {
563+ /*
564+ * This is usually caused by a dummy ACPI method. Do not return an error
565+ * as failing to probe this device will result in sub-master device being
566+ * unbound. This behavior aligns with lwmi_cd_cache().
567+ */
568+ count = 0;
569+ }
570+
571+ list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL);
572+ if (!list)
573+ return -ENOMEM;
574+
575+ for (idx = 0; idx < count; idx++) {
576+ /* Do not calculate array index using count, as it may be truncated. */
577+ list->cd_fan[idx] = (struct capdata_fan) {
578+ .id = block->data[idx],
579+ .max_rpm = block->data[idx + block->nr],
580+ .min_rpm = block->data[idx + (2 * block->nr)],
581+ };
582+ }
583+
584+ *listptr = list;
585+ return count;
586+}
587+
588+/**
589+ * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
590+ * @priv: lenovo-wmi-capdata driver data.
591+ * @type: The type of capability data.
592+ *
593+ * Allocate a cd_list struct large enough to contain data from all WMI data
594+ * blocks provided by the interface.
595+ *
596+ * Return: 0 on success, or an error.
597+ */
598+static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
599+{
600+ struct cd_list *list;
601+ size_t list_size;
602+ int count, ret;
603+
604+ count = wmidev_instance_count(priv->wdev);
605+
606+ switch (type) {
607+ case LENOVO_CAPABILITY_DATA_00:
608+ list_size = struct_size(list, cd00, count);
609+ break;
610+ case LENOVO_CAPABILITY_DATA_01:
611+ list_size = struct_size(list, cd01, count);
612+ break;
613+ case LENOVO_FAN_TEST_DATA:
614+ count = lwmi_cd_fan_list_alloc_cache(priv, &list);
615+ if (count < 0)
616+ return count;
617+
618+ goto got_list;
619+ default:
620+ return -EINVAL;
621+ }
622+
623+ list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
624+ if (!list)
625+ return -ENOMEM;
626+
627+got_list:
628+ ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
629+ if (ret)
630+ return ret;
631+
632+ list->type = type;
633+ list->count = count;
634+ priv->list = list;
635+
636+ return 0;
637+}
638+
639+/**
640+ * lwmi_cd_setup() - Cache all WMI data block information
641+ * @priv: lenovo-wmi-capdata driver data.
642+ * @type: The type of capability data.
643+ *
644+ * Allocate a cd_list struct large enough to contain data from all WMI data
645+ * blocks provided by the interface. Then loop through each data block and
646+ * cache the data.
647+ *
648+ * Return: 0 on success, or an error code.
649+ */
650+static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
651+{
652+ int ret;
653+
654+ ret = lwmi_cd_alloc(priv, type);
655+ if (ret)
656+ return ret;
657+
658+ return lwmi_cd_cache(priv);
659+}
660+
661+/**
662+ * lwmi_cd01_notifier_call() - Call method for cd01 notifier.
663+ * block call chain.
664+ * @nb: The notifier_block registered to lenovo-wmi-events driver.
665+ * @action: Unused.
666+ * @data: The ACPI event.
667+ *
668+ * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
669+ * of a change.
670+ *
671+ * Return: notifier_block status.
672+ */
673+static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
674+ void *data)
675+{
676+ struct acpi_bus_event *event = data;
677+ struct lwmi_cd_priv *priv;
678+ int ret;
679+
680+ if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
681+ return NOTIFY_DONE;
682+
683+ priv = container_of(nb, struct lwmi_cd_priv, acpi_nb);
684+
685+ switch (event->type) {
686+ case ACPI_AC_NOTIFY_STATUS:
687+ ret = lwmi_cd_cache(priv);
688+ if (ret)
689+ return NOTIFY_BAD;
690+
691+ return NOTIFY_OK;
692+ default:
693+ return NOTIFY_DONE;
694+ }
695+}
696+
697+/**
698+ * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
699+ * @data: The ACPI event notifier_block to unregister.
700+ */
701+static void lwmi_cd01_unregister(void *data)
702+{
703+ struct notifier_block *acpi_nb = data;
704+
705+ unregister_acpi_notifier(acpi_nb);
706+}
707+
708+static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
709+{
710+ const struct lwmi_cd_info *info = context;
711+ struct lwmi_cd_priv *priv;
712+ int ret;
713+
714+ if (!info)
715+ return -EINVAL;
716+
717+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
718+ if (!priv)
719+ return -ENOMEM;
720+
721+ priv->wdev = wdev;
722+ dev_set_drvdata(&wdev->dev, priv);
723+
724+ ret = lwmi_cd_setup(priv, info->type);
725+ if (ret)
726+ goto out;
727+
728+ switch (info->type) {
729+ case LENOVO_CAPABILITY_DATA_00: {
730+ enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA;
731+ struct capdata00 capdata00;
732+
733+ ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00);
734+ if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) {
735+ dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n");
736+ sub_component_type = CD_TYPE_NONE;
737+ }
738+
739+ /* Sub-master (capdata00) <-> sub-component (capdata_fan) */
740+ ret = lwmi_cd_sub_master_add(priv, sub_component_type);
741+ if (ret)
742+ goto out;
743+
744+ /* Master (lenovo-wmi-other) <-> sub-master (capdata00) */
745+ ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
746+ if (ret)
747+ lwmi_cd_sub_master_del(priv);
748+
749+ goto out;
750+ }
751+ case LENOVO_CAPABILITY_DATA_01:
752+ priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
753+
754+ ret = register_acpi_notifier(&priv->acpi_nb);
755+ if (ret)
756+ goto out;
757+
758+ ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister,
759+ &priv->acpi_nb);
760+ if (ret)
761+ goto out;
762+
763+ ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
764+ goto out;
765+ case LENOVO_FAN_TEST_DATA:
766+ ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops);
767+ goto out;
768+ default:
769+ return -EINVAL;
770+ }
771+out:
772+ if (ret) {
773+ dev_err(&wdev->dev, "failed to register %s: %d\n",
774+ info->name, ret);
775+ } else {
776+ dev_dbg(&wdev->dev, "registered %s with %u items\n",
777+ info->name, priv->list->count);
778+ }
779+ return ret;
780+}
781+
782+static void lwmi_cd_remove(struct wmi_device *wdev)
783+{
784+ struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev);
785+
786+ switch (priv->list->type) {
787+ case LENOVO_CAPABILITY_DATA_00:
788+ lwmi_cd_sub_master_del(priv);
789+ fallthrough;
790+ case LENOVO_CAPABILITY_DATA_01:
791+ component_del(&wdev->dev, &lwmi_cd_component_ops);
792+ break;
793+ case LENOVO_FAN_TEST_DATA:
794+ component_del(&wdev->dev, &lwmi_cd_sub_component_ops);
795+ break;
796+ default:
797+ WARN_ON(1);
798+ }
799+}
800+
801+#define LWMI_CD_WDEV_ID(_type) \
802+ .guid_string = _type##_GUID, \
803+ .context = &lwmi_cd_table[_type],
804+
805+static const struct wmi_device_id lwmi_cd_id_table[] = {
806+ { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) },
807+ { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) },
808+ { LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) },
809+ {}
810+};
811+
812+static struct wmi_driver lwmi_cd_driver = {
813+ .driver = {
814+ .name = "lenovo_wmi_capdata",
815+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
816+ },
817+ .id_table = lwmi_cd_id_table,
818+ .probe = lwmi_cd_probe,
819+ .remove = lwmi_cd_remove,
820+ .no_singleton = true,
821+};
822+
823+module_wmi_driver(lwmi_cd_driver);
824+
825+MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
826+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
827+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
828+MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver");
829+MODULE_LICENSE("GPL");
drivers/platform/x86/lenovo/wmi-capdata.h+65−0
@@ -0,0 +1,65 @@
1+/* SPDX-License-Identifier: GPL-2.0-or-later */
2+
3+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
4+
5+#ifndef _LENOVO_WMI_CAPDATA_H_
6+#define _LENOVO_WMI_CAPDATA_H_
7+
8+#include <linux/bits.h>
9+#include <linux/types.h>
10+
11+#define LWMI_SUPP_VALID BIT(0)
12+#define LWMI_SUPP_GET BIT(1)
13+#define LWMI_SUPP_SET BIT(2)
14+
15+#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
16+#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
17+#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
18+#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
19+
20+#define LWMI_DEVICE_ID_FAN 0x04
21+
22+struct component_match;
23+struct device;
24+struct cd_list;
25+
26+struct capdata00 {
27+ u32 id;
28+ u32 supported;
29+ u32 default_value;
30+};
31+
32+struct capdata01 {
33+ u32 id;
34+ u32 supported;
35+ u32 default_value;
36+ u32 step;
37+ u32 min_value;
38+ u32 max_value;
39+};
40+
41+struct capdata_fan {
42+ u32 id;
43+ u32 min_rpm;
44+ u32 max_rpm;
45+};
46+
47+typedef void (*cd_list_cb_t)(struct device *master_dev, struct cd_list *cd_list);
48+
49+struct lwmi_cd_binder {
50+ struct cd_list *cd00_list;
51+ struct cd_list *cd01_list;
52+ /*
53+ * May be called during or after the bind callback.
54+ * Will be called with NULL if capdata_fan does not exist.
55+ * The pointer is only valid in the callback; never keep it for later use!
56+ */
57+ cd_list_cb_t cd_fan_list_cb;
58+};
59+
60+void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
61+int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capdata00 *output);
62+int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output);
63+int lwmi_cd_fan_get_data(struct cd_list *list, u32 attribute_id, struct capdata_fan *output);
64+
65+#endif /* !_LENOVO_WMI_CAPDATA_H_ */
drivers/platform/x86/lenovo/wmi-capdata01.c+0−302
@@ -1,302 +0,0 @@
1−// SPDX-License-Identifier: GPL-2.0-or-later
2−/*
3− * Lenovo Capability Data 01 WMI Data Block driver.
4− *
5− * Lenovo Capability Data 01 provides information on tunable attributes used by
6− * the "Other Mode" WMI interface. The data includes if the attribute is
7− * supported by the hardware, the default_value, max_value, min_value, and step
8− * increment. Each attribute has multiple pages, one for each of the thermal
9− * modes managed by the Gamezone interface.
10− *
11− * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
12− */
13−
14−#include <linux/acpi.h>
15−#include <linux/cleanup.h>
16−#include <linux/component.h>
17−#include <linux/container_of.h>
18−#include <linux/device.h>
19−#include <linux/export.h>
20−#include <linux/gfp_types.h>
21−#include <linux/module.h>
22−#include <linux/mutex.h>
23−#include <linux/mutex_types.h>
24−#include <linux/notifier.h>
25−#include <linux/overflow.h>
26−#include <linux/types.h>
27−#include <linux/wmi.h>
28−
29−#include "wmi-capdata01.h"
30−
31−#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
32−
33−#define ACPI_AC_CLASS "ac_adapter"
34−#define ACPI_AC_NOTIFY_STATUS 0x80
35−
36−struct lwmi_cd01_priv {
37− struct notifier_block acpi_nb; /* ACPI events */
38− struct wmi_device *wdev;
39− struct cd01_list *list;
40−};
41−
42−struct cd01_list {
43− struct mutex list_mutex; /* list R/W mutex */
44− u8 count;
45− struct capdata01 data[];
46−};
47−
48−/**
49− * lwmi_cd01_component_bind() - Bind component to master device.
50− * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
51− * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
52− * @data: capdata01_list object pointer used to return the capability data.
53− *
54− * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
55− * list. This is used to call lwmi_cd01_get_data to look up attribute data
56− * from the lenovo-wmi-other driver.
57− *
58− * Return: 0
59− */
60−static int lwmi_cd01_component_bind(struct device *cd01_dev,
61− struct device *om_dev, void *data)
62−{
63− struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
64− struct cd01_list **cd01_list = data;
65−
66− *cd01_list = priv->list;
67−
68− return 0;
69−}
70−
71−static const struct component_ops lwmi_cd01_component_ops = {
72− .bind = lwmi_cd01_component_bind,
73−};
74−
75−/**
76− * lwmi_cd01_get_data - Get the data of the specified attribute
77− * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct.
78− * @attribute_id: The capdata attribute ID to be found.
79− * @output: Pointer to a capdata01 struct to return the data.
80− *
81− * Retrieves the capability data 01 struct pointer for the given
82− * attribute for its specified thermal mode.
83− *
84− * Return: 0 on success, or -EINVAL.
85− */
86−int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output)
87−{
88− u8 idx;
89−
90− guard(mutex)(&list->list_mutex);
91− for (idx = 0; idx < list->count; idx++) {
92− if (list->data[idx].id != attribute_id)
93− continue;
94− memcpy(output, &list->data[idx], sizeof(list->data[idx]));
95− return 0;
96− }
97−
98− return -EINVAL;
99−}
100−EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
101−
102−/**
103− * lwmi_cd01_cache() - Cache all WMI data block information
104− * @priv: lenovo-wmi-capdata01 driver data.
105− *
106− * Loop through each WMI data block and cache the data.
107− *
108− * Return: 0 on success, or an error.
109− */
110−static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
111−{
112− int idx;
113−
114− guard(mutex)(&priv->list->list_mutex);
115− for (idx = 0; idx < priv->list->count; idx++) {
116− union acpi_object *ret_obj __free(kfree) = NULL;
117−
118− ret_obj = wmidev_block_query(priv->wdev, idx);
119− if (!ret_obj)
120− return -ENODEV;
121−
122− if (ret_obj->type != ACPI_TYPE_BUFFER ||
123− ret_obj->buffer.length < sizeof(priv->list->data[idx]))
124− continue;
125−
126− memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
127− ret_obj->buffer.length);
128− }
129−
130− return 0;
131−}
132−
133−/**
134− * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
135− * @priv: lenovo-wmi-capdata01 driver data.
136− *
137− * Allocate a cd01_list struct large enough to contain data from all WMI data
138− * blocks provided by the interface.
139− *
140− * Return: 0 on success, or an error.
141− */
142−static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
143−{
144− struct cd01_list *list;
145− size_t list_size;
146− int count, ret;
147−
148− count = wmidev_instance_count(priv->wdev);
149− list_size = struct_size(list, data, count);
150−
151− list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
152− if (!list)
153− return -ENOMEM;
154−
155− ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
156− if (ret)
157− return ret;
158−
159− list->count = count;
160− priv->list = list;
161−
162− return 0;
163−}
164−
165−/**
166− * lwmi_cd01_setup() - Cache all WMI data block information
167− * @priv: lenovo-wmi-capdata01 driver data.
168− *
169− * Allocate a cd01_list struct large enough to contain data from all WMI data
170− * blocks provided by the interface. Then loop through each data block and
171− * cache the data.
172− *
173− * Return: 0 on success, or an error code.
174− */
175−static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv)
176−{
177− int ret;
178−
179− ret = lwmi_cd01_alloc(priv);
180− if (ret)
181− return ret;
182−
183− return lwmi_cd01_cache(priv);
184−}
185−
186−/**
187− * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier.
188− * block call chain.
189− * @nb: The notifier_block registered to lenovo-wmi-events driver.
190− * @action: Unused.
191− * @data: The ACPI event.
192− *
193− * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
194− * of a change.
195− *
196− * Return: notifier_block status.
197− */
198−static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
199− void *data)
200−{
201− struct acpi_bus_event *event = data;
202− struct lwmi_cd01_priv *priv;
203− int ret;
204−
205− if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
206− return NOTIFY_DONE;
207−
208− priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
209−
210− switch (event->type) {
211− case ACPI_AC_NOTIFY_STATUS:
212− ret = lwmi_cd01_cache(priv);
213− if (ret)
214− return NOTIFY_BAD;
215−
216− return NOTIFY_OK;
217− default:
218− return NOTIFY_DONE;
219− }
220−}
221−
222−/**
223− * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
224− * @data: The ACPI event notifier_block to unregister.
225− */
226−static void lwmi_cd01_unregister(void *data)
227−{
228− struct notifier_block *acpi_nb = data;
229−
230− unregister_acpi_notifier(acpi_nb);
231−}
232−
233−static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
234−
235−{
236− struct lwmi_cd01_priv *priv;
237− int ret;
238−
239− priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
240− if (!priv)
241− return -ENOMEM;
242−
243− priv->wdev = wdev;
244− dev_set_drvdata(&wdev->dev, priv);
245−
246− ret = lwmi_cd01_setup(priv);
247− if (ret)
248− return ret;
249−
250− priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
251−
252− ret = register_acpi_notifier(&priv->acpi_nb);
253− if (ret)
254− return ret;
255−
256− ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb);
257− if (ret)
258− return ret;
259−
260− return component_add(&wdev->dev, &lwmi_cd01_component_ops);
261−}
262−
263−static void lwmi_cd01_remove(struct wmi_device *wdev)
264−{
265− component_del(&wdev->dev, &lwmi_cd01_component_ops);
266−}
267−
268−static const struct wmi_device_id lwmi_cd01_id_table[] = {
269− { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
270− {}
271−};
272−
273−static struct wmi_driver lwmi_cd01_driver = {
274− .driver = {
275− .name = "lenovo_wmi_cd01",
276− .probe_type = PROBE_PREFER_ASYNCHRONOUS,
277− },
278− .id_table = lwmi_cd01_id_table,
279− .probe = lwmi_cd01_probe,
280− .remove = lwmi_cd01_remove,
281− .no_singleton = true,
282−};
283−
284−/**
285− * lwmi_cd01_match() - Match rule for the master driver.
286− * @dev: Pointer to the capability data 01 parent device.
287− * @data: Unused void pointer for passing match criteria.
288− *
289− * Return: int.
290− */
291−int lwmi_cd01_match(struct device *dev, void *data)
292−{
293− return dev->driver == &lwmi_cd01_driver.driver;
294−}
295−EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
296−
297−module_wmi_driver(lwmi_cd01_driver);
298−
299−MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table);
300−MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
301−MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
302−MODULE_LICENSE("GPL");
drivers/platform/x86/lenovo/wmi-capdata01.h+0−25
@@ -1,25 +0,0 @@
1−/* SPDX-License-Identifier: GPL-2.0-or-later */
2−
3−/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
4−
5−#ifndef _LENOVO_WMI_CAPDATA01_H_
6−#define _LENOVO_WMI_CAPDATA01_H_
7−
8−#include <linux/types.h>
9−
10−struct device;
11−struct cd01_list;
12−
13−struct capdata01 {
14− u32 id;
15− u32 supported;
16− u32 default_value;
17− u32 step;
18− u32 min_value;
19− u32 max_value;
20−};
21−
22−int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output);
23−int lwmi_cd01_match(struct device *dev, void *data);
24−
25−#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
drivers/platform/x86/lenovo/wmi-helpers.c+18−3
@@ -21,6 +21,7 @@ · 20 unmodified lines
2121 #include <linux/errno.h>
2222 #include <linux/export.h>
2323 #include <linux/module.h>
24+#include <linux/unaligned.h>
2425 #include <linux/wmi.h>
2526
2627 #include "wmi-helpers.h"
@@ -59,10 +60,24 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id, · 32 unmodified lines
5960 if (!ret_obj)
6061 return -ENODATA;
6162
62− if (ret_obj->type != ACPI_TYPE_INTEGER)
63− return -ENXIO;
63+ switch (ret_obj->type) {
64+ /*
65+ * The ACPI method may simply return a buffer when a u32
66+ * is expected. This is valid on Windows as its WMI-ACPI
67+ * driver converts everything to a common buffer.
68+ */
69+ case ACPI_TYPE_BUFFER:
70+ if (ret_obj->buffer.length < sizeof(u32))
71+ return -ENXIO;
6472
65− *retval = (u32)ret_obj->integer.value;
73+ *retval = get_unaligned_le32(ret_obj->buffer.pointer);
74+ return 0;
75+ case ACPI_TYPE_INTEGER:
76+ *retval = (u32)ret_obj->integer.value;
77+ return 0;
78+ default:
79+ return -ENXIO;
80+ }
6681 }
6782
6883 return 0;
drivers/platform/x86/lenovo/wmi-other.c+507−21
@@ -14,7 +14,16 @@ · 13 unmodified lines
1414 * These attributes typically don't fit anywhere else in the sysfs and are set
1515 * in Windows using one of Lenovo's multiple user applications.
1616 *
17+ * Additionally, this driver also exports tunable fan speed RPM to HWMON.
18+ * Min/max RPM are also provided for reference.
19+ *
1720 * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
21+ * - fw_attributes
22+ * - binding to Capability Data 01
23+ *
24+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
25+ * - HWMON
26+ * - binding to Capability Data 00 and Fan
1827 */
1928
2029 #include <linux/acpi.h>
@@ -25,16 +34,18 @@ · 4 unmodified lines
2534 #include <linux/device.h>
2635 #include <linux/export.h>
2736 #include <linux/gfp_types.h>
37+#include <linux/hwmon.h>
2838 #include <linux/idr.h>
2939 #include <linux/kdev_t.h>
3040 #include <linux/kobject.h>
41+#include <linux/limits.h>
3142 #include <linux/module.h>
3243 #include <linux/notifier.h>
3344 #include <linux/platform_profile.h>
3445 #include <linux/types.h>
3546 #include <linux/wmi.h>
3647
37−#include "wmi-capdata01.h"
48+#include "wmi-capdata.h"
3849 #include "wmi-events.h"
3950 #include "wmi-gamezone.h"
4051 #include "wmi-helpers.h"
@@ -49,17 +60,26 @@ · 8 unmodified lines
4960 #define LWMI_FEATURE_ID_CPU_SPL 0x02
5061 #define LWMI_FEATURE_ID_CPU_FPPT 0x03
5162
63+#define LWMI_FEATURE_ID_FAN_RPM 0x03
64+
5265 #define LWMI_TYPE_ID_NONE 0x00
5366
5467 #define LWMI_FEATURE_VALUE_GET 17
5568 #define LWMI_FEATURE_VALUE_SET 18
5669
57−#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
58−#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
59−#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
60−#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
70+#define LWMI_FAN_ID_BASE 1
71+#define LWMI_FAN_NR 4
72+#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE)
73+
74+#define LWMI_ATTR_ID_FAN_RPM(x) \
75+ (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
76+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \
77+ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x)))
78+
79+#define LWMI_FAN_DIV 100
6180
6281 #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
82+#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
6383
6484 static BLOCKING_NOTIFIER_HEAD(om_chain_head);
6585 static DEFINE_IDA(lwmi_om_ida);
@@ -72,16 +92,459 @@ enum attribute_property { · 6 unmodified lines
7292 SUPPORTED,
7393 };
7494
95+struct lwmi_fan_info {
96+ u32 supported;
97+ u32 last_target;
98+ long min_rpm;
99+ long max_rpm;
100+};
101+
75102 struct lwmi_om_priv {
76103 struct component_master_ops *ops;
77− struct cd01_list *cd01_list; /* only valid after capdata01 bind */
104+
105+ /* only valid after capdata bind */
106+ struct cd_list *cd00_list;
107+ struct cd_list *cd01_list;
108+
109+ struct device *hwmon_dev;
78110 struct device *fw_attr_dev;
79111 struct kset *fw_attr_kset;
80112 struct notifier_block nb;
81113 struct wmi_device *wdev;
82114 int ida_id;
115+
116+ struct lwmi_fan_info fan_info[LWMI_FAN_NR];
117+
118+ struct {
119+ bool capdata00_collected : 1;
120+ bool capdata_fan_collected : 1;
121+ } fan_flags;
83122 };
84123
124+/*
125+ * Visibility of fan channels:
126+ *
127+ * +-------------------+---------+------------------+-----------------------+------------+
128+ * | | default | +expose_all_fans | +relax_fan_constraint | +both |
129+ * +-------------------+---------+------------------+-----------------------+------------+
130+ * | canonical | RW | RW | RW+relaxed | RW+relaxed |
131+ * +-------------------+---------+------------------+-----------------------+------------+
132+ * | -capdata_fan[idx] | N | RO | N | RW+relaxed |
133+ * +-------------------+---------+------------------+-----------------------+------------+
134+ *
135+ * Note:
136+ * 1. LWMI_ATTR_ID_FAN_RPM[idx].supported is always checked before exposing a channel.
137+ * 2. -capdata_fan implies -capdata_fan[idx].
138+ */
139+static bool expose_all_fans;
140+module_param(expose_all_fans, bool, 0444);
141+MODULE_PARM_DESC(expose_all_fans,
142+ "This option skips some capability checks and solely relies on per-channel ones "
143+ "to expose fan attributes. Use with caution.");
144+
145+static bool relax_fan_constraint;
146+module_param(relax_fan_constraint, bool, 0444);
147+MODULE_PARM_DESC(relax_fan_constraint,
148+ "Do not enforce fan RPM constraint (div/min/max) "
149+ "and enables fan tuning when such data is missing. "
150+ "Enabling this may results in HWMON attributes being out-of-sync, "
151+ "and setting a too low RPM stops the fan. Use with caution.");
152+
153+/* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */
154+
155+/**
156+ * lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan
157+ * @priv: Driver private data structure
158+ * @channel: Fan channel index (0-based)
159+ * @val: Pointer to value (input for set, output for get)
160+ * @set: True to set value, false to get value
161+ *
162+ * Communicates with WMI interface to either retrieve current fan RPM
163+ * or set target fan RPM.
164+ *
165+ * Return: 0 on success, or an error code.
166+ */
167+static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val, bool set)
168+{
169+ struct wmi_method_args_32 args;
170+ u32 method_id, retval;
171+ int err;
172+
173+ method_id = set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET;
174+ args.arg0 = LWMI_ATTR_ID_FAN_RPM(channel);
175+ args.arg1 = set ? *val : 0;
176+
177+ err = lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id,
178+ (unsigned char *)&args, sizeof(args), &retval);
179+ if (err)
180+ return err;
181+
182+ if (!set) {
183+ *val = retval;
184+ return 0;
185+ }
186+
187+ /*
188+ * It seems that 0 means "no error" and 1 means "done". Apparently
189+ * different firmware teams have different thoughts on indicating
190+ * success, so we accepts both.
191+ */
192+ return (retval == 0 || retval == 1) ? 0 : -EIO;
193+}
194+
195+/**
196+ * lwmi_om_hwmon_is_visible() - Determine visibility of HWMON attributes
197+ * @drvdata: Driver private data
198+ * @type: Sensor type
199+ * @attr: Attribute identifier
200+ * @channel: Channel index
201+ *
202+ * Determines whether an HWMON attribute should be visible in sysfs
203+ * based on hardware capabilities and current configuration.
204+ *
205+ * Return: permission mode, or 0 if invisible.
206+ */
207+static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
208+ u32 attr, int channel)
209+{
210+ struct lwmi_om_priv *priv = (struct lwmi_om_priv *)drvdata;
211+ bool visible = false;
212+
213+ if (type == hwmon_fan) {
214+ if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID))
215+ return 0;
216+
217+ switch (attr) {
218+ case hwmon_fan_target:
219+ if (!(priv->fan_info[channel].supported & LWMI_SUPP_SET))
220+ return 0;
221+
222+ if (relax_fan_constraint ||
223+ (priv->fan_info[channel].min_rpm >= 0 &&
224+ priv->fan_info[channel].max_rpm >= 0))
225+ return 0644;
226+
227+ /*
228+ * Reaching here implies expose_all_fans is set.
229+ * See lwmi_om_hwmon_add().
230+ */
231+ dev_warn_once(&priv->wdev->dev,
232+ "fan tuning disabled due to missing RPM constraint\n");
233+ return 0;
234+ case hwmon_fan_div:
235+ case hwmon_fan_input:
236+ visible = priv->fan_info[channel].supported & LWMI_SUPP_GET;
237+ break;
238+ case hwmon_fan_min:
239+ visible = priv->fan_info[channel].min_rpm >= 0;
240+ break;
241+ case hwmon_fan_max:
242+ visible = priv->fan_info[channel].max_rpm >= 0;
243+ break;
244+ }
245+ }
246+
247+ return visible ? 0444 : 0;
248+}
249+
250+/**
251+ * lwmi_om_hwmon_read() - Read HWMON sensor data
252+ * @dev: Device pointer
253+ * @type: Sensor type
254+ * @attr: Attribute identifier
255+ * @channel: Channel index
256+ * @val: Pointer to store value
257+ *
258+ * Reads current sensor values from hardware through WMI interface.
259+ *
260+ * Return: 0 on success, or an error code.
261+ */
262+static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
263+ u32 attr, int channel, long *val)
264+{
265+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
266+ u32 retval = 0;
267+ int err;
268+
269+ if (type == hwmon_fan) {
270+ switch (attr) {
271+ /*
272+ * The EC has an internal RPM divisor (i.e., the raw register value is
273+ * RPM / fanY_div). For fanY_input, the WMI method reads the register
274+ * value and returns raw * fanY_div. For fanY_target, the WMI method
275+ * divides the written value by fanY_div before writing it to the EC.
276+ *
277+ * As a result, reading fanY_input always returns a multiple of fanY_div,
278+ * while writing to fanY_target loses the remainder.
279+ */
280+ case hwmon_fan_div:
281+ *val = LWMI_FAN_DIV;
282+ return 0;
283+ case hwmon_fan_input:
284+ err = lwmi_om_fan_get_set(priv, channel, &retval, false);
285+ if (err)
286+ return err;
287+
288+ *val = retval;
289+ return 0;
290+ case hwmon_fan_target:
291+ *val = priv->fan_info[channel].last_target;
292+ return 0;
293+ case hwmon_fan_min:
294+ *val = priv->fan_info[channel].min_rpm;
295+ return 0;
296+ case hwmon_fan_max:
297+ *val = priv->fan_info[channel].max_rpm;
298+ return 0;
299+ }
300+ }
301+
302+ return -EOPNOTSUPP;
303+}
304+
305+/**
306+ * lwmi_om_hwmon_write() - Write HWMON sensor data
307+ * @dev: Device pointer
308+ * @type: Sensor type
309+ * @attr: Attribute identifier
310+ * @channel: Channel index
311+ * @val: Value to write
312+ *
313+ * Writes configuration values to hardware through WMI interface.
314+ *
315+ * Return: 0 on success, or an error code.
316+ */
317+static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
318+ u32 attr, int channel, long val)
319+{
320+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
321+ u32 raw, min_rpm, max_rpm;
322+ int err;
323+
324+ if (type == hwmon_fan) {
325+ switch (attr) {
326+ case hwmon_fan_target:
327+ if (relax_fan_constraint) {
328+ min_rpm = 1;
329+ max_rpm = U16_MAX;
330+ } else {
331+ min_rpm = priv->fan_info[channel].min_rpm;
332+ max_rpm = priv->fan_info[channel].max_rpm;
333+ }
334+
335+ /* 0 means "auto". */
336+ if (val != 0 && (val < min_rpm || val > max_rpm))
337+ return -EINVAL;
338+
339+ /*
340+ * The effective fanY_target is always a multiple of fanY_div
341+ * due to the EC's internal RPM divisor (see lwmi_om_hwmon_read).
342+ *
343+ * Round down the written value to the nearest multiple of fanY_div
344+ * to prevent mismatch between the effective value and last_target.
345+ *
346+ * For relax_fan_constraint, skip this conversion as setting a
347+ * sub-fanY_div value is necessary to completely stop the fan on
348+ * some devices.
349+ */
350+ if (!relax_fan_constraint)
351+ raw = val / LWMI_FAN_DIV * LWMI_FAN_DIV;
352+
353+ err = lwmi_om_fan_get_set(priv, channel, &raw, true);
354+ if (err)
355+ return err;
356+
357+ priv->fan_info[channel].last_target = raw;
358+ return 0;
359+ }
360+ }
361+
362+ return -EOPNOTSUPP;
363+}
364+
365+static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = {
366+ /* Must match LWMI_FAN_NR. */
367+ HWMON_CHANNEL_INFO(fan,
368+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
369+ HWMON_F_MIN | HWMON_F_MAX,
370+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
371+ HWMON_F_MIN | HWMON_F_MAX,
372+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
373+ HWMON_F_MIN | HWMON_F_MAX,
374+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
375+ HWMON_F_MIN | HWMON_F_MAX),
376+ NULL
377+};
378+
379+static const struct hwmon_ops lwmi_om_hwmon_ops = {
380+ .is_visible = lwmi_om_hwmon_is_visible,
381+ .read = lwmi_om_hwmon_read,
382+ .write = lwmi_om_hwmon_write,
383+};
384+
385+static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = {
386+ .ops = &lwmi_om_hwmon_ops,
387+ .info = lwmi_om_hwmon_info,
388+};
389+
390+/**
391+ * lwmi_om_hwmon_add() - Register HWMON device if all info is collected
392+ * @priv: Driver private data
393+ */
394+static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
395+{
396+ int i, valid;
397+
398+ if (WARN_ON(priv->hwmon_dev))
399+ return;
400+
401+ if (!priv->fan_flags.capdata00_collected || !priv->fan_flags.capdata_fan_collected) {
402+ dev_dbg(&priv->wdev->dev, "HWMON registration pending (00: %d, fan: %d)\n",
403+ priv->fan_flags.capdata00_collected,
404+ priv->fan_flags.capdata_fan_collected);
405+ return;
406+ }
407+
408+ if (expose_all_fans)
409+ dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n");
410+
411+ if (relax_fan_constraint)
412+ dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution\n");
413+
414+ valid = 0;
415+ for (i = 0; i < LWMI_FAN_NR; i++) {
416+ if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID))
417+ continue;
418+
419+ valid++;
420+
421+ if (!expose_all_fans &&
422+ (priv->fan_info[i].min_rpm < 0 || priv->fan_info[i].max_rpm < 0)) {
423+ dev_dbg(&priv->wdev->dev, "missing RPM constraint for fan%d, hiding\n",
424+ LWMI_FAN_ID(i));
425+ priv->fan_info[i].supported = 0;
426+ valid--;
427+ }
428+ }
429+
430+ if (valid == 0) {
431+ dev_warn(&priv->wdev->dev,
432+ "fan reporting/tuning is unsupported on this device\n");
433+ return;
434+ }
435+
436+ priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev,
437+ LWMI_OM_HWMON_NAME, priv,
438+ &lwmi_om_hwmon_chip_info,
439+ NULL);
440+ if (IS_ERR(priv->hwmon_dev)) {
441+ dev_warn(&priv->wdev->dev, "failed to register HWMON device: %ld\n",
442+ PTR_ERR(priv->hwmon_dev));
443+ priv->hwmon_dev = NULL;
444+ return;
445+ }
446+
447+ dev_dbg(&priv->wdev->dev, "registered HWMON device\n");
448+}
449+
450+/**
451+ * lwmi_om_hwmon_remove() - Unregister HWMON device
452+ * @priv: Driver private data
453+ *
454+ * Unregisters the HWMON device if applicable.
455+ */
456+static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv)
457+{
458+ if (!priv->hwmon_dev)
459+ return;
460+
461+ hwmon_device_unregister(priv->hwmon_dev);
462+ priv->hwmon_dev = NULL;
463+}
464+
465+/**
466+ * lwmi_om_fan_info_init() - Initialzie fan info
467+ * @priv: Driver private data
468+ *
469+ * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan() may be
470+ * called in an arbitrary order. Hence, initializion must be done before.
471+ */
472+static void lwmi_om_fan_info_init(struct lwmi_om_priv *priv)
473+{
474+ int i;
475+
476+ for (i = 0; i < LWMI_FAN_NR; i++) {
477+ priv->fan_info[i] = (struct lwmi_fan_info) {
478+ .supported = 0,
479+ /*
480+ * Assume 0 on probe as the EC resets all fans to auto mode on (re)boot.
481+ *
482+ * Note that S0ix (s2idle) preserves the RPM target, so we don't need
483+ * suspend/resume callbacks. This behavior has not been tested on S3-
484+ * capable devices, but I doubt if such devices even have this interface.
485+ */
486+ .last_target = 0,
487+ .min_rpm = -ENODATA,
488+ .max_rpm = -ENODATA,
489+ };
490+ }
491+
492+ priv->fan_flags.capdata00_collected = false;
493+ priv->fan_flags.capdata_fan_collected = false;
494+}
495+
496+/**
497+ * lwmi_om_fan_info_collect_cd00() - Collect fan info from capdata 00
498+ * @priv: Driver private data
499+ */
500+static void lwmi_om_fan_info_collect_cd00(struct lwmi_om_priv *priv)
501+{
502+ struct capdata00 capdata00;
503+ int i, err;
504+
505+ dev_dbg(&priv->wdev->dev, "Collecting fan info from capdata00\n");
506+
507+ for (i = 0; i < LWMI_FAN_NR; i++) {
508+ err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &capdata00);
509+ priv->fan_info[i].supported = err ? 0 : capdata00.supported;
510+ }
511+
512+ priv->fan_flags.capdata00_collected = true;
513+ lwmi_om_hwmon_add(priv);
514+}
515+
516+/**
517+ * lwmi_om_fan_info_collect_cd_fan() - Collect fan info from capdata fan
518+ * @dev: Pointer to the lenovo-wmi-other device
519+ * @cd_fan_list: Pointer to the capdata fan list
520+ */
521+static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *cd_fan_list)
522+{
523+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
524+ struct capdata_fan capdata_fan;
525+ int i, err;
526+
527+ dev_dbg(dev, "Collecting fan info from capdata_fan\n");
528+
529+ if (!cd_fan_list)
530+ goto out;
531+
532+ for (i = 0; i < LWMI_FAN_NR; i++) {
533+ err = lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan);
534+ if (err)
535+ continue;
536+
537+ priv->fan_info[i].min_rpm = capdata_fan.min_rpm;
538+ priv->fan_info[i].max_rpm = capdata_fan.max_rpm;
539+ }
540+
541+out:
542+ priv->fan_flags.capdata_fan_collected = true;
543+ lwmi_om_hwmon_add(priv);
544+}
545+
546+/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
547+
85548 struct tunable_attr_01 {
86549 struct capdata01 *capdata;
87550 struct device *dev;
@@ -561,32 +1024,45 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) · 473 unmodified lines
5611024 device_unregister(priv->fw_attr_dev);
5621025 }
5631026
1027+/* ======== Self (master: lenovo-wmi-other) ======== */
1028+
5641029 /**
5651030 * lwmi_om_master_bind() - Bind all components of the other mode driver
5661031 * @dev: The lenovo-wmi-other driver basic device.
5671032 *
568− * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the
569− * lenovo-wmi-other master driver. On success, assign the capability data 01
570− * list pointer to the driver data struct for later access. This pointer
571− * is only valid while the capdata01 interface exists. Finally, register all
572− * firmware attribute groups.
1033+ * Call component_bind_all to bind the lenovo-wmi-capdata devices to the
1034+ * lenovo-wmi-other master driver, with a callback to collect fan info from
1035+ * capdata_fan. On success, assign the capability data list pointers to the
1036+ * driver data struct for later access. These pointers are only valid while the
1037+ * capdata interfaces exist. Finally, collect fan info from capdata00 and
1038+ * register all firmware attribute groups. Note that the HWMON device is
1039+ * registered only if all fan info is collected. Hence, it is not registered
1040+ * here. See lwmi_om_fan_info_collect_cd00() and
1041+ * lwmi_om_fan_info_collect_cd_fan().
5731042 *
5741043 * Return: 0 on success, or an error code.
5751044 */
5761045 static int lwmi_om_master_bind(struct device *dev)
5771046 {
5781047 struct lwmi_om_priv *priv = dev_get_drvdata(dev);
579− struct cd01_list *tmp_list;
1048+ struct lwmi_cd_binder binder = {
1049+ .cd_fan_list_cb = lwmi_om_fan_info_collect_cd_fan,
1050+ };
5801051 int ret;
5811052
582− ret = component_bind_all(dev, &tmp_list);
1053+ lwmi_om_fan_info_init(priv);
1054+
1055+ ret = component_bind_all(dev, &binder);
5831056 if (ret)
5841057 return ret;
5851058
586− priv->cd01_list = tmp_list;
587− if (!priv->cd01_list)
1059+ priv->cd00_list = binder.cd00_list;
1060+ priv->cd01_list = binder.cd01_list;
1061+ if (!priv->cd00_list || !priv->cd01_list)
5881062 return -ENODEV;
5891063
1064+ lwmi_om_fan_info_collect_cd00(priv);
1065+
5901066 return lwmi_om_fw_attr_add(priv);
5911067 }
5921068
@@ -594,15 +1070,18 @@ static int lwmi_om_master_bind(struct device *dev) · 1 unmodified line
5941070 * lwmi_om_master_unbind() - Unbind all components of the other mode driver
5951071 * @dev: The lenovo-wmi-other driver basic device
5961072 *
597− * Unregister all capability data attribute groups. Then call
598− * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the
599− * lenovo-wmi-other master driver. Finally, free the IDA for this device.
1073+ * Unregister all firmware attribute groups and the HWMON device. Then call
1074+ * component_unbind_all to unbind lenovo-wmi-capdata devices from the
1075+ * lenovo-wmi-other master driver.
6001076 */
6011077 static void lwmi_om_master_unbind(struct device *dev)
6021078 {
6031079 struct lwmi_om_priv *priv = dev_get_drvdata(dev);
6041080
6051081 lwmi_om_fw_attr_remove(priv);
1082+
1083+ lwmi_om_hwmon_remove(priv);
1084+
6061085 component_unbind_all(dev, NULL);
6071086 }
6081087
@@ -620,10 +1099,13 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context) · 11 unmodified lines
6201099 if (!priv)
6211100 return -ENOMEM;
6221101
1102+ /* Sentinel for on-demand ida_free(). */
1103+ priv->ida_id = -EIDRM;
1104+
6231105 priv->wdev = wdev;
6241106 dev_set_drvdata(&wdev->dev, priv);
6251107
626− component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
1108+ lwmi_cd_match_add_all(&wdev->dev, &master_match);
6271109 if (IS_ERR(master_match))
6281110 return PTR_ERR(master_match);
6291111
@@ -636,7 +1118,10 @@ static void lwmi_other_remove(struct wmi_device *wdev) · 6 unmodified lines
6361118 struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
6371119
6381120 component_master_del(&wdev->dev, &lwmi_om_master_ops);
639− ida_free(&lwmi_om_ida, priv->ida_id);
1121+
1122+ /* No IDA to free if the driver is never bound to its components. */
1123+ if (priv->ida_id >= 0)
1124+ ida_free(&lwmi_om_ida, priv->ida_id);
6401125 }
6411126
6421127 static const struct wmi_device_id lwmi_other_id_table[] = {
@@ -657,9 +1142,10 @@ static struct wmi_driver lwmi_other_driver = { · 14 unmodified lines
6571142
6581143 module_wmi_driver(lwmi_other_driver);
6591144
660−MODULE_IMPORT_NS("LENOVO_WMI_CD01");
1145+MODULE_IMPORT_NS("LENOVO_WMI_CAPDATA");
6611146 MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
6621147 MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
6631148 MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
1149+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
6641150 MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
6651151 MODULE_LICENSE("GPL");
drivers/platform/x86/lenovo/yogabook.c+7−5
@@ -57,7 +57,7 @@ struct yogabook_data { · 56 unmodified lines
5757 struct work_struct work;
5858 struct led_classdev kbd_bl_led;
5959 unsigned long flags;
60− uint8_t brightness;
60+ u8 brightness;
6161 };
6262
6363 static void yogabook_work(struct work_struct *work)
@@ -338,16 +338,18 @@ static int yogabook_wmi_probe(struct wmi_device *wdev, const void *context) · 274 unmodified lines
338338 int r;
339339
340340 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
341− if (data == NULL)
341+ if (!data)
342342 return -ENOMEM;
343343
344344 data->kbd_adev = acpi_dev_get_first_match_dev("GDIX1001", NULL, -1);
345345 if (!data->kbd_adev)
346− return dev_err_probe(dev, -ENODEV, "Cannot find the touchpad device in ACPI tables\n");
346+ return dev_err_probe(dev, -ENODEV,
347+ "Cannot find the touchpad device in ACPI tables\n");
347348
348349 data->dig_adev = acpi_dev_get_first_match_dev("WCOM0019", NULL, -1);
349350 if (!data->dig_adev) {
350− r = dev_err_probe(dev, -ENODEV, "Cannot find the digitizer device in ACPI tables\n");
351+ r = dev_err_probe(dev, -ENODEV,
352+ "Cannot find the digitizer device in ACPI tables\n");
351353 goto error_put_devs;
352354 }
353355
@@ -453,7 +455,7 @@ static int yogabook_pdev_probe(struct platform_device *pdev) · 99 unmodified lines
453455 int r;
454456
455457 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
456− if (data == NULL)
458+ if (!data)
457459 return -ENOMEM;
458460
459461 data->kbd_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "i2c-goodix_ts");
drivers/platform/x86/uniwill/uniwill-acpi.c+272−30
@@ -88,6 +88,9 @@ · 87 unmodified lines
8888
8989 #define EC_ADDR_GPU_TEMP 0x044F
9090
91+#define EC_ADDR_SYSTEM_ID 0x0456
92+#define HAS_GPU BIT(7)
93+
9194 #define EC_ADDR_MAIN_FAN_RPM_1 0x0464
9295
9396 #define EC_ADDR_MAIN_FAN_RPM_2 0x0465
@@ -122,11 +125,11 @@ · 28 unmodified lines
122125 #define CTGP_DB_DB_ENABLE BIT(1)
123126 #define CTGP_DB_CTGP_ENABLE BIT(2)
124127
125−#define EC_ADDR_CTGP_OFFSET 0x0744
128+#define EC_ADDR_CTGP_DB_CTGP_OFFSET 0x0744
126129
127−#define EC_ADDR_TPP_OFFSET 0x0745
130+#define EC_ADDR_CTGP_DB_TPP_OFFSET 0x0745
128131
129−#define EC_ADDR_MAX_TGP 0x0746
132+#define EC_ADDR_CTGP_DB_DB_OFFSET 0x0746
130133
131134 #define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748
132135 #define LIGHTBAR_APP_EXISTS BIT(0)
@@ -317,11 +320,13 @@ · 184 unmodified lines
317320 #define UNIWILL_FEATURE_LIGHTBAR BIT(3)
318321 #define UNIWILL_FEATURE_BATTERY BIT(4)
319322 #define UNIWILL_FEATURE_HWMON BIT(5)
323+#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(6)
320324
321325 struct uniwill_data {
322326 struct device *dev;
323327 acpi_handle handle;
324328 struct regmap *regmap;
329+ unsigned int features;
325330 struct acpi_battery_hook hook;
326331 unsigned int last_charge_ctrl;
327332 struct mutex battery_lock; /* Protects the list of currently registered batteries */
@@ -341,12 +346,21 @@ struct uniwill_battery_entry { · 13 unmodified lines
341346 struct power_supply *battery;
342347 };
343348
349+struct uniwill_device_descriptor {
350+ unsigned int features;
351+ /* Executed during driver probing */
352+ int (*probe)(struct uniwill_data *data);
353+};
354+
344355 static bool force;
345356 module_param_unsafe(force, bool, 0);
346357 MODULE_PARM_DESC(force, "Force loading without checking for supported devices\n");
347358
348−/* Feature bitmask since the associated registers are not reliable */
349−static unsigned int supported_features;
359+/*
360+ * Contains device specific data like the feature bitmap since
361+ * the associated registers are not always reliable.
362+ */
363+static struct uniwill_device_descriptor device_descriptor __ro_after_init;
350364
351365 static const char * const uniwill_temp_labels[] = {
352366 "CPU",
@@ -411,6 +425,12 @@ static const struct key_entry uniwill_keymap[] = { · 58 unmodified lines
411425 { KE_END }
412426 };
413427
428+static inline bool uniwill_device_supports(struct uniwill_data *data,
429+ unsigned int features)
430+{
431+ return (data->features & features) == features;
432+}
433+
414434 static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val)
415435 {
416436 union acpi_object params[2] = {
@@ -498,6 +518,10 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) · 81 unmodified lines
498518 case EC_ADDR_LIGHTBAR_BAT_RED:
499519 case EC_ADDR_LIGHTBAR_BAT_GREEN:
500520 case EC_ADDR_LIGHTBAR_BAT_BLUE:
521+ case EC_ADDR_CTGP_DB_CTRL:
522+ case EC_ADDR_CTGP_DB_CTGP_OFFSET:
523+ case EC_ADDR_CTGP_DB_TPP_OFFSET:
524+ case EC_ADDR_CTGP_DB_DB_OFFSET:
501525 return true;
502526 default:
503527 return false;
@@ -531,6 +555,11 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) · 27 unmodified lines
531555 case EC_ADDR_LIGHTBAR_BAT_RED:
532556 case EC_ADDR_LIGHTBAR_BAT_GREEN:
533557 case EC_ADDR_LIGHTBAR_BAT_BLUE:
558+ case EC_ADDR_SYSTEM_ID:
559+ case EC_ADDR_CTGP_DB_CTRL:
560+ case EC_ADDR_CTGP_DB_CTGP_OFFSET:
561+ case EC_ADDR_CTGP_DB_TPP_OFFSET:
562+ case EC_ADDR_CTGP_DB_DB_OFFSET:
534563 return true;
535564 default:
536565 return false;
@@ -786,6 +815,70 @@ static ssize_t breathing_in_suspend_show(struct device *dev, struct device_attri · 249 unmodified lines
786815
787816 static DEVICE_ATTR_RW(breathing_in_suspend);
788817
818+static ssize_t ctgp_offset_store(struct device *dev, struct device_attribute *attr,
819+ const char *buf, size_t count)
820+{
821+ struct uniwill_data *data = dev_get_drvdata(dev);
822+ unsigned int value;
823+ int ret;
824+
825+ ret = kstrtouint(buf, 0, &value);
826+ if (ret < 0)
827+ return ret;
828+
829+ if (value > U8_MAX)
830+ return -EINVAL;
831+
832+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, value);
833+ if (ret < 0)
834+ return ret;
835+
836+ return count;
837+}
838+
839+static ssize_t ctgp_offset_show(struct device *dev, struct device_attribute *attr,
840+ char *buf)
841+{
842+ struct uniwill_data *data = dev_get_drvdata(dev);
843+ unsigned int value;
844+ int ret;
845+
846+ ret = regmap_read(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, &value);
847+ if (ret < 0)
848+ return ret;
849+
850+ return sysfs_emit(buf, "%u\n", value);
851+}
852+
853+static DEVICE_ATTR_RW(ctgp_offset);
854+
855+static int uniwill_nvidia_ctgp_init(struct uniwill_data *data)
856+{
857+ int ret;
858+
859+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
860+ return 0;
861+
862+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, 0);
863+ if (ret < 0)
864+ return ret;
865+
866+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_TPP_OFFSET, 255);
867+ if (ret < 0)
868+ return ret;
869+
870+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_DB_OFFSET, 25);
871+ if (ret < 0)
872+ return ret;
873+
874+ ret = regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
875+ CTGP_DB_GENERAL_ENABLE | CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
876+ if (ret < 0)
877+ return ret;
878+
879+ return 0;
880+}
881+
789882 static struct attribute *uniwill_attrs[] = {
790883 /* Keyboard-related */
791884 &dev_attr_fn_lock_toggle_enable.attr,
@@ -794,29 +887,39 @@ static struct attribute *uniwill_attrs[] = { · 2 unmodified lines
794887 /* Lightbar-related */
795888 &dev_attr_rainbow_animation.attr,
796889 &dev_attr_breathing_in_suspend.attr,
890+ /* Power-management-related */
891+ &dev_attr_ctgp_offset.attr,
797892 NULL
798893 };
799894
800895 static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
801896 {
897+ struct device *dev = kobj_to_dev(kobj);
898+ struct uniwill_data *data = dev_get_drvdata(dev);
899+
802900 if (attr == &dev_attr_fn_lock_toggle_enable.attr) {
803− if (supported_features & UNIWILL_FEATURE_FN_LOCK_TOGGLE)
901+ if (uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK_TOGGLE))
804902 return attr->mode;
805903 }
806904
807905 if (attr == &dev_attr_super_key_toggle_enable.attr) {
808− if (supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE)
906+ if (uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
809907 return attr->mode;
810908 }
811909
812910 if (attr == &dev_attr_touchpad_toggle_enable.attr) {
813− if (supported_features & UNIWILL_FEATURE_TOUCHPAD_TOGGLE)
911+ if (uniwill_device_supports(data, UNIWILL_FEATURE_TOUCHPAD_TOGGLE))
814912 return attr->mode;
815913 }
816914
817915 if (attr == &dev_attr_rainbow_animation.attr ||
818916 attr == &dev_attr_breathing_in_suspend.attr) {
819− if (supported_features & UNIWILL_FEATURE_LIGHTBAR)
917+ if (uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR))
918+ return attr->mode;
919+ }
920+
921+ if (attr == &dev_attr_ctgp_offset.attr) {
922+ if (uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
820923 return attr->mode;
821924 }
822925
@@ -944,7 +1047,7 @@ static int uniwill_hwmon_init(struct uniwill_data *data) · 121 unmodified lines
9441047 {
9451048 struct device *hdev;
9461049
947− if (!(supported_features & UNIWILL_FEATURE_HWMON))
1050+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_HWMON))
9481051 return 0;
9491052
9501053 hdev = devm_hwmon_device_register_with_info(data->dev, "uniwill", data,
@@ -1019,7 +1122,7 @@ static int uniwill_led_init(struct uniwill_data *data) · 68 unmodified lines
10191122 unsigned int value;
10201123 int ret;
10211124
1022− if (!(supported_features & UNIWILL_FEATURE_LIGHTBAR))
1125+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR))
10231126 return 0;
10241127
10251128 ret = devm_mutex_init(data->dev, &data->led_lock);
@@ -1232,7 +1335,7 @@ static int uniwill_battery_init(struct uniwill_data *data) · 206 unmodified lines
12321335 {
12331336 int ret;
12341337
1235− if (!(supported_features & UNIWILL_FEATURE_BATTERY))
1338+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
12361339 return 0;
12371340
12381341 ret = devm_mutex_init(data->dev, &data->battery_lock);
@@ -1361,6 +1464,19 @@ static int uniwill_probe(struct platform_device *pdev) · 122 unmodified lines
13611464 if (ret < 0)
13621465 return ret;
13631466
1467+ data->features = device_descriptor.features;
1468+
1469+ /*
1470+ * Some devices might need to perform some device-specific initialization steps
1471+ * before the supported features are initialized. Because of this we have to call
1472+ * this callback just after the EC itself was initialized.
1473+ */
1474+ if (device_descriptor.probe) {
1475+ ret = device_descriptor.probe(data);
1476+ if (ret < 0)
1477+ return ret;
1478+ }
1479+
13641480 ret = uniwill_battery_init(data);
13651481 if (ret < 0)
13661482 return ret;
@@ -1373,6 +1489,10 @@ static int uniwill_probe(struct platform_device *pdev) · 6 unmodified lines
13731489 if (ret < 0)
13741490 return ret;
13751491
1492+ ret = uniwill_nvidia_ctgp_init(data);
1493+ if (ret < 0)
1494+ return ret;
1495+
13761496 return uniwill_input_init(data);
13771497 }
13781498
@@ -1385,7 +1505,7 @@ static void uniwill_shutdown(struct platform_device *pdev) · 6 unmodified lines
13851505
13861506 static int uniwill_suspend_keyboard(struct uniwill_data *data)
13871507 {
1388− if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
1508+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
13891509 return 0;
13901510
13911511 /*
@@ -1397,7 +1517,7 @@ static int uniwill_suspend_keyboard(struct uniwill_data *data) · 5 unmodified lines
13971517
13981518 static int uniwill_suspend_battery(struct uniwill_data *data)
13991519 {
1400− if (!(supported_features & UNIWILL_FEATURE_BATTERY))
1520+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
14011521 return 0;
14021522
14031523 /*
@@ -1408,6 +1528,15 @@ static int uniwill_suspend_battery(struct uniwill_data *data) · 4 unmodified lines
14081528 return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl);
14091529 }
14101530
1531+static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data)
1532+{
1533+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
1534+ return 0;
1535+
1536+ return regmap_clear_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
1537+ CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
1538+}
1539+
14111540 static int uniwill_suspend(struct device *dev)
14121541 {
14131542 struct uniwill_data *data = dev_get_drvdata(dev);
@@ -1421,6 +1550,10 @@ static int uniwill_suspend(struct device *dev) · 7 unmodified lines
14211550 if (ret < 0)
14221551 return ret;
14231552
1553+ ret = uniwill_suspend_nvidia_ctgp(data);
1554+ if (ret < 0)
1555+ return ret;
1556+
14241557 regcache_cache_only(data->regmap, true);
14251558 regcache_mark_dirty(data->regmap);
14261559
@@ -1432,7 +1565,7 @@ static int uniwill_resume_keyboard(struct uniwill_data *data) · 5 unmodified lines
14321565 unsigned int value;
14331566 int ret;
14341567
1435− if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
1568+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
14361569 return 0;
14371570
14381571 ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value);
@@ -1448,13 +1581,22 @@ static int uniwill_resume_keyboard(struct uniwill_data *data) · 9 unmodified lines
14481581
14491582 static int uniwill_resume_battery(struct uniwill_data *data)
14501583 {
1451− if (!(supported_features & UNIWILL_FEATURE_BATTERY))
1584+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
14521585 return 0;
14531586
14541587 return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK,
14551588 data->last_charge_ctrl);
14561589 }
14571590
1591+static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data)
1592+{
1593+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
1594+ return 0;
1595+
1596+ return regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
1597+ CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
1598+}
1599+
14581600 static int uniwill_resume(struct device *dev)
14591601 {
14601602 struct uniwill_data *data = dev_get_drvdata(dev);
@@ -1470,7 +1612,11 @@ static int uniwill_resume(struct device *dev) · 9 unmodified lines
14701612 if (ret < 0)
14711613 return ret;
14721614
1473− return uniwill_resume_battery(data);
1615+ ret = uniwill_resume_battery(data);
1616+ if (ret < 0)
1617+ return ret;
1618+
1619+ return uniwill_resume_nvidia_ctgp(data);
14741620 }
14751621
14761622 static DEFINE_SIMPLE_DEV_PM_OPS(uniwill_pm_ops, uniwill_suspend, uniwill_resume);
@@ -1496,6 +1642,48 @@ static struct platform_driver uniwill_driver = { · 19 unmodified lines
14961642 .shutdown = uniwill_shutdown,
14971643 };
14981644
1645+static struct uniwill_device_descriptor lapac71h_descriptor __initdata = {
1646+ .features = UNIWILL_FEATURE_FN_LOCK_TOGGLE |
1647+ UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
1648+ UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
1649+ UNIWILL_FEATURE_BATTERY |
1650+ UNIWILL_FEATURE_HWMON,
1651+};
1652+
1653+static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = {
1654+ .features = UNIWILL_FEATURE_FN_LOCK_TOGGLE |
1655+ UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
1656+ UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
1657+ UNIWILL_FEATURE_LIGHTBAR |
1658+ UNIWILL_FEATURE_BATTERY |
1659+ UNIWILL_FEATURE_HWMON,
1660+};
1661+
1662+static int phxarx1_phxaqf1_probe(struct uniwill_data *data)
1663+{
1664+ unsigned int value;
1665+ int ret;
1666+
1667+ ret = regmap_read(data->regmap, EC_ADDR_SYSTEM_ID, &value);
1668+ if (ret < 0)
1669+ return ret;
1670+
1671+ if (value & HAS_GPU)
1672+ data->features |= UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL;
1673+
1674+ return 0;
1675+};
1676+
1677+static struct uniwill_device_descriptor phxarx1_phxaqf1_descriptor __initdata = {
1678+ .probe = phxarx1_phxaqf1_probe,
1679+};
1680+
1681+static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = {
1682+ .features = UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
1683+};
1684+
1685+static struct uniwill_device_descriptor empty_descriptor __initdata = {};
1686+
14991687 static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
15001688 {
15011689 .ident = "XMG FUSION 15",
@@ -1503,6 +1691,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15031691 DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
15041692 DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"),
15051693 },
1694+ .driver_data = &empty_descriptor,
15061695 },
15071696 {
15081697 .ident = "XMG FUSION 15",
@@ -1510,6 +1699,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15101699 DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
15111700 DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"),
15121701 },
1702+ .driver_data = &empty_descriptor,
15131703 },
15141704 {
15151705 .ident = "Intel NUC x15",
@@ -1517,11 +1707,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15171707 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
15181708 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPAC71H"),
15191709 },
1520− .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE |
1521− UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
1522− UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
1523− UNIWILL_FEATURE_BATTERY |
1524− UNIWILL_FEATURE_HWMON),
1710+ .driver_data = &lapac71h_descriptor,
15251711 },
15261712 {
15271713 .ident = "Intel NUC x15",
@@ -1529,12 +1715,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15291715 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
15301716 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"),
15311717 },
1532− .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE |
1533− UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
1534− UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
1535− UNIWILL_FEATURE_LIGHTBAR |
1536− UNIWILL_FEATURE_BATTERY |
1537− UNIWILL_FEATURE_HWMON),
1718+ .driver_data = &lapkc71f_descriptor,
15381719 },
15391720 {
15401721 .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel",
@@ -1542,6 +1723,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15421723 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
15431724 DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"),
15441725 },
1726+ .driver_data = &empty_descriptor,
15451727 },
15461728 {
15471729 .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel",
@@ -1549,6 +1731,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15491731 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
15501732 DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"),
15511733 },
1734+ .driver_data = &tux_featureset_1_descriptor,
15521735 },
15531736 {
15541737 .ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel",
@@ -1556,6 +1739,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15561739 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
15571740 DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"),
15581741 },
1742+ .driver_data = &phxarx1_phxaqf1_descriptor,
15591743 },
15601744 {
15611745 .ident = "TUXEDO InfinityBook Pro 16 Gen7 Intel/Commodore Omnia-Book Pro Gen 7",
@@ -1563,6 +1747,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15631747 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
15641748 DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"),
15651749 },
1750+ .driver_data = &tux_featureset_1_descriptor,
15661751 },
15671752 {
15681753 .ident = "TUXEDO InfinityBook Pro 14/16 Gen8 Intel/Commodore Omnia-Book Pro Gen 8",
@@ -1570,6 +1755,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15701755 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
15711756 DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"),
15721757 },
1758+ .driver_data = &empty_descriptor,
15731759 },
15741760 {
15751761 .ident = "TUXEDO InfinityBook Pro 14 Gen8 Intel/Commodore Omnia-Book Pro Gen 8",
@@ -1577,6 +1763,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15771763 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
15781764 DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"),
15791765 },
1766+ .driver_data = &tux_featureset_1_descriptor,
15801767 },
15811768 {
15821769 .ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel",
@@ -1584,6 +1771,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15841771 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
15851772 DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"),
15861773 },
1774+ .driver_data = &tux_featureset_1_descriptor,
15871775 },
15881776 {
15891777 .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD",
@@ -1591,6 +1779,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15911779 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
15921780 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"),
15931781 },
1782+ .driver_data = &empty_descriptor,
15941783 },
15951784 {
15961785 .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9",
@@ -1598,6 +1787,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
15981787 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
15991788 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"),
16001789 },
1790+ .driver_data = &empty_descriptor,
16011791 },
16021792 {
16031793 .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -1605,6 +1795,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16051795 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16061796 DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"),
16071797 },
1798+ .driver_data = &empty_descriptor,
16081799 },
16091800 {
16101801 .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -1612,6 +1803,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16121803 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16131804 DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"),
16141805 },
1806+ .driver_data = &empty_descriptor,
16151807 },
16161808 {
16171809 .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel",
@@ -1619,6 +1811,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16191811 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16201812 DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"),
16211813 },
1814+ .driver_data = &empty_descriptor,
16221815 },
16231816 {
16241817 .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD",
@@ -1626,6 +1819,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16261819 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16271820 DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"),
16281821 },
1822+ .driver_data = &empty_descriptor,
16291823 },
16301824 {
16311825 .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -1633,6 +1827,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16331827 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16341828 DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"),
16351829 },
1830+ .driver_data = &empty_descriptor,
16361831 },
16371832 {
16381833 .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -1640,6 +1835,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16401835 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16411836 DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"),
16421837 },
1838+ .driver_data = &empty_descriptor,
16431839 },
16441840 {
16451841 .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel",
@@ -1647,6 +1843,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16471843 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16481844 DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"),
16491845 },
1846+ .driver_data = &empty_descriptor,
16501847 },
16511848 {
16521849 .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel",
@@ -1654,6 +1851,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16541851 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16551852 DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"),
16561853 },
1854+ .driver_data = &empty_descriptor,
16571855 },
16581856 {
16591857 .ident = "TUXEDO Polaris 15 Gen1 AMD",
@@ -1661,6 +1859,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16611859 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16621860 DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"),
16631861 },
1862+ .driver_data = &empty_descriptor,
16641863 },
16651864 {
16661865 .ident = "TUXEDO Polaris 15 Gen1 AMD",
@@ -1668,6 +1867,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16681867 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16691868 DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"),
16701869 },
1870+ .driver_data = &empty_descriptor,
16711871 },
16721872 {
16731873 .ident = "TUXEDO Polaris 17 Gen1 AMD",
@@ -1675,6 +1875,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16751875 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16761876 DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"),
16771877 },
1878+ .driver_data = &empty_descriptor,
16781879 },
16791880 {
16801881 .ident = "TUXEDO Polaris 17 Gen1 AMD",
@@ -1682,6 +1883,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16821883 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16831884 DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"),
16841885 },
1886+ .driver_data = &empty_descriptor,
16851887 },
16861888 {
16871889 .ident = "TUXEDO Polaris 15 Gen1 Intel",
@@ -1689,6 +1891,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16891891 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16901892 DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"),
16911893 },
1894+ .driver_data = &empty_descriptor,
16921895 },
16931896 {
16941897 .ident = "TUXEDO Polaris 15 Gen1 Intel",
@@ -1696,6 +1899,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
16961899 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
16971900 DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"),
16981901 },
1902+ .driver_data = &empty_descriptor,
16991903 },
17001904 {
17011905 .ident = "TUXEDO Polaris 17 Gen1 Intel",
@@ -1703,6 +1907,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17031907 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17041908 DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"),
17051909 },
1910+ .driver_data = &empty_descriptor,
17061911 },
17071912 {
17081913 .ident = "TUXEDO Polaris 17 Gen1 Intel",
@@ -1710,6 +1915,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17101915 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17111916 DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"),
17121917 },
1918+ .driver_data = &empty_descriptor,
17131919 },
17141920 {
17151921 .ident = "TUXEDO Trinity 15 Intel Gen1",
@@ -1717,6 +1923,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17171923 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17181924 DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"),
17191925 },
1926+ .driver_data = &empty_descriptor,
17201927 },
17211928 {
17221929 .ident = "TUXEDO Trinity 17 Intel Gen1",
@@ -1724,6 +1931,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17241931 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17251932 DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"),
17261933 },
1934+ .driver_data = &empty_descriptor,
17271935 },
17281936 {
17291937 .ident = "TUXEDO Polaris 15/17 Gen2 AMD",
@@ -1731,6 +1939,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17311939 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17321940 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"),
17331941 },
1942+ .driver_data = &tux_featureset_1_descriptor,
17341943 },
17351944 {
17361945 .ident = "TUXEDO Polaris 15/17 Gen2 Intel",
@@ -1738,6 +1947,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17381947 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17391948 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"),
17401949 },
1950+ .driver_data = &tux_featureset_1_descriptor,
17411951 },
17421952 {
17431953 .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD",
@@ -1745,6 +1955,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17451955 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17461956 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"),
17471957 },
1958+ .driver_data = &tux_featureset_1_descriptor,
17481959 },
17491960 {
17501961 .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel",
@@ -1752,6 +1963,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17521963 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17531964 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"),
17541965 },
1966+ .driver_data = &tux_featureset_1_descriptor,
17551967 },
17561968 {
17571969 .ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD",
@@ -1759,6 +1971,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17591971 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17601972 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"),
17611973 },
1974+ .driver_data = &tux_featureset_1_descriptor,
17621975 },
17631976 {
17641977 .ident = "TUXEDO Stellaris 15 Gen4 Intel",
@@ -1766,6 +1979,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17661979 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17671980 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"),
17681981 },
1982+ .driver_data = &tux_featureset_1_descriptor,
17691983 },
17701984 {
17711985 .ident = "TUXEDO Polaris 15/17 Gen5 AMD",
@@ -1773,6 +1987,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17731987 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17741988 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"),
17751989 },
1990+ .driver_data = &tux_featureset_1_descriptor,
17761991 },
17771992 {
17781993 .ident = "TUXEDO Stellaris 16 Gen5 AMD",
@@ -1780,6 +1995,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17801995 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17811996 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"),
17821997 },
1998+ .driver_data = &tux_featureset_1_descriptor,
17831999 },
17842000 {
17852001 .ident = "TUXEDO Stellaris 16/17 Gen5 Intel/Commodore ORION Gen 5",
@@ -1787,6 +2003,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17872003 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17882004 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"),
17892005 },
2006+ .driver_data = &tux_featureset_1_descriptor,
17902007 },
17912008 {
17922009 .ident = "TUXEDO Stellaris Slim 15 Gen6 AMD",
@@ -1794,6 +2011,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
17942011 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
17952012 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"),
17962013 },
2014+ .driver_data = &tux_featureset_1_descriptor,
17972015 },
17982016 {
17992017 .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6",
@@ -1801,6 +2019,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
18012019 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
18022020 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"),
18032021 },
2022+ .driver_data = &tux_featureset_1_descriptor,
18042023 },
18052024 {
18062025 .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -1808,6 +2027,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
18082027 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
18092028 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"),
18102029 },
2030+ .driver_data = &tux_featureset_1_descriptor,
18112031 },
18122032 {
18132033 .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -1815,6 +2035,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
18152035 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
18162036 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"),
18172037 },
2038+ .driver_data = &tux_featureset_1_descriptor,
18182039 },
18192040 {
18202041 .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6",
@@ -1822,6 +2043,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
18222043 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
18232044 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"),
18242045 },
2046+ .driver_data = &tux_featureset_1_descriptor,
18252047 },
18262048 {
18272049 .ident = "TUXEDO Stellaris 16 Gen7 AMD",
@@ -1829,6 +2051,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
18292051 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
18302052 DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"),
18312053 },
2054+ .driver_data = &tux_featureset_1_descriptor,
18322055 },
18332056 {
18342057 .ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -1836,6 +2059,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
18362059 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
18372060 DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"),
18382061 },
2062+ .driver_data = &tux_featureset_1_descriptor,
18392063 },
18402064 {
18412065 .ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -1843,6 +2067,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
18432067 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
18442068 DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"),
18452069 },
2070+ .driver_data = &tux_featureset_1_descriptor,
18462071 },
18472072 {
18482073 .ident = "TUXEDO Book BA15 Gen10 AMD",
@@ -1850,6 +2075,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
18502075 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
18512076 DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"),
18522077 },
2078+ .driver_data = &empty_descriptor,
18532079 },
18542080 {
18552081 .ident = "TUXEDO Pulse 14 Gen1 AMD",
@@ -1857,6 +2083,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
18572083 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
18582084 DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"),
18592085 },
2086+ .driver_data = &empty_descriptor,
18602087 },
18612088 {
18622089 .ident = "TUXEDO Pulse 15 Gen1 AMD",
@@ -1864,6 +2091,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
18642091 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
18652092 DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"),
18662093 },
2094+ .driver_data = &empty_descriptor,
18672095 },
18682096 {
18692097 .ident = "TUXEDO Pulse 15 Gen2 AMD",
@@ -1871,6 +2099,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line
18712099 DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
18722100 DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"),
18732101 },
2102+ .driver_data = &empty_descriptor,
18742103 },
18752104 { }
18762105 };
@@ -1878,6 +2107,7 @@ MODULE_DEVICE_TABLE(dmi, uniwill_dmi_table); · 1 unmodified line
18782107
18792108 static int __init uniwill_init(void)
18802109 {
2110+ const struct uniwill_device_descriptor *descriptor;
18812111 const struct dmi_system_id *id;
18822112 int ret;
18832113
@@ -1887,10 +2117,22 @@ static int __init uniwill_init(void) · 3 unmodified lines
18872117 return -ENODEV;
18882118
18892119 /* Assume that the device supports all features */
1890− supported_features = UINT_MAX;
2120+ device_descriptor.features = UINT_MAX;
18912121 pr_warn("Loading on a potentially unsupported device\n");
18922122 } else {
1893− supported_features = (uintptr_t)id->driver_data;
2123+ /*
2124+ * Some devices might support additional features depending on
2125+ * the BIOS version/date, so we call this callback to let them
2126+ * modify their device descriptor accordingly.
2127+ */
2128+ if (id->callback) {
2129+ ret = id->callback(id);
2130+ if (ret < 0)
2131+ return ret;
2132+ }
2133+
2134+ descriptor = id->driver_data;
2135+ device_descriptor = *descriptor;
18942136 }
18952137
18962138 ret = platform_driver_register(&uniwill_driver);
drivers/platform/x86/wmi-bmof.c+15−19
@@ -8,7 +8,6 @@ · 7 unmodified lines
88
99 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1010
11−#include <linux/acpi.h>
1211 #include <linux/device.h>
1312 #include <linux/fs.h>
1413 #include <linux/kernel.h>
@@ -24,9 +23,9 @@ static ssize_t bmof_read(struct file *filp, struct kobject *kobj, const struct b · 9 unmodified lines
2423 char *buf, loff_t off, size_t count)
2524 {
2625 struct device *dev = kobj_to_dev(kobj);
27− union acpi_object *obj = dev_get_drvdata(dev);
26+ struct wmi_buffer *buffer = dev_get_drvdata(dev);
2827
29− return memory_read_from_buffer(buf, count, &off, obj->buffer.pointer, obj->buffer.length);
28+ return memory_read_from_buffer(buf, count, &off, buffer->data, buffer->length);
3029 }
3130
3231 static const BIN_ATTR_ADMIN_RO(bmof, 0);
@@ -39,9 +38,9 @@ static const struct bin_attribute * const bmof_attrs[] = { · 6 unmodified lines
3938 static size_t bmof_bin_size(struct kobject *kobj, const struct bin_attribute *attr, int n)
4039 {
4140 struct device *dev = kobj_to_dev(kobj);
42− union acpi_object *obj = dev_get_drvdata(dev);
41+ struct wmi_buffer *buffer = dev_get_drvdata(dev);
4342
44− return obj->buffer.length;
43+ return buffer->length;
4544 }
4645
4746 static const struct attribute_group bmof_group = {
@@ -56,30 +55,27 @@ static const struct attribute_group *bmof_groups[] = { · 8 unmodified lines
5655
5756 static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
5857 {
59− union acpi_object *obj;
58+ struct wmi_buffer *buffer;
59+ int ret;
6060
61− obj = wmidev_block_query(wdev, 0);
62− if (!obj) {
63− dev_err(&wdev->dev, "failed to read Binary MOF\n");
64− return -EIO;
65− }
61+ buffer = devm_kzalloc(&wdev->dev, sizeof(*buffer), GFP_KERNEL);
62+ if (!buffer)
63+ return -ENOMEM;
6664
67− if (obj->type != ACPI_TYPE_BUFFER) {
68− dev_err(&wdev->dev, "Binary MOF is not a buffer\n");
69− kfree(obj);
70− return -EIO;
71− }
65+ ret = wmidev_query_block(wdev, 0, buffer);
66+ if (ret < 0)
67+ return ret;
7268
73− dev_set_drvdata(&wdev->dev, obj);
69+ dev_set_drvdata(&wdev->dev, buffer);
7470
7571 return 0;
7672 }
7773
7874 static void wmi_bmof_remove(struct wmi_device *wdev)
7975 {
80− union acpi_object *obj = dev_get_drvdata(&wdev->dev);
76+ struct wmi_buffer *buffer = dev_get_drvdata(&wdev->dev);
8177
82− kfree(obj);
78+ kfree(buffer->data);
8379 }
8480
8581 static const struct wmi_device_id wmi_bmof_id_table[] = {
drivers/platform/x86/xiaomi-wmi.c+2−3
@@ -1,7 +1,6 @@
11 // SPDX-License-Identifier: GPL-2.0
22 /* WMI driver for Xiaomi Laptops */
33
4−#include <linux/acpi.h>
54 #include <linux/device.h>
65 #include <linux/input.h>
76 #include <linux/module.h>
@@ -56,7 +55,7 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context) · 48 unmodified lines
5655 return input_register_device(data->input_dev);
5756 }
5857
59−static void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy)
58+static void xiaomi_wmi_notify(struct wmi_device *wdev, const struct wmi_buffer *dummy)
6059 {
6160 struct xiaomi_wmi *data = dev_get_drvdata(&wdev->dev);
6261
@@ -85,7 +84,7 @@ static struct wmi_driver xiaomi_wmi_driver = { · 22 unmodified lines
8584 },
8685 .id_table = xiaomi_wmi_id_table,
8786 .probe = xiaomi_wmi_probe,
88− .notify = xiaomi_wmi_notify,
87+ .notify_new = xiaomi_wmi_notify,
8988 .no_singleton = true,
9089 };
9190 module_wmi_driver(xiaomi_wmi_driver);
include/linux/amd-pmf-io.h+21−0
@@ -61,5 +61,26 @@ enum laptop_placement { · 60 unmodified lines
6161 LP_UNDEFINED,
6262 };
6363
64+/**
65+ * struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver
66+ * @npuclk_freq: NPU clock frequency [MHz]
67+ * @npu_busy: NPU busy % [0-100]
68+ * @npu_power: NPU power [mW]
69+ * @mpnpuclk_freq: MPNPU [MHz]
70+ * @npu_reads: NPU read bandwidth [MB/sec]
71+ * @npu_writes: NPU write bandwidth [MB/sec]
72+ */
73+struct amd_pmf_npu_metrics {
74+ u16 npuclk_freq;
75+ u16 npu_busy[8];
76+ u16 npu_power;
77+ u16 mpnpuclk_freq;
78+ u16 npu_reads;
79+ u16 npu_writes;
80+};
81+
6482 int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op);
83+
84+/* AMD PMF and NPU interface */
85+int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info);
6586 #endif
include/linux/platform_data/x86/asus-wmi-leds-ids.h+0−50
@@ -1,50 +0,0 @@
1−/* SPDX-License-Identifier: GPL-2.0 */
2−#ifndef __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H
3−#define __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H
4−
5−#include <linux/dmi.h>
6−#include <linux/types.h>
7−
8−/* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */
9−#if IS_REACHABLE(CONFIG_ASUS_WMI) || IS_REACHABLE(CONFIG_HID_ASUS)
10−static const struct dmi_system_id asus_use_hid_led_dmi_ids[] = {
11− {
12− .matches = {
13− DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Zephyrus"),
14− },
15− },
16− {
17− .matches = {
18− DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Strix"),
19− },
20− },
21− {
22− .matches = {
23− DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Flow"),
24− },
25− },
26− {
27− .matches = {
28− DMI_MATCH(DMI_PRODUCT_FAMILY, "ProArt P16"),
29− },
30− },
31− {
32− .matches = {
33− DMI_MATCH(DMI_BOARD_NAME, "GA403U"),
34− },
35− },
36− {
37− .matches = {
38− DMI_MATCH(DMI_BOARD_NAME, "GU605M"),
39− },
40− },
41− {
42− .matches = {
43− DMI_MATCH(DMI_BOARD_NAME, "RC71L"),
44− },
45− },
46− { },
47−};
48−#endif
49−
50−#endif /* __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H */
include/linux/platform_data/x86/asus-wmi.h+28−0
@@ -173,12 +173,29 @@ enum asus_ally_mcu_hack { · 172 unmodified lines
173173 ASUS_WMI_ALLY_MCU_HACK_DISABLED,
174174 };
175175
176+/* Used to notify hid-asus when asus-wmi changes keyboard backlight */
177+struct asus_hid_listener {
178+ struct list_head list;
179+ void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
180+};
181+
182+enum asus_hid_event {
183+ ASUS_EV_BRTUP,
184+ ASUS_EV_BRTDOWN,
185+ ASUS_EV_BRTTOGGLE,
186+};
187+
188+#define ASUS_EV_MAX_BRIGHTNESS 3
189+
176190 #if IS_REACHABLE(CONFIG_ASUS_WMI)
177191 void set_ally_mcu_hack(enum asus_ally_mcu_hack status);
178192 void set_ally_mcu_powersave(bool enabled);
179193 int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval);
180194 int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval);
181195 int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
196+int asus_hid_register_listener(struct asus_hid_listener *cdev);
197+void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
198+int asus_hid_event(enum asus_hid_event event);
182199 #else
183200 static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status)
184201 {
@@ -199,6 +216,17 @@ static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, · 14 unmodified lines
199216 {
200217 return -ENODEV;
201218 }
219+static inline int asus_hid_register_listener(struct asus_hid_listener *bdev)
220+{
221+ return -ENODEV;
222+}
223+static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
224+{
225+}
226+static inline int asus_hid_event(enum asus_hid_event event)
227+{
228+ return -ENODEV;
229+}
202230 #endif
203231
204232 #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */
include/linux/psp.h+1−0
@@ -18,6 +18,7 @@ · 17 unmodified lines
1818 * and should include an appropriate local definition in their source file.
1919 */
2020 #define PSP_CMDRESP_STS GENMASK(15, 0)
21+#define PSP_TEE_STS_RING_BUSY 0x0000000d /* Ring already initialized */
2122 #define PSP_CMDRESP_CMD GENMASK(23, 16)
2223 #define PSP_CMDRESP_RESERVED GENMASK(29, 24)
2324 #define PSP_CMDRESP_RECOVERY BIT(30)
include/linux/wmi.h+43−2
@@ -8,9 +8,11 @@ · 7 unmodified lines
88 #ifndef _LINUX_WMI_H
99 #define _LINUX_WMI_H
1010
11+#include <linux/compiler_attributes.h>
1112 #include <linux/device.h>
1213 #include <linux/acpi.h>
1314 #include <linux/mod_devicetable.h>
15+#include <linux/types.h>
1416
1517 /**
1618 * struct wmi_device - WMI device structure
@@ -36,6 +38,42 @@ struct wmi_device { · 19 unmodified lines
3638 */
3739 #define to_wmi_device(device) container_of_const(device, struct wmi_device, dev)
3840
41+/**
42+ * struct wmi_buffer - WMI data buffer
43+ * @length: Buffer length in bytes
44+ * @data: Pointer to the buffer content
45+ *
46+ * This structure is used to exchange data with the WMI driver core.
47+ */
48+struct wmi_buffer {
49+ size_t length;
50+ void *data;
51+};
52+
53+/**
54+ * struct wmi_string - WMI string representation
55+ * @length: Size of @chars in bytes
56+ * @chars: UTF16-LE characters with optional nul termination and padding
57+ *
58+ * This structure is used when exchanging string data over the WMI interface.
59+ */
60+struct wmi_string {
61+ __le16 length;
62+ __le16 chars[];
63+} __packed;
64+
65+ssize_t wmi_string_to_utf8s(const struct wmi_string *str, u8 *dst, size_t length);
66+
67+ssize_t wmi_string_from_utf8s(struct wmi_string *str, size_t max_chars, const u8 *src,
68+ size_t src_length);
69+
70+int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id,
71+ const struct wmi_buffer *in, struct wmi_buffer *out);
72+
73+int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out);
74+
75+int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in);
76+
3977 acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id,
4078 const struct acpi_buffer *in, struct acpi_buffer *out);
4179
@@ -54,9 +92,11 @@ u8 wmidev_instance_count(struct wmi_device *wdev); · 12 unmodified lines
5492 * @probe: Callback for device binding
5593 * @remove: Callback for device unbinding
5694 * @shutdown: Callback for device shutdown
57− * @notify: Callback for receiving WMI events
95+ * @notify: Callback for receiving WMI events (deprecated)
96+ * @notify_new: Callback for receiving WMI events
5897 *
59− * This represents WMI drivers which handle WMI devices.
98+ * This represents WMI drivers which handle WMI devices. The data inside the buffer
99+ * passed to the @notify_new callback is guaranteed to be aligned on a 8-byte boundary.
60100 */
61101 struct wmi_driver {
62102 struct device_driver driver;
@@ -68,6 +108,7 @@ struct wmi_driver { · 5 unmodified lines
68108 void (*remove)(struct wmi_device *wdev);
69109 void (*shutdown)(struct wmi_device *wdev);
70110 void (*notify)(struct wmi_device *device, union acpi_object *data);
111+ void (*notify_new)(struct wmi_device *device, const struct wmi_buffer *data);
71112 };
72113
73114 /**
tools/power/x86/intel-speed-select/Makefile+7−1
@@ -13,7 +13,13 @@ endif · 12 unmodified lines
1313 # Do not use make's built-in rules
1414 # (this improves performance and avoids hard-to-debug behaviour);
1515 MAKEFLAGS += -r
16−override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include -I$(shell $(CC) -print-sysroot)/usr/include/libnl3
16+
17+NL3_CFLAGS = $(shell pkg-config --cflags libnl-3.0 2>/dev/null)
18+ifeq ($(NL3_CFLAGS),)
19+NL3_CFLAGS = -I/usr/include/libnl3
20+endif
21+
22+override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include $(NL3_CFLAGS)
1723 override LDFLAGS += -lnl-genl-3 -lnl-3
1824
1925 ALL_TARGETS := intel-speed-select
tools/power/x86/intel-speed-select/isst-config.c+40−3
@@ -16,7 +16,7 @@ struct process_cmd_struct { · 15 unmodified lines
1616 int arg;
1717 };
1818
19−static const char *version_str = "v1.24";
19+static const char *version_str = "v1.25";
2020
2121 static const int supported_api_ver = 3;
2222 static struct isst_if_platform_info isst_platform_info;
@@ -80,6 +80,18 @@ struct cpu_topology { · 57 unmodified lines
8080 short die_id;
8181 };
8282
83+static int read_only;
84+
85+static void check_privilege(void)
86+{
87+ if (!read_only)
88+ return;
89+
90+ isst_display_error_info_message(1, "Insufficient privileges", 0, 0);
91+ isst_ctdp_display_information_end(outf);
92+ exit(1);
93+}
94+
8395 FILE *get_output_file(void)
8496 {
8597 return outf;
@@ -950,9 +962,11 @@ int isolate_cpus(struct isst_id *id, int mask_size, cpu_set_t *cpu_mask, int lev · 864 unmodified lines
950962 ret = write(fd, "member", strlen("member"));
951963 if (ret == -1) {
952964 printf("Can't update to member\n");
965+ close(fd);
953966 return ret;
954967 }
955968
969+ close(fd);
956970 return 0;
957971 }
958972
@@ -1578,6 +1592,8 @@ free_mask: · 619 unmodified lines
15781592
15791593 static void set_tdp_level(int arg)
15801594 {
1595+ check_privilege();
1596+
15811597 if (cmd_help) {
15821598 fprintf(stderr, "Set Config TDP level\n");
15831599 fprintf(stderr,
@@ -2046,6 +2062,8 @@ static void set_pbf_enable(int arg) · 462 unmodified lines
20462062 {
20472063 int enable = arg;
20482064
2065+ check_privilege();
2066+
20492067 if (cmd_help) {
20502068 if (enable) {
20512069 fprintf(stderr,
@@ -2212,6 +2230,8 @@ static void set_fact_enable(int arg) · 160 unmodified lines
22122230 int i, ret, enable = arg;
22132231 struct isst_id id;
22142232
2233+ check_privilege();
2234+
22152235 if (cmd_help) {
22162236 if (enable) {
22172237 fprintf(stderr,
@@ -2361,6 +2381,8 @@ static void set_clos_enable(int arg) · 143 unmodified lines
23612381 {
23622382 int enable = arg;
23632383
2384+ check_privilege();
2385+
23642386 if (cmd_help) {
23652387 if (enable) {
23662388 fprintf(stderr,
@@ -2491,6 +2513,8 @@ static void set_clos_config_for_cpu(struct isst_id *id, void *arg1, void *arg2, · 124 unmodified lines
24912513
24922514 static void set_clos_config(int arg)
24932515 {
2516+ check_privilege();
2517+
24942518 if (cmd_help) {
24952519 fprintf(stderr,
24962520 "Set core-power configuration for one of the four clos ids\n");
@@ -2556,6 +2580,8 @@ static void set_clos_assoc_for_cpu(struct isst_id *id, void *arg1, void *arg2, v · 59 unmodified lines
25562580
25572581 static void set_clos_assoc(int arg)
25582582 {
2583+ check_privilege();
2584+
25592585 if (cmd_help) {
25602586 fprintf(stderr, "Associate a clos id to a CPU\n");
25612587 fprintf(stderr,
@@ -2637,6 +2663,8 @@ static void set_turbo_mode(int arg) · 75 unmodified lines
26372663 int i, disable = arg;
26382664 struct isst_id id;
26392665
2666+ check_privilege();
2667+
26402668 if (cmd_help) {
26412669 if (disable)
26422670 fprintf(stderr, "Set turbo mode disable\n");
@@ -2682,6 +2710,7 @@ static void get_set_trl(struct isst_id *id, void *arg1, void *arg2, void *arg3, · 39 unmodified lines
26822710 }
26832711
26842712 if (set) {
2713+ check_privilege();
26852714 ret = isst_set_trl(id, fact_trl);
26862715 isst_display_result(id, outf, "turbo-mode", "set-trl", ret);
26872716 return;
@@ -3204,8 +3233,16 @@ static void cmdline(int argc, char **argv) · 516 unmodified lines
32043233 };
32053234
32063235 if (geteuid() != 0) {
3207− fprintf(stderr, "Must run as root\n");
3208− exit(0);
3236+ int fd;
3237+
3238+ fd = open(pathname, O_RDWR);
3239+ if (fd < 0) {
3240+ fprintf(stderr, "Must run as root\n");
3241+ exit(0);
3242+ }
3243+ fprintf(stderr, "\nNot running as root, Only read only operations are supported\n");
3244+ close(fd);
3245+ read_only = 1;
32093246 }
32103247
32113248 ret = update_cpu_model();