summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-02-13 15:39:15 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2026-02-13 15:39:15 -0800
commitf50822fd8675c68d294e89bd102f7b487ca3acd3 (patch)
tree75d0db9313494a2ede74761d5ff712e3c547ff37
parent1b49e363252632d0493546511a41a65ed1a6fbbb (diff)
parent5a5203a45b063a594e89a2aeaf9e4923893a5b4c (diff)
Merge tag 'platform-drivers-x86-v7.0-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86HEADtorvalds/mastertorvalds/HEADmaster
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() ...
-rw-r--r--Documentation/admin-guide/laptops/thinkpad-acpi.rst37
-rw-r--r--Documentation/driver-api/wmi.rst3
-rw-r--r--Documentation/wmi/acpi-interface.rst68
-rw-r--r--Documentation/wmi/devices/lenovo-wmi-other.rst46
-rw-r--r--Documentation/wmi/driver-development-guide.rst76
-rw-r--r--drivers/crypto/ccp/psp-dev.c11
-rw-r--r--drivers/crypto/ccp/sp-dev.c12
-rw-r--r--drivers/crypto/ccp/sp-dev.h3
-rw-r--r--drivers/crypto/ccp/sp-pci.c16
-rw-r--r--drivers/crypto/ccp/tee-dev.c56
-rw-r--r--drivers/crypto/ccp/tee-dev.h1
-rw-r--r--drivers/hid/hid-asus.c221
-rw-r--r--drivers/platform/mellanox/mlx-platform.c484
-rw-r--r--drivers/platform/surface/surface_aggregator_registry.c18
-rw-r--r--drivers/platform/surface/surfacepro3_button.c11
-rw-r--r--drivers/platform/wmi/Kconfig3
-rw-r--r--drivers/platform/wmi/Makefile5
-rw-r--r--drivers/platform/wmi/core.c160
-rw-r--r--drivers/platform/wmi/internal.h17
-rw-r--r--drivers/platform/wmi/marshalling.c247
-rw-r--r--drivers/platform/wmi/string.c92
-rw-r--r--drivers/platform/wmi/tests/Kconfig27
-rw-r--r--drivers/platform/wmi/tests/Makefile11
-rw-r--r--drivers/platform/wmi/tests/marshalling_kunit.c452
-rw-r--r--drivers/platform/wmi/tests/string_kunit.c296
-rw-r--r--drivers/platform/x86/amd/pmf/acpi.c40
-rw-r--r--drivers/platform/x86/amd/pmf/core.c161
-rw-r--r--drivers/platform/x86/amd/pmf/pmf.h33
-rw-r--r--drivers/platform/x86/amd/pmf/spc.c33
-rw-r--r--drivers/platform/x86/amd/pmf/tee-if.c14
-rw-r--r--drivers/platform/x86/amd/wbrf.c25
-rw-r--r--drivers/platform/x86/asus-wmi.c223
-rw-r--r--drivers/platform/x86/hp/hp-wmi.c603
-rw-r--r--drivers/platform/x86/intel/pmc/core.c85
-rw-r--r--drivers/platform/x86/intel/pmc/core.h15
-rw-r--r--drivers/platform/x86/intel/pmt/class.c6
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c79
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c13
-rw-r--r--drivers/platform/x86/intel/wmi/sbl-fw-update.c43
-rw-r--r--drivers/platform/x86/intel/wmi/thunderbolt.c26
-rw-r--r--drivers/platform/x86/lenovo/Kconfig5
-rw-r--r--drivers/platform/x86/lenovo/Makefile2
-rw-r--r--drivers/platform/x86/lenovo/ideapad-laptop.c39
-rw-r--r--drivers/platform/x86/lenovo/thinkpad_acpi.c206
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata.c829
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata.h65
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata01.c302
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata01.h25
-rw-r--r--drivers/platform/x86/lenovo/wmi-helpers.c21
-rw-r--r--drivers/platform/x86/lenovo/wmi-other.c528
-rw-r--r--drivers/platform/x86/lenovo/yogabook.c12
-rw-r--r--drivers/platform/x86/uniwill/uniwill-acpi.c302
-rw-r--r--drivers/platform/x86/wmi-bmof.c34
-rw-r--r--drivers/platform/x86/xiaomi-wmi.c5
-rw-r--r--include/linux/amd-pmf-io.h21
-rw-r--r--include/linux/platform_data/x86/asus-wmi-leds-ids.h50
-rw-r--r--include/linux/platform_data/x86/asus-wmi.h28
-rw-r--r--include/linux/psp.h1
-rw-r--r--include/linux/wmi.h45
-rw-r--r--tools/power/x86/intel-speed-select/Makefile8
-rw-r--r--tools/power/x86/intel-speed-select/isst-config.c43
61 files changed, 5390 insertions, 953 deletions
diff --git a/Documentation/admin-guide/laptops/thinkpad-acpi.rst b/Documentation/admin-guide/laptops/thinkpad-acpi.rst
index 4ab0fef7d440..03951ed6b628 100644
--- a/Documentation/admin-guide/laptops/thinkpad-acpi.rst
+++ b/Documentation/admin-guide/laptops/thinkpad-acpi.rst
@@ -54,6 +54,7 @@ detailed description):
- Setting keyboard language
- WWAN Antenna type
- Auxmac
+ - Hardware damage detection capability
A compatibility table by model and feature is maintained on the web
site, http://ibm-acpi.sf.net/. I appreciate any success or failure
@@ -1576,6 +1577,42 @@ percentage level, above which charging will stop.
The exact semantics of the attributes may be found in
Documentation/ABI/testing/sysfs-class-power.
+Hardware damage detection capability
+------------------------------------
+
+sysfs attributes: hwdd_status, hwdd_detail
+
+Thinkpads are adding the ability to detect and report hardware damage.
+Add new sysfs interface to identify the damaged device status.
+Initial support is available for the USB-C replaceable connector.
+
+The command to check device damaged status is::
+
+ cat /sys/devices/platform/thinkpad_acpi/hwdd_status
+
+This value displays status of device damaged.
+
+- 0 = Not Damaged
+- 1 = Damaged
+
+The command to check location of damaged device is::
+
+ cat /sys/devices/platform/thinkpad_acpi/hwdd_detail
+
+This value displays location of damaged device having 1 line per damaged "item".
+For example:
+
+if no damage is detected:
+
+- No damage detected
+
+if damage detected:
+
+- TYPE-C: Base, Right side, Center port
+
+The property is read-only. If feature is not supported then sysfs
+attribute is not created.
+
Multiple Commands, Module Parameters
------------------------------------
diff --git a/Documentation/driver-api/wmi.rst b/Documentation/driver-api/wmi.rst
index db835b43c937..b847bcdcbb09 100644
--- a/Documentation/driver-api/wmi.rst
+++ b/Documentation/driver-api/wmi.rst
@@ -16,5 +16,8 @@ which will be bound to compatible WMI devices by the driver core.
.. kernel-doc:: include/linux/wmi.h
:internal:
+.. kernel-doc:: drivers/platform/wmi/string.c
+ :export:
+
.. kernel-doc:: drivers/platform/wmi/core.c
:export:
diff --git a/Documentation/wmi/acpi-interface.rst b/Documentation/wmi/acpi-interface.rst
index 1ef003b033bf..4657101c528a 100644
--- a/Documentation/wmi/acpi-interface.rst
+++ b/Documentation/wmi/acpi-interface.rst
@@ -104,3 +104,71 @@ holding the notification ID of the event. This method should be evaluated every
time an ACPI notification is received, since some ACPI implementations use a
queue to store WMI event data items. This queue will overflow after a couple
of WMI events are received without retrieving the associated WMI event data.
+
+Conversion rules for ACPI data types
+------------------------------------
+
+Consumers of the ACPI-WMI interface use binary buffers to exchange data with the WMI driver core,
+with the internal structure of the buffer being only know to the consumers. The WMI driver core is
+thus responsible for converting the data inside the buffer into an appropriate ACPI data type for
+consumption by the ACPI firmware. Additionally, any data returned by the various ACPI methods needs
+to be converted back into a binary buffer.
+
+The layout of said buffers is defined by the MOF description of the WMI method or data block in
+question [1]_:
+
+=============== ======================================================================= =========
+Data Type Layout Alignment
+=============== ======================================================================= =========
+``string`` Starts with an unsigned 16-bit little endian integer specifying 2 bytes
+ the length of the string data in bytes, followed by the string data
+ encoded as UTF-16LE with **optional** NULL termination and padding.
+ Keep in mind that some firmware implementations might depend on the
+ terminating NULL character to be present. Also the padding should
+ always be performed with NULL characters.
+``boolean`` Single byte where 0 means ``false`` and nonzero means ``true``. 1 byte
+``sint8`` Signed 8-bit integer. 1 byte
+``uint8`` Unsigned 8-bit integer. 1 byte
+``sint16`` Signed 16-bit little endian integer. 2 bytes
+``uint16`` Unsigned 16-bit little endian integer. 2 bytes
+``sint32`` Signed 32-bit little endian integer. 4 bytes
+``uint32`` Unsigned 32-bit little endian integer. 4 bytes
+``sint64`` Signed 64-bit little endian integer. 8 bytes
+``uint64`` Unsigned 64-bit little endian integer. 8 bytes
+``datetime`` A fixed-length 25-character UTF-16LE string with the format 2 bytes
+ *yyyymmddhhmmss.mmmmmmsutc* where *yyyy* is the 4-digit year, *mm* is
+ the 2-digit month, *dd* is the 2-digit day, *hh* is the 2-digit hour
+ based on a 24-hour clock, *mm* is the 2-digit minute, *ss* is the
+ 2-digit second, *mmmmmm* is the 6-digit microsecond, *s* is a plus or
+ minus character depending on whether *utc* is a positive or negative
+ offset from UTC (or a colon if the date is an interval). Unpopulated
+ fields should be filled with asterisks.
+=============== ======================================================================= =========
+
+Arrays should be aligned based on the alignment of their base type, while objects should be
+aligned based on the largest alignment of an element inside them.
+
+All buffers returned by the WMI driver core are 8-byte aligned. When converting ACPI data types
+into such buffers the following conversion rules apply:
+
+=============== ============================================================
+ACPI Data Type Converted into
+=============== ============================================================
+Buffer Copied as-is.
+Integer Converted into a ``uint32``.
+String Converted into a ``string`` with a terminating NULL character
+ to match the behavior the of the Windows driver.
+Package Each element inside the package is converted with alignment
+ of the resulting data types being respected. Nested packages
+ are not allowed.
+=============== ============================================================
+
+The Windows driver does attempt to handle nested packages, but this results in internal data
+structures (``_ACPI_METHOD_ARGUMENT_V1``) erroneously being copied into the resulting buffer.
+ACPI firmware implementations should thus not return nested packages from ACPI methods
+associated with the ACPI-WMI interface.
+
+References
+==========
+
+.. [1] https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items
diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
index d7928b8dfb4b..01d471156738 100644
--- a/Documentation/wmi/devices/lenovo-wmi-other.rst
+++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
@@ -31,13 +31,32 @@ under the following path:
/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
+Additionally, this driver also exports attributes to HWMON.
+
+LENOVO_CAPABILITY_DATA_00
+-------------------------
+
+WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E``
+
+The LENOVO_CAPABILITY_DATA_00 interface provides various information that
+does not rely on the gamezone thermal mode.
+
+The following HWMON attributes are implemented:
+ - fanX_div: internal RPM divisor
+ - fanX_input: current RPM
+ - fanX_target: target RPM (tunable, 0=auto)
+
+Due to the internal RPM divisor, the current/target RPMs are rounded down to
+its nearest multiple. The divisor itself is not necessary to be a power of two.
+
LENOVO_CAPABILITY_DATA_01
-------------------------
WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154``
-The LENOVO_CAPABILITY_DATA_01 interface provides information on various
-power limits of integrated CPU and GPU components.
+The LENOVO_CAPABILITY_DATA_01 interface provides various information that
+relies on the gamezone thermal mode, including power limits of integrated
+CPU and GPU components.
Each attribute has the following properties:
- current_value
@@ -48,11 +67,22 @@ Each attribute has the following properties:
- scalar_increment
- type
-The following attributes are implemented:
+The following firmware-attributes are implemented:
- ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
- ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
- ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
+LENOVO_FAN_TEST_DATA
+-------------------------
+
+WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21``
+
+The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of
+cooling fans.
+
+The following HWMON attributes are implemented:
+ - fanX_max: maximum RPM
+ - fanX_min: minimum RPM
WMI interface description
=========================
@@ -106,3 +136,13 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
[WmiDataId(3), read, Description("Data Size.")] uint32 DataSize;
[WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[];
};
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of Fan Test Data"), guid("{B642801B-3D21-45DE-90AE-6E86F164FB21}")]
+ class LENOVO_FAN_TEST_DATA {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+ [WmiDataId(1), read, Description("Mode.")] uint32 NumOfFans;
+ [WmiDataId(2), read, Description("Fan ID."), WmiSizeIs("NumOfFans")] uint32 FanId[];
+ [WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMaxSpeed[];
+ [WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[];
+ };
diff --git a/Documentation/wmi/driver-development-guide.rst b/Documentation/wmi/driver-development-guide.rst
index 5680303ae314..fbc2d9b12fe9 100644
--- a/Documentation/wmi/driver-development-guide.rst
+++ b/Documentation/wmi/driver-development-guide.rst
@@ -70,7 +70,7 @@ to matching WMI devices using a struct wmi_device_id table:
.probe = foo_probe,
.remove = foo_remove, /* optional, devres is preferred */
.shutdown = foo_shutdown, /* optional, called during shutdown */
- .notify = foo_notify, /* optional, for event handling */
+ .notify_new = foo_notify, /* optional, for event handling */
.no_notify_data = true, /* optional, enables events containing no additional data */
.no_singleton = true, /* required for new WMI drivers */
};
@@ -90,9 +90,9 @@ the WMI device and put it in a well-known state for the WMI driver to pick up la
or kexec. Most WMI drivers need no special shutdown handling and can thus omit this callback.
Please note that new WMI drivers are required to be able to be instantiated multiple times,
-and are forbidden from using any deprecated GUID-based WMI functions. This means that the
-WMI driver should be prepared for the scenario that multiple matching WMI devices are present
-on a given machine.
+and are forbidden from using any deprecated GUID-based or ACPI-based WMI functions. This means
+that the WMI driver should be prepared for the scenario that multiple matching WMI devices are
+present on a given machine.
Because of this, WMI drivers should use the state container design pattern as described in
Documentation/driver-api/driver-model/design-patterns.rst.
@@ -104,38 +104,37 @@ Documentation/driver-api/driver-model/design-patterns.rst.
WMI method drivers
------------------
-WMI drivers can call WMI device methods using wmidev_evaluate_method(), the
-structure of the ACPI buffer passed to this function is device-specific and usually
-needs some tinkering to get right. Looking at the ACPI tables containing the WMI
-device usually helps here. The method id and instance number passed to this function
-are also device-specific, looking at the decoded Binary MOF is usually enough to
-find the right values.
+WMI drivers can call WMI device methods using wmidev_invoke_method(). For each WMI method
+invocation the WMI driver needs to provide the instance number and the method ID, as well as
+a buffer with the method arguments and optionally a buffer for the results.
-The maximum instance number can be retrieved during runtime using wmidev_instance_count().
+The layout of said buffers is device-specific and described by the Binary MOF data associated
+with a given WMI device. Said Binary MOF data also describes the method ID of a given WMI method
+with the ``WmiMethodId`` qualifier. WMI devices exposing WMI methods usually expose only a single
+instance (instance number 0), but in theory can expose multiple instances as well. In such a case
+the number of instances can be retrieved using wmidev_instance_count().
-Take a look at drivers/platform/x86/inspur_platform_profile.c for an example WMI method driver.
+Take a look at drivers/platform/x86/intel/wmi/thunderbolt.c for an example WMI method driver.
WMI data block drivers
----------------------
-WMI drivers can query WMI device data blocks using wmidev_block_query(), the
-structure of the returned ACPI object is again device-specific. Some WMI devices
-also allow for setting data blocks using wmidev_block_set().
+WMI drivers can query WMI data blocks using wmidev_query_block(), the layout of the returned
+buffer is again device-specific and described by the Binary MOF data. Some WMI data blocks are
+also writeable and can be set using wmidev_set_block(). The number of data block instances can
+again be retrieved using wmidev_instance_count().
-The maximum instance number can also be retrieved using wmidev_instance_count().
-
-Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example
-WMI data block driver.
+Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example WMI data block driver.
WMI event drivers
-----------------
-WMI drivers can receive WMI events via the notify() callback inside the struct wmi_driver.
+WMI drivers can receive WMI events via the notify_new() callback inside the struct wmi_driver.
The WMI subsystem will then take care of setting up the WMI event accordingly. Please note that
-the structure of the ACPI object passed to this callback is device-specific, and freeing the
-ACPI object is being done by the WMI subsystem, not the driver.
+the layout of the buffer passed to this callback is device-specific, and freeing of the buffer
+is done by the WMI subsystem itself, not the driver.
-The WMI driver core will take care that the notify() callback will only be called after
+The WMI driver core will take care that the notify_new() callback will only be called after
the probe() callback has been called, and that no events are being received by the driver
right before and after calling its remove() or shutdown() callback.
@@ -147,6 +146,36 @@ the ``no_notify_data`` flag inside struct wmi_driver should be set to ``true``.
Take a look at drivers/platform/x86/xiaomi-wmi.c for an example WMI event driver.
+Exchanging data with the WMI driver core
+----------------------------------------
+
+WMI drivers can exchange data with the WMI driver core using struct wmi_buffer. The internal
+structure of those buffers is device-specific and only known by the WMI driver. Because of this
+the WMI driver itself is responsible for parsing and validating the data received from its
+WMI device.
+
+The structure of said buffers is described by the MOF data associated with the WMI device in
+question. When such a buffer contains multiple data items it usually makes sense to define a
+C structure and use it during parsing. Since the WMI driver core guarantees that all buffers
+received from a WMI device are aligned on an 8-byte boundary, WMI drivers can simply perform
+a cast between the WMI buffer data and this C structure.
+
+This however should only be done after the size of the buffer was verified to be large enough
+to hold the whole C structure. WMI drivers should reject undersized buffers as they are usually
+sent by the WMI device to signal an internal error. Oversized buffers however should be accepted
+to emulate the behavior of the Windows WMI implementation.
+
+When defining a C structure for parsing WMI buffers the alignment of the data items should be
+respected. This is especially important for 64-bit integers as those have different alignments
+on 64-bit (8-byte alignment) and 32-bit (4-byte alignment) architectures. It is thus a good idea
+to manually specify the alignment of such data items or mark the whole structure as packed when
+appropriate. Integer data items in general are little-endian integers and should be marked as
+such using ``__le64`` and friends. When parsing WMI string data items the struct wmi_string should
+be used as WMI strings have a different layout than C strings.
+
+See Documentation/wmi/acpi-interface.rst for more information regarding the binary format
+of WMI data items.
+
Handling multiple WMI devices at once
-------------------------------------
@@ -171,6 +200,7 @@ Things to avoid
When developing WMI drivers, there are a couple of things which should be avoided:
- usage of the deprecated GUID-based WMI interface which uses GUIDs instead of WMI device structs
+- usage of the deprecated ACPI-based WMI interface which uses ACPI objects instead of plain buffers
- bypassing of the WMI subsystem when talking to WMI devices
- WMI drivers which cannot be instantiated multiple times.
diff --git a/drivers/crypto/ccp/psp-dev.c b/drivers/crypto/ccp/psp-dev.c
index 9e21da0e298a..5c7f7e02a7d8 100644
--- a/drivers/crypto/ccp/psp-dev.c
+++ b/drivers/crypto/ccp/psp-dev.c
@@ -351,6 +351,17 @@ struct psp_device *psp_get_master_device(void)
return sp ? sp->psp_data : NULL;
}
+int psp_restore(struct sp_device *sp)
+{
+ struct psp_device *psp = sp->psp_data;
+ int ret = 0;
+
+ if (psp->tee_data)
+ ret = tee_restore(psp);
+
+ return ret;
+}
+
void psp_pci_init(void)
{
psp_master = psp_get_master_device();
diff --git a/drivers/crypto/ccp/sp-dev.c b/drivers/crypto/ccp/sp-dev.c
index 3467f6db4f50..f204aa5df96e 100644
--- a/drivers/crypto/ccp/sp-dev.c
+++ b/drivers/crypto/ccp/sp-dev.c
@@ -230,6 +230,18 @@ int sp_resume(struct sp_device *sp)
return 0;
}
+int sp_restore(struct sp_device *sp)
+{
+ if (sp->psp_data) {
+ int ret = psp_restore(sp);
+
+ if (ret)
+ return ret;
+ }
+
+ return sp_resume(sp);
+}
+
struct sp_device *sp_get_psp_master_device(void)
{
struct sp_device *i, *ret = NULL;
diff --git a/drivers/crypto/ccp/sp-dev.h b/drivers/crypto/ccp/sp-dev.h
index 1335a83fe052..a83751cfd006 100644
--- a/drivers/crypto/ccp/sp-dev.h
+++ b/drivers/crypto/ccp/sp-dev.h
@@ -141,6 +141,7 @@ void sp_destroy(struct sp_device *sp);
int sp_suspend(struct sp_device *sp);
int sp_resume(struct sp_device *sp);
+int sp_restore(struct sp_device *sp);
int sp_request_ccp_irq(struct sp_device *sp, irq_handler_t handler,
const char *name, void *data);
void sp_free_ccp_irq(struct sp_device *sp, void *data);
@@ -174,6 +175,7 @@ int psp_dev_init(struct sp_device *sp);
void psp_pci_init(void);
void psp_dev_destroy(struct sp_device *sp);
void psp_pci_exit(void);
+int psp_restore(struct sp_device *sp);
#else /* !CONFIG_CRYPTO_DEV_SP_PSP */
@@ -181,6 +183,7 @@ static inline int psp_dev_init(struct sp_device *sp) { return 0; }
static inline void psp_pci_init(void) { }
static inline void psp_dev_destroy(struct sp_device *sp) { }
static inline void psp_pci_exit(void) { }
+static inline int psp_restore(struct sp_device *sp) { return 0; }
#endif /* CONFIG_CRYPTO_DEV_SP_PSP */
diff --git a/drivers/crypto/ccp/sp-pci.c b/drivers/crypto/ccp/sp-pci.c
index 8891ceee1d7d..6ac805d99ccb 100644
--- a/drivers/crypto/ccp/sp-pci.c
+++ b/drivers/crypto/ccp/sp-pci.c
@@ -353,6 +353,13 @@ static int __maybe_unused sp_pci_resume(struct device *dev)
return sp_resume(sp);
}
+static int __maybe_unused sp_pci_restore(struct device *dev)
+{
+ struct sp_device *sp = dev_get_drvdata(dev);
+
+ return sp_restore(sp);
+}
+
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
static const struct sev_vdata sevv1 = {
.cmdresp_reg = 0x10580, /* C2PMSG_32 */
@@ -563,7 +570,14 @@ static const struct pci_device_id sp_pci_table[] = {
};
MODULE_DEVICE_TABLE(pci, sp_pci_table);
-static SIMPLE_DEV_PM_OPS(sp_pci_pm_ops, sp_pci_suspend, sp_pci_resume);
+static const struct dev_pm_ops sp_pci_pm_ops = {
+ .suspend = pm_sleep_ptr(sp_pci_suspend),
+ .resume = pm_sleep_ptr(sp_pci_resume),
+ .freeze = pm_sleep_ptr(sp_pci_suspend),
+ .thaw = pm_sleep_ptr(sp_pci_resume),
+ .poweroff = pm_sleep_ptr(sp_pci_suspend),
+ .restore_early = pm_sleep_ptr(sp_pci_restore),
+};
static struct pci_driver sp_pci_driver = {
.name = "ccp",
diff --git a/drivers/crypto/ccp/tee-dev.c b/drivers/crypto/ccp/tee-dev.c
index 5e1d80724678..92ffa412622a 100644
--- a/drivers/crypto/ccp/tee-dev.c
+++ b/drivers/crypto/ccp/tee-dev.c
@@ -86,10 +86,34 @@ static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd)
kfree(cmd);
}
+static bool tee_send_destroy_cmd(struct psp_tee_device *tee)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL,
+ TEE_DEFAULT_CMD_TIMEOUT, &reg);
+ if (ret) {
+ dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n");
+ psp_dead = true;
+ return false;
+ }
+
+ if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
+ dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n",
+ FIELD_GET(PSP_CMDRESP_STS, reg));
+ psp_dead = true;
+ return false;
+ }
+
+ return true;
+}
+
static int tee_init_ring(struct psp_tee_device *tee)
{
int ring_size = MAX_RING_BUFFER_ENTRIES * sizeof(struct tee_ring_cmd);
struct tee_init_ring_cmd *cmd;
+ bool retry = false;
unsigned int reg;
int ret;
@@ -112,6 +136,7 @@ static int tee_init_ring(struct psp_tee_device *tee)
/* Send command buffer details to Trusted OS by writing to
* CPU-PSP message registers
*/
+retry_init:
ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_INIT, cmd,
TEE_DEFAULT_CMD_TIMEOUT, &reg);
if (ret) {
@@ -122,9 +147,22 @@ static int tee_init_ring(struct psp_tee_device *tee)
}
if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
+ /*
+ * During the hibernate resume sequence driver may have gotten loaded
+ * but the ring not properly destroyed. If the ring doesn't work, try
+ * to destroy and re-init once.
+ */
+ if (!retry && FIELD_GET(PSP_CMDRESP_STS, reg) == PSP_TEE_STS_RING_BUSY) {
+ dev_info(tee->dev, "tee: ring init command failed with busy status, retrying\n");
+ if (tee_send_destroy_cmd(tee)) {
+ retry = true;
+ goto retry_init;
+ }
+ }
dev_err(tee->dev, "tee: ring init command failed (%#010lx)\n",
FIELD_GET(PSP_CMDRESP_STS, reg));
tee_free_ring(tee);
+ psp_dead = true;
ret = -EIO;
}
@@ -136,24 +174,13 @@ free_buf:
static void tee_destroy_ring(struct psp_tee_device *tee)
{
- unsigned int reg;
- int ret;
-
if (!tee->rb_mgr.ring_start)
return;
if (psp_dead)
goto free_ring;
- ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL,
- TEE_DEFAULT_CMD_TIMEOUT, &reg);
- if (ret) {
- dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n");
- psp_dead = true;
- } else if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
- dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n",
- FIELD_GET(PSP_CMDRESP_STS, reg));
- }
+ tee_send_destroy_cmd(tee);
free_ring:
tee_free_ring(tee);
@@ -365,3 +392,8 @@ int psp_check_tee_status(void)
return 0;
}
EXPORT_SYMBOL(psp_check_tee_status);
+
+int tee_restore(struct psp_device *psp)
+{
+ return tee_init_ring(psp->tee_data);
+}
diff --git a/drivers/crypto/ccp/tee-dev.h b/drivers/crypto/ccp/tee-dev.h
index ea9a2b7c05f5..c23416cb7bb3 100644
--- a/drivers/crypto/ccp/tee-dev.h
+++ b/drivers/crypto/ccp/tee-dev.h
@@ -111,5 +111,6 @@ struct tee_ring_cmd {
int tee_dev_init(struct psp_device *psp);
void tee_dev_destroy(struct psp_device *psp);
+int tee_restore(struct psp_device *psp);
#endif /* __TEE_DEV_H__ */
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index df7c03dde67f..8ffcd12038e8 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -28,7 +28,6 @@
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/platform_data/x86/asus-wmi.h>
-#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
#include <linux/input/mt.h>
#include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
#include <linux/power_supply.h>
@@ -50,7 +49,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define FEATURE_REPORT_ID 0x0d
#define INPUT_REPORT_ID 0x5d
#define FEATURE_KBD_REPORT_ID 0x5a
-#define FEATURE_KBD_REPORT_SIZE 16
+#define FEATURE_KBD_REPORT_SIZE 64
#define FEATURE_KBD_LED_REPORT_ID1 0x5d
#define FEATURE_KBD_LED_REPORT_ID2 0x5e
@@ -99,9 +98,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define QUIRK_T90CHI BIT(9)
#define QUIRK_MEDION_E1239T BIT(10)
#define QUIRK_ROG_NKEY_KEYBOARD BIT(11)
-#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
+#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
#define QUIRK_ROG_ALLY_XPAD BIT(13)
#define QUIRK_HID_FN_LOCK BIT(14)
+#define QUIRK_ROG_NKEY_ID1ID2_INIT BIT(15)
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
QUIRK_NO_INIT_REPORTS | \
@@ -113,7 +113,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define TRKID_SGN ((TRKID_MAX + 1) >> 1)
struct asus_kbd_leds {
- struct led_classdev cdev;
+ struct asus_hid_listener listener;
struct hid_device *hdev;
struct work_struct work;
unsigned int brightness;
@@ -138,7 +138,6 @@ struct asus_drvdata {
struct input_dev *tp_kbd_input;
struct asus_kbd_leds *kbd_backlight;
const struct asus_touchpad_info *tp;
- bool enable_backlight;
struct power_supply *battery;
struct power_supply_desc battery_desc;
int battery_capacity;
@@ -363,10 +362,21 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
usage->hid & HID_USAGE);
}
- if (drvdata->quirks & QUIRK_HID_FN_LOCK &&
- usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) {
- drvdata->fn_lock = !drvdata->fn_lock;
- schedule_work(&drvdata->fn_lock_sync_work);
+ if (usage->type == EV_KEY && value) {
+ switch (usage->code) {
+ case KEY_KBDILLUMUP:
+ return !asus_hid_event(ASUS_EV_BRTUP);
+ case KEY_KBDILLUMDOWN:
+ return !asus_hid_event(ASUS_EV_BRTDOWN);
+ case KEY_KBDILLUMTOGGLE:
+ return !asus_hid_event(ASUS_EV_BRTTOGGLE);
+ case KEY_FN_ESC:
+ if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
+ drvdata->fn_lock = !drvdata->fn_lock;
+ schedule_work(&drvdata->fn_lock_sync_work);
+ }
+ break;
+ }
}
return 0;
@@ -476,15 +486,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
{
+ /*
+ * The handshake is first sent as a set_report, then retrieved
+ * from a get_report. They should be equal.
+ */
const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
int ret;
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
- if (ret < 0)
- hid_err(hdev, "Asus failed to send init command: %d\n", ret);
+ if (ret < 0) {
+ hid_err(hdev, "Asus handshake %02x failed to send: %d\n",
+ report_id, ret);
+ return ret;
+ }
- return ret;
+ u8 *readbuf __free(kfree) = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
+ if (!readbuf)
+ return -ENOMEM;
+
+ ret = hid_hw_raw_request(hdev, report_id, readbuf,
+ FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
+ HID_REQ_GET_REPORT);
+ if (ret < 0) {
+ hid_warn(hdev, "Asus handshake %02x failed to receive ack: %d\n",
+ report_id, ret);
+ } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
+ hid_warn(hdev, "Asus handshake %02x returned invalid response: %*ph\n",
+ report_id, FEATURE_KBD_REPORT_SIZE, readbuf);
+ }
+
+ /*
+ * Do not return error if handshake is wrong until this is
+ * verified to work for all devices.
+ */
+ return 0;
}
static int asus_kbd_get_functions(struct hid_device *hdev,
@@ -505,7 +541,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
if (!readbuf)
return -ENOMEM;
- ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
+ ret = hid_hw_raw_request(hdev, report_id, readbuf,
FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
HID_REQ_GET_REPORT);
if (ret < 0) {
@@ -565,11 +601,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
spin_unlock_irqrestore(&led->lock, flags);
}
-static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
- enum led_brightness brightness)
+static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
+ int brightness)
{
- struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
- cdev);
+ struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
+ listener);
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
@@ -579,20 +615,6 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
asus_schedule_work(led);
}
-static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
-{
- struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
- cdev);
- enum led_brightness brightness;
- unsigned long flags;
-
- spin_lock_irqsave(&led->lock, flags);
- brightness = led->brightness;
- spin_unlock_irqrestore(&led->lock, flags);
-
- return brightness;
-}
-
static void asus_kbd_backlight_work(struct work_struct *work)
{
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)
hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
}
-/* WMI-based keyboard backlight LED control (via asus-wmi driver) takes
- * precedence. We only activate HID-based backlight control when the
- * WMI control is not available.
- */
-static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
-{
- struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
- u32 value;
- int ret;
-
- if (!IS_ENABLED(CONFIG_ASUS_WMI))
- return false;
-
- if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD &&
- dmi_check_system(asus_use_hid_led_dmi_ids)) {
- hid_info(hdev, "using HID for asus::kbd_backlight\n");
- return false;
- }
-
- ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS,
- ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value);
- hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value);
- if (ret)
- return false;
-
- return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT);
-}
-
/*
* We don't care about any other part of the string except the version section.
* Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01
@@ -736,48 +730,35 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
unsigned char kbd_func;
int ret;
- if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
- /* Initialize keyboard */
- ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
- if (ret < 0)
- return ret;
-
- /* The LED endpoint is initialised in two HID */
- ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
- if (ret < 0)
- return ret;
-
- ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
- if (ret < 0)
- return ret;
+ ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
+ if (ret < 0)
+ return ret;
- if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
- ret = asus_kbd_disable_oobe(hdev);
- if (ret < 0)
- return ret;
- }
+ /* Get keyboard functions */
+ ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
+ if (ret < 0)
+ return ret;
- if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
- intf = to_usb_interface(hdev->dev.parent);
- udev = interface_to_usbdev(intf);
- validate_mcu_fw_version(hdev,
- le16_to_cpu(udev->descriptor.idProduct));
- }
+ /* Check for backlight support */
+ if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
+ return -ENODEV;
- } else {
- /* Initialize keyboard */
- ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
- if (ret < 0)
- return ret;
+ if (drvdata->quirks & QUIRK_ROG_NKEY_ID1ID2_INIT) {
+ asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
+ asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
+ }
- /* Get keyboard functions */
- ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
+ if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
+ ret = asus_kbd_disable_oobe(hdev);
if (ret < 0)
return ret;
+ }
- /* Check for backlight support */
- if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
- return -ENODEV;
+ if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
+ intf = to_usb_interface(hdev->dev.parent);
+ udev = interface_to_usbdev(intf);
+ validate_mcu_fw_version(hdev,
+ le16_to_cpu(udev->descriptor.idProduct));
}
drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
@@ -789,14 +770,11 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
drvdata->kbd_backlight->removed = false;
drvdata->kbd_backlight->brightness = 0;
drvdata->kbd_backlight->hdev = hdev;
- drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight";
- drvdata->kbd_backlight->cdev.max_brightness = 3;
- drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
- drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
+ drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
spin_lock_init(&drvdata->kbd_backlight->lock);
- ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
+ ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
if (ret < 0) {
/* No need to have this still around */
devm_kfree(&hdev->dev, drvdata->kbd_backlight);
@@ -1021,11 +999,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
drvdata->input = input;
- if (drvdata->enable_backlight &&
- !asus_kbd_wmi_led_control_present(hdev) &&
- asus_kbd_register_leds(hdev))
- hid_warn(hdev, "Failed to initialize backlight.\n");
-
if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
drvdata->fn_lock = true;
INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock);
@@ -1104,15 +1077,6 @@ static int asus_input_mapping(struct hid_device *hdev,
return -1;
}
- /*
- * Check and enable backlight only on devices with UsagePage ==
- * 0xff31 to avoid initializing the keyboard firmware multiple
- * times on devices with multiple HID descriptors but same
- * PID/VID.
- */
- if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
- drvdata->enable_backlight = true;
-
set_bit(EV_REP, hi->input->evbit);
return 1;
}
@@ -1205,7 +1169,7 @@ static int __maybe_unused asus_resume(struct hid_device *hdev) {
if (drvdata->kbd_backlight) {
const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4,
- drvdata->kbd_backlight->cdev.brightness };
+ drvdata->kbd_backlight->brightness };
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
if (ret < 0) {
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)
static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
- int ret;
+ struct hid_report_enum *rep_enum;
struct asus_drvdata *drvdata;
+ struct hid_report *rep;
+ bool is_vendor = false;
+ int ret;
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
if (drvdata == NULL) {
@@ -1314,12 +1281,30 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
}
+ /* Check for vendor for RGB init and handle generic devices properly. */
+ rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
+ list_for_each_entry(rep, &rep_enum->report_list, list) {
+ if ((rep->application & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR)
+ is_vendor = true;
+ }
+
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "Asus hw start failed: %d\n", ret);
return ret;
}
+ if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
+ asus_kbd_register_leds(hdev))
+ hid_warn(hdev, "Failed to initialize backlight.\n");
+
+ /*
+ * For ROG keyboards, skip rename for consistency and ->input check as
+ * some devices do not have inputs.
+ */
+ if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
+ return 0;
+
/*
* Check that input registration succeeded. Checking that
* HID_CLAIMED_INPUT is set prevents a UAF when all input devices
@@ -1356,6 +1341,8 @@ static void asus_remove(struct hid_device *hdev)
unsigned long flags;
if (drvdata->kbd_backlight) {
+ asus_hid_unregister_listener(&drvdata->kbd_backlight->listener);
+
spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
drvdata->kbd_backlight->removed = true;
spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
@@ -1490,10 +1477,10 @@ static const struct hid_device_id asus_devices[] = {
QUIRK_USE_KBD_BACKLIGHT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
- QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
- QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK },
+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK | QUIRK_ROG_NKEY_ID1ID2_INIT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c
index efd0c074ad93..efcba68d3aa5 100644
--- a/drivers/platform/mellanox/mlx-platform.c
+++ b/drivers/platform/mellanox/mlx-platform.c
@@ -6,6 +6,8 @@
* Copyright (C) 2016-2018 Vadim Pasternak <vadimp@mellanox.com>
*/
+#include <linux/array_size.h>
+#include <linux/bits.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/i2c.h>
@@ -727,6 +729,16 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = {
},
};
+/* Platform hotplug dgx data */
+static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pdb_items_data[] = {
+ {
+ .label = "pdb1",
+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
+ .mask = BIT(0),
+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE,
+ },
+};
+
static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_items_data[] = {
{
.label = "pwr1",
@@ -776,6 +788,15 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_ng800_items_data[] =
},
};
+static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pwr_items_data[] = {
+ {
+ .label = "pwr1",
+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
+ .mask = BIT(0),
+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE,
+ },
+};
+
static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_items_data[] = {
{
.label = "fan1",
@@ -1399,6 +1420,45 @@ static struct mlxreg_core_item mlxplat_mlxcpld_ext_items[] = {
}
};
+static struct mlxreg_core_item mlxplat_mlxcpld_ext_dgx_items[] = {
+ {
+ .data = mlxplat_mlxcpld_dgx_pdb_items_data,
+ .aggr_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF,
+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
+ .mask = MLXPLAT_CPLD_PSU_MASK,
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pdb_items_data),
+ .inversed = 1,
+ .health = false,
+ },
+ {
+ .data = mlxplat_mlxcpld_dgx_pwr_items_data,
+ .aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF,
+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
+ .mask = MLXPLAT_CPLD_PWR_MASK,
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pwr_items_data),
+ .inversed = 0,
+ .health = false,
+ },
+ {
+ .data = mlxplat_mlxcpld_default_ng_fan_items_data,
+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF,
+ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET,
+ .mask = MLXPLAT_CPLD_FAN_NG_MASK,
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_fan_items_data),
+ .inversed = 1,
+ .health = false,
+ },
+ {
+ .data = mlxplat_mlxcpld_default_asic_items_data,
+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF,
+ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET,
+ .mask = MLXPLAT_CPLD_ASIC_MASK,
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_asic_items_data),
+ .inversed = 0,
+ .health = true,
+ },
+};
+
static struct mlxreg_core_item mlxplat_mlxcpld_ng800_items[] = {
{
.data = mlxplat_mlxcpld_default_ng_psu_items_data,
@@ -1451,6 +1511,16 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ext_data = {
};
static
+struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_dgx_ext_data = {
+ .items = mlxplat_mlxcpld_ext_dgx_items,
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_dgx_items),
+ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET,
+ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX,
+ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET,
+ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2,
+};
+
+static
struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_data = {
.items = mlxplat_mlxcpld_ng800_items,
.count = ARRAY_SIZE(mlxplat_mlxcpld_ng800_items),
@@ -4625,6 +4695,359 @@ static struct mlxreg_core_platform_data mlxplat_default_ng_regs_io_data = {
.counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_regs_io_data),
};
+/* Platform register access for next generation systems families data */
+static struct mlxreg_core_data mlxplat_mlxcpld_dgx_ng_regs_io_data[] = {
+ {
+ .label = "cpld1_version",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld2_version",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld3_version",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld4_version",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld1_pn",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET,
+ .bit = GENMASK(15, 0),
+ .mode = 0444,
+ .regnum = 2,
+ },
+ {
+ .label = "cpld2_pn",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET,
+ .bit = GENMASK(15, 0),
+ .mode = 0444,
+ .regnum = 2,
+ },
+ {
+ .label = "cpld3_pn",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET,
+ .bit = GENMASK(15, 0),
+ .mode = 0444,
+ .regnum = 2,
+ },
+ {
+ .label = "cpld4_pn",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET,
+ .bit = GENMASK(15, 0),
+ .mode = 0444,
+ .regnum = 2,
+ },
+ {
+ .label = "cpld1_version_min",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld2_version_min",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld3_version_min",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld4_version_min",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "asic_reset",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0200,
+ },
+ {
+ .label = "reset_long_pb",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(0),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_short_pb",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(1),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_aux_pwr_or_ref",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(2),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_swb_dc_dc_pwr_fail",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_from_asic",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_swb_wd",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(6),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_asic_thermal",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(7),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_sw_reset",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(0),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_comex_pwr_fail",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_platform",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(4),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_soc",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_comex_wd",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(6),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_system",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(1),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_sw_pwr_off",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(2),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_comex_thermal",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_reload_bios",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_pdb_pwr_fail",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(6),
+ .mode = 0444,
+ },
+ {
+ .label = "pdb_reset_stby",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(0),
+ .mode = 0200,
+ },
+ {
+ .label = "pwr_cycle",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(2),
+ .mode = 0200,
+ },
+ {
+ .label = "pwr_down",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0200,
+ },
+ {
+ .label = "deep_pwr_cycle",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0200,
+ },
+ {
+ .label = "latch_reset",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(6),
+ .mode = 0200,
+ },
+ {
+ .label = "jtag_cap",
+ .reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET,
+ .mask = MLXPLAT_CPLD_FU_CAP_MASK,
+ .bit = 1,
+ .mode = 0444,
+ },
+ {
+ .label = "jtag_enable",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(4),
+ .mode = 0644,
+ },
+ {
+ .label = "dbg1",
+ .reg = MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0644,
+ },
+ {
+ .label = "dbg2",
+ .reg = MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0644,
+ },
+ {
+ .label = "dbg3",
+ .reg = MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0644,
+ },
+ {
+ .label = "dbg4",
+ .reg = MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0644,
+ },
+ {
+ .label = "asic_health",
+ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET,
+ .mask = MLXPLAT_CPLD_ASIC_MASK,
+ .bit = 1,
+ .mode = 0444,
+ },
+ {
+ .label = "fan_dir",
+ .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "bios_safe_mode",
+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(4),
+ .mode = 0444,
+ },
+ {
+ .label = "bios_active_image",
+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0444,
+ },
+ {
+ .label = "bios_auth_fail",
+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(6),
+ .mode = 0444,
+ },
+ {
+ .label = "bios_upgrade_fail",
+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(7),
+ .mode = 0444,
+ },
+ {
+ .label = "voltreg_update_status",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET,
+ .mask = MLXPLAT_CPLD_VOLTREG_UPD_MASK,
+ .bit = 5,
+ .mode = 0444,
+ },
+ {
+ .label = "pwr_converter_prog_en",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(0),
+ .mode = 0644,
+ .secured = 1,
+ },
+ {
+ .label = "vpd_wp",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0644,
+ },
+ {
+ .label = "pcie_asic_reset_dis",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(4),
+ .mode = 0644,
+ },
+ {
+ .label = "shutdown_unlock",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0644,
+ },
+ {
+ .label = "config1",
+ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "config2",
+ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "config3",
+ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "ufm_version",
+ .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+};
+
+static struct mlxreg_core_platform_data mlxplat_dgx_ng_regs_io_data = {
+ .data = mlxplat_mlxcpld_dgx_ng_regs_io_data,
+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_dgx_ng_regs_io_data),
+};
+
/* Platform register access for modular systems families data */
static struct mlxreg_core_data mlxplat_mlxcpld_modular_regs_io_data[] = {
{
@@ -7239,6 +7662,32 @@ static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi)
return mlxplat_register_platform_device();
}
+static int __init mlxplat_dmi_ng400_dgx_matched(const struct dmi_system_id *dmi)
+{
+ int i;
+
+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM;
+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data);
+ mlxplat_mux_data = mlxplat_default_mux_data;
+ for (i = 0; i < mlxplat_mux_num; i++) {
+ mlxplat_mux_data[i].values = mlxplat_msn21xx_channels;
+ mlxplat_mux_data[i].n_values =
+ ARRAY_SIZE(mlxplat_msn21xx_channels);
+ }
+ mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data;
+ mlxplat_hotplug->deferred_nr =
+ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
+ mlxplat_led = &mlxplat_default_ng_led_data;
+ mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data;
+ mlxplat_fan = &mlxplat_default_fan_data;
+ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++)
+ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i];
+ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
+ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
+
+ return mlxplat_register_platform_device();
+}
+
static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi)
{
int i;
@@ -7323,6 +7772,27 @@ static int __init mlxplat_dmi_ng800_matched(const struct dmi_system_id *dmi)
return mlxplat_register_platform_device();
}
+static int __init mlxplat_dmi_ng800_dgx_matched(const struct dmi_system_id *dmi)
+{
+ int i;
+
+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM;
+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_ng800_mux_data);
+ mlxplat_mux_data = mlxplat_ng800_mux_data;
+ mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data;
+ mlxplat_hotplug->deferred_nr =
+ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
+ mlxplat_led = &mlxplat_default_ng_led_data;
+ mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data;
+ mlxplat_fan = &mlxplat_default_fan_data;
+ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++)
+ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i];
+ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
+ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
+
+ return mlxplat_register_platform_device();
+}
+
static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi)
{
int i;
@@ -7459,6 +7929,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = {
},
},
{
+ .callback = mlxplat_dmi_ng400_dgx_matched,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0010"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI173"),
+ },
+ },
+ {
.callback = mlxplat_dmi_ng400_matched,
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "VMOD0010"),
@@ -7471,6 +7948,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = {
},
},
{
+ .callback = mlxplat_dmi_ng800_dgx_matched,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0013"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI174"),
+ },
+ },
+ {
.callback = mlxplat_dmi_ng800_matched,
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "VMOD0013"),
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
index 78ac3a8fbb73..0599d5adf02e 100644
--- a/drivers/platform/surface/surface_aggregator_registry.c
+++ b/drivers/platform/surface/surface_aggregator_registry.c
@@ -406,6 +406,22 @@ static const struct software_node *ssam_node_group_sp9_5g[] = {
NULL,
};
+/* Devices for Surface Pro 11 (ARM/QCOM) */
+static const struct software_node *ssam_node_group_sp11[] = {
+ &ssam_node_root,
+ &ssam_node_hub_kip,
+ &ssam_node_bat_ac,
+ &ssam_node_bat_main,
+ &ssam_node_tmp_sensors,
+ &ssam_node_hid_kip_keyboard,
+ &ssam_node_hid_kip_penstash,
+ &ssam_node_hid_kip_touchpad,
+ &ssam_node_hid_kip_fwupd,
+ &ssam_node_hid_sam_sensors,
+ &ssam_node_kip_tablet_switch,
+ NULL,
+};
+
/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
static const struct acpi_device_id ssam_platform_hub_acpi_match[] = {
@@ -482,6 +498,8 @@ MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_acpi_match);
static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
/* Surface Pro 9 5G (ARM/QCOM) */
{ .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g },
+ /* Surface Pro 11 (ARM/QCOM) */
+ { .compatible = "microsoft,denali", (void *)ssam_node_group_sp11 },
/* Surface Laptop 7 */
{ .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
{ .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },
diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c
index 2755601f979c..e652c85c9161 100644
--- a/drivers/platform/surface/surfacepro3_button.c
+++ b/drivers/platform/surface/surfacepro3_button.c
@@ -10,6 +10,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
+#include <linux/string.h>
#include <linux/types.h>
#include <linux/input.h>
#include <linux/acpi.h>
@@ -189,7 +190,6 @@ static int surface_button_add(struct acpi_device *device)
struct surface_button *button;
struct input_dev *input;
const char *hid = acpi_device_hid(device);
- char *name;
int error;
if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME,
@@ -210,11 +210,10 @@ static int surface_button_add(struct acpi_device *device)
goto err_free_button;
}
- name = acpi_device_name(device);
- strcpy(name, SURFACE_BUTTON_DEVICE_NAME);
+ strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME);
snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid);
- input->name = name;
+ input->name = acpi_device_name(device);
input->phys = button->phys;
input->id.bustype = BUS_HOST;
input->dev.parent = &device->dev;
@@ -228,8 +227,8 @@ static int surface_button_add(struct acpi_device *device)
goto err_free_input;
device_init_wakeup(&device->dev, true);
- dev_info(&device->dev,
- "%s [%s]\n", name, acpi_device_bid(device));
+ dev_info(&device->dev, "%s [%s]\n", acpi_device_name(device),
+ acpi_device_bid(device));
return 0;
err_free_input:
diff --git a/drivers/platform/wmi/Kconfig b/drivers/platform/wmi/Kconfig
index 77fcbb18746b..d62f51ff3b7f 100644
--- a/drivers/platform/wmi/Kconfig
+++ b/drivers/platform/wmi/Kconfig
@@ -6,6 +6,7 @@
menuconfig ACPI_WMI
tristate "ACPI-WMI support"
depends on ACPI && X86
+ select NLS
help
This option enables support for the ACPI-WMI driver core.
@@ -31,4 +32,6 @@ config ACPI_WMI_LEGACY_DEVICE_NAMES
userspace applications but will cause the registration of WMI devices with
the same GUID to fail in some corner cases.
+source "drivers/platform/wmi/tests/Kconfig"
+
endif # ACPI_WMI
diff --git a/drivers/platform/wmi/Makefile b/drivers/platform/wmi/Makefile
index 98393d7391ec..2feff94a5594 100644
--- a/drivers/platform/wmi/Makefile
+++ b/drivers/platform/wmi/Makefile
@@ -4,5 +4,8 @@
# ACPI WMI core
#
-wmi-y := core.o
+wmi-y := core.o marshalling.o string.o
obj-$(CONFIG_ACPI_WMI) += wmi.o
+
+# Unit tests
+obj-y += tests/
diff --git a/drivers/platform/wmi/core.c b/drivers/platform/wmi/core.c
index 6878c4fcb0b5..1601bf9fe135 100644
--- a/drivers/platform/wmi/core.c
+++ b/drivers/platform/wmi/core.c
@@ -23,6 +23,7 @@
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/limits.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/rwsem.h>
@@ -33,6 +34,8 @@
#include <linux/wmi.h>
#include <linux/fs.h>
+#include "internal.h"
+
MODULE_AUTHOR("Carlos Corbacho");
MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
MODULE_LICENSE("GPL");
@@ -302,7 +305,7 @@ acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method
EXPORT_SYMBOL_GPL(wmi_evaluate_method);
/**
- * wmidev_evaluate_method - Evaluate a WMI method
+ * wmidev_evaluate_method - Evaluate a WMI method (deprecated)
* @wdev: A wmi bus device from a driver
* @instance: Instance index
* @method_id: Method ID to call
@@ -360,6 +363,70 @@ acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 met
}
EXPORT_SYMBOL_GPL(wmidev_evaluate_method);
+/**
+ * wmidev_invoke_method - Invoke a WMI method
+ * @wdev: A wmi bus device from a driver
+ * @instance: Instance index
+ * @method_id: Method ID to call
+ * @in: Mandatory WMI buffer containing input for the method call
+ * @out: Optional WMI buffer to return the method results
+ *
+ * Invoke a WMI method, the caller must free the resulting data inside @out.
+ * Said data is guaranteed to be aligned on a 8-byte boundary.
+ *
+ * Return: 0 on success or negative error code on failure.
+ */
+int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id,
+ const struct wmi_buffer *in, struct wmi_buffer *out)
+{
+ struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
+ struct acpi_buffer aout = { ACPI_ALLOCATE_BUFFER, NULL };
+ struct acpi_buffer ain;
+ union acpi_object *obj;
+ acpi_status status;
+ int ret;
+
+ if (wblock->gblock.flags & ACPI_WMI_STRING) {
+ ret = wmi_marshal_string(in, &ain);
+ if (ret < 0)
+ return ret;
+ } else {
+ if (in->length > U32_MAX)
+ return -E2BIG;
+
+ ain.length = in->length;
+ ain.pointer = in->data;
+ }
+
+ if (out)
+ status = wmidev_evaluate_method(wdev, instance, method_id, &ain, &aout);
+ else
+ status = wmidev_evaluate_method(wdev, instance, method_id, &ain, NULL);
+
+ if (wblock->gblock.flags & ACPI_WMI_STRING)
+ kfree(ain.pointer);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ if (!out)
+ return 0;
+
+ obj = aout.pointer;
+ if (!obj) {
+ out->length = 0;
+ out->data = ZERO_SIZE_PTR;
+
+ return 0;
+ }
+
+ ret = wmi_unmarshal_acpi_object(obj, out);
+ kfree(obj);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wmidev_invoke_method);
+
static acpi_status __query_block(struct wmi_block *wblock, u8 instance,
struct acpi_buffer *out)
{
@@ -432,7 +499,7 @@ acpi_status wmi_query_block(const char *guid_string, u8 instance,
EXPORT_SYMBOL_GPL(wmi_query_block);
/**
- * wmidev_block_query - Return contents of a WMI block
+ * wmidev_block_query - Return contents of a WMI block (deprectated)
* @wdev: A wmi bus device from a driver
* @instance: Instance index
*
@@ -453,6 +520,33 @@ union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance)
EXPORT_SYMBOL_GPL(wmidev_block_query);
/**
+ * wmidev_query_block - Return contents of a WMI data block
+ * @wdev: A wmi bus device from a driver
+ * @instance: Instance index
+ * @out: WMI buffer to fill
+ *
+ * Query a WMI data block, the caller must free the resulting data inside @out.
+ * Said data is guaranteed to be aligned on a 8-byte boundary.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out)
+{
+ union acpi_object *obj;
+ int ret;
+
+ obj = wmidev_block_query(wdev, instance);
+ if (!obj)
+ return -EIO;
+
+ ret = wmi_unmarshal_acpi_object(obj, out);
+ kfree(obj);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wmidev_query_block);
+
+/**
* wmi_set_block - Write to a WMI block (deprecated)
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
* @instance: Instance index
@@ -486,7 +580,7 @@ acpi_status wmi_set_block(const char *guid_string, u8 instance, const struct acp
EXPORT_SYMBOL_GPL(wmi_set_block);
/**
- * wmidev_block_set - Write to a WMI block
+ * wmidev_block_set - Write to a WMI block (deprecated)
* @wdev: A wmi bus device from a driver
* @instance: Instance index
* @in: Buffer containing new values for the data block
@@ -536,6 +630,46 @@ acpi_status wmidev_block_set(struct wmi_device *wdev, u8 instance, const struct
EXPORT_SYMBOL_GPL(wmidev_block_set);
/**
+ * wmidev_set_block - Write to a WMI data block
+ * @wdev: A wmi bus device from a driver
+ * @instance: Instance index
+ * @in: WMI buffer containing new values for the data block
+ *
+ * Write the content of @in into a WMI data block.
+ *
+ * Return: 0 on success or negative error code on failure.
+ */
+int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in)
+{
+ struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
+ struct acpi_buffer buffer;
+ acpi_status status;
+ int ret;
+
+ if (wblock->gblock.flags & ACPI_WMI_STRING) {
+ ret = wmi_marshal_string(in, &buffer);
+ if (ret < 0)
+ return ret;
+ } else {
+ if (in->length > U32_MAX)
+ return -E2BIG;
+
+ buffer.length = in->length;
+ buffer.pointer = in->data;
+ }
+
+ status = wmidev_block_set(wdev, instance, &buffer);
+ if (wblock->gblock.flags & ACPI_WMI_STRING)
+ kfree(buffer.pointer);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wmidev_set_block);
+
+/**
* wmi_install_notify_handler - Register handler for WMI events (deprecated)
* @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
* @handler: Function to handle notifications
@@ -862,7 +996,7 @@ static int wmi_dev_probe(struct device *dev)
return -ENODEV;
}
- if (wdriver->notify) {
+ if (wdriver->notify || wdriver->notify_new) {
if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data)
return -ENODEV;
}
@@ -1221,6 +1355,8 @@ static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj
static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj)
{
struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver);
+ struct wmi_buffer buffer;
+ int ret;
if (!obj && !driver->no_notify_data) {
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)
if (driver->notify)
driver->notify(&wblock->dev, obj);
+
+ if (driver->notify_new) {
+ if (!obj) {
+ driver->notify_new(&wblock->dev, NULL);
+ return;
+ }
+
+ ret = wmi_unmarshal_acpi_object(obj, &buffer);
+ if (ret < 0) {
+ dev_warn(&wblock->dev.dev, "Failed to unmarshal event data: %d\n", ret);
+ return;
+ }
+
+ driver->notify_new(&wblock->dev, &buffer);
+ kfree(buffer.data);
+ }
}
static int wmi_notify_device(struct device *dev, void *data)
diff --git a/drivers/platform/wmi/internal.h b/drivers/platform/wmi/internal.h
new file mode 100644
index 000000000000..9a39ffa31ad1
--- /dev/null
+++ b/drivers/platform/wmi/internal.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Internal interfaces used by the WMI core.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#ifndef _WMI_INTERNAL_H_
+#define _WMI_INTERNAL_H_
+
+union acpi_object;
+struct wmi_buffer;
+
+int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer);
+int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out);
+
+#endif /* _WMI_INTERNAL_H_ */
diff --git a/drivers/platform/wmi/marshalling.c b/drivers/platform/wmi/marshalling.c
new file mode 100644
index 000000000000..63a92c4ebab5
--- /dev/null
+++ b/drivers/platform/wmi/marshalling.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ACPI-WMI buffer marshalling.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#include <linux/acpi.h>
+#include <linux/align.h>
+#include <linux/math.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+#include <linux/wmi.h>
+
+#include <kunit/visibility.h>
+
+#include "internal.h"
+
+static int wmi_adjust_buffer_length(size_t *length, const union acpi_object *obj)
+{
+ size_t alignment, size;
+
+ switch (obj->type) {
+ case ACPI_TYPE_INTEGER:
+ /*
+ * Integers are threated as 32 bit even if the ACPI DSDT
+ * declares 64 bit integer width.
+ */
+ alignment = 4;
+ size = sizeof(u32);
+ break;
+ case ACPI_TYPE_STRING:
+ /*
+ * Strings begin with a single little-endian 16-bit field containing
+ * the string length in bytes and are encoded as UTF-16LE with a terminating
+ * nul character.
+ */
+ if (obj->string.length + 1 > U16_MAX / 2)
+ return -EOVERFLOW;
+
+ alignment = 2;
+ size = struct_size_t(struct wmi_string, chars, obj->string.length + 1);
+ break;
+ case ACPI_TYPE_BUFFER:
+ /*
+ * Buffers are copied as-is.
+ */
+ alignment = 1;
+ size = obj->buffer.length;
+ break;
+ default:
+ return -EPROTO;
+ }
+
+ *length = size_add(ALIGN(*length, alignment), size);
+
+ return 0;
+}
+
+static int wmi_obj_get_buffer_length(const union acpi_object *obj, size_t *length)
+{
+ size_t total = 0;
+ int ret;
+
+ if (obj->type == ACPI_TYPE_PACKAGE) {
+ for (int i = 0; i < obj->package.count; i++) {
+ ret = wmi_adjust_buffer_length(&total, &obj->package.elements[i]);
+ if (ret < 0)
+ return ret;
+ }
+ } else {
+ ret = wmi_adjust_buffer_length(&total, obj);
+ if (ret < 0)
+ return ret;
+ }
+
+ *length = total;
+
+ return 0;
+}
+
+static int wmi_obj_transform_simple(const union acpi_object *obj, u8 *buffer, size_t *consumed)
+{
+ struct wmi_string *string;
+ size_t length;
+ __le32 value;
+ u8 *aligned;
+
+ switch (obj->type) {
+ case ACPI_TYPE_INTEGER:
+ aligned = PTR_ALIGN(buffer, 4);
+ length = sizeof(value);
+
+ value = cpu_to_le32(obj->integer.value);
+ memcpy(aligned, &value, length);
+ break;
+ case ACPI_TYPE_STRING:
+ aligned = PTR_ALIGN(buffer, 2);
+ string = (struct wmi_string *)aligned;
+ length = struct_size(string, chars, obj->string.length + 1);
+
+ /* We do not have to worry about unaligned accesses here as the WMI
+ * string will already be aligned on a two-byte boundary.
+ */
+ string->length = cpu_to_le16((obj->string.length + 1) * 2);
+ for (int i = 0; i < obj->string.length; i++)
+ string->chars[i] = cpu_to_le16(obj->string.pointer[i]);
+
+ /*
+ * The Windows WMI-ACPI driver always emits a terminating nul character,
+ * so we emulate this behavior here as well.
+ */
+ string->chars[obj->string.length] = '\0';
+ break;
+ case ACPI_TYPE_BUFFER:
+ aligned = buffer;
+ length = obj->buffer.length;
+
+ memcpy(aligned, obj->buffer.pointer, length);
+ break;
+ default:
+ return -EPROTO;
+ }
+
+ *consumed = (aligned - buffer) + length;
+
+ return 0;
+}
+
+static int wmi_obj_transform(const union acpi_object *obj, u8 *buffer)
+{
+ size_t consumed;
+ int ret;
+
+ if (obj->type == ACPI_TYPE_PACKAGE) {
+ for (int i = 0; i < obj->package.count; i++) {
+ ret = wmi_obj_transform_simple(&obj->package.elements[i], buffer,
+ &consumed);
+ if (ret < 0)
+ return ret;
+
+ buffer += consumed;
+ }
+ } else {
+ ret = wmi_obj_transform_simple(obj, buffer, &consumed);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer)
+{
+ size_t length, alloc_length;
+ u8 *data;
+ int ret;
+
+ ret = wmi_obj_get_buffer_length(obj, &length);
+ if (ret < 0)
+ return ret;
+
+ if (ARCH_KMALLOC_MINALIGN < 8) {
+ /*
+ * kmalloc() guarantees that the alignment of the resulting memory allocation is at
+ * least the largest power-of-two divisor of the allocation size. The WMI buffer
+ * data needs to be aligned on a 8 byte boundary to properly support 64-bit WMI
+ * integers, so we have to round the allocation size to the next multiple of 8.
+ */
+ alloc_length = round_up(length, 8);
+ } else {
+ alloc_length = length;
+ }
+
+ data = kzalloc(alloc_length, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ret = wmi_obj_transform(obj, data);
+ if (ret < 0) {
+ kfree(data);
+ return ret;
+ }
+
+ buffer->length = length;
+ buffer->data = data;
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(wmi_unmarshal_acpi_object);
+
+int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out)
+{
+ const struct wmi_string *string;
+ u16 length, value;
+ size_t chars;
+ char *str;
+
+ if (buffer->length < sizeof(*string))
+ return -ENODATA;
+
+ string = buffer->data;
+ length = get_unaligned_le16(&string->length);
+ if (buffer->length < sizeof(*string) + length)
+ return -ENODATA;
+
+ /* Each character needs to be 16 bits long */
+ if (length % 2)
+ return -EINVAL;
+
+ chars = length / 2;
+ str = kmalloc(chars + 1, GFP_KERNEL);
+ if (!str)
+ return -ENOMEM;
+
+ for (int i = 0; i < chars; i++) {
+ value = get_unaligned_le16(&string->chars[i]);
+
+ /* ACPI only accepts ASCII strings */
+ if (value > 0x7F) {
+ kfree(str);
+ return -EINVAL;
+ }
+
+ str[i] = value & 0xFF;
+
+ /*
+ * ACPI strings should only contain a single nul character at the end.
+ * Because of this we must not copy any padding from the WMI string.
+ */
+ if (!value) {
+ /* ACPICA wants the length of the string without the nul character */
+ out->length = i;
+ out->pointer = str;
+ return 0;
+ }
+ }
+
+ str[chars] = '\0';
+
+ out->length = chars;
+ out->pointer = str;
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(wmi_marshal_string);
diff --git a/drivers/platform/wmi/string.c b/drivers/platform/wmi/string.c
new file mode 100644
index 000000000000..0fc43218aa5b
--- /dev/null
+++ b/drivers/platform/wmi/string.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * WMI string utility functions.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#include <linux/build_bug.h>
+#include <linux/compiler_types.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/nls.h>
+#include <linux/limits.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include <asm/byteorder.h>
+
+static_assert(sizeof(__le16) == sizeof(wchar_t));
+
+/**
+ * wmi_string_to_utf8s - Convert a WMI string into a UTF8 string.
+ * @str: WMI string representation
+ * @dst: Buffer to fill with UTF8 characters
+ * @length: Length of the destination buffer
+ *
+ * Convert as WMI string into a standard UTF8 string. The conversion will stop
+ * once a NUL character is detected or when the buffer is full. Any invalid UTF16
+ * characters will be ignored. The resulting UTF8 string will always be NUL-terminated
+ * when this function returns successfully.
+ *
+ * Return: Length of the resulting UTF8 string or negative errno code on failure.
+ */
+ssize_t wmi_string_to_utf8s(const struct wmi_string *str, u8 *dst, size_t length)
+{
+ /* Contains the maximum number of UTF16 code points to read */
+ int inlen = le16_to_cpu(str->length) / 2;
+ int ret;
+
+ if (length < 1)
+ return -EINVAL;
+
+ /* We must leave room for the NUL character at the end of the destination buffer */
+ ret = utf16s_to_utf8s((__force const wchar_t *)str->chars, inlen, UTF16_LITTLE_ENDIAN, dst,
+ length - 1);
+ if (ret < 0)
+ return ret;
+
+ dst[ret] = '\0';
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wmi_string_to_utf8s);
+
+/**
+ * wmi_string_from_utf8s - Convert a UTF8 string into a WMI string.
+ * @str: WMI string representation
+ * @max_chars: Maximum number of UTF16 code points to store inside the WMI string
+ * @src: UTF8 string to convert
+ * @src_length: Length of the source string without any trailing NUL-characters
+ *
+ * Convert a UTF8 string into a WMI string. The conversion will stop when the WMI string is
+ * full. The resulting WMI string will always be NUL-terminated and have its length field set
+ * to and appropriate value when this function returns successfully.
+ *
+ * Return: Number of UTF16 code points inside the WMI string or negative errno code on failure.
+ */
+ssize_t wmi_string_from_utf8s(struct wmi_string *str, size_t max_chars, const u8 *src,
+ size_t src_length)
+{
+ size_t str_length;
+ int ret;
+
+ if (max_chars < 1)
+ return -EINVAL;
+
+ /* We must leave room for the NUL character at the end of the WMI string */
+ ret = utf8s_to_utf16s(src, src_length, UTF16_LITTLE_ENDIAN, (__force wchar_t *)str->chars,
+ max_chars - 1);
+ if (ret < 0)
+ return ret;
+
+ str_length = (ret + 1) * sizeof(u16);
+ if (str_length > U16_MAX)
+ return -EOVERFLOW;
+
+ str->length = cpu_to_le16(str_length);
+ str->chars[ret] = '\0';
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wmi_string_from_utf8s);
diff --git a/drivers/platform/wmi/tests/Kconfig b/drivers/platform/wmi/tests/Kconfig
new file mode 100644
index 000000000000..f7f0f3c540f5
--- /dev/null
+++ b/drivers/platform/wmi/tests/Kconfig
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# ACPI WMI KUnit tests
+#
+
+config ACPI_WMI_MARSHALLING_KUNIT_TEST
+ tristate "KUnit Test for ACPI-WMI marshalling" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds unit tests for the ACPI-WMI marshalling code.
+
+ For more information on KUnit and unit tests in general, please refer
+ to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+ If unsure, say N.
+
+config ACPI_WMI_STRING_KUNIT_TEST
+ tristate "KUnit Test for ACPI-WMI string conversion" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds unit tests for the ACPI-WMI string conversion code.
+ For more information on KUnit and unit tests in general, please refer
+ to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+ If unsure, say N.
diff --git a/drivers/platform/wmi/tests/Makefile b/drivers/platform/wmi/tests/Makefile
new file mode 100644
index 000000000000..62c438e26259
--- /dev/null
+++ b/drivers/platform/wmi/tests/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for linux/drivers/platform/x86/wmi/tests
+# ACPI WMI KUnit tests
+#
+
+wmi_marshalling_kunit-y := marshalling_kunit.o
+obj-$(CONFIG_ACPI_WMI_MARSHALLING_KUNIT_TEST) += wmi_marshalling_kunit.o
+
+wmi_string_kunit-y := string_kunit.o
+obj-$(CONFIG_ACPI_WMI_STRING_KUNIT_TEST) += wmi_string_kunit.o
diff --git a/drivers/platform/wmi/tests/marshalling_kunit.c b/drivers/platform/wmi/tests/marshalling_kunit.c
new file mode 100644
index 000000000000..0c7cd8774aa3
--- /dev/null
+++ b/drivers/platform/wmi/tests/marshalling_kunit.c
@@ -0,0 +1,452 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit test for the ACPI-WMI marshalling code.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#include <linux/acpi.h>
+#include <linux/align.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include <kunit/resource.h>
+#include <kunit/test.h>
+
+#include "../internal.h"
+
+struct wmi_acpi_param {
+ const char *name;
+ const union acpi_object obj;
+ const struct wmi_buffer buffer;
+};
+
+struct wmi_string_param {
+ const char *name;
+ const char *string;
+ const struct wmi_buffer buffer;
+};
+
+struct wmi_invalid_acpi_param {
+ const char *name;
+ const union acpi_object obj;
+};
+
+struct wmi_invalid_string_param {
+ const char *name;
+ const struct wmi_buffer buffer;
+};
+
+/* 0xdeadbeef */
+static u8 expected_single_integer[] = {
+ 0xef, 0xbe, 0xad, 0xde,
+};
+
+/* "TEST" */
+static u8 expected_single_string[] = {
+ 0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00,
+};
+
+static u8 test_buffer[] = {
+ 0xab, 0xcd,
+};
+
+static u8 expected_single_buffer[] = {
+ 0xab, 0xcd,
+};
+
+static union acpi_object simple_package_elements[] = {
+ {
+ .buffer = {
+ .type = ACPI_TYPE_BUFFER,
+ .length = sizeof(test_buffer),
+ .pointer = test_buffer,
+ },
+ },
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = 0x01020304,
+ },
+ },
+};
+
+static u8 expected_simple_package[] = {
+ 0xab, 0xcd,
+ 0x00, 0x00,
+ 0x04, 0x03, 0x02, 0x01,
+};
+
+static u8 test_small_buffer[] = {
+ 0xde,
+};
+
+static union acpi_object complex_package_elements[] = {
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = 0xdeadbeef,
+ },
+ },
+ {
+ .buffer = {
+ .type = ACPI_TYPE_BUFFER,
+ .length = sizeof(test_small_buffer),
+ .pointer = test_small_buffer,
+ },
+ },
+ {
+ .string = {
+ .type = ACPI_TYPE_STRING,
+ .length = sizeof("TEST") - 1,
+ .pointer = "TEST",
+ },
+ },
+ {
+ .buffer = {
+ .type = ACPI_TYPE_BUFFER,
+ .length = sizeof(test_small_buffer),
+ .pointer = test_small_buffer,
+ },
+ },
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = 0x01020304,
+ },
+ }
+};
+
+static u8 expected_complex_package[] = {
+ 0xef, 0xbe, 0xad, 0xde,
+ 0xde,
+ 0x00,
+ 0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0xde,
+ 0x00,
+ 0x04, 0x03, 0x02, 0x01,
+};
+
+static const struct wmi_acpi_param wmi_acpi_params_array[] = {
+ {
+ .name = "single_integer",
+ .obj = {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = 0xdeadbeef,
+ },
+ },
+ .buffer = {
+ .data = expected_single_integer,
+ .length = sizeof(expected_single_integer),
+ },
+ },
+ {
+ .name = "single_string",
+ .obj = {
+ .string = {
+ .type = ACPI_TYPE_STRING,
+ .length = sizeof("TEST") - 1,
+ .pointer = "TEST",
+ },
+ },
+ .buffer = {
+ .data = expected_single_string,
+ .length = sizeof(expected_single_string),
+ },
+ },
+ {
+ .name = "single_buffer",
+ .obj = {
+ .buffer = {
+ .type = ACPI_TYPE_BUFFER,
+ .length = sizeof(test_buffer),
+ .pointer = test_buffer,
+ },
+ },
+ .buffer = {
+ .data = expected_single_buffer,
+ .length = sizeof(expected_single_buffer),
+ },
+ },
+ {
+ .name = "simple_package",
+ .obj = {
+ .package = {
+ .type = ACPI_TYPE_PACKAGE,
+ .count = ARRAY_SIZE(simple_package_elements),
+ .elements = simple_package_elements,
+ },
+ },
+ .buffer = {
+ .data = expected_simple_package,
+ .length = sizeof(expected_simple_package),
+ },
+ },
+ {
+ .name = "complex_package",
+ .obj = {
+ .package = {
+ .type = ACPI_TYPE_PACKAGE,
+ .count = ARRAY_SIZE(complex_package_elements),
+ .elements = complex_package_elements,
+ },
+ },
+ .buffer = {
+ .data = expected_complex_package,
+ .length = sizeof(expected_complex_package),
+ },
+ },
+};
+
+static void wmi_acpi_param_get_desc(const struct wmi_acpi_param *param, char *desc)
+{
+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object, wmi_acpi_params_array, wmi_acpi_param_get_desc);
+
+/* "WMI\0" */
+static u8 padded_wmi_string[] = {
+ 0x0a, 0x00,
+ 0x57, 0x00,
+ 0x4D, 0x00,
+ 0x49, 0x00,
+ 0x00, 0x00,
+ 0x00, 0x00,
+};
+
+static const struct wmi_string_param wmi_string_params_array[] = {
+ {
+ .name = "test",
+ .string = "TEST",
+ .buffer = {
+ .length = sizeof(expected_single_string),
+ .data = expected_single_string,
+ },
+ },
+ {
+ .name = "padded",
+ .string = "WMI",
+ .buffer = {
+ .length = sizeof(padded_wmi_string),
+ .data = padded_wmi_string,
+ },
+ },
+};
+
+static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc)
+{
+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(wmi_marshal_string, wmi_string_params_array, wmi_string_param_get_desc);
+
+static union acpi_object nested_package_elements[] = {
+ {
+ .package = {
+ .type = ACPI_TYPE_PACKAGE,
+ .count = ARRAY_SIZE(simple_package_elements),
+ .elements = simple_package_elements,
+ },
+ }
+};
+
+static const struct wmi_invalid_acpi_param wmi_invalid_acpi_params_array[] = {
+ {
+ .name = "nested_package",
+ .obj = {
+ .package = {
+ .type = ACPI_TYPE_PACKAGE,
+ .count = ARRAY_SIZE(nested_package_elements),
+ .elements = nested_package_elements,
+ },
+ },
+ },
+ {
+ .name = "reference",
+ .obj = {
+ .reference = {
+ .type = ACPI_TYPE_LOCAL_REFERENCE,
+ .actual_type = ACPI_TYPE_ANY,
+ .handle = NULL,
+ },
+ },
+ },
+ {
+ .name = "processor",
+ .obj = {
+ .processor = {
+ .type = ACPI_TYPE_PROCESSOR,
+ .proc_id = 0,
+ .pblk_address = 0,
+ .pblk_length = 0,
+ },
+ },
+ },
+ {
+ .name = "power_resource",
+ .obj = {
+ .power_resource = {
+ .type = ACPI_TYPE_POWER,
+ .system_level = 0,
+ .resource_order = 0,
+ },
+ },
+ },
+};
+
+static void wmi_invalid_acpi_param_get_desc(const struct wmi_invalid_acpi_param *param, char *desc)
+{
+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object_failure, wmi_invalid_acpi_params_array,
+ wmi_invalid_acpi_param_get_desc);
+
+static u8 oversized_wmi_string[] = {
+ 0x04, 0x00, 0x00, 0x00,
+};
+
+/*
+ * The error is that 3 bytes can not hold UTF-16 characters
+ * without cutting of the last one.
+ */
+static u8 undersized_wmi_string[] = {
+ 0x03, 0x00, 0x00, 0x00, 0x00,
+};
+
+static u8 non_ascii_wmi_string[] = {
+ 0x04, 0x00, 0xC4, 0x00, 0x00, 0x00,
+};
+
+static const struct wmi_invalid_string_param wmi_invalid_string_params_array[] = {
+ {
+ .name = "empty_buffer",
+ .buffer = {
+ .length = 0,
+ .data = ZERO_SIZE_PTR,
+ },
+
+ },
+ {
+ .name = "oversized",
+ .buffer = {
+ .length = sizeof(oversized_wmi_string),
+ .data = oversized_wmi_string,
+ },
+ },
+ {
+ .name = "undersized",
+ .buffer = {
+ .length = sizeof(undersized_wmi_string),
+ .data = undersized_wmi_string,
+ },
+ },
+ {
+ .name = "non_ascii",
+ .buffer = {
+ .length = sizeof(non_ascii_wmi_string),
+ .data = non_ascii_wmi_string,
+ },
+ },
+};
+
+static void wmi_invalid_string_param_get_desc(const struct wmi_invalid_string_param *param,
+ char *desc)
+{
+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(wmi_marshal_string_failure, wmi_invalid_string_params_array,
+ wmi_invalid_string_param_get_desc);
+
+KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *);
+
+static void wmi_unmarshal_acpi_object_test(struct kunit *test)
+{
+ const struct wmi_acpi_param *param = test->param_value;
+ struct wmi_buffer result;
+ int ret;
+
+ ret = wmi_unmarshal_acpi_object(&param->obj, &result);
+ if (ret < 0)
+ KUNIT_FAIL_AND_ABORT(test, "Unmarshalling of ACPI object failed\n");
+
+ kunit_add_action(test, kfree_wrapper, result.data);
+
+ KUNIT_EXPECT_TRUE(test, IS_ALIGNED((uintptr_t)result.data, 8));
+ KUNIT_EXPECT_EQ(test, result.length, param->buffer.length);
+ KUNIT_EXPECT_MEMEQ(test, result.data, param->buffer.data, result.length);
+}
+
+static void wmi_unmarshal_acpi_object_failure_test(struct kunit *test)
+{
+ const struct wmi_invalid_acpi_param *param = test->param_value;
+ struct wmi_buffer result;
+ int ret;
+
+ ret = wmi_unmarshal_acpi_object(&param->obj, &result);
+ if (ret < 0)
+ return;
+
+ kfree(result.data);
+ KUNIT_FAIL(test, "Invalid ACPI object was not rejected\n");
+}
+
+static void wmi_marshal_string_test(struct kunit *test)
+{
+ const struct wmi_string_param *param = test->param_value;
+ struct acpi_buffer result;
+ int ret;
+
+ ret = wmi_marshal_string(&param->buffer, &result);
+ if (ret < 0)
+ KUNIT_FAIL_AND_ABORT(test, "Marshalling of WMI string failed\n");
+
+ kunit_add_action(test, kfree_wrapper, result.pointer);
+
+ KUNIT_EXPECT_EQ(test, result.length, strlen(param->string));
+ KUNIT_EXPECT_STREQ(test, result.pointer, param->string);
+}
+
+static void wmi_marshal_string_failure_test(struct kunit *test)
+{
+ const struct wmi_invalid_string_param *param = test->param_value;
+ struct acpi_buffer result;
+ int ret;
+
+ ret = wmi_marshal_string(&param->buffer, &result);
+ if (ret < 0)
+ return;
+
+ kfree(result.pointer);
+ KUNIT_FAIL(test, "Invalid string was not rejected\n");
+}
+
+static struct kunit_case wmi_marshalling_test_cases[] = {
+ KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_test,
+ wmi_unmarshal_acpi_object_gen_params),
+ KUNIT_CASE_PARAM(wmi_marshal_string_test,
+ wmi_marshal_string_gen_params),
+ KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_failure_test,
+ wmi_unmarshal_acpi_object_failure_gen_params),
+ KUNIT_CASE_PARAM(wmi_marshal_string_failure_test,
+ wmi_marshal_string_failure_gen_params),
+ {}
+};
+
+static struct kunit_suite wmi_marshalling_test_suite = {
+ .name = "wmi_marshalling",
+ .test_cases = wmi_marshalling_test_cases,
+};
+
+kunit_test_suite(wmi_marshalling_test_suite);
+
+MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
+MODULE_DESCRIPTION("KUnit test for the ACPI-WMI marshalling code");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/wmi/tests/string_kunit.c b/drivers/platform/wmi/tests/string_kunit.c
new file mode 100644
index 000000000000..117f32ee26a8
--- /dev/null
+++ b/drivers/platform/wmi/tests/string_kunit.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit test for the ACPI-WMI string conversion code.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/wmi.h>
+
+#include <kunit/resource.h>
+#include <kunit/test.h>
+
+#include <asm/byteorder.h>
+
+struct wmi_string_param {
+ const char *name;
+ const struct wmi_string *wmi_string;
+ /*
+ * Remember that using sizeof() on a struct wmi_string will
+ * always return a size of two bytes due to the flexible
+ * array member!
+ */
+ size_t wmi_string_length;
+ const u8 *utf8_string;
+ size_t utf8_string_length;
+};
+
+#define TEST_WMI_STRING_LENGTH 12
+
+static const struct wmi_string test_wmi_string = {
+ .length = cpu_to_le16(10),
+ .chars = {
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'E'),
+ cpu_to_le16(u'S'),
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+static const u8 test_utf8_string[] = "TEST";
+
+#define SPECIAL_WMI_STRING_LENGTH 14
+
+static const struct wmi_string special_wmi_string = {
+ .length = cpu_to_le16(12),
+ .chars = {
+ cpu_to_le16(u'Ä'),
+ cpu_to_le16(u'Ö'),
+ cpu_to_le16(u'Ü'),
+ cpu_to_le16(u'ß'),
+ cpu_to_le16(u'€'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+static const u8 special_utf8_string[] = "ÄÖÜ߀";
+
+#define MULTI_POINT_WMI_STRING_LENGTH 12
+
+static const struct wmi_string multi_point_wmi_string = {
+ .length = cpu_to_le16(10),
+ .chars = {
+ cpu_to_le16(u'K'),
+ /* 🐧 */
+ cpu_to_le16(0xD83D),
+ cpu_to_le16(0xDC27),
+ cpu_to_le16(u'!'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+static const u8 multi_point_utf8_string[] = "K🐧!";
+
+#define PADDED_TEST_WMI_STRING_LENGTH 14
+
+static const struct wmi_string padded_test_wmi_string = {
+ .length = cpu_to_le16(12),
+ .chars = {
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'E'),
+ cpu_to_le16(u'S'),
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'\0'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+static const u8 padded_test_utf8_string[] = "TEST\0";
+
+#define OVERSIZED_TEST_WMI_STRING_LENGTH 14
+
+static const struct wmi_string oversized_test_wmi_string = {
+ .length = cpu_to_le16(8),
+ .chars = {
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'E'),
+ cpu_to_le16(u'S'),
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'!'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+static const u8 oversized_test_utf8_string[] = "TEST!";
+
+#define INVALID_TEST_WMI_STRING_LENGTH 14
+
+static const struct wmi_string invalid_test_wmi_string = {
+ .length = cpu_to_le16(12),
+ .chars = {
+ cpu_to_le16(u'T'),
+ /* 🐧, with low surrogate missing */
+ cpu_to_le16(0xD83D),
+ cpu_to_le16(u'E'),
+ cpu_to_le16(u'S'),
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+/* We have to split the string here to end the hex escape sequence */
+static const u8 invalid_test_utf8_string[] = "T" "\xF0\x9F" "EST";
+
+static const struct wmi_string_param wmi_string_params_array[] = {
+ {
+ .name = "ascii_string",
+ .wmi_string = &test_wmi_string,
+ .wmi_string_length = TEST_WMI_STRING_LENGTH,
+ .utf8_string = test_utf8_string,
+ .utf8_string_length = sizeof(test_utf8_string),
+ },
+ {
+ .name = "special_string",
+ .wmi_string = &special_wmi_string,
+ .wmi_string_length = SPECIAL_WMI_STRING_LENGTH,
+ .utf8_string = special_utf8_string,
+ .utf8_string_length = sizeof(special_utf8_string),
+ },
+ {
+ .name = "multi_point_string",
+ .wmi_string = &multi_point_wmi_string,
+ .wmi_string_length = MULTI_POINT_WMI_STRING_LENGTH,
+ .utf8_string = multi_point_utf8_string,
+ .utf8_string_length = sizeof(multi_point_utf8_string),
+ },
+};
+
+static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc)
+{
+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(wmi_string, wmi_string_params_array, wmi_string_param_get_desc);
+
+static void wmi_string_to_utf8s_test(struct kunit *test)
+{
+ const struct wmi_string_param *param = test->param_value;
+ ssize_t ret;
+ u8 *result;
+
+ result = kunit_kzalloc(test, param->utf8_string_length, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
+
+ ret = wmi_string_to_utf8s(param->wmi_string, result, param->utf8_string_length);
+
+ KUNIT_EXPECT_EQ(test, ret, param->utf8_string_length - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, param->utf8_string, param->utf8_string_length);
+}
+
+static void wmi_string_from_utf8s_test(struct kunit *test)
+{
+ const struct wmi_string_param *param = test->param_value;
+ struct wmi_string *result;
+ size_t max_chars;
+ ssize_t ret;
+
+ max_chars = (param->wmi_string_length - sizeof(*result)) / 2;
+ result = kunit_kzalloc(test, param->wmi_string_length, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
+
+ ret = wmi_string_from_utf8s(result, max_chars, param->utf8_string,
+ param->utf8_string_length);
+
+ KUNIT_EXPECT_EQ(test, ret, max_chars - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, param->wmi_string, param->wmi_string_length);
+}
+
+static void wmi_string_to_utf8s_padded_test(struct kunit *test)
+{
+ u8 result[sizeof(padded_test_utf8_string)];
+ ssize_t ret;
+
+ ret = wmi_string_to_utf8s(&padded_test_wmi_string, result, sizeof(result));
+
+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
+}
+
+static void wmi_string_from_utf8s_padded_test(struct kunit *test)
+{
+ struct wmi_string *result;
+ size_t max_chars;
+ ssize_t ret;
+
+ max_chars = (PADDED_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
+ result = kunit_kzalloc(test, PADDED_TEST_WMI_STRING_LENGTH, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
+
+ ret = wmi_string_from_utf8s(result, max_chars, padded_test_utf8_string,
+ sizeof(padded_test_utf8_string));
+
+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string));
+}
+
+static void wmi_string_to_utf8s_oversized_test(struct kunit *test)
+{
+ u8 result[sizeof(oversized_test_utf8_string)];
+ ssize_t ret;
+
+ ret = wmi_string_to_utf8s(&oversized_test_wmi_string, result, sizeof(result));
+
+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
+}
+
+static void wmi_string_from_utf8s_oversized_test(struct kunit *test)
+{
+ struct wmi_string *result;
+ size_t max_chars;
+ ssize_t ret;
+
+ max_chars = (TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
+ result = kunit_kzalloc(test, TEST_WMI_STRING_LENGTH, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
+
+ ret = wmi_string_from_utf8s(result, max_chars, oversized_test_utf8_string,
+ sizeof(oversized_test_utf8_string));
+
+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string));
+}
+
+static void wmi_string_to_utf8s_invalid_test(struct kunit *test)
+{
+ u8 result[sizeof(invalid_test_utf8_string)];
+ ssize_t ret;
+
+ ret = wmi_string_to_utf8s(&invalid_test_wmi_string, result, sizeof(result));
+
+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
+}
+
+static void wmi_string_from_utf8s_invalid_test(struct kunit *test)
+{
+ struct wmi_string *result;
+ size_t max_chars;
+ ssize_t ret;
+
+ max_chars = (INVALID_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
+ result = kunit_kzalloc(test, INVALID_TEST_WMI_STRING_LENGTH, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
+
+ ret = wmi_string_from_utf8s(result, max_chars, invalid_test_utf8_string,
+ sizeof(invalid_test_utf8_string));
+
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+static struct kunit_case wmi_string_test_cases[] = {
+ KUNIT_CASE_PARAM(wmi_string_to_utf8s_test, wmi_string_gen_params),
+ KUNIT_CASE_PARAM(wmi_string_from_utf8s_test, wmi_string_gen_params),
+ KUNIT_CASE(wmi_string_to_utf8s_padded_test),
+ KUNIT_CASE(wmi_string_from_utf8s_padded_test),
+ KUNIT_CASE(wmi_string_to_utf8s_oversized_test),
+ KUNIT_CASE(wmi_string_from_utf8s_oversized_test),
+ KUNIT_CASE(wmi_string_to_utf8s_invalid_test),
+ KUNIT_CASE(wmi_string_from_utf8s_invalid_test),
+ {}
+};
+
+static struct kunit_suite wmi_string_test_suite = {
+ .name = "wmi_string",
+ .test_cases = wmi_string_test_cases,
+};
+
+kunit_test_suite(wmi_string_test_suite);
+
+MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
+MODULE_DESCRIPTION("KUnit test for the ACPI-WMI string conversion code");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
index 13c4fec2c7ef..3d94b03cf794 100644
--- a/drivers/platform/x86/amd/pmf/acpi.c
+++ b/drivers/platform/x86/amd/pmf/acpi.c
@@ -9,6 +9,9 @@
*/
#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/dev_printk.h>
#include "pmf.h"
#define APMF_CQL_NOTIFICATION 2
@@ -331,6 +334,39 @@ int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req
req, sizeof(*req));
}
+/* Store custom BIOS inputs data in ring buffer */
+static void amd_pmf_custom_bios_inputs_rb(struct amd_pmf_dev *pmf_dev)
+{
+ struct pmf_cbi_ring_buffer *rb = &pmf_dev->cbi_buf;
+ int i;
+
+ guard(mutex)(&pmf_dev->cbi_mutex);
+
+ switch (pmf_dev->cpu_id) {
+ case AMD_CPU_ID_PS:
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs_v1); i++)
+ rb->data[rb->head].val[i] = pmf_dev->req1.custom_policy[i];
+ rb->data[rb->head].preq = pmf_dev->req1.pending_req;
+ break;
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
+ rb->data[rb->head].val[i] = pmf_dev->req.custom_policy[i];
+ rb->data[rb->head].preq = pmf_dev->req.pending_req;
+ break;
+ default:
+ return;
+ }
+
+ if (CIRC_SPACE(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0) {
+ /* Rare case: ensures the newest BIOS input value is kept */
+ dev_warn(pmf_dev->dev, "Overwriting BIOS input value, data may be lost\n");
+ rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
+ }
+
+ rb->head = (rb->head + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
+}
+
static void amd_pmf_handle_early_preq(struct amd_pmf_dev *pdev)
{
if (!pdev->cb_flag)
@@ -356,6 +392,8 @@ static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data)
dev_dbg(pmf_dev->dev, "Pending request (preq): 0x%x\n", pmf_dev->req.pending_req);
amd_pmf_handle_early_preq(pmf_dev);
+
+ amd_pmf_custom_bios_inputs_rb(pmf_dev);
}
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)
dev_dbg(pmf_dev->dev, "Pending request (preq1): 0x%x\n", pmf_dev->req1.pending_req);
amd_pmf_handle_early_preq(pmf_dev);
+
+ amd_pmf_custom_bios_inputs_rb(pmf_dev);
}
static void apmf_event_handler(acpi_handle handle, u32 event, void *data)
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
index 8fc293c9c538..b9e5a2cf3aae 100644
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -8,12 +8,16 @@
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/iopoll.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
+#include <linux/string.h>
#include <asm/amd/node.h>
#include "pmf.h"
@@ -53,6 +57,12 @@ static bool force_load;
module_param(force_load, bool, 0444);
MODULE_PARM_DESC(force_load, "Force load this driver on supported older platforms (experimental)");
+static bool smart_pc_support = true;
+module_param(smart_pc_support, bool, 0444);
+MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)");
+
+static struct device *pmf_device;
+
static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data)
{
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)
return 0;
}
+static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
+{
+ switch (pdev->cpu_id) {
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
+{
+ int ret, i;
+
+ guard(mutex)(&dev->metrics_mutex);
+
+ ret = is_npu_metrics_supported(dev);
+ if (ret)
+ return ret;
+
+ ret = amd_pmf_set_dram_addr(dev, true);
+ if (ret)
+ return ret;
+
+ memset(dev->buf, 0, dev->mtable_size);
+
+ /* Send SMU command to get NPU metrics */
+ ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
+ if (ret) {
+ dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
+ return ret;
+ }
+
+ memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
+
+ data->npuclk_freq = dev->m_table_v2.npuclk_freq;
+ for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
+ data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
+ data->npu_power = dev->m_table_v2.npu_power;
+ data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
+ data->npu_reads = dev->m_table_v2.npu_reads;
+ data->npu_writes = dev->m_table_v2.npu_writes;
+
+ return 0;
+}
+
+int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
+{
+ struct amd_pmf_dev *pdev;
+
+ if (!info)
+ return -EINVAL;
+
+ if (!pmf_device)
+ return -ENODEV;
+
+ pdev = dev_get_drvdata(pmf_device);
+ if (!pdev)
+ return -ENODEV;
+
+ return amd_pmf_get_smu_metrics(pdev, info);
+}
+EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
+
+static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev)
+{
+ bool status;
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(amd_pmf_ta_uuid); i++) {
+ ret = amd_pmf_tee_init(pdev, &amd_pmf_ta_uuid[i]);
+ if (ret) {
+ dev_err(pdev->dev, "TEE init failed for UUID[%d] ret: %d\n", i, ret);
+ return ret;
+ }
+
+ ret = amd_pmf_start_policy_engine(pdev);
+ dev_dbg(pdev->dev, "start policy engine ret: %d (UUID idx: %d)\n", ret, i);
+ status = ret == TA_PMF_TYPE_SUCCESS;
+ if (status)
+ break;
+ amd_pmf_tee_deinit(pdev);
+ }
+
+ return 0;
+}
+
+static int amd_pmf_restore_handler(struct device *dev)
+{
+ struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
+ int ret;
+
+ if (pdev->buf) {
+ ret = amd_pmf_set_dram_addr(pdev, false);
+ if (ret)
+ return ret;
+ }
+
+ if (pdev->smart_pc_enabled)
+ amd_pmf_reinit_ta(pdev);
+
+ return 0;
+}
+
+static int amd_pmf_freeze_handler(struct device *dev)
+{
+ struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
+
+ if (!pdev->smart_pc_enabled)
+ return 0;
+
+ cancel_delayed_work_sync(&pdev->pb_work);
+ /* Clear all TEE resources */
+ amd_pmf_tee_deinit(pdev);
+ pdev->session_id = 0;
+
+ return 0;
+}
+
static int amd_pmf_suspend_handler(struct device *dev)
{
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
@@ -347,7 +477,12 @@ static int amd_pmf_resume_handler(struct device *dev)
return 0;
}
-static DEFINE_SIMPLE_DEV_PM_OPS(amd_pmf_pm, amd_pmf_suspend_handler, amd_pmf_resume_handler);
+static const struct dev_pm_ops amd_pmf_pm = {
+ .suspend = amd_pmf_suspend_handler,
+ .resume = amd_pmf_resume_handler,
+ .freeze = amd_pmf_freeze_handler,
+ .restore = amd_pmf_restore_handler,
+};
static void amd_pmf_init_features(struct amd_pmf_dev *dev)
{
@@ -362,11 +497,15 @@ static void amd_pmf_init_features(struct amd_pmf_dev *dev)
dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n");
}
- amd_pmf_init_smart_pc(dev);
- if (dev->smart_pc_enabled) {
- dev_dbg(dev->dev, "Smart PC Solution Enabled\n");
- /* If Smart PC is enabled, no need to check for other features */
- return;
+ if (smart_pc_support) {
+ amd_pmf_init_smart_pc(dev);
+ if (dev->smart_pc_enabled) {
+ dev_dbg(dev->dev, "Smart PC Solution Enabled\n");
+ /* If Smart PC is enabled, no need to check for other features */
+ return;
+ }
+ } else {
+ dev->smart_pc_enabled = false;
}
if (is_apmf_func_supported(dev, APMF_FUNC_AUTO_MODE)) {
@@ -477,6 +616,14 @@ static int amd_pmf_probe(struct platform_device *pdev)
if (err)
return err;
+ err = devm_mutex_init(dev->dev, &dev->cbi_mutex);
+ if (err)
+ return err;
+
+ err = devm_mutex_init(dev->dev, &dev->metrics_mutex);
+ if (err)
+ return err;
+
apmf_acpi_init(dev);
platform_set_drvdata(pdev, dev);
amd_pmf_dbgfs_register(dev);
@@ -485,6 +632,8 @@ static int amd_pmf_probe(struct platform_device *pdev)
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD);
+ pmf_device = dev->dev;
+
dev_info(dev->dev, "registered PMF device successfully\n");
return 0;
diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
index 9144c8c3bbaf..69fef7448744 100644
--- a/drivers/platform/x86/amd/pmf/pmf.h
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -12,7 +12,10 @@
#define PMF_H
#include <linux/acpi.h>
+#include <linux/amd-pmf-io.h>
+#include <linux/circ_buf.h>
#include <linux/input.h>
+#include <linux/mutex_types.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
@@ -120,6 +123,7 @@ struct cookie_header {
#define APTS_MAX_STATES 16
#define CUSTOM_BIOS_INPUT_BITS GENMASK(16, 7)
#define BIOS_INPUTS_MAX 10
+#define CUSTOM_BIOS_INPUT_RING_ENTRIES 64 /* Must be power of two for CIRC_* macros */
/* amd_pmf_send_cmd() set/get */
#define SET_CMD false
@@ -129,6 +133,12 @@ struct cookie_header {
typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data);
+static const uuid_t amd_pmf_ta_uuid[] __used = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8,
+ 0x8a, 0xcc, 0x2b, 0x2b, 0x60, 0xd6),
+ UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d,
+ 0xc5, 0x29, 0xb1, 0x3d, 0x85, 0x43),
+ };
+
/* APTS PMF BIOS Interface */
struct amd_pmf_apts_output {
u16 table_version;
@@ -365,6 +375,22 @@ struct pmf_bios_inputs_prev {
u32 custom_bios_inputs[BIOS_INPUTS_MAX];
};
+/**
+ * struct pmf_bios_input_entry - Snapshot of custom BIOS input event
+ * @val: Array of custom BIOS input values
+ * @preq: Pending request value associated with this event
+ */
+struct pmf_bios_input_entry {
+ u32 val[BIOS_INPUTS_MAX];
+ u32 preq;
+};
+
+struct pmf_cbi_ring_buffer {
+ struct pmf_bios_input_entry data[CUSTOM_BIOS_INPUT_RING_ENTRIES];
+ int head;
+ int tail;
+};
+
struct amd_pmf_dev {
void __iomem *regbase;
void __iomem *smu_virt_addr;
@@ -413,6 +439,9 @@ struct amd_pmf_dev {
struct apmf_sbios_req_v1 req1;
struct pmf_bios_inputs_prev cb_prev; /* To preserve custom BIOS inputs */
bool cb_flag; /* To handle first custom BIOS input */
+ struct pmf_cbi_ring_buffer cbi_buf;
+ struct mutex cbi_mutex; /* Protects ring buffer access */
+ struct mutex metrics_mutex;
};
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
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev);
+int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid);
+void amd_pmf_tee_deinit(struct amd_pmf_dev *dev);
+int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev);
+
#endif /* PMF_H */
diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c
index 0a37dc6a7950..f48678a23cc7 100644
--- a/drivers/platform/x86/amd/pmf/spc.c
+++ b/drivers/platform/x86/amd/pmf/spc.c
@@ -11,6 +11,7 @@
#include <acpi/button.h>
#include <linux/amd-pmf-io.h>
+#include <linux/cleanup.h>
#include <linux/power_supply.h>
#include <linux/units.h>
#include "pmf.h"
@@ -132,32 +133,39 @@ static void amd_pmf_set_ta_custom_bios_input(struct ta_pmf_enact_table *in, int
}
}
-static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, u32 pending_req,
+static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, struct pmf_bios_input_entry *data,
const struct amd_pmf_pb_bitmap *inputs,
- const u32 *custom_policy, struct ta_pmf_enact_table *in)
+ struct ta_pmf_enact_table *in)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) {
- if (!(pending_req & inputs[i].bit_mask))
+ if (!(data->preq & inputs[i].bit_mask))
continue;
- amd_pmf_set_ta_custom_bios_input(in, i, custom_policy[i]);
- pdev->cb_prev.custom_bios_inputs[i] = custom_policy[i];
- dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, custom_policy[i]);
+ amd_pmf_set_ta_custom_bios_input(in, i, data->val[i]);
+ pdev->cb_prev.custom_bios_inputs[i] = data->val[i];
+ dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, data->val[i]);
}
}
static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
struct ta_pmf_enact_table *in)
{
+ struct pmf_cbi_ring_buffer *rb = &pdev->cbi_buf;
unsigned int i;
+ guard(mutex)(&pdev->cbi_mutex);
+
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
amd_pmf_set_ta_custom_bios_input(in, i, pdev->cb_prev.custom_bios_inputs[i]);
- if (!(pdev->req.pending_req || pdev->req1.pending_req))
+ if (CIRC_CNT(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0)
return;
+ /* If no active custom BIOS input pending request, do not consume further work */
+ if (!rb->data[rb->tail].preq)
+ goto out_rbadvance;
+
if (!pdev->smart_pc_enabled)
return;
@@ -165,20 +173,17 @@ static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
case PMF_IF_V1:
if (!is_apmf_bios_input_notifications_supported(pdev))
return;
- amd_pmf_update_bios_inputs(pdev, pdev->req1.pending_req, custom_bios_inputs_v1,
- pdev->req1.custom_policy, in);
+ amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs_v1, in);
break;
case PMF_IF_V2:
- amd_pmf_update_bios_inputs(pdev, pdev->req.pending_req, custom_bios_inputs,
- pdev->req.custom_policy, in);
+ amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs, in);
break;
default:
break;
}
- /* Clear pending requests after handling */
- memset(&pdev->req, 0, sizeof(pdev->req));
- memset(&pdev->req1, 0, sizeof(pdev->req1));
+out_rbadvance:
+ rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
}
static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in)
diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c
index 0abce76f89ff..7ccd93f506b2 100644
--- a/drivers/platform/x86/amd/pmf/tee-if.c
+++ b/drivers/platform/x86/amd/pmf/tee-if.c
@@ -27,12 +27,6 @@ module_param(pb_side_load, bool, 0444);
MODULE_PARM_DESC(pb_side_load, "Sideload policy binaries debug policy failures");
#endif
-static const uuid_t amd_pmf_ta_uuid[] = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, 0x8a,
- 0xcc, 0x2b, 0x2b, 0x60, 0xd6),
- UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d, 0xc5,
- 0x29, 0xb1, 0x3d, 0x85, 0x43),
- };
-
static const char *amd_pmf_uevent_as_str(unsigned int state)
{
switch (state) {
@@ -324,7 +318,7 @@ static void amd_pmf_invoke_cmd(struct work_struct *work)
schedule_delayed_work(&dev->pb_work, msecs_to_jiffies(pb_actions_ms));
}
-static int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
+int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
{
struct cookie_header *header;
int res;
@@ -480,7 +474,7 @@ static int amd_pmf_register_input_device(struct amd_pmf_dev *dev)
return 0;
}
-static int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
+int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
{
u32 size;
int ret;
@@ -528,7 +522,7 @@ out_ctx:
return ret;
}
-static void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
+void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
{
if (!dev->tee_ctx)
return;
@@ -591,6 +585,8 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
status = ret == TA_PMF_TYPE_SUCCESS;
if (status) {
dev->cb_flag = true;
+ dev->cbi_buf.head = 0;
+ dev->cbi_buf.tail = 0;
break;
}
amd_pmf_tee_deinit(dev);
diff --git a/drivers/platform/x86/amd/wbrf.c b/drivers/platform/x86/amd/wbrf.c
index 0f58d252b620..dc10d12bc80d 100644
--- a/drivers/platform/x86/amd/wbrf.c
+++ b/drivers/platform/x86/amd/wbrf.c
@@ -42,8 +42,6 @@ static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head);
static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ranges_in_out *in)
{
union acpi_object argv4;
- union acpi_object *tmp;
- union acpi_object *obj;
u32 num_of_ranges = 0;
u32 num_of_elements;
u32 arg_idx = 0;
@@ -74,7 +72,7 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran
*/
num_of_elements = 2 * num_of_ranges + 2;
- tmp = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL);
+ union acpi_object *tmp __free(kfree) = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL);
if (!tmp)
return -ENOMEM;
@@ -101,26 +99,19 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran
tmp[arg_idx++].integer.value = in->band_list[i].end;
}
- obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
- WBRF_REVISION, WBRF_RECORD, &argv4);
+ union acpi_object *obj __free(kfree) =
+ acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
+ WBRF_REVISION, WBRF_RECORD, &argv4);
- if (!obj) {
- kfree(tmp);
+ if (!obj)
return -EINVAL;
- }
- if (obj->type != ACPI_TYPE_INTEGER) {
- ret = -EINVAL;
- goto out;
- }
+ if (obj->type != ACPI_TYPE_INTEGER)
+ return -EINVAL;
ret = obj->integer.value;
if (ret)
- ret = -EINVAL;
-
-out:
- ACPI_FREE(obj);
- kfree(tmp);
+ return -EINVAL;
return ret;
}
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 0775fadedd10..275b56d6a09f 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -31,13 +31,13 @@
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/platform_data/x86/asus-wmi.h>
-#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#include <linux/power_supply.h>
#include <linux/rfkill.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/units.h>
@@ -256,6 +256,9 @@ struct asus_wmi {
int tpd_led_wk;
struct led_classdev kbd_led;
int kbd_led_wk;
+ bool kbd_led_notify;
+ bool kbd_led_avail;
+ bool kbd_led_registered;
struct led_classdev lightbar_led;
int lightbar_led_wk;
struct led_classdev micmute_led;
@@ -264,6 +267,7 @@ struct asus_wmi {
struct work_struct tpd_led_work;
struct work_struct wlan_led_work;
struct work_struct lightbar_led_work;
+ struct work_struct kbd_led_work;
struct asus_rfkill wlan;
struct asus_rfkill bluetooth;
@@ -1615,6 +1619,144 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
/* LEDs ***********************************************************************/
+struct asus_hid_ref {
+ struct list_head listeners;
+ struct asus_wmi *asus;
+ /* Protects concurrent access from hid-asus and asus-wmi to leds */
+ spinlock_t lock;
+};
+
+static struct asus_hid_ref asus_ref = {
+ .listeners = LIST_HEAD_INIT(asus_ref.listeners),
+ .asus = NULL,
+ /*
+ * Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other
+ * asus variables are read-only after .asus is set.
+ *
+ * The led cdev device is not protected because it calls backlight_get
+ * during initialization, which would result in a nested lock attempt.
+ *
+ * The led cdev is safe to access without a lock because if
+ * kbd_led_avail is true it is initialized before .asus is set and never
+ * changed until .asus is dropped. If kbd_led_avail is false, the led
+ * cdev is registered by the workqueue, which is single-threaded and
+ * cancelled before asus-wmi would access the led cdev to unregister it.
+ *
+ * A spinlock is used, because the protected variables can be accessed
+ * from an IRQ context from asus-hid.
+ */
+ .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
+};
+
+/*
+ * Allows registering hid-asus listeners that want to be notified of
+ * keyboard backlight changes.
+ */
+int asus_hid_register_listener(struct asus_hid_listener *bdev)
+{
+ struct asus_wmi *asus;
+
+ guard(spinlock_irqsave)(&asus_ref.lock);
+ list_add_tail(&bdev->list, &asus_ref.listeners);
+ asus = asus_ref.asus;
+ if (asus)
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asus_hid_register_listener);
+
+/*
+ * Allows unregistering hid-asus listeners that were added with
+ * asus_hid_register_listener().
+ */
+void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
+{
+ guard(spinlock_irqsave)(&asus_ref.lock);
+ list_del(&bdev->list);
+}
+EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
+
+static void do_kbd_led_set(struct led_classdev *led_cdev, int value);
+
+static void kbd_led_update_all(struct work_struct *work)
+{
+ struct asus_wmi *asus;
+ bool registered, notify;
+ int ret, value;
+
+ asus = container_of(work, struct asus_wmi, kbd_led_work);
+
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ registered = asus->kbd_led_registered;
+ value = asus->kbd_led_wk;
+ notify = asus->kbd_led_notify;
+ }
+
+ if (!registered) {
+ /*
+ * This workqueue runs under asus-wmi, which means probe has
+ * completed and asus-wmi will keep running until it finishes.
+ * Therefore, we can safely register the LED without holding
+ * a spinlock.
+ */
+ ret = devm_led_classdev_register(&asus->platform_device->dev,
+ &asus->kbd_led);
+ if (!ret) {
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_registered = true;
+ } else {
+ pr_warn("Failed to register keyboard backlight LED: %d\n", ret);
+ return;
+ }
+ }
+
+ if (value >= 0)
+ do_kbd_led_set(&asus->kbd_led, value);
+ if (notify) {
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_notify = false;
+ led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value);
+ }
+}
+
+/*
+ * This function is called from hid-asus to inform asus-wmi of brightness
+ * changes initiated by the keyboard backlight keys.
+ */
+int asus_hid_event(enum asus_hid_event event)
+{
+ struct asus_wmi *asus;
+ int brightness;
+
+ guard(spinlock_irqsave)(&asus_ref.lock);
+ asus = asus_ref.asus;
+ if (!asus || !asus->kbd_led_registered)
+ return -EBUSY;
+
+ brightness = asus->kbd_led_wk;
+
+ switch (event) {
+ case ASUS_EV_BRTUP:
+ brightness += 1;
+ break;
+ case ASUS_EV_BRTDOWN:
+ brightness -= 1;
+ break;
+ case ASUS_EV_BRTTOGGLE:
+ if (brightness >= ASUS_EV_MAX_BRIGHTNESS)
+ brightness = 0;
+ else
+ brightness += 1;
+ break;
+ }
+
+ asus->kbd_led_wk = clamp_val(brightness, 0, ASUS_EV_MAX_BRIGHTNESS);
+ asus->kbd_led_notify = true;
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asus_hid_event);
+
/*
* These functions actually update the LED's, and are called from a
* 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)
{
int ctrl_param = 0;
- ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
}
@@ -1694,14 +1837,21 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
{
+ struct asus_hid_listener *listener;
struct asus_wmi *asus;
- int max_level;
asus = container_of(led_cdev, struct asus_wmi, kbd_led);
- max_level = asus->kbd_led.max_brightness;
- asus->kbd_led_wk = clamp_val(value, 0, max_level);
- kbd_led_update(asus);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_wk = clamp_val(value, 0, ASUS_EV_MAX_BRIGHTNESS);
+
+ if (asus->kbd_led_avail)
+ kbd_led_update(asus);
+
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ list_for_each_entry(listener, &asus_ref.listeners, list)
+ listener->brightness_set(listener, asus->kbd_led_wk);
+ }
}
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)
static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
{
- struct led_classdev *led_cdev = &asus->kbd_led;
-
- do_kbd_led_set(led_cdev, value);
- led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ asus->kbd_led_wk = value;
+ asus->kbd_led_notify = true;
+ }
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
}
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)
asus = container_of(led_cdev, struct asus_wmi, kbd_led);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ if (!asus->kbd_led_avail)
+ return asus->kbd_led_wk;
+ }
+
retval = kbd_led_read(asus, &value, NULL);
if (retval < 0)
return retval;
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_wk = value;
+
return value;
}
@@ -1844,7 +2003,9 @@ static int camera_led_set(struct led_classdev *led_cdev,
static void asus_wmi_led_exit(struct asus_wmi *asus)
{
- led_classdev_unregister(&asus->kbd_led);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus_ref.asus = NULL;
+
led_classdev_unregister(&asus->tpd_led);
led_classdev_unregister(&asus->wlan_led);
led_classdev_unregister(&asus->lightbar_led);
@@ -1882,22 +2043,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
goto error;
}
- if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
- pr_info("using asus-wmi for asus::kbd_backlight\n");
- asus->kbd_led_wk = led_val;
- asus->kbd_led.name = "asus::kbd_backlight";
- asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
- asus->kbd_led.brightness_set_blocking = kbd_led_set;
- asus->kbd_led.brightness_get = kbd_led_get;
- asus->kbd_led.max_brightness = 3;
+ asus->kbd_led.name = "asus::kbd_backlight";
+ asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
+ asus->kbd_led.brightness_set_blocking = kbd_led_set;
+ asus->kbd_led.brightness_get = kbd_led_get;
+ asus->kbd_led.max_brightness = ASUS_EV_MAX_BRIGHTNESS;
+ asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
+ INIT_WORK(&asus->kbd_led_work, kbd_led_update_all);
+ if (asus->kbd_led_avail) {
+ asus->kbd_led_wk = led_val;
if (num_rgb_groups != 0)
asus->kbd_led.groups = kbd_rgb_mode_groups;
+ } else {
+ asus->kbd_led_wk = -1;
+ }
- rv = led_classdev_register(&asus->platform_device->dev,
- &asus->kbd_led);
- if (rv)
- goto error;
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ asus_ref.asus = asus;
+ if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners))
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
}
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)
static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
{
+ enum led_brightness led_value;
unsigned int key_value = 1;
bool autorelease = 1;
@@ -4388,19 +4554,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
return;
}
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ led_value = asus->kbd_led_wk;
+
if (code == NOTIFY_KBD_BRTUP) {
- kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
+ kbd_led_set_by_kbd(asus, led_value + 1);
return;
}
if (code == NOTIFY_KBD_BRTDWN) {
- kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1);
+ kbd_led_set_by_kbd(asus, led_value - 1);
return;
}
if (code == NOTIFY_KBD_BRTTOGGLE) {
- if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
+ if (led_value >= ASUS_EV_MAX_BRIGHTNESS)
kbd_led_set_by_kbd(asus, 0);
else
- kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
+ kbd_led_set_by_kbd(asus, led_value + 1);
return;
}
diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
index f4ea1ea05997..304d9ac63c8a 100644
--- a/drivers/platform/x86/hp/hp-wmi.c
+++ b/drivers/platform/x86/hp/hp-wmi.c
@@ -13,23 +13,28 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/kernel.h>
-#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/compiler_attributes.h>
+#include <linux/dmi.h>
+#include <linux/fixp-arith.h>
+#include <linux/hwmon.h>
#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/types.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
-#include <linux/hwmon.h>
-#include <linux/acpi.h>
-#include <linux/mutex.h>
-#include <linux/cleanup.h>
#include <linux/power_supply.h>
#include <linux/rfkill.h>
+#include <linux/slab.h>
#include <linux/string.h>
-#include <linux/dmi.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
MODULE_DESCRIPTION("HP laptop WMI driver");
@@ -41,9 +46,13 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4");
#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
#define HPWMI_BIOS_GUID "5FB7F034-2C63-45E9-BE91-3D44E2C707E4"
-#define HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET 0x62
-#define HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET 0x63
-#define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95
+enum hp_ec_offsets {
+ HP_EC_OFFSET_UNKNOWN = 0x00,
+ HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET = 0x59,
+ HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET = 0x62,
+ HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET = 0x63,
+ HP_OMEN_EC_THERMAL_PROFILE_OFFSET = 0x95,
+};
#define HP_FAN_SPEED_AUTOMATIC 0x00
#define HP_POWER_LIMIT_DEFAULT 0x00
@@ -53,6 +62,70 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4");
#define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
+enum hp_thermal_profile_omen_v0 {
+ HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00,
+ HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01,
+ HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02,
+};
+
+enum hp_thermal_profile_omen_v1 {
+ HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30,
+ HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31,
+ HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
+};
+
+enum hp_thermal_profile_omen_flags {
+ HP_OMEN_EC_FLAGS_TURBO = 0x04,
+ HP_OMEN_EC_FLAGS_NOTIMER = 0x02,
+ HP_OMEN_EC_FLAGS_JUSTSET = 0x01,
+};
+
+enum hp_thermal_profile_victus {
+ HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00,
+ HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01,
+ HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03,
+};
+
+enum hp_thermal_profile_victus_s {
+ HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00,
+ HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01,
+};
+
+enum hp_thermal_profile {
+ HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
+ HP_THERMAL_PROFILE_DEFAULT = 0x01,
+ HP_THERMAL_PROFILE_COOL = 0x02,
+ HP_THERMAL_PROFILE_QUIET = 0x03,
+};
+
+
+struct thermal_profile_params {
+ u8 performance;
+ u8 balanced;
+ u8 low_power;
+ u8 ec_tp_offset;
+};
+
+static const struct thermal_profile_params victus_s_thermal_params = {
+ .performance = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE,
+ .balanced = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT,
+ .low_power = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT,
+ .ec_tp_offset = HP_EC_OFFSET_UNKNOWN,
+};
+
+static const struct thermal_profile_params omen_v1_thermal_params = {
+ .performance = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE,
+ .balanced = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
+ .low_power = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
+ .ec_tp_offset = HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET,
+};
+
+/*
+ * A generic pointer for the currently-active board's thermal profile
+ * parameters.
+ */
+static struct thermal_profile_params *active_thermal_profile_params;
+
/* DMI board names of devices that should use the omen specific path for
* thermal profiles.
* 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[] = {
};
/* DMI Board names of Victus 16-r and Victus 16-s laptops */
-static const char * const victus_s_thermal_profile_boards[] = {
- "8BBE", "8BD4", "8BD5",
- "8C78", "8C99", "8C9C",
- "8D41",
+static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = {
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") },
+ .driver_data = (void *)&omen_v1_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {},
};
+static bool is_victus_s_board;
+
enum hp_wmi_radio {
HPWMI_WIFI = 0x0,
HPWMI_BLUETOOTH = 0x1,
@@ -190,7 +291,8 @@ enum hp_wmi_gm_commandtype {
HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22,
HPWMI_SET_POWER_LIMITS_QUERY = 0x29,
HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY = 0x2D,
- HPWMI_FAN_SPEED_SET_QUERY = 0x2E,
+ HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY = 0x2E,
+ HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY = 0x2F,
};
enum hp_wmi_command {
@@ -225,42 +327,6 @@ enum hp_wireless2_bits {
HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD,
};
-enum hp_thermal_profile_omen_v0 {
- HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00,
- HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01,
- HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02,
-};
-
-enum hp_thermal_profile_omen_v1 {
- HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30,
- HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31,
- HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
-};
-
-enum hp_thermal_profile_omen_flags {
- HP_OMEN_EC_FLAGS_TURBO = 0x04,
- HP_OMEN_EC_FLAGS_NOTIMER = 0x02,
- HP_OMEN_EC_FLAGS_JUSTSET = 0x01,
-};
-
-enum hp_thermal_profile_victus {
- HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00,
- HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01,
- HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03,
-};
-
-enum hp_thermal_profile_victus_s {
- HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00,
- HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01,
-};
-
-enum hp_thermal_profile {
- HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
- HP_THERMAL_PROFILE_DEFAULT = 0x01,
- HP_THERMAL_PROFILE_COOL = 0x02,
- HP_THERMAL_PROFILE_QUIET = 0x03,
-};
-
#define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW)
#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
@@ -348,6 +414,58 @@ static const char * const tablet_chassis_types[] = {
#define DEVICE_MODE_TABLET 0x06
+#define CPU_FAN 0
+#define GPU_FAN 1
+
+enum pwm_modes {
+ PWM_MODE_MAX = 0,
+ PWM_MODE_MANUAL = 1,
+ PWM_MODE_AUTO = 2,
+};
+
+struct hp_wmi_hwmon_priv {
+ u8 min_rpm;
+ u8 max_rpm;
+ u8 gpu_delta;
+ u8 mode;
+ u8 pwm;
+ struct delayed_work keep_alive_dwork;
+};
+
+struct victus_s_fan_table_header {
+ u8 unknown;
+ u8 num_entries;
+} __packed;
+
+struct victus_s_fan_table_entry {
+ u8 cpu_rpm;
+ u8 gpu_rpm;
+ u8 unknown;
+} __packed;
+
+struct victus_s_fan_table {
+ struct victus_s_fan_table_header header;
+ struct victus_s_fan_table_entry entries[];
+} __packed;
+
+/*
+ * 90s delay to prevent the firmware from resetting fan mode after fixed
+ * 120s timeout
+ */
+#define KEEP_ALIVE_DELAY_SECS 90
+
+static inline u8 rpm_to_pwm(u8 rpm, struct hp_wmi_hwmon_priv *priv)
+{
+ return fixp_linear_interpolate(0, 0, priv->max_rpm, U8_MAX,
+ clamp_val(rpm, 0, priv->max_rpm));
+}
+
+static inline u8 pwm_to_rpm(u8 pwm, struct hp_wmi_hwmon_priv *priv)
+{
+ return fixp_linear_interpolate(0, 0, U8_MAX, priv->max_rpm,
+ clamp_val(pwm, 0, U8_MAX));
+}
+
/* map output size to the corresponding WMI method id */
static inline int encode_outsize_for_pvsz(int outsize)
{
@@ -637,41 +755,53 @@ static int hp_wmi_fan_speed_max_set(int enabled)
return enabled;
}
-static int hp_wmi_fan_speed_reset(void)
+static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed)
{
- u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC };
- int ret;
+ u8 fan_speed[2];
+ int gpu_speed, ret;
- ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM,
- &fan_speed, sizeof(fan_speed), 0);
+ fan_speed[CPU_FAN] = speed;
+ fan_speed[GPU_FAN] = speed;
- return ret;
-}
-
-static int hp_wmi_fan_speed_max_reset(void)
-{
- int ret;
+ /*
+ * GPU fan speed is always a little higher than CPU fan speed, we fetch
+ * this delta value from the fan table during hwmon init.
+ * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to
+ * automatic mode.
+ */
+ if (speed != HP_FAN_SPEED_AUTOMATIC) {
+ gpu_speed = speed + priv->gpu_delta;
+ fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX);
+ }
+ ret = hp_wmi_get_fan_count_userdefine_trigger();
+ if (ret < 0)
+ return ret;
+ /* Max fans need to be explicitly disabled */
ret = hp_wmi_fan_speed_max_set(0);
- if (ret)
+ if (ret < 0)
return ret;
+ ret = hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY, HPWMI_GM,
+ &fan_speed, sizeof(fan_speed), 0);
- /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */
- ret = hp_wmi_fan_speed_reset();
return ret;
}
-static int hp_wmi_fan_speed_max_get(void)
+static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv)
{
- int val = 0, ret;
+ return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC);
+}
- ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM,
- &val, zero_if_sup(val), sizeof(val));
+static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv)
+{
+ int ret;
+ ret = hp_wmi_fan_speed_max_set(0);
if (ret)
- return ret < 0 ? ret : -EINVAL;
+ return ret;
- return val;
+ /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */
+ return hp_wmi_fan_speed_reset(priv);
}
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)
static bool is_victus_s_thermal_profile(void)
{
- const char *board_name;
-
- board_name = dmi_get_system_info(DMI_BOARD_NAME);
- if (!board_name)
- return false;
-
- return match_string(victus_s_thermal_profile_boards,
- ARRAY_SIZE(victus_s_thermal_profile_boards),
- board_name) >= 0;
+ /* Initialised in driver init, hence safe to use here */
+ return is_victus_s_board;
}
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)
return ret;
}
+static int platform_profile_victus_s_get_ec(enum platform_profile_option *profile)
+{
+ int ret = 0;
+ bool current_ctgp_state, current_ppab_state;
+ u8 current_dstate, current_gpu_slowdown_temp, tp;
+ const struct thermal_profile_params *params;
+
+ params = active_thermal_profile_params;
+ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
+ *profile = active_platform_profile;
+ return 0;
+ }
+
+ ret = ec_read(params->ec_tp_offset, &tp);
+ if (ret)
+ return ret;
+
+ /*
+ * We cannot use active_thermal_profile_params here, because boards
+ * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically
+ * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen
+ * Gaming Hub on windows. Hence accept both of these values.
+ */
+ if (tp == victus_s_thermal_params.performance ||
+ tp == omen_v1_thermal_params.performance) {
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ } else if (tp == victus_s_thermal_params.balanced ||
+ tp == omen_v1_thermal_params.balanced) {
+ /*
+ * Since both PLATFORM_PROFILE_LOW_POWER and
+ * PLATFORM_PROFILE_BALANCED share the same thermal profile
+ * parameter value, hence to differentiate between them, we
+ * query the GPU CTGP and PPAB states and compare based off of
+ * that.
+ */
+ ret = victus_s_gpu_thermal_profile_get(&current_ctgp_state,
+ &current_ppab_state,
+ &current_dstate,
+ &current_gpu_slowdown_temp);
+ if (ret < 0)
+ return ret;
+ if (current_ctgp_state == 0 && current_ppab_state == 0)
+ *profile = PLATFORM_PROFILE_LOW_POWER;
+ else if (current_ctgp_state == 0 && current_ppab_state == 1)
+ *profile = PLATFORM_PROFILE_BALANCED;
+ else
+ return -EINVAL;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int platform_profile_victus_s_set_ec(enum platform_profile_option profile)
{
+ struct thermal_profile_params *params;
bool gpu_ctgp_enable, gpu_ppab_enable;
u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */
int err, tp;
+ params = active_thermal_profile_params;
+ if (!params)
+ return -ENODEV;
+
switch (profile) {
case PLATFORM_PROFILE_PERFORMANCE:
- tp = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE;
+ tp = params->performance;
gpu_ctgp_enable = true;
gpu_ppab_enable = true;
gpu_dstate = 1;
break;
case PLATFORM_PROFILE_BALANCED:
- tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
+ tp = params->balanced;
gpu_ctgp_enable = false;
gpu_ppab_enable = true;
gpu_dstate = 1;
break;
case PLATFORM_PROFILE_LOW_POWER:
- tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
+ tp = params->low_power;
gpu_ctgp_enable = false;
gpu_ppab_enable = false;
gpu_dstate = 1;
@@ -1832,6 +2014,7 @@ static int victus_s_powersource_event(struct notifier_block *nb,
void *data)
{
struct acpi_bus_event *event_entry = data;
+ enum platform_profile_option actual_profile;
int err;
if (strcmp(event_entry->device_class, ACPI_AC_CLASS) != 0)
@@ -1839,6 +2022,17 @@ static int victus_s_powersource_event(struct notifier_block *nb,
pr_debug("Received power source device event\n");
+ guard(mutex)(&active_platform_profile_lock);
+ err = platform_profile_victus_s_get_ec(&actual_profile);
+ if (err < 0) {
+ /*
+ * Although we failed to get the current platform profile, we
+ * still want the other event consumers to process it.
+ */
+ pr_warn("Failed to read current platform profile (%d)\n", err);
+ return NOTIFY_DONE;
+ }
+
/*
* Switching to battery power source while Performance mode is active
* 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,
* Seen on HP 16-s1034nf (board 8C9C) with F.11 and F.13 BIOS versions.
*/
- if (active_platform_profile == PLATFORM_PROFILE_PERFORMANCE) {
+ if (actual_profile == PLATFORM_PROFILE_PERFORMANCE) {
pr_debug("Triggering CPU PL1/PL2 actualization\n");
err = victus_s_set_cpu_pl1_pl2(HP_POWER_LIMIT_DEFAULT,
HP_POWER_LIMIT_DEFAULT);
@@ -1958,11 +2152,22 @@ static int thermal_profile_setup(struct platform_device *device)
ops = &platform_profile_victus_ops;
} else if (is_victus_s_thermal_profile()) {
/*
- * Being unable to retrieve laptop's current thermal profile,
- * during this setup, we set it to Balanced by default.
+ * For an unknown EC layout board, platform_profile_victus_s_get_ec(),
+ * behaves like a wrapper around active_platform_profile, to avoid using
+ * uninitialized data, we default to PLATFORM_PROFILE_BALANCED.
*/
- active_platform_profile = PLATFORM_PROFILE_BALANCED;
+ if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
+ active_platform_profile = PLATFORM_PROFILE_BALANCED;
+ } else {
+ err = platform_profile_victus_s_get_ec(&active_platform_profile);
+ if (err < 0)
+ return err;
+ }
+ /*
+ * call thermal profile write command to ensure that the
+ * firmware correctly sets the OEM variables
+ */
err = platform_profile_victus_s_set_ec(active_platform_profile);
if (err < 0)
return err;
@@ -2031,6 +2236,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
static void __exit hp_wmi_bios_remove(struct platform_device *device)
{
int i;
+ struct hp_wmi_hwmon_priv *priv;
for (i = 0; i < rfkill2_count; i++) {
rfkill_unregister(rfkill2[i].rfkill);
@@ -2049,6 +2255,10 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device)
rfkill_unregister(wwan_rfkill);
rfkill_destroy(wwan_rfkill);
}
+
+ priv = platform_get_drvdata(device);
+ if (priv)
+ cancel_delayed_work_sync(&priv->keep_alive_dwork);
}
static int hp_wmi_resume_handler(struct device *device)
@@ -2108,12 +2318,56 @@ static struct platform_driver hp_wmi_driver __refdata = {
.remove = __exit_p(hp_wmi_bios_remove),
};
+static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
+{
+ int ret;
+
+ switch (priv->mode) {
+ case PWM_MODE_MAX:
+ if (is_victus_s_thermal_profile())
+ hp_wmi_get_fan_count_userdefine_trigger();
+ ret = hp_wmi_fan_speed_max_set(1);
+ if (ret < 0)
+ return ret;
+ schedule_delayed_work(&priv->keep_alive_dwork,
+ secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
+ return 0;
+ case PWM_MODE_MANUAL:
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+ ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv));
+ if (ret < 0)
+ return ret;
+ schedule_delayed_work(&priv->keep_alive_dwork,
+ secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
+ return 0;
+ case PWM_MODE_AUTO:
+ if (is_victus_s_thermal_profile()) {
+ hp_wmi_get_fan_count_userdefine_trigger();
+ ret = hp_wmi_fan_speed_max_reset(priv);
+ } else {
+ ret = hp_wmi_fan_speed_max_set(0);
+ }
+ if (ret < 0)
+ return ret;
+ cancel_delayed_work_sync(&priv->keep_alive_dwork);
+ return 0;
+ default:
+ /* shouldn't happen */
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static umode_t hp_wmi_hwmon_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (type) {
case hwmon_pwm:
+ if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile())
+ return 0;
return 0644;
case hwmon_fan:
if (is_victus_s_thermal_profile()) {
@@ -2134,8 +2388,10 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data,
static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
- int ret;
+ struct hp_wmi_hwmon_priv *priv;
+ int rpm, ret;
+ priv = dev_get_drvdata(dev);
switch (type) {
case hwmon_fan:
if (is_victus_s_thermal_profile())
@@ -2147,16 +2403,21 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
*val = ret;
return 0;
case hwmon_pwm:
- switch (hp_wmi_fan_speed_max_get()) {
- case 0:
- /* 0 is automatic fan, which is 2 for hwmon */
- *val = 2;
+ if (attr == hwmon_pwm_input) {
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+
+ rpm = hp_wmi_get_fan_speed_victus_s(channel);
+ if (rpm < 0)
+ return rpm;
+ *val = rpm_to_pwm(rpm / 100, priv);
return 0;
- case 1:
- /* 1 is max fan, which is 0
- * (no fan speed control) for hwmon
- */
- *val = 0;
+ }
+ switch (priv->mode) {
+ case PWM_MODE_MAX:
+ case PWM_MODE_MANUAL:
+ case PWM_MODE_AUTO:
+ *val = priv->mode;
return 0;
default:
/* shouldn't happen */
@@ -2170,23 +2431,46 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
+ struct hp_wmi_hwmon_priv *priv;
+ int rpm;
+
+ priv = dev_get_drvdata(dev);
switch (type) {
case hwmon_pwm:
+ if (attr == hwmon_pwm_input) {
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+ /* PWM input is invalid when not in manual mode */
+ if (priv->mode != PWM_MODE_MANUAL)
+ return -EINVAL;
+
+ /* ensure PWM input is within valid fan speeds */
+ rpm = pwm_to_rpm(val, priv);
+ rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm);
+ priv->pwm = rpm_to_pwm(rpm, priv);
+ return hp_wmi_apply_fan_settings(priv);
+ }
switch (val) {
- case 0:
- if (is_victus_s_thermal_profile())
- hp_wmi_get_fan_count_userdefine_trigger();
- /* 0 is no fan speed control (max), which is 1 for us */
- return hp_wmi_fan_speed_max_set(1);
- case 2:
- /* 2 is automatic speed control, which is 0 for us */
- if (is_victus_s_thermal_profile()) {
- hp_wmi_get_fan_count_userdefine_trigger();
- return hp_wmi_fan_speed_max_reset();
- } else
- return hp_wmi_fan_speed_max_set(0);
+ case PWM_MODE_MAX:
+ priv->mode = PWM_MODE_MAX;
+ return hp_wmi_apply_fan_settings(priv);
+ case PWM_MODE_MANUAL:
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+ /*
+ * When switching to manual mode, set fan speed to
+ * current RPM values to ensure a smooth transition.
+ */
+ rpm = hp_wmi_get_fan_speed_victus_s(channel);
+ if (rpm < 0)
+ return rpm;
+ priv->pwm = rpm_to_pwm(rpm / 100, priv);
+ priv->mode = PWM_MODE_MANUAL;
+ return hp_wmi_apply_fan_settings(priv);
+ case PWM_MODE_AUTO:
+ priv->mode = PWM_MODE_AUTO;
+ return hp_wmi_apply_fan_settings(priv);
default:
- /* we don't support manual fan speed control */
return -EINVAL;
}
default:
@@ -2196,7 +2480,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
static const struct hwmon_channel_info * const info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT),
- HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE),
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT),
NULL
};
@@ -2211,12 +2495,70 @@ static const struct hwmon_chip_info chip_info = {
.info = info,
};
+static void hp_wmi_hwmon_keep_alive_handler(struct work_struct *work)
+{
+ struct delayed_work *dwork;
+ struct hp_wmi_hwmon_priv *priv;
+
+ dwork = to_delayed_work(work);
+ priv = container_of(dwork, struct hp_wmi_hwmon_priv, keep_alive_dwork);
+ /*
+ * Re-apply the current hwmon context settings.
+ * NOTE: hp_wmi_apply_fan_settings will handle the re-scheduling.
+ */
+ hp_wmi_apply_fan_settings(priv);
+}
+
+static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
+{
+ u8 fan_data[128] = { 0 };
+ struct victus_s_fan_table *fan_table;
+ u8 min_rpm, max_rpm, gpu_delta;
+ int ret;
+
+ /* Default behaviour on hwmon init is automatic mode */
+ priv->mode = PWM_MODE_AUTO;
+
+ /* Bypass all non-Victus S devices */
+ if (!is_victus_s_thermal_profile())
+ return 0;
+
+ ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY,
+ HPWMI_GM, &fan_data, 4, sizeof(fan_data));
+ if (ret)
+ return ret;
+
+ fan_table = (struct victus_s_fan_table *)fan_data;
+ if (fan_table->header.num_entries == 0 ||
+ sizeof(struct victus_s_fan_table_header) +
+ sizeof(struct victus_s_fan_table_entry) * fan_table->header.num_entries > sizeof(fan_data))
+ return -EINVAL;
+
+ min_rpm = fan_table->entries[0].cpu_rpm;
+ max_rpm = fan_table->entries[fan_table->header.num_entries - 1].cpu_rpm;
+ gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm;
+ priv->min_rpm = min_rpm;
+ priv->max_rpm = max_rpm;
+ priv->gpu_delta = gpu_delta;
+
+ return 0;
+}
+
static int hp_wmi_hwmon_init(void)
{
struct device *dev = &hp_wmi_platform_dev->dev;
+ struct hp_wmi_hwmon_priv *priv;
struct device *hwmon;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
- hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver,
+ ret = hp_wmi_setup_fan_settings(priv);
+ if (ret)
+ return ret;
+ hwmon = devm_hwmon_device_register_with_info(dev, "hp", priv,
&chip_info, NULL);
if (IS_ERR(hwmon)) {
@@ -2224,9 +2566,37 @@ static int hp_wmi_hwmon_init(void)
return PTR_ERR(hwmon);
}
+ INIT_DELAYED_WORK(&priv->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler);
+ platform_set_drvdata(hp_wmi_platform_dev, priv);
+ hp_wmi_apply_fan_settings(priv);
+
return 0;
}
+static void __init setup_active_thermal_profile_params(void)
+{
+ const struct dmi_system_id *id;
+
+ /*
+ * Currently only victus_s devices use the
+ * active_thermal_profile_params
+ */
+ id = dmi_first_match(victus_s_thermal_profile_boards);
+ if (id) {
+ /*
+ * Marking this boolean is required to ensure that
+ * is_victus_s_thermal_profile() behaves like a valid
+ * wrapper.
+ */
+ is_victus_s_board = true;
+ active_thermal_profile_params = id->driver_data;
+ if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
+ 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",
+ dmi_get_system_info(DMI_BOARD_NAME));
+ }
+ }
+}
+
static int __init hp_wmi_init(void)
{
int event_capable = wmi_has_guid(HPWMI_EVENT_GUID);
@@ -2254,6 +2624,11 @@ static int __init hp_wmi_init(void)
goto err_destroy_input;
}
+ /*
+ * Setup active board's thermal profile parameters before
+ * starting platform driver probe.
+ */
+ setup_active_thermal_profile_params();
err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup);
if (err)
goto err_unregister_device;
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index 7d7ae8a40b0e..02b303418d18 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -776,16 +776,26 @@ static inline u64 adjust_lpm_residency(struct pmc *pmc, u32 offset,
static int pmc_core_substate_res_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
- struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
- const int lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
- u32 offset = pmc->map->lpm_residency_offset;
- int mode;
+ unsigned int pmc_idx;
+
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
+ int lpm_adj_x2;
+ struct pmc *pmc;
+ u32 offset;
+ u8 mode;
- seq_printf(s, "%-10s %-15s\n", "Substate", "Residency");
+ pmc = pmcdev->pmcs[pmc_idx];
+ if (!pmc)
+ continue;
+
+ lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
+ offset = pmc->map->lpm_residency_offset;
- pmc_for_each_mode(mode, pmcdev) {
- seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode],
- adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2));
+ seq_printf(s, "pmc%u %10s %15s\n", pmc_idx, "Substate", "Residency");
+ pmc_for_each_mode(mode, pmc) {
+ seq_printf(s, "%15s %15llu\n", pmc_lpm_modes[mode],
+ adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2));
+ }
}
return 0;
@@ -838,10 +848,11 @@ static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index,
enum header_type type)
{
struct pmc_dev *pmcdev = s->private;
- int mode;
+ struct pmc *pmc = pmcdev->pmcs[pmc_index];
+ u8 mode;
seq_printf(s, "%40s |", "Element");
- pmc_for_each_mode(mode, pmcdev)
+ pmc_for_each_mode(mode, pmc)
seq_printf(s, " %9s |", pmc_lpm_modes[mode]);
if (type == HEADER_STATUS) {
@@ -880,14 +891,14 @@ static int pmc_core_substate_blk_req_show(struct seq_file *s, void *unused)
const struct pmc_bit_map *map;
for (map = maps[r_idx]; map->name; map++) {
- int mode;
+ u8 mode;
if (!map->blk)
continue;
counter = pmc_core_reg_read(pmc, offset);
seq_printf(s, "pmc%u: %34s |", pmc_idx, map->name);
- pmc_for_each_mode(mode, pmcdev) {
+ pmc_for_each_mode(mode, pmc) {
bool required = *lpm_req_regs & BIT(mode);
seq_printf(s, " %9s |", required ? "Required" : " ");
@@ -953,14 +964,15 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
u32 lpm_status;
u32 lpm_status_live;
const struct pmc_bit_map *map;
- int mode, i, len = 32;
+ int i, len = 32;
+ u8 mode;
/*
* Capture the requirements and create a mask so that we only
* show an element if it's required for at least one of the
* enabled low power modes
*/
- pmc_for_each_mode(mode, pmcdev)
+ pmc_for_each_mode(mode, pmc)
req_mask |= lpm_req_regs[mp + (mode * num_maps)];
/* 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)
seq_printf(s, "pmc%d: %34s |", pmc_idx, map[i].name);
/* Loop over the enabled states and display if required */
- pmc_for_each_mode(mode, pmcdev) {
+ pmc_for_each_mode(mode, pmc) {
bool required = lpm_req_regs[mp + (mode * num_maps)] &
bit_mask;
seq_printf(s, " %9s |", required ? "Required" : " ");
@@ -1065,7 +1077,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
bool c10;
u32 reg;
- int mode;
+ u8 mode;
reg = pmc_core_reg_read(pmc, pmc->map->lpm_sts_latch_en_offset);
if (reg & LPM_STS_LATCH_MODE) {
@@ -1076,7 +1088,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
c10 = true;
}
- pmc_for_each_mode(mode, pmcdev) {
+ pmc_for_each_mode(mode, pmc) {
if ((BIT(mode) & reg) && !c10)
seq_printf(s, " [%s]", pmc_lpm_modes[mode]);
else
@@ -1097,8 +1109,9 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
bool clear = false, c10 = false;
unsigned char buf[8];
- int m, mode;
+ int mode;
u32 reg;
+ u8 m;
if (count > sizeof(buf) - 1)
return -EINVAL;
@@ -1115,7 +1128,7 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
mode = sysfs_match_string(pmc_lpm_modes, buf);
/* Check string matches enabled mode */
- pmc_for_each_mode(m, pmcdev)
+ pmc_for_each_mode(m, pmc)
if (mode == m)
break;
@@ -1211,15 +1224,15 @@ static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order)
return true;
}
-void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
+static void pmc_core_pmc_get_low_power_modes(struct pmc_dev *pmcdev, struct pmc *pmc)
{
- struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI;
u8 mode_order[LPM_MAX_NUM_MODES];
u32 lpm_pri;
u32 lpm_en;
+ u8 mode;
unsigned int i;
- int mode, p;
+ int p;
/* Use LPM Maps to indicate support for substates */
if (!pmc->map->lpm_num_maps)
@@ -1230,12 +1243,11 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
* Lower byte is enough to cover the number of lpm modes for all
* platforms and hence mask the upper 3 bytes.
*/
- pmcdev->num_lpm_modes = hweight32(lpm_en & 0xFF);
+ pmc->num_lpm_modes = hweight32(lpm_en & 0xFF);
/* Read 32 bit LPM_PRI register */
lpm_pri = pmc_core_reg_read(pmc, pmc->map->lpm_priority_offset);
-
/*
* If lpm_pri value passes verification, then override the default
* modes here. Otherwise stick with the default.
@@ -1254,12 +1266,27 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
*/
i = 0;
for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) {
- int mode = pri_order[p];
+ u8 mode = pri_order[p];
if (!(BIT(mode) & lpm_en))
continue;
- pmcdev->lpm_en_modes[i++] = mode;
+ pmc->lpm_en_modes[i++] = mode;
+ }
+}
+
+static void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
+{
+ unsigned int pmc_idx;
+
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) {
+ struct pmc *pmc;
+
+ pmc = pmcdev->pmcs[pmc_idx];
+ if (!pmc)
+ continue;
+
+ pmc_core_pmc_get_low_power_modes(pmcdev, pmc);
}
}
@@ -1490,8 +1517,8 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel
{
const u8 *lpm_indices;
int num_maps, mode_offset = 0;
- int ret, mode;
- int lpm_size;
+ int ret, lpm_size;
+ u8 mode;
lpm_indices = pmc->map->lpm_reg_index;
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
return -ENOMEM;
mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET;
- pmc_for_each_mode(mode, pmcdev) {
+ pmc_for_each_mode(mode, pmc) {
u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps);
int m;
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
index 272fb4f57f34..118c8740ad3a 100644
--- a/drivers/platform/x86/intel/pmc/core.h
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -423,6 +423,8 @@ struct pmc_info {
* specific attributes
* @lpm_req_regs: List of substate requirements
* @ltr_ign: Holds LTR ignore data while suspended
+ * @num_lpm_modes: Count of enabled modes
+ * @lpm_en_modes: Array of enabled modes from lowest to highest priority
*
* pmc contains info about one power management controller device.
*/
@@ -432,6 +434,8 @@ struct pmc {
const struct pmc_reg_map *map;
u32 *lpm_req_regs;
u32 ltr_ign;
+ u8 num_lpm_modes;
+ u8 lpm_en_modes[LPM_MAX_NUM_MODES];
};
/**
@@ -446,8 +450,6 @@ struct pmc {
* @pkgc_res_cnt: Array of PKGC residency counters
* @num_of_pkgc: Number of PKGC
* @s0ix_counter: S0ix residency (step adjusted)
- * @num_lpm_modes: Count of enabled modes
- * @lpm_en_modes: Array of enabled modes from lowest to highest priority
* @suspend: Function to perform platform specific suspend
* @resume: Function to perform platform specific resume
*
@@ -462,8 +464,6 @@ struct pmc_dev {
struct mutex lock; /* generic mutex lock for PMC Core */
u64 s0ix_counter;
- int num_lpm_modes;
- int lpm_en_modes[LPM_MAX_NUM_MODES];
void (*suspend)(struct pmc_dev *pmcdev);
int (*resume)(struct pmc_dev *pmcdev);
@@ -535,7 +535,6 @@ int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore);
int pmc_core_resume_common(struct pmc_dev *pmcdev);
int get_primary_reg_base(struct pmc *pmc);
-void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev);
void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids);
void pmc_core_set_device_d3(unsigned int device);
@@ -563,10 +562,10 @@ int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc,
extern const struct file_operations pmc_core_substate_req_regs_fops;
extern const struct file_operations pmc_core_substate_blk_req_fops;
-#define pmc_for_each_mode(mode, pmcdev) \
+#define pmc_for_each_mode(mode, pmc) \
for (unsigned int __i = 0, __cond; \
- __cond = __i < (pmcdev)->num_lpm_modes, \
- __cond && ((mode) = (pmcdev)->lpm_en_modes[__i]), \
+ __cond = __i < (pmc)->num_lpm_modes, \
+ __cond && ((mode) = (pmc)->lpm_en_modes[__i]), \
__cond; \
__i++)
diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c
index 7c3023d5d91d..be3c8d9e4fff 100644
--- a/drivers/platform/x86/intel/pmt/class.c
+++ b/drivers/platform/x86/intel/pmt/class.c
@@ -140,7 +140,7 @@ guid_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sprintf(buf, "0x%x\n", entry->guid);
+ return sysfs_emit(buf, "0x%x\n", entry->guid);
}
static DEVICE_ATTR_RO(guid);
@@ -149,7 +149,7 @@ static ssize_t size_show(struct device *dev, struct device_attribute *attr,
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sprintf(buf, "%zu\n", entry->size);
+ return sysfs_emit(buf, "%zu\n", entry->size);
}
static DEVICE_ATTR_RO(size);
@@ -158,7 +158,7 @@ offset_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr));
+ return sysfs_emit(buf, "%lu\n", offset_in_page(entry->base_addr));
}
static DEVICE_ATTR_RO(offset);
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
index 34bff2f65a83..9c078c8acb50 100644
--- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
+++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
@@ -612,6 +612,9 @@ static long isst_if_core_power_state(void __user *argp)
return -EINVAL;
if (core_power.get_set) {
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
_write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET,
SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE)
_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)
return -EINVAL;
if (clos_param.get_set) {
- if (power_domain_info->write_blocked)
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
_write_cp_info("clos.min_freq", clos_param.min_freq_mhz,
@@ -748,7 +751,8 @@ static long isst_if_clos_assoc(void __user *argp)
power_domain_info = &sst_inst->power_domain_info[part][punit_id];
- if (assoc_cmds.get_set && power_domain_info->write_blocked)
+ if (assoc_cmds.get_set && (power_domain_info->write_blocked ||
+ !capable(CAP_SYS_ADMIN)))
return -EPERM;
offset = SST_CLOS_ASSOC_0_OFFSET +
@@ -925,7 +929,7 @@ static int isst_if_set_perf_level(void __user *argp)
if (!power_domain_info)
return -EINVAL;
- if (power_domain_info->write_blocked)
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
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)
if (!power_domain_info)
return -EINVAL;
- if (power_domain_info->write_blocked)
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
_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)
}
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_remove, "INTEL_TPMI_SST");
+#define SST_PP_CAP_CP_ENABLE BIT(0)
+#define SST_PP_CAP_PP_ENABLE BIT(1)
+
void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev)
{
struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
- struct tpmi_per_power_domain_info *power_domain_info;
+ struct tpmi_per_power_domain_info *power_domain_info, *pd_info;
struct oobmsm_plat_info *plat_info;
void __iomem *cp_base;
+ int num_resources, i;
plat_info = tpmi_get_platform_data(auxdev);
if (!plat_info)
return;
power_domain_info = tpmi_sst->power_domain_info[plat_info->partition];
+ num_resources = tpmi_sst->number_of_power_domains[plat_info->partition];
- cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
- power_domain_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET);
+ for (i = 0; i < num_resources; i++) {
+ pd_info = &power_domain_info[i];
+ if (!pd_info || !pd_info->sst_base)
+ continue;
+
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE))
+ goto process_pp_suspend;
- memcpy_fromio(power_domain_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET,
- sizeof(power_domain_info->saved_clos_configs));
+ cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset;
+ pd_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET);
+ memcpy_fromio(pd_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET,
+ sizeof(pd_info->saved_clos_configs));
+ memcpy_fromio(pd_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET,
+ sizeof(pd_info->saved_clos_assocs));
- memcpy_fromio(power_domain_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET,
- sizeof(power_domain_info->saved_clos_assocs));
+process_pp_suspend:
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE))
+ continue;
- power_domain_info->saved_pp_control = readq(power_domain_info->sst_base +
- power_domain_info->sst_header.pp_offset +
- SST_PP_CONTROL_OFFSET);
+ pd_info->saved_pp_control = readq(pd_info->sst_base +
+ pd_info->sst_header.pp_offset +
+ SST_PP_CONTROL_OFFSET);
+ }
}
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_suspend, "INTEL_TPMI_SST");
void tpmi_sst_dev_resume(struct auxiliary_device *auxdev)
{
struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
- struct tpmi_per_power_domain_info *power_domain_info;
+ struct tpmi_per_power_domain_info *power_domain_info, *pd_info;
struct oobmsm_plat_info *plat_info;
void __iomem *cp_base;
+ int num_resources, i;
plat_info = tpmi_get_platform_data(auxdev);
if (!plat_info)
return;
power_domain_info = tpmi_sst->power_domain_info[plat_info->partition];
+ num_resources = tpmi_sst->number_of_power_domains[plat_info->partition];
+
+ for (i = 0; i < num_resources; i++) {
+ pd_info = &power_domain_info[i];
+ if (!pd_info || !pd_info->sst_base)
+ continue;
- cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
- writeq(power_domain_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET);
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE))
+ goto process_pp_resume;
- memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, power_domain_info->saved_clos_configs,
- sizeof(power_domain_info->saved_clos_configs));
+ cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset;
+ writeq(pd_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET);
+ memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, pd_info->saved_clos_configs,
+ sizeof(pd_info->saved_clos_configs));
+ memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, pd_info->saved_clos_assocs,
+ sizeof(pd_info->saved_clos_assocs));
- memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, power_domain_info->saved_clos_assocs,
- sizeof(power_domain_info->saved_clos_assocs));
+process_pp_resume:
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE))
+ continue;
- writeq(power_domain_info->saved_pp_control, power_domain_info->sst_base +
- power_domain_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
+ writeq(pd_info->saved_pp_control, power_domain_info->sst_base +
+ pd_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
+ }
}
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_resume, "INTEL_TPMI_SST");
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
index 65897fae17df..7070c94324e0 100644
--- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
@@ -26,21 +26,21 @@ static ssize_t show_domain_id(struct kobject *kobj, struct kobj_attribute *attr,
{
struct uncore_data *data = container_of(attr, struct uncore_data, domain_id_kobj_attr);
- return sprintf(buf, "%u\n", data->domain_id);
+ return sysfs_emit(buf, "%u\n", data->domain_id);
}
static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_kobj_attr);
- return sprintf(buf, "%u\n", data->cluster_id);
+ return sysfs_emit(buf, "%u\n", data->cluster_id);
}
static ssize_t show_package_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct uncore_data *data = container_of(attr, struct uncore_data, package_id_kobj_attr);
- return sprintf(buf, "%u\n", data->package_id);
+ return sysfs_emit(buf, "%u\n", data->package_id);
}
#define MAX_UNCORE_AGENT_TYPES 4
@@ -77,7 +77,7 @@ static ssize_t show_attr(struct uncore_data *data, char *buf, enum uncore_index
if (ret)
return ret;
- return sprintf(buf, "%u\n", value);
+ return sysfs_emit(buf, "%u\n", value);
}
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)
goto uncore_unlock;
data->instance_id = ret;
- sprintf(data->name, "uncore%02d", ret);
+ scnprintf(data->name, sizeof(data->name), "uncore%02d", ret);
} else {
- sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id);
+ scnprintf(data->name, sizeof(data->name), "package_%02d_die_%02d",
+ data->package_id, data->die_id);
}
uncore_read(data, &data->initial_min_freq_khz, UNCORE_INDEX_MIN_FREQ);
diff --git a/drivers/platform/x86/intel/wmi/sbl-fw-update.c b/drivers/platform/x86/intel/wmi/sbl-fw-update.c
index 75c82c08117f..3716ccaaed6a 100644
--- a/drivers/platform/x86/intel/wmi/sbl-fw-update.c
+++ b/drivers/platform/x86/intel/wmi/sbl-fw-update.c
@@ -14,7 +14,6 @@
* https://slimbootloader.github.io/security/firmware-update.html
*/
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/slab.h>
@@ -25,41 +24,35 @@
static int get_fwu_request(struct device *dev, u32 *out)
{
- union acpi_object *obj;
+ struct wmi_buffer buffer;
+ __le32 *result;
+ int ret;
- obj = wmidev_block_query(to_wmi_device(dev), 0);
- if (!obj)
- return -ENODEV;
+ ret = wmidev_query_block(to_wmi_device(dev), 0, &buffer);
+ if (ret < 0)
+ return ret;
- if (obj->type != ACPI_TYPE_INTEGER) {
- dev_warn(dev, "wmidev_block_query returned invalid value\n");
- kfree(obj);
- return -EINVAL;
+ if (buffer.length < sizeof(*result)) {
+ kfree(buffer.data);
+ return -ENODATA;
}
- *out = obj->integer.value;
- kfree(obj);
+ result = buffer.data;
+ *out = le32_to_cpu(*result);
+ kfree(result);
return 0;
}
static int set_fwu_request(struct device *dev, u32 in)
{
- struct acpi_buffer input;
- acpi_status status;
- u32 value;
-
- value = in;
- input.length = sizeof(u32);
- input.pointer = &value;
-
- status = wmidev_block_set(to_wmi_device(dev), 0, &input);
- if (ACPI_FAILURE(status)) {
- dev_err(dev, "wmidev_block_set failed\n");
- return -ENODEV;
- }
+ __le32 value = cpu_to_le32(in);
+ struct wmi_buffer buffer = {
+ .length = sizeof(value),
+ .data = &value,
+ };
- return 0;
+ return wmidev_set_block(to_wmi_device(dev), 0, &buffer);
}
static ssize_t firmware_update_request_show(struct device *dev,
diff --git a/drivers/platform/x86/intel/wmi/thunderbolt.c b/drivers/platform/x86/intel/wmi/thunderbolt.c
index 15e5763a20dd..47017f2d7597 100644
--- a/drivers/platform/x86/intel/wmi/thunderbolt.c
+++ b/drivers/platform/x86/intel/wmi/thunderbolt.c
@@ -7,7 +7,6 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/hex.h>
@@ -24,24 +23,21 @@ static ssize_t force_power_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
- struct acpi_buffer input;
- acpi_status status;
+ struct wmi_buffer buffer;
+ int ret;
u8 mode;
- input.length = sizeof(u8);
- input.pointer = &mode;
+ buffer.length = sizeof(mode);
+ buffer.data = &mode;
+
mode = hex_to_bin(buf[0]);
- dev_dbg(dev, "force_power: storing %#x\n", mode);
- if (mode == 0 || mode == 1) {
- status = wmidev_evaluate_method(to_wmi_device(dev), 0, 1, &input, NULL);
- if (ACPI_FAILURE(status)) {
- dev_dbg(dev, "force_power: failed to evaluate ACPI method\n");
- return -ENODEV;
- }
- } else {
- dev_dbg(dev, "force_power: unsupported mode\n");
+ if (mode > 1)
return -EINVAL;
- }
+
+ ret = wmidev_invoke_method(to_wmi_device(dev), 0, 1, &buffer, NULL);
+ if (ret < 0)
+ return ret;
+
return count;
}
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index d22b774e0236..f885127b007f 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -233,7 +233,7 @@ config YT2_1380
To compile this driver as a module, choose M here: the module will
be called lenovo-yogabook.
-config LENOVO_WMI_DATA01
+config LENOVO_WMI_CAPDATA
tristate
depends on ACPI_WMI
@@ -263,8 +263,9 @@ config LENOVO_WMI_GAMEZONE
config LENOVO_WMI_TUNING
tristate "Lenovo Other Mode WMI Driver"
depends on ACPI_WMI
+ select HWMON
select FW_ATTR_CLASS
- select LENOVO_WMI_DATA01
+ select LENOVO_WMI_CAPDATA
select LENOVO_WMI_EVENTS
select LENOVO_WMI_HELPERS
help
diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
index 7b2128e3a214..91a9370f11b3 100644
--- a/drivers/platform/x86/lenovo/Makefile
+++ b/drivers/platform/x86/lenovo/Makefile
@@ -12,7 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o
-lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) += wmi-capdata01.o
+lenovo-target-$(CONFIG_LENOVO_WMI_CAPDATA) += wmi-capdata.o
lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o
lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o
lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += wmi-gamezone.o
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 7d5f7a2f6564..06ac002a1ebc 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -219,38 +219,32 @@ MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
static bool allow_v4_dytc;
module_param(allow_v4_dytc, bool, 0444);
MODULE_PARM_DESC(allow_v4_dytc,
- "Enable DYTC version 4 platform-profile support. "
- "If you need this please report this to: platform-driver-x86@vger.kernel.org");
+ "Enable DYTC version 4 platform-profile support. If you need this please report this to: platform-driver-x86@vger.kernel.org");
static bool hw_rfkill_switch;
module_param(hw_rfkill_switch, bool, 0444);
MODULE_PARM_DESC(hw_rfkill_switch,
- "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");
+ "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");
static bool set_fn_lock_led;
module_param(set_fn_lock_led, bool, 0444);
MODULE_PARM_DESC(set_fn_lock_led,
- "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");
+ "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");
static bool ctrl_ps2_aux_port;
module_param(ctrl_ps2_aux_port, bool, 0444);
MODULE_PARM_DESC(ctrl_ps2_aux_port,
- "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");
+ "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");
static bool touchpad_ctrl_via_ec;
module_param(touchpad_ctrl_via_ec, bool, 0444);
MODULE_PARM_DESC(touchpad_ctrl_via_ec,
- "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.");
+ "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.");
static bool ymc_ec_trigger __read_mostly;
module_param(ymc_ec_trigger, bool, 0444);
MODULE_PARM_DESC(ymc_ec_trigger,
- "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");
+ "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");
/*
* shared data
@@ -1446,7 +1440,7 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv)
if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value))
return;
- for_each_set_bit (bit, &value, 16) {
+ for_each_set_bit(bit, &value, 16) {
switch (bit) {
case 6: /* Z570 */
case 0: /* Z580 */
@@ -1706,11 +1700,10 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
if (WARN_ON(priv->kbd_bl.initialized))
return -EEXIST;
- if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) {
+ if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type))
priv->kbd_bl.led.max_brightness = 2;
- } else {
+ else
priv->kbd_bl.led.max_brightness = 1;
- }
brightness = ideapad_kbd_bl_brightness_get(priv);
if (brightness < 0)
@@ -1752,7 +1745,7 @@ static enum led_brightness ideapad_fn_lock_led_cdev_get(struct led_classdev *led
}
static int ideapad_fn_lock_led_cdev_set(struct led_classdev *led_cdev,
- enum led_brightness brightness)
+ enum led_brightness brightness)
{
struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, fn_lock.led);
@@ -1928,7 +1921,7 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
vpc1 = (vpc2 << 8) | vpc1;
- for_each_set_bit (bit, &vpc1, 16) {
+ for_each_set_bit(bit, &vpc1, 16) {
switch (bit) {
case 13:
case 11:
@@ -2142,14 +2135,14 @@ static const enum power_supply_property ideapad_power_supply_props[] = {
}
DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v1,
- (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
- BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
+ (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
);
DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v2,
- (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
- BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
- BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
+ (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
);
static int ideapad_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
index cc19fe520ea9..6b0e4b4c485e 100644
--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c
+++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c
@@ -36,6 +36,7 @@
#include <linux/acpi.h>
#include <linux/backlight.h>
+#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/dmi.h>
@@ -11080,6 +11081,206 @@ static const struct attribute_group auxmac_attr_group = {
.attrs = auxmac_attributes,
};
+/*************************************************************************
+ * HWDD subdriver, for the Lenovo Hardware Damage Detection feature.
+ */
+
+#define HWDD_GET_DMG_USBC 0x80000001
+#define HWDD_GET_CAP 0
+#define HWDD_NOT_SUPPORTED BIT(31)
+#define HWDD_SUPPORT_USBC BIT(0)
+
+#define PORT_STATUS GENMASK(7, 4)
+#define LID_STATUS GENMASK(11, 8)
+#define BASE_STATUS GENMASK(15, 12)
+#define POS_STATUS GENMASK(3, 2)
+#define PANEL_STATUS GENMASK(1, 0)
+
+#define PORT_DETAIL_OFFSET 16
+
+#define PANEL_TOP 0
+#define PANEL_BASE 1
+#define PANEL_LEFT 2
+#define PANEL_RIGHT 3
+
+#define POS_LEFT 0
+#define POS_CENTER 1
+#define POS_RIGHT 2
+
+#define NUM_PORTS 4
+
+static bool hwdd_support_available;
+static bool ucdd_supported;
+
+static int hwdd_command(int command, int *output)
+{
+ acpi_handle hwdd_handle;
+
+ if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "HWDD", &hwdd_handle)))
+ return -ENODEV;
+
+ if (!acpi_evalf(hwdd_handle, output, NULL, "dd", command))
+ return -EIO;
+
+ return 0;
+}
+
+static bool display_damage(char *buf, int *count, char *type, unsigned int dmg_status)
+{
+ unsigned char lid_status, base_status, port_status;
+ unsigned char loc_status, pos_status, panel_status;
+ bool damage_detected = false;
+ int i;
+
+ port_status = FIELD_GET(PORT_STATUS, dmg_status);
+ lid_status = FIELD_GET(LID_STATUS, dmg_status);
+ base_status = FIELD_GET(BASE_STATUS, dmg_status);
+ for (i = 0; i < NUM_PORTS; i++) {
+ if (!(dmg_status & BIT(i)) || !(port_status & BIT(i)))
+ continue;
+
+ *count += sysfs_emit_at(buf, *count, "%s: ", type);
+ loc_status = (dmg_status >> (PORT_DETAIL_OFFSET + (4 * i))) & 0xF;
+ pos_status = FIELD_GET(POS_STATUS, loc_status);
+ panel_status = FIELD_GET(PANEL_STATUS, loc_status);
+
+ if (lid_status & BIT(i))
+ *count += sysfs_emit_at(buf, *count, "Lid, ");
+ if (base_status & BIT(i))
+ *count += sysfs_emit_at(buf, *count, "Base, ");
+
+ switch (pos_status) {
+ case PANEL_TOP:
+ *count += sysfs_emit_at(buf, *count, "Top, ");
+ break;
+ case PANEL_BASE:
+ *count += sysfs_emit_at(buf, *count, "Bottom, ");
+ break;
+ case PANEL_LEFT:
+ *count += sysfs_emit_at(buf, *count, "Left, ");
+ break;
+ case PANEL_RIGHT:
+ *count += sysfs_emit_at(buf, *count, "Right, ");
+ break;
+ default:
+ pr_err("Unexpected value %d in switch statement\n", pos_status);
+ }
+
+ switch (panel_status) {
+ case POS_LEFT:
+ *count += sysfs_emit_at(buf, *count, "Left port\n");
+ break;
+ case POS_CENTER:
+ *count += sysfs_emit_at(buf, *count, "Center port\n");
+ break;
+ case POS_RIGHT:
+ *count += sysfs_emit_at(buf, *count, "Right port\n");
+ break;
+ default:
+ *count += sysfs_emit_at(buf, *count, "Undefined\n");
+ break;
+ }
+ damage_detected = true;
+ }
+ return damage_detected;
+}
+
+/* sysfs type-c damage detection detail */
+static ssize_t hwdd_detail_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int damage_status;
+ int err, count = 0;
+
+ if (!ucdd_supported)
+ return -ENODEV;
+
+ /* Get USB TYPE-C damage status */
+ err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status);
+ if (err)
+ return err;
+
+ if (!display_damage(buf, &count, "Type-C", damage_status))
+ count += sysfs_emit_at(buf, count, "No damage detected\n");
+
+ return count;
+}
+
+/* sysfs type-c damage detection capability */
+static ssize_t hwdd_status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int damage_status, port_status;
+ int err, i;
+
+ if (!ucdd_supported)
+ return -ENODEV;
+
+ /* Get USB TYPE-C damage status */
+ err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status);
+ if (err)
+ return err;
+
+ port_status = FIELD_GET(PORT_STATUS, damage_status);
+ for (i = 0; i < NUM_PORTS; i++) {
+ if (!(damage_status & BIT(i)))
+ continue;
+ if (port_status & BIT(i))
+ return sysfs_emit(buf, "1\n");
+ }
+
+ return sysfs_emit(buf, "0\n");
+}
+static DEVICE_ATTR_RO(hwdd_status);
+static DEVICE_ATTR_RO(hwdd_detail);
+
+static struct attribute *hwdd_attributes[] = {
+ &dev_attr_hwdd_status.attr,
+ &dev_attr_hwdd_detail.attr,
+ NULL
+};
+
+static umode_t hwdd_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ return hwdd_support_available ? attr->mode : 0;
+}
+
+static const struct attribute_group hwdd_attr_group = {
+ .is_visible = hwdd_attr_is_visible,
+ .attrs = hwdd_attributes,
+};
+
+static int tpacpi_hwdd_init(struct ibm_init_struct *iibm)
+{
+ int err, output;
+
+ /* Below command checks the HWDD damage capability */
+ err = hwdd_command(HWDD_GET_CAP, &output);
+ if (err)
+ return err;
+
+ if (!(output & HWDD_NOT_SUPPORTED))
+ return -ENODEV;
+
+ hwdd_support_available = true;
+
+ /*
+ * BIT(0) is assigned to check capability of damage detection is
+ * supported for USB Type-C port or not.
+ */
+ if (output & HWDD_SUPPORT_USBC)
+ ucdd_supported = true;
+
+ return err;
+}
+
+static struct ibm_struct hwdd_driver_data = {
+ .name = "hwdd",
+};
+
/* --------------------------------------------------------------------- */
static struct attribute *tpacpi_driver_attributes[] = {
@@ -11139,6 +11340,7 @@ static const struct attribute_group *tpacpi_groups[] = {
&kbdlang_attr_group,
&dprc_attr_group,
&auxmac_attr_group,
+ &hwdd_attr_group,
NULL,
};
@@ -11752,6 +11954,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.init = auxmac_init,
.data = &auxmac_data,
},
+ {
+ .init = tpacpi_hwdd_init,
+ .data = &hwdd_driver_data,
+ },
};
static int __init set_ibm_param(const char *val, const struct kernel_param *kp)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
new file mode 100644
index 000000000000..ee1fb02d8e31
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -0,0 +1,829 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Capability Data WMI Data Block driver.
+ *
+ * Lenovo Capability Data provides information on tunable attributes used by
+ * the "Other Mode" WMI interface.
+ *
+ * Capability Data 00 includes if the attribute is supported by the hardware,
+ * and the default_value. All attributes are independent of thermal modes.
+ *
+ * Capability Data 01 includes if the attribute is supported by the hardware,
+ * and the default_value, max_value, min_value, and step increment. Each
+ * attribute has multiple pages, one for each of the thermal modes managed by
+ * the Gamezone interface.
+ *
+ * Fan Test Data includes the max/min fan speed RPM for each fan. This is
+ * reference data for self-test. If the fan is in good condition, it is capable
+ * to spin faster than max RPM or slower than min RPM.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ * - Initial implementation (formerly named lenovo-wmi-capdata01)
+ *
+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
+ * - Unified implementation
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/gfp_types.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mutex_types.h>
+#include <linux/notifier.h>
+#include <linux/overflow.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "wmi-capdata.h"
+
+#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E"
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+#define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21"
+
+#define ACPI_AC_CLASS "ac_adapter"
+#define ACPI_AC_NOTIFY_STATUS 0x80
+
+#define LWMI_FEATURE_ID_FAN_TEST 0x05
+
+#define LWMI_ATTR_ID_FAN_TEST \
+ (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST))
+
+enum lwmi_cd_type {
+ LENOVO_CAPABILITY_DATA_00,
+ LENOVO_CAPABILITY_DATA_01,
+ LENOVO_FAN_TEST_DATA,
+ CD_TYPE_NONE = -1,
+};
+
+#define LWMI_CD_TABLE_ITEM(_type) \
+ [_type] = { \
+ .name = #_type, \
+ .type = _type, \
+ }
+
+static const struct lwmi_cd_info {
+ const char *name;
+ enum lwmi_cd_type type;
+} lwmi_cd_table[] = {
+ LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00),
+ LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01),
+ LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA),
+};
+
+struct lwmi_cd_priv {
+ struct notifier_block acpi_nb; /* ACPI events */
+ struct wmi_device *wdev;
+ struct cd_list *list;
+
+ /*
+ * A capdata device may be a component master of another capdata device.
+ * E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan
+ * |- master |- component
+ * |- sub-master
+ * |- sub-component
+ */
+ struct lwmi_cd_sub_master_priv {
+ struct device *master_dev;
+ cd_list_cb_t master_cb;
+ struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */
+ bool registered; /* Has the sub-master been registered? */
+ } *sub_master;
+};
+
+struct cd_list {
+ struct mutex list_mutex; /* list R/W mutex */
+ enum lwmi_cd_type type;
+ u8 count;
+
+ union {
+ DECLARE_FLEX_ARRAY(struct capdata00, cd00);
+ DECLARE_FLEX_ARRAY(struct capdata01, cd01);
+ DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan);
+ };
+};
+
+static struct wmi_driver lwmi_cd_driver;
+
+/**
+ * lwmi_cd_match() - Match rule for the master driver.
+ * @dev: Pointer to the capability data parent device.
+ * @type: Pointer to capability data type (enum lwmi_cd_type *) to match.
+ *
+ * Return: int.
+ */
+static int lwmi_cd_match(struct device *dev, void *type)
+{
+ struct lwmi_cd_priv *priv;
+
+ if (dev->driver != &lwmi_cd_driver.driver)
+ return false;
+
+ priv = dev_get_drvdata(dev);
+ return priv->list->type == *(enum lwmi_cd_type *)type;
+}
+
+/**
+ * lwmi_cd_match_add_all() - Add all match rule for the master driver.
+ * @master: Pointer to the master device.
+ * @matchptr: Pointer to the returned component_match pointer.
+ *
+ * Adds all component matches to the list stored in @matchptr for the @master
+ * device. @matchptr must be initialized to NULL.
+ */
+void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr)
+{
+ int i;
+
+ if (WARN_ON(*matchptr))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) {
+ /* Skip sub-components. */
+ if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA)
+ continue;
+
+ component_match_add(master, matchptr, lwmi_cd_match,
+ (void *)&lwmi_cd_table[i].type);
+ if (IS_ERR(*matchptr))
+ return;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA");
+
+/**
+ * lwmi_cd_call_master_cb() - Call the master callback for the sub-component.
+ * @priv: Pointer to the capability data private data.
+ *
+ * Call the master callback and pass the sub-component list to it if the
+ * dependency chain (master <-> sub-master <-> sub-component) is complete.
+ */
+static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv)
+{
+ struct cd_list *sub_component_list = priv->sub_master->sub_component_list;
+
+ /*
+ * Call the callback only if the dependency chain is ready:
+ * - Binding between master and sub-master: fills master_dev and master_cb
+ * - Binding between sub-master and sub-component: fills sub_component_list
+ *
+ * If a binding has been unbound before the other binding is bound, the
+ * corresponding members filled by the former are guaranteed to be cleared.
+ *
+ * This function is only called in bind callbacks, and the component
+ * framework guarantees bind/unbind callbacks may never execute
+ * simultaneously, which implies that it's impossible to have a race
+ * condition.
+ *
+ * Hence, this check is sufficient to ensure that the callback is called
+ * at most once and with the correct state, without relying on a specific
+ * sequence of binding establishment.
+ */
+ if (!sub_component_list ||
+ !priv->sub_master->master_dev ||
+ !priv->sub_master->master_cb)
+ return;
+
+ if (PTR_ERR(sub_component_list) == -ENODEV)
+ sub_component_list = NULL;
+ else if (WARN_ON(IS_ERR(sub_component_list)))
+ return;
+
+ priv->sub_master->master_cb(priv->sub_master->master_dev,
+ sub_component_list);
+
+ /*
+ * Userspace may unbind a device from its driver and bind it again
+ * through sysfs. Let's call this operation "reprobe" to distinguish it
+ * from component "rebind".
+ *
+ * When reprobing capdata00/01 or the master device, the master device
+ * is unbound from us with appropriate cleanup before we bind to it and
+ * call master_cb. Everything is fine in this case.
+ *
+ * When reprobing capdata_fan, the master device has never been unbound
+ * from us (hence no cleanup is done)[1], but we call master_cb the
+ * second time. To solve this issue, we clear master_cb and master_dev
+ * so we won't call master_cb twice while a binding is still complete.
+ *
+ * Note that we can't clear sub_component_list, otherwise reprobing
+ * capdata01 or the master device causes master_cb to be never called
+ * after we rebind to the master device.
+ *
+ * [1]: The master device does not need capdata_fan in run time, so
+ * losing capdata_fan will not break the binding to the master device.
+ */
+ priv->sub_master->master_cb = NULL;
+ priv->sub_master->master_dev = NULL;
+}
+
+/**
+ * lwmi_cd_component_bind() - Bind component to master device.
+ * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
+ * @data: lwmi_cd_binder object pointer used to return the capability data.
+ *
+ * On lenovo-wmi-other's master bind, provide a pointer to the local capdata
+ * list. This is used to call lwmi_cd*_get_data to look up attribute data
+ * from the lenovo-wmi-other driver.
+ *
+ * If cd_dev is a sub-master, try to call the master callback.
+ *
+ * Return: 0
+ */
+static int lwmi_cd_component_bind(struct device *cd_dev,
+ struct device *om_dev, void *data)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
+ struct lwmi_cd_binder *binder = data;
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ binder->cd00_list = priv->list;
+
+ priv->sub_master->master_dev = om_dev;
+ priv->sub_master->master_cb = binder->cd_fan_list_cb;
+ lwmi_cd_call_master_cb(priv);
+
+ break;
+ case LENOVO_CAPABILITY_DATA_01:
+ binder->cd01_list = priv->list;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_component_unbind() - Unbind component to master device.
+ * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
+ * @data: Unused.
+ *
+ * If cd_dev is a sub-master, clear the collected data from the master device to
+ * prevent the binding establishment between the sub-master and the sub-
+ * component (if it's about to happen) from calling the master callback.
+ */
+static void lwmi_cd_component_unbind(struct device *cd_dev,
+ struct device *om_dev, void *data)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ priv->sub_master->master_dev = NULL;
+ priv->sub_master->master_cb = NULL;
+ return;
+ default:
+ return;
+ }
+}
+
+static const struct component_ops lwmi_cd_component_ops = {
+ .bind = lwmi_cd_component_bind,
+ .unbind = lwmi_cd_component_unbind,
+};
+
+/**
+ * lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device
+ * @dev: The sub-master capdata basic device.
+ *
+ * Call component_bind_all to bind the sub-component device to the sub-master
+ * device. On success, collect the pointer to the sub-component list and try
+ * to call the master callback.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd_sub_master_bind(struct device *dev)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
+ struct cd_list *sub_component_list;
+ int ret;
+
+ ret = component_bind_all(dev, &sub_component_list);
+ if (ret)
+ return ret;
+
+ priv->sub_master->sub_component_list = sub_component_list;
+ lwmi_cd_call_master_cb(priv);
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device
+ * @dev: The sub-master capdata basic device
+ *
+ * Clear the collected pointer to the sub-component list to prevent the binding
+ * establishment between the sub-master and the sub-component (if it's about to
+ * happen) from calling the master callback. Then, call component_unbind_all to
+ * unbind the sub-component device from the sub-master device.
+ */
+static void lwmi_cd_sub_master_unbind(struct device *dev)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
+
+ priv->sub_master->sub_component_list = NULL;
+
+ component_unbind_all(dev, NULL);
+}
+
+static const struct component_master_ops lwmi_cd_sub_master_ops = {
+ .bind = lwmi_cd_sub_master_bind,
+ .unbind = lwmi_cd_sub_master_unbind,
+};
+
+/**
+ * lwmi_cd_sub_master_add() - Register a sub-master with its sub-component
+ * @priv: Pointer to the sub-master capdata device private data.
+ * @sub_component_type: Type of the sub-component.
+ *
+ * Match the sub-component type and register the current capdata device as a
+ * sub-master. If the given sub-component type is CD_TYPE_NONE, mark the sub-
+ * component as non-existent without registering sub-master.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv,
+ enum lwmi_cd_type sub_component_type)
+{
+ struct component_match *master_match = NULL;
+ int ret;
+
+ priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL);
+ if (!priv->sub_master)
+ return -ENOMEM;
+
+ if (sub_component_type == CD_TYPE_NONE) {
+ /* The master callback will be called with NULL on bind. */
+ priv->sub_master->sub_component_list = ERR_PTR(-ENODEV);
+ priv->sub_master->registered = false;
+ return 0;
+ }
+
+ /*
+ * lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack
+ * data cannot be used here. Steal one from lwmi_cd_table.
+ */
+ component_match_add(&priv->wdev->dev, &master_match, lwmi_cd_match,
+ (void *)&lwmi_cd_table[sub_component_type].type);
+ if (IS_ERR(master_match))
+ return PTR_ERR(master_match);
+
+ ret = component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_master_ops,
+ master_match);
+ if (ret)
+ return ret;
+
+ priv->sub_master->registered = true;
+ return 0;
+}
+
+/**
+ * lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered
+ * @priv: Pointer to the sub-master capdata device private data.
+ */
+static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv)
+{
+ if (!priv->sub_master->registered)
+ return;
+
+ component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops);
+ priv->sub_master->registered = false;
+}
+
+/**
+ * lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device.
+ * @sc_dev: Pointer to the sub-component capdata parent device.
+ * @sm_dev: Pointer to the sub-master capdata parent device.
+ * @data: Pointer used to return the capability data list pointer.
+ *
+ * On sub-master's bind, provide a pointer to the local capdata list.
+ * This is used by the sub-master to call the master callback.
+ *
+ * Return: 0
+ */
+static int lwmi_cd_sub_component_bind(struct device *sc_dev,
+ struct device *sm_dev, void *data)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(sc_dev);
+ struct cd_list **listp = data;
+
+ *listp = priv->list;
+
+ return 0;
+}
+
+static const struct component_ops lwmi_cd_sub_component_ops = {
+ .bind = lwmi_cd_sub_component_bind,
+};
+
+/*
+ * lwmi_cd*_get_data - Get the data of the specified attribute
+ * @list: The lenovo-wmi-capdata pointer to its cd_list struct.
+ * @attribute_id: The capdata attribute ID to be found.
+ * @output: Pointer to a capdata* struct to return the data.
+ *
+ * Retrieves the capability data struct pointer for the given
+ * attribute.
+ *
+ * Return: 0 on success, or -EINVAL.
+ */
+#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \
+ int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output) \
+ { \
+ u8 idx; \
+ \
+ if (WARN_ON(list->type != _cd_type)) \
+ return -EINVAL; \
+ \
+ guard(mutex)(&list->list_mutex); \
+ for (idx = 0; idx < list->count; idx++) { \
+ if (list->_cdxx[idx].id != attribute_id) \
+ continue; \
+ memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx])); \
+ return 0; \
+ } \
+ return -EINVAL; \
+ }
+
+DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00);
+EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA");
+
+DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA");
+
+DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan);
+EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA");
+
+/**
+ * lwmi_cd_cache() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata driver data.
+ *
+ * Loop through each WMI data block and cache the data.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
+{
+ size_t size;
+ int idx;
+ void *p;
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ p = &priv->list->cd00[0];
+ size = sizeof(priv->list->cd00[0]);
+ break;
+ case LENOVO_CAPABILITY_DATA_01:
+ p = &priv->list->cd01[0];
+ size = sizeof(priv->list->cd01[0]);
+ break;
+ case LENOVO_FAN_TEST_DATA:
+ /* Done by lwmi_cd_alloc() => lwmi_cd_fan_list_alloc_cache(). */
+ return 0;
+ default:
+ return -EINVAL;
+ }
+
+ guard(mutex)(&priv->list->list_mutex);
+ for (idx = 0; idx < priv->list->count; idx++, p += size) {
+ union acpi_object *ret_obj __free(kfree) = NULL;
+
+ ret_obj = wmidev_block_query(priv->wdev, idx);
+ if (!ret_obj)
+ return -ENODEV;
+
+ if (ret_obj->type != ACPI_TYPE_BUFFER ||
+ ret_obj->buffer.length < size)
+ continue;
+
+ memcpy(p, ret_obj->buffer.pointer, size);
+ }
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list
+ * @priv: lenovo-wmi-capdata driver data.
+ * @listptr: Pointer to returned cd_list pointer.
+ *
+ * Return: count of fans found, or an error.
+ */
+static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr)
+{
+ struct cd_list *list;
+ size_t size;
+ u32 count;
+ int idx;
+
+ /* Emit unaligned access to u8 buffer with __packed. */
+ struct cd_fan_block {
+ u32 nr;
+ u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */
+ } __packed * block;
+
+ union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0);
+ if (!ret_obj)
+ return -ENODEV;
+
+ if (ret_obj->type == ACPI_TYPE_BUFFER) {
+ block = (struct cd_fan_block *)ret_obj->buffer.pointer;
+ size = ret_obj->buffer.length;
+
+ count = size >= sizeof(*block) ? block->nr : 0;
+ if (size < struct_size(block, data, count * 3)) {
+ dev_warn(&priv->wdev->dev,
+ "incomplete fan test data block: %zu < %zu, ignoring\n",
+ size, struct_size(block, data, count * 3));
+ count = 0;
+ } else if (count > U8_MAX) {
+ dev_warn(&priv->wdev->dev,
+ "too many fans reported: %u > %u, truncating\n",
+ count, U8_MAX);
+ count = U8_MAX;
+ }
+ } else {
+ /*
+ * This is usually caused by a dummy ACPI method. Do not return an error
+ * as failing to probe this device will result in sub-master device being
+ * unbound. This behavior aligns with lwmi_cd_cache().
+ */
+ count = 0;
+ }
+
+ list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+ for (idx = 0; idx < count; idx++) {
+ /* Do not calculate array index using count, as it may be truncated. */
+ list->cd_fan[idx] = (struct capdata_fan) {
+ .id = block->data[idx],
+ .max_rpm = block->data[idx + block->nr],
+ .min_rpm = block->data[idx + (2 * block->nr)],
+ };
+ }
+
+ *listptr = list;
+ return count;
+}
+
+/**
+ * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
+ * @priv: lenovo-wmi-capdata driver data.
+ * @type: The type of capability data.
+ *
+ * Allocate a cd_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
+{
+ struct cd_list *list;
+ size_t list_size;
+ int count, ret;
+
+ count = wmidev_instance_count(priv->wdev);
+
+ switch (type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ list_size = struct_size(list, cd00, count);
+ break;
+ case LENOVO_CAPABILITY_DATA_01:
+ list_size = struct_size(list, cd01, count);
+ break;
+ case LENOVO_FAN_TEST_DATA:
+ count = lwmi_cd_fan_list_alloc_cache(priv, &list);
+ if (count < 0)
+ return count;
+
+ goto got_list;
+ default:
+ return -EINVAL;
+ }
+
+ list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+got_list:
+ ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
+ if (ret)
+ return ret;
+
+ list->type = type;
+ list->count = count;
+ priv->list = list;
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_setup() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata driver data.
+ * @type: The type of capability data.
+ *
+ * Allocate a cd_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface. Then loop through each data block and
+ * cache the data.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
+{
+ int ret;
+
+ ret = lwmi_cd_alloc(priv, type);
+ if (ret)
+ return ret;
+
+ return lwmi_cd_cache(priv);
+}
+
+/**
+ * lwmi_cd01_notifier_call() - Call method for cd01 notifier.
+ * block call chain.
+ * @nb: The notifier_block registered to lenovo-wmi-events driver.
+ * @action: Unused.
+ * @data: The ACPI event.
+ *
+ * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
+ * of a change.
+ *
+ * Return: notifier_block status.
+ */
+static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct acpi_bus_event *event = data;
+ struct lwmi_cd_priv *priv;
+ int ret;
+
+ if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
+ return NOTIFY_DONE;
+
+ priv = container_of(nb, struct lwmi_cd_priv, acpi_nb);
+
+ switch (event->type) {
+ case ACPI_AC_NOTIFY_STATUS:
+ ret = lwmi_cd_cache(priv);
+ if (ret)
+ return NOTIFY_BAD;
+
+ return NOTIFY_OK;
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+/**
+ * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
+ * @data: The ACPI event notifier_block to unregister.
+ */
+static void lwmi_cd01_unregister(void *data)
+{
+ struct notifier_block *acpi_nb = data;
+
+ unregister_acpi_notifier(acpi_nb);
+}
+
+static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
+{
+ const struct lwmi_cd_info *info = context;
+ struct lwmi_cd_priv *priv;
+ int ret;
+
+ if (!info)
+ return -EINVAL;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->wdev = wdev;
+ dev_set_drvdata(&wdev->dev, priv);
+
+ ret = lwmi_cd_setup(priv, info->type);
+ if (ret)
+ goto out;
+
+ switch (info->type) {
+ case LENOVO_CAPABILITY_DATA_00: {
+ enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA;
+ struct capdata00 capdata00;
+
+ ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00);
+ if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) {
+ dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n");
+ sub_component_type = CD_TYPE_NONE;
+ }
+
+ /* Sub-master (capdata00) <-> sub-component (capdata_fan) */
+ ret = lwmi_cd_sub_master_add(priv, sub_component_type);
+ if (ret)
+ goto out;
+
+ /* Master (lenovo-wmi-other) <-> sub-master (capdata00) */
+ ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
+ if (ret)
+ lwmi_cd_sub_master_del(priv);
+
+ goto out;
+ }
+ case LENOVO_CAPABILITY_DATA_01:
+ priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
+
+ ret = register_acpi_notifier(&priv->acpi_nb);
+ if (ret)
+ goto out;
+
+ ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister,
+ &priv->acpi_nb);
+ if (ret)
+ goto out;
+
+ ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
+ goto out;
+ case LENOVO_FAN_TEST_DATA:
+ ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops);
+ goto out;
+ default:
+ return -EINVAL;
+ }
+out:
+ if (ret) {
+ dev_err(&wdev->dev, "failed to register %s: %d\n",
+ info->name, ret);
+ } else {
+ dev_dbg(&wdev->dev, "registered %s with %u items\n",
+ info->name, priv->list->count);
+ }
+ return ret;
+}
+
+static void lwmi_cd_remove(struct wmi_device *wdev)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev);
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ lwmi_cd_sub_master_del(priv);
+ fallthrough;
+ case LENOVO_CAPABILITY_DATA_01:
+ component_del(&wdev->dev, &lwmi_cd_component_ops);
+ break;
+ case LENOVO_FAN_TEST_DATA:
+ component_del(&wdev->dev, &lwmi_cd_sub_component_ops);
+ break;
+ default:
+ WARN_ON(1);
+ }
+}
+
+#define LWMI_CD_WDEV_ID(_type) \
+ .guid_string = _type##_GUID, \
+ .context = &lwmi_cd_table[_type],
+
+static const struct wmi_device_id lwmi_cd_id_table[] = {
+ { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) },
+ { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) },
+ { LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) },
+ {}
+};
+
+static struct wmi_driver lwmi_cd_driver = {
+ .driver = {
+ .name = "lenovo_wmi_capdata",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = lwmi_cd_id_table,
+ .probe = lwmi_cd_probe,
+ .remove = lwmi_cd_remove,
+ .no_singleton = true,
+};
+
+module_wmi_driver(lwmi_cd_driver);
+
+MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
+MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
new file mode 100644
index 000000000000..8c1df3efcc55
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_CAPDATA_H_
+#define _LENOVO_WMI_CAPDATA_H_
+
+#include <linux/bits.h>
+#include <linux/types.h>
+
+#define LWMI_SUPP_VALID BIT(0)
+#define LWMI_SUPP_GET BIT(1)
+#define LWMI_SUPP_SET BIT(2)
+
+#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
+#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
+#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
+#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
+
+#define LWMI_DEVICE_ID_FAN 0x04
+
+struct component_match;
+struct device;
+struct cd_list;
+
+struct capdata00 {
+ u32 id;
+ u32 supported;
+ u32 default_value;
+};
+
+struct capdata01 {
+ u32 id;
+ u32 supported;
+ u32 default_value;
+ u32 step;
+ u32 min_value;
+ u32 max_value;
+};
+
+struct capdata_fan {
+ u32 id;
+ u32 min_rpm;
+ u32 max_rpm;
+};
+
+typedef void (*cd_list_cb_t)(struct device *master_dev, struct cd_list *cd_list);
+
+struct lwmi_cd_binder {
+ struct cd_list *cd00_list;
+ struct cd_list *cd01_list;
+ /*
+ * May be called during or after the bind callback.
+ * Will be called with NULL if capdata_fan does not exist.
+ * The pointer is only valid in the callback; never keep it for later use!
+ */
+ cd_list_cb_t cd_fan_list_cb;
+};
+
+void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
+int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capdata00 *output);
+int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output);
+int lwmi_cd_fan_get_data(struct cd_list *list, u32 attribute_id, struct capdata_fan *output);
+
+#endif /* !_LENOVO_WMI_CAPDATA_H_ */
diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.c b/drivers/platform/x86/lenovo/wmi-capdata01.c
deleted file mode 100644
index fc7e3454e71d..000000000000
--- a/drivers/platform/x86/lenovo/wmi-capdata01.c
+++ /dev/null
@@ -1,302 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Lenovo Capability Data 01 WMI Data Block driver.
- *
- * Lenovo Capability Data 01 provides information on tunable attributes used by
- * the "Other Mode" WMI interface. The data includes if the attribute is
- * supported by the hardware, the default_value, max_value, min_value, and step
- * increment. Each attribute has multiple pages, one for each of the thermal
- * modes managed by the Gamezone interface.
- *
- * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
- */
-
-#include <linux/acpi.h>
-#include <linux/cleanup.h>
-#include <linux/component.h>
-#include <linux/container_of.h>
-#include <linux/device.h>
-#include <linux/export.h>
-#include <linux/gfp_types.h>
-#include <linux/module.h>
-#include <linux/mutex.h>
-#include <linux/mutex_types.h>
-#include <linux/notifier.h>
-#include <linux/overflow.h>
-#include <linux/types.h>
-#include <linux/wmi.h>
-
-#include "wmi-capdata01.h"
-
-#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
-
-#define ACPI_AC_CLASS "ac_adapter"
-#define ACPI_AC_NOTIFY_STATUS 0x80
-
-struct lwmi_cd01_priv {
- struct notifier_block acpi_nb; /* ACPI events */
- struct wmi_device *wdev;
- struct cd01_list *list;
-};
-
-struct cd01_list {
- struct mutex list_mutex; /* list R/W mutex */
- u8 count;
- struct capdata01 data[];
-};
-
-/**
- * lwmi_cd01_component_bind() - Bind component to master device.
- * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
- * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
- * @data: capdata01_list object pointer used to return the capability data.
- *
- * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
- * list. This is used to call lwmi_cd01_get_data to look up attribute data
- * from the lenovo-wmi-other driver.
- *
- * Return: 0
- */
-static int lwmi_cd01_component_bind(struct device *cd01_dev,
- struct device *om_dev, void *data)
-{
- struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
- struct cd01_list **cd01_list = data;
-
- *cd01_list = priv->list;
-
- return 0;
-}
-
-static const struct component_ops lwmi_cd01_component_ops = {
- .bind = lwmi_cd01_component_bind,
-};
-
-/**
- * lwmi_cd01_get_data - Get the data of the specified attribute
- * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct.
- * @attribute_id: The capdata attribute ID to be found.
- * @output: Pointer to a capdata01 struct to return the data.
- *
- * Retrieves the capability data 01 struct pointer for the given
- * attribute for its specified thermal mode.
- *
- * Return: 0 on success, or -EINVAL.
- */
-int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output)
-{
- u8 idx;
-
- guard(mutex)(&list->list_mutex);
- for (idx = 0; idx < list->count; idx++) {
- if (list->data[idx].id != attribute_id)
- continue;
- memcpy(output, &list->data[idx], sizeof(list->data[idx]));
- return 0;
- }
-
- return -EINVAL;
-}
-EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
-
-/**
- * lwmi_cd01_cache() - Cache all WMI data block information
- * @priv: lenovo-wmi-capdata01 driver data.
- *
- * Loop through each WMI data block and cache the data.
- *
- * Return: 0 on success, or an error.
- */
-static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
-{
- int idx;
-
- guard(mutex)(&priv->list->list_mutex);
- for (idx = 0; idx < priv->list->count; idx++) {
- union acpi_object *ret_obj __free(kfree) = NULL;
-
- ret_obj = wmidev_block_query(priv->wdev, idx);
- if (!ret_obj)
- return -ENODEV;
-
- if (ret_obj->type != ACPI_TYPE_BUFFER ||
- ret_obj->buffer.length < sizeof(priv->list->data[idx]))
- continue;
-
- memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
- ret_obj->buffer.length);
- }
-
- return 0;
-}
-
-/**
- * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
- * @priv: lenovo-wmi-capdata01 driver data.
- *
- * Allocate a cd01_list struct large enough to contain data from all WMI data
- * blocks provided by the interface.
- *
- * Return: 0 on success, or an error.
- */
-static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
-{
- struct cd01_list *list;
- size_t list_size;
- int count, ret;
-
- count = wmidev_instance_count(priv->wdev);
- list_size = struct_size(list, data, count);
-
- list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
- if (!list)
- return -ENOMEM;
-
- ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
- if (ret)
- return ret;
-
- list->count = count;
- priv->list = list;
-
- return 0;
-}
-
-/**
- * lwmi_cd01_setup() - Cache all WMI data block information
- * @priv: lenovo-wmi-capdata01 driver data.
- *
- * Allocate a cd01_list struct large enough to contain data from all WMI data
- * blocks provided by the interface. Then loop through each data block and
- * cache the data.
- *
- * Return: 0 on success, or an error code.
- */
-static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv)
-{
- int ret;
-
- ret = lwmi_cd01_alloc(priv);
- if (ret)
- return ret;
-
- return lwmi_cd01_cache(priv);
-}
-
-/**
- * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier.
- * block call chain.
- * @nb: The notifier_block registered to lenovo-wmi-events driver.
- * @action: Unused.
- * @data: The ACPI event.
- *
- * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
- * of a change.
- *
- * Return: notifier_block status.
- */
-static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
- void *data)
-{
- struct acpi_bus_event *event = data;
- struct lwmi_cd01_priv *priv;
- int ret;
-
- if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
- return NOTIFY_DONE;
-
- priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
-
- switch (event->type) {
- case ACPI_AC_NOTIFY_STATUS:
- ret = lwmi_cd01_cache(priv);
- if (ret)
- return NOTIFY_BAD;
-
- return NOTIFY_OK;
- default:
- return NOTIFY_DONE;
- }
-}
-
-/**
- * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
- * @data: The ACPI event notifier_block to unregister.
- */
-static void lwmi_cd01_unregister(void *data)
-{
- struct notifier_block *acpi_nb = data;
-
- unregister_acpi_notifier(acpi_nb);
-}
-
-static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
-
-{
- struct lwmi_cd01_priv *priv;
- int ret;
-
- priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- priv->wdev = wdev;
- dev_set_drvdata(&wdev->dev, priv);
-
- ret = lwmi_cd01_setup(priv);
- if (ret)
- return ret;
-
- priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
-
- ret = register_acpi_notifier(&priv->acpi_nb);
- if (ret)
- return ret;
-
- ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb);
- if (ret)
- return ret;
-
- return component_add(&wdev->dev, &lwmi_cd01_component_ops);
-}
-
-static void lwmi_cd01_remove(struct wmi_device *wdev)
-{
- component_del(&wdev->dev, &lwmi_cd01_component_ops);
-}
-
-static const struct wmi_device_id lwmi_cd01_id_table[] = {
- { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
- {}
-};
-
-static struct wmi_driver lwmi_cd01_driver = {
- .driver = {
- .name = "lenovo_wmi_cd01",
- .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- },
- .id_table = lwmi_cd01_id_table,
- .probe = lwmi_cd01_probe,
- .remove = lwmi_cd01_remove,
- .no_singleton = true,
-};
-
-/**
- * lwmi_cd01_match() - Match rule for the master driver.
- * @dev: Pointer to the capability data 01 parent device.
- * @data: Unused void pointer for passing match criteria.
- *
- * Return: int.
- */
-int lwmi_cd01_match(struct device *dev, void *data)
-{
- return dev->driver == &lwmi_cd01_driver.driver;
-}
-EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
-
-module_wmi_driver(lwmi_cd01_driver);
-
-MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table);
-MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
-MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.h b/drivers/platform/x86/lenovo/wmi-capdata01.h
deleted file mode 100644
index bd06c5751f68..000000000000
--- a/drivers/platform/x86/lenovo/wmi-capdata01.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
-
-#ifndef _LENOVO_WMI_CAPDATA01_H_
-#define _LENOVO_WMI_CAPDATA01_H_
-
-#include <linux/types.h>
-
-struct device;
-struct cd01_list;
-
-struct capdata01 {
- u32 id;
- u32 supported;
- u32 default_value;
- u32 step;
- u32 min_value;
- u32 max_value;
-};
-
-int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output);
-int lwmi_cd01_match(struct device *dev, void *data);
-
-#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c
index f6fef6296251..7379defac500 100644
--- a/drivers/platform/x86/lenovo/wmi-helpers.c
+++ b/drivers/platform/x86/lenovo/wmi-helpers.c
@@ -21,6 +21,7 @@
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/module.h>
+#include <linux/unaligned.h>
#include <linux/wmi.h>
#include "wmi-helpers.h"
@@ -59,10 +60,24 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
if (!ret_obj)
return -ENODATA;
- if (ret_obj->type != ACPI_TYPE_INTEGER)
- return -ENXIO;
+ switch (ret_obj->type) {
+ /*
+ * The ACPI method may simply return a buffer when a u32
+ * is expected. This is valid on Windows as its WMI-ACPI
+ * driver converts everything to a common buffer.
+ */
+ case ACPI_TYPE_BUFFER:
+ if (ret_obj->buffer.length < sizeof(u32))
+ return -ENXIO;
- *retval = (u32)ret_obj->integer.value;
+ *retval = get_unaligned_le32(ret_obj->buffer.pointer);
+ return 0;
+ case ACPI_TYPE_INTEGER:
+ *retval = (u32)ret_obj->integer.value;
+ return 0;
+ default:
+ return -ENXIO;
+ }
}
return 0;
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index 2a960b278f11..6040f45aa2b0 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -14,7 +14,16 @@
* These attributes typically don't fit anywhere else in the sysfs and are set
* in Windows using one of Lenovo's multiple user applications.
*
+ * Additionally, this driver also exports tunable fan speed RPM to HWMON.
+ * Min/max RPM are also provided for reference.
+ *
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ * - fw_attributes
+ * - binding to Capability Data 01
+ *
+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
+ * - HWMON
+ * - binding to Capability Data 00 and Fan
*/
#include <linux/acpi.h>
@@ -25,16 +34,18 @@
#include <linux/device.h>
#include <linux/export.h>
#include <linux/gfp_types.h>
+#include <linux/hwmon.h>
#include <linux/idr.h>
#include <linux/kdev_t.h>
#include <linux/kobject.h>
+#include <linux/limits.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_profile.h>
#include <linux/types.h>
#include <linux/wmi.h>
-#include "wmi-capdata01.h"
+#include "wmi-capdata.h"
#include "wmi-events.h"
#include "wmi-gamezone.h"
#include "wmi-helpers.h"
@@ -49,17 +60,26 @@
#define LWMI_FEATURE_ID_CPU_SPL 0x02
#define LWMI_FEATURE_ID_CPU_FPPT 0x03
+#define LWMI_FEATURE_ID_FAN_RPM 0x03
+
#define LWMI_TYPE_ID_NONE 0x00
#define LWMI_FEATURE_VALUE_GET 17
#define LWMI_FEATURE_VALUE_SET 18
-#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
-#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
-#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
-#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
+#define LWMI_FAN_ID_BASE 1
+#define LWMI_FAN_NR 4
+#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE)
+
+#define LWMI_ATTR_ID_FAN_RPM(x) \
+ (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \
+ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x)))
+
+#define LWMI_FAN_DIV 100
#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
+#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
static BLOCKING_NOTIFIER_HEAD(om_chain_head);
static DEFINE_IDA(lwmi_om_ida);
@@ -72,16 +92,459 @@ enum attribute_property {
SUPPORTED,
};
+struct lwmi_fan_info {
+ u32 supported;
+ u32 last_target;
+ long min_rpm;
+ long max_rpm;
+};
+
struct lwmi_om_priv {
struct component_master_ops *ops;
- struct cd01_list *cd01_list; /* only valid after capdata01 bind */
+
+ /* only valid after capdata bind */
+ struct cd_list *cd00_list;
+ struct cd_list *cd01_list;
+
+ struct device *hwmon_dev;
struct device *fw_attr_dev;
struct kset *fw_attr_kset;
struct notifier_block nb;
struct wmi_device *wdev;
int ida_id;
+
+ struct lwmi_fan_info fan_info[LWMI_FAN_NR];
+
+ struct {
+ bool capdata00_collected : 1;
+ bool capdata_fan_collected : 1;
+ } fan_flags;
};
+/*
+ * Visibility of fan channels:
+ *
+ * +-------------------+---------+------------------+-----------------------+------------+
+ * | | default | +expose_all_fans | +relax_fan_constraint | +both |
+ * +-------------------+---------+------------------+-----------------------+------------+
+ * | canonical | RW | RW | RW+relaxed | RW+relaxed |
+ * +-------------------+---------+------------------+-----------------------+------------+
+ * | -capdata_fan[idx] | N | RO | N | RW+relaxed |
+ * +-------------------+---------+------------------+-----------------------+------------+
+ *
+ * Note:
+ * 1. LWMI_ATTR_ID_FAN_RPM[idx].supported is always checked before exposing a channel.
+ * 2. -capdata_fan implies -capdata_fan[idx].
+ */
+static bool expose_all_fans;
+module_param(expose_all_fans, bool, 0444);
+MODULE_PARM_DESC(expose_all_fans,
+ "This option skips some capability checks and solely relies on per-channel ones "
+ "to expose fan attributes. Use with caution.");
+
+static bool relax_fan_constraint;
+module_param(relax_fan_constraint, bool, 0444);
+MODULE_PARM_DESC(relax_fan_constraint,
+ "Do not enforce fan RPM constraint (div/min/max) "
+ "and enables fan tuning when such data is missing. "
+ "Enabling this may results in HWMON attributes being out-of-sync, "
+ "and setting a too low RPM stops the fan. Use with caution.");
+
+/* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */
+
+/**
+ * lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan
+ * @priv: Driver private data structure
+ * @channel: Fan channel index (0-based)
+ * @val: Pointer to value (input for set, output for get)
+ * @set: True to set value, false to get value
+ *
+ * Communicates with WMI interface to either retrieve current fan RPM
+ * or set target fan RPM.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val, bool set)
+{
+ struct wmi_method_args_32 args;
+ u32 method_id, retval;
+ int err;
+
+ method_id = set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET;
+ args.arg0 = LWMI_ATTR_ID_FAN_RPM(channel);
+ args.arg1 = set ? *val : 0;
+
+ err = lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id,
+ (unsigned char *)&args, sizeof(args), &retval);
+ if (err)
+ return err;
+
+ if (!set) {
+ *val = retval;
+ return 0;
+ }
+
+ /*
+ * It seems that 0 means "no error" and 1 means "done". Apparently
+ * different firmware teams have different thoughts on indicating
+ * success, so we accepts both.
+ */
+ return (retval == 0 || retval == 1) ? 0 : -EIO;
+}
+
+/**
+ * lwmi_om_hwmon_is_visible() - Determine visibility of HWMON attributes
+ * @drvdata: Driver private data
+ * @type: Sensor type
+ * @attr: Attribute identifier
+ * @channel: Channel index
+ *
+ * Determines whether an HWMON attribute should be visible in sysfs
+ * based on hardware capabilities and current configuration.
+ *
+ * Return: permission mode, or 0 if invisible.
+ */
+static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ struct lwmi_om_priv *priv = (struct lwmi_om_priv *)drvdata;
+ bool visible = false;
+
+ if (type == hwmon_fan) {
+ if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID))
+ return 0;
+
+ switch (attr) {
+ case hwmon_fan_target:
+ if (!(priv->fan_info[channel].supported & LWMI_SUPP_SET))
+ return 0;
+
+ if (relax_fan_constraint ||
+ (priv->fan_info[channel].min_rpm >= 0 &&
+ priv->fan_info[channel].max_rpm >= 0))
+ return 0644;
+
+ /*
+ * Reaching here implies expose_all_fans is set.
+ * See lwmi_om_hwmon_add().
+ */
+ dev_warn_once(&priv->wdev->dev,
+ "fan tuning disabled due to missing RPM constraint\n");
+ return 0;
+ case hwmon_fan_div:
+ case hwmon_fan_input:
+ visible = priv->fan_info[channel].supported & LWMI_SUPP_GET;
+ break;
+ case hwmon_fan_min:
+ visible = priv->fan_info[channel].min_rpm >= 0;
+ break;
+ case hwmon_fan_max:
+ visible = priv->fan_info[channel].max_rpm >= 0;
+ break;
+ }
+ }
+
+ return visible ? 0444 : 0;
+}
+
+/**
+ * lwmi_om_hwmon_read() - Read HWMON sensor data
+ * @dev: Device pointer
+ * @type: Sensor type
+ * @attr: Attribute identifier
+ * @channel: Channel index
+ * @val: Pointer to store value
+ *
+ * Reads current sensor values from hardware through WMI interface.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+ u32 retval = 0;
+ int err;
+
+ if (type == hwmon_fan) {
+ switch (attr) {
+ /*
+ * The EC has an internal RPM divisor (i.e., the raw register value is
+ * RPM / fanY_div). For fanY_input, the WMI method reads the register
+ * value and returns raw * fanY_div. For fanY_target, the WMI method
+ * divides the written value by fanY_div before writing it to the EC.
+ *
+ * As a result, reading fanY_input always returns a multiple of fanY_div,
+ * while writing to fanY_target loses the remainder.
+ */
+ case hwmon_fan_div:
+ *val = LWMI_FAN_DIV;
+ return 0;
+ case hwmon_fan_input:
+ err = lwmi_om_fan_get_set(priv, channel, &retval, false);
+ if (err)
+ return err;
+
+ *val = retval;
+ return 0;
+ case hwmon_fan_target:
+ *val = priv->fan_info[channel].last_target;
+ return 0;
+ case hwmon_fan_min:
+ *val = priv->fan_info[channel].min_rpm;
+ return 0;
+ case hwmon_fan_max:
+ *val = priv->fan_info[channel].max_rpm;
+ return 0;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+/**
+ * lwmi_om_hwmon_write() - Write HWMON sensor data
+ * @dev: Device pointer
+ * @type: Sensor type
+ * @attr: Attribute identifier
+ * @channel: Channel index
+ * @val: Value to write
+ *
+ * Writes configuration values to hardware through WMI interface.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+ u32 raw, min_rpm, max_rpm;
+ int err;
+
+ if (type == hwmon_fan) {
+ switch (attr) {
+ case hwmon_fan_target:
+ if (relax_fan_constraint) {
+ min_rpm = 1;
+ max_rpm = U16_MAX;
+ } else {
+ min_rpm = priv->fan_info[channel].min_rpm;
+ max_rpm = priv->fan_info[channel].max_rpm;
+ }
+
+ /* 0 means "auto". */
+ if (val != 0 && (val < min_rpm || val > max_rpm))
+ return -EINVAL;
+
+ /*
+ * The effective fanY_target is always a multiple of fanY_div
+ * due to the EC's internal RPM divisor (see lwmi_om_hwmon_read).
+ *
+ * Round down the written value to the nearest multiple of fanY_div
+ * to prevent mismatch between the effective value and last_target.
+ *
+ * For relax_fan_constraint, skip this conversion as setting a
+ * sub-fanY_div value is necessary to completely stop the fan on
+ * some devices.
+ */
+ if (!relax_fan_constraint)
+ raw = val / LWMI_FAN_DIV * LWMI_FAN_DIV;
+
+ err = lwmi_om_fan_get_set(priv, channel, &raw, true);
+ if (err)
+ return err;
+
+ priv->fan_info[channel].last_target = raw;
+ return 0;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = {
+ /* Must match LWMI_FAN_NR. */
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX),
+ NULL
+};
+
+static const struct hwmon_ops lwmi_om_hwmon_ops = {
+ .is_visible = lwmi_om_hwmon_is_visible,
+ .read = lwmi_om_hwmon_read,
+ .write = lwmi_om_hwmon_write,
+};
+
+static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = {
+ .ops = &lwmi_om_hwmon_ops,
+ .info = lwmi_om_hwmon_info,
+};
+
+/**
+ * lwmi_om_hwmon_add() - Register HWMON device if all info is collected
+ * @priv: Driver private data
+ */
+static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
+{
+ int i, valid;
+
+ if (WARN_ON(priv->hwmon_dev))
+ return;
+
+ if (!priv->fan_flags.capdata00_collected || !priv->fan_flags.capdata_fan_collected) {
+ dev_dbg(&priv->wdev->dev, "HWMON registration pending (00: %d, fan: %d)\n",
+ priv->fan_flags.capdata00_collected,
+ priv->fan_flags.capdata_fan_collected);
+ return;
+ }
+
+ if (expose_all_fans)
+ dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n");
+
+ if (relax_fan_constraint)
+ dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution\n");
+
+ valid = 0;
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID))
+ continue;
+
+ valid++;
+
+ if (!expose_all_fans &&
+ (priv->fan_info[i].min_rpm < 0 || priv->fan_info[i].max_rpm < 0)) {
+ dev_dbg(&priv->wdev->dev, "missing RPM constraint for fan%d, hiding\n",
+ LWMI_FAN_ID(i));
+ priv->fan_info[i].supported = 0;
+ valid--;
+ }
+ }
+
+ if (valid == 0) {
+ dev_warn(&priv->wdev->dev,
+ "fan reporting/tuning is unsupported on this device\n");
+ return;
+ }
+
+ priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev,
+ LWMI_OM_HWMON_NAME, priv,
+ &lwmi_om_hwmon_chip_info,
+ NULL);
+ if (IS_ERR(priv->hwmon_dev)) {
+ dev_warn(&priv->wdev->dev, "failed to register HWMON device: %ld\n",
+ PTR_ERR(priv->hwmon_dev));
+ priv->hwmon_dev = NULL;
+ return;
+ }
+
+ dev_dbg(&priv->wdev->dev, "registered HWMON device\n");
+}
+
+/**
+ * lwmi_om_hwmon_remove() - Unregister HWMON device
+ * @priv: Driver private data
+ *
+ * Unregisters the HWMON device if applicable.
+ */
+static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv)
+{
+ if (!priv->hwmon_dev)
+ return;
+
+ hwmon_device_unregister(priv->hwmon_dev);
+ priv->hwmon_dev = NULL;
+}
+
+/**
+ * lwmi_om_fan_info_init() - Initialzie fan info
+ * @priv: Driver private data
+ *
+ * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan() may be
+ * called in an arbitrary order. Hence, initializion must be done before.
+ */
+static void lwmi_om_fan_info_init(struct lwmi_om_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ priv->fan_info[i] = (struct lwmi_fan_info) {
+ .supported = 0,
+ /*
+ * Assume 0 on probe as the EC resets all fans to auto mode on (re)boot.
+ *
+ * Note that S0ix (s2idle) preserves the RPM target, so we don't need
+ * suspend/resume callbacks. This behavior has not been tested on S3-
+ * capable devices, but I doubt if such devices even have this interface.
+ */
+ .last_target = 0,
+ .min_rpm = -ENODATA,
+ .max_rpm = -ENODATA,
+ };
+ }
+
+ priv->fan_flags.capdata00_collected = false;
+ priv->fan_flags.capdata_fan_collected = false;
+}
+
+/**
+ * lwmi_om_fan_info_collect_cd00() - Collect fan info from capdata 00
+ * @priv: Driver private data
+ */
+static void lwmi_om_fan_info_collect_cd00(struct lwmi_om_priv *priv)
+{
+ struct capdata00 capdata00;
+ int i, err;
+
+ dev_dbg(&priv->wdev->dev, "Collecting fan info from capdata00\n");
+
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &capdata00);
+ priv->fan_info[i].supported = err ? 0 : capdata00.supported;
+ }
+
+ priv->fan_flags.capdata00_collected = true;
+ lwmi_om_hwmon_add(priv);
+}
+
+/**
+ * lwmi_om_fan_info_collect_cd_fan() - Collect fan info from capdata fan
+ * @dev: Pointer to the lenovo-wmi-other device
+ * @cd_fan_list: Pointer to the capdata fan list
+ */
+static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *cd_fan_list)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+ struct capdata_fan capdata_fan;
+ int i, err;
+
+ dev_dbg(dev, "Collecting fan info from capdata_fan\n");
+
+ if (!cd_fan_list)
+ goto out;
+
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ err = lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan);
+ if (err)
+ continue;
+
+ priv->fan_info[i].min_rpm = capdata_fan.min_rpm;
+ priv->fan_info[i].max_rpm = capdata_fan.max_rpm;
+ }
+
+out:
+ priv->fan_flags.capdata_fan_collected = true;
+ lwmi_om_hwmon_add(priv);
+}
+
+/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
+
struct tunable_attr_01 {
struct capdata01 *capdata;
struct device *dev;
@@ -561,32 +1024,45 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
device_unregister(priv->fw_attr_dev);
}
+/* ======== Self (master: lenovo-wmi-other) ======== */
+
/**
* lwmi_om_master_bind() - Bind all components of the other mode driver
* @dev: The lenovo-wmi-other driver basic device.
*
- * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the
- * lenovo-wmi-other master driver. On success, assign the capability data 01
- * list pointer to the driver data struct for later access. This pointer
- * is only valid while the capdata01 interface exists. Finally, register all
- * firmware attribute groups.
+ * Call component_bind_all to bind the lenovo-wmi-capdata devices to the
+ * lenovo-wmi-other master driver, with a callback to collect fan info from
+ * capdata_fan. On success, assign the capability data list pointers to the
+ * driver data struct for later access. These pointers are only valid while the
+ * capdata interfaces exist. Finally, collect fan info from capdata00 and
+ * register all firmware attribute groups. Note that the HWMON device is
+ * registered only if all fan info is collected. Hence, it is not registered
+ * here. See lwmi_om_fan_info_collect_cd00() and
+ * lwmi_om_fan_info_collect_cd_fan().
*
* Return: 0 on success, or an error code.
*/
static int lwmi_om_master_bind(struct device *dev)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
- struct cd01_list *tmp_list;
+ struct lwmi_cd_binder binder = {
+ .cd_fan_list_cb = lwmi_om_fan_info_collect_cd_fan,
+ };
int ret;
- ret = component_bind_all(dev, &tmp_list);
+ lwmi_om_fan_info_init(priv);
+
+ ret = component_bind_all(dev, &binder);
if (ret)
return ret;
- priv->cd01_list = tmp_list;
- if (!priv->cd01_list)
+ priv->cd00_list = binder.cd00_list;
+ priv->cd01_list = binder.cd01_list;
+ if (!priv->cd00_list || !priv->cd01_list)
return -ENODEV;
+ lwmi_om_fan_info_collect_cd00(priv);
+
return lwmi_om_fw_attr_add(priv);
}
@@ -594,15 +1070,18 @@ static int lwmi_om_master_bind(struct device *dev)
* lwmi_om_master_unbind() - Unbind all components of the other mode driver
* @dev: The lenovo-wmi-other driver basic device
*
- * Unregister all capability data attribute groups. Then call
- * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the
- * lenovo-wmi-other master driver. Finally, free the IDA for this device.
+ * Unregister all firmware attribute groups and the HWMON device. Then call
+ * component_unbind_all to unbind lenovo-wmi-capdata devices from the
+ * lenovo-wmi-other master driver.
*/
static void lwmi_om_master_unbind(struct device *dev)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
lwmi_om_fw_attr_remove(priv);
+
+ lwmi_om_hwmon_remove(priv);
+
component_unbind_all(dev, NULL);
}
@@ -620,10 +1099,13 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
if (!priv)
return -ENOMEM;
+ /* Sentinel for on-demand ida_free(). */
+ priv->ida_id = -EIDRM;
+
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
- component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
+ lwmi_cd_match_add_all(&wdev->dev, &master_match);
if (IS_ERR(master_match))
return PTR_ERR(master_match);
@@ -636,7 +1118,10 @@ static void lwmi_other_remove(struct wmi_device *wdev)
struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
component_master_del(&wdev->dev, &lwmi_om_master_ops);
- ida_free(&lwmi_om_ida, priv->ida_id);
+
+ /* No IDA to free if the driver is never bound to its components. */
+ if (priv->ida_id >= 0)
+ ida_free(&lwmi_om_ida, priv->ida_id);
}
static const struct wmi_device_id lwmi_other_id_table[] = {
@@ -657,9 +1142,10 @@ static struct wmi_driver lwmi_other_driver = {
module_wmi_driver(lwmi_other_driver);
-MODULE_IMPORT_NS("LENOVO_WMI_CD01");
+MODULE_IMPORT_NS("LENOVO_WMI_CAPDATA");
MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/yogabook.c b/drivers/platform/x86/lenovo/yogabook.c
index 31b298dc5046..69887de36c9b 100644
--- a/drivers/platform/x86/lenovo/yogabook.c
+++ b/drivers/platform/x86/lenovo/yogabook.c
@@ -57,7 +57,7 @@ struct yogabook_data {
struct work_struct work;
struct led_classdev kbd_bl_led;
unsigned long flags;
- uint8_t brightness;
+ u8 brightness;
};
static void yogabook_work(struct work_struct *work)
@@ -338,16 +338,18 @@ static int yogabook_wmi_probe(struct wmi_device *wdev, const void *context)
int r;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
- if (data == NULL)
+ if (!data)
return -ENOMEM;
data->kbd_adev = acpi_dev_get_first_match_dev("GDIX1001", NULL, -1);
if (!data->kbd_adev)
- return dev_err_probe(dev, -ENODEV, "Cannot find the touchpad device in ACPI tables\n");
+ return dev_err_probe(dev, -ENODEV,
+ "Cannot find the touchpad device in ACPI tables\n");
data->dig_adev = acpi_dev_get_first_match_dev("WCOM0019", NULL, -1);
if (!data->dig_adev) {
- r = dev_err_probe(dev, -ENODEV, "Cannot find the digitizer device in ACPI tables\n");
+ r = dev_err_probe(dev, -ENODEV,
+ "Cannot find the digitizer device in ACPI tables\n");
goto error_put_devs;
}
@@ -453,7 +455,7 @@ static int yogabook_pdev_probe(struct platform_device *pdev)
int r;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
- if (data == NULL)
+ if (!data)
return -ENOMEM;
data->kbd_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "i2c-goodix_ts");
diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
index 0f935532f250..3c9af441d133 100644
--- a/drivers/platform/x86/uniwill/uniwill-acpi.c
+++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
@@ -88,6 +88,9 @@
#define EC_ADDR_GPU_TEMP 0x044F
+#define EC_ADDR_SYSTEM_ID 0x0456
+#define HAS_GPU BIT(7)
+
#define EC_ADDR_MAIN_FAN_RPM_1 0x0464
#define EC_ADDR_MAIN_FAN_RPM_2 0x0465
@@ -122,11 +125,11 @@
#define CTGP_DB_DB_ENABLE BIT(1)
#define CTGP_DB_CTGP_ENABLE BIT(2)
-#define EC_ADDR_CTGP_OFFSET 0x0744
+#define EC_ADDR_CTGP_DB_CTGP_OFFSET 0x0744
-#define EC_ADDR_TPP_OFFSET 0x0745
+#define EC_ADDR_CTGP_DB_TPP_OFFSET 0x0745
-#define EC_ADDR_MAX_TGP 0x0746
+#define EC_ADDR_CTGP_DB_DB_OFFSET 0x0746
#define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748
#define LIGHTBAR_APP_EXISTS BIT(0)
@@ -317,11 +320,13 @@
#define UNIWILL_FEATURE_LIGHTBAR BIT(3)
#define UNIWILL_FEATURE_BATTERY BIT(4)
#define UNIWILL_FEATURE_HWMON BIT(5)
+#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(6)
struct uniwill_data {
struct device *dev;
acpi_handle handle;
struct regmap *regmap;
+ unsigned int features;
struct acpi_battery_hook hook;
unsigned int last_charge_ctrl;
struct mutex battery_lock; /* Protects the list of currently registered batteries */
@@ -341,12 +346,21 @@ struct uniwill_battery_entry {
struct power_supply *battery;
};
+struct uniwill_device_descriptor {
+ unsigned int features;
+ /* Executed during driver probing */
+ int (*probe)(struct uniwill_data *data);
+};
+
static bool force;
module_param_unsafe(force, bool, 0);
MODULE_PARM_DESC(force, "Force loading without checking for supported devices\n");
-/* Feature bitmask since the associated registers are not reliable */
-static unsigned int supported_features;
+/*
+ * Contains device specific data like the feature bitmap since
+ * the associated registers are not always reliable.
+ */
+static struct uniwill_device_descriptor device_descriptor __ro_after_init;
static const char * const uniwill_temp_labels[] = {
"CPU",
@@ -411,6 +425,12 @@ static const struct key_entry uniwill_keymap[] = {
{ KE_END }
};
+static inline bool uniwill_device_supports(struct uniwill_data *data,
+ unsigned int features)
+{
+ return (data->features & features) == features;
+}
+
static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val)
{
union acpi_object params[2] = {
@@ -498,6 +518,10 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_LIGHTBAR_BAT_RED:
case EC_ADDR_LIGHTBAR_BAT_GREEN:
case EC_ADDR_LIGHTBAR_BAT_BLUE:
+ case EC_ADDR_CTGP_DB_CTRL:
+ case EC_ADDR_CTGP_DB_CTGP_OFFSET:
+ case EC_ADDR_CTGP_DB_TPP_OFFSET:
+ case EC_ADDR_CTGP_DB_DB_OFFSET:
return true;
default:
return false;
@@ -531,6 +555,11 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_LIGHTBAR_BAT_RED:
case EC_ADDR_LIGHTBAR_BAT_GREEN:
case EC_ADDR_LIGHTBAR_BAT_BLUE:
+ case EC_ADDR_SYSTEM_ID:
+ case EC_ADDR_CTGP_DB_CTRL:
+ case EC_ADDR_CTGP_DB_CTGP_OFFSET:
+ case EC_ADDR_CTGP_DB_TPP_OFFSET:
+ case EC_ADDR_CTGP_DB_DB_OFFSET:
return true;
default:
return false;
@@ -786,6 +815,70 @@ static ssize_t breathing_in_suspend_show(struct device *dev, struct device_attri
static DEVICE_ATTR_RW(breathing_in_suspend);
+static ssize_t ctgp_offset_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value > U8_MAX)
+ return -EINVAL;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ctgp_offset_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, &value);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", value);
+}
+
+static DEVICE_ATTR_RW(ctgp_offset);
+
+static int uniwill_nvidia_ctgp_init(struct uniwill_data *data)
+{
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
+ return 0;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_TPP_OFFSET, 255);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_DB_OFFSET, 25);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
+ CTGP_DB_GENERAL_ENABLE | CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static struct attribute *uniwill_attrs[] = {
/* Keyboard-related */
&dev_attr_fn_lock_toggle_enable.attr,
@@ -794,29 +887,39 @@ static struct attribute *uniwill_attrs[] = {
/* Lightbar-related */
&dev_attr_rainbow_animation.attr,
&dev_attr_breathing_in_suspend.attr,
+ /* Power-management-related */
+ &dev_attr_ctgp_offset.attr,
NULL
};
static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
{
+ struct device *dev = kobj_to_dev(kobj);
+ struct uniwill_data *data = dev_get_drvdata(dev);
+
if (attr == &dev_attr_fn_lock_toggle_enable.attr) {
- if (supported_features & UNIWILL_FEATURE_FN_LOCK_TOGGLE)
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK_TOGGLE))
return attr->mode;
}
if (attr == &dev_attr_super_key_toggle_enable.attr) {
- if (supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE)
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
return attr->mode;
}
if (attr == &dev_attr_touchpad_toggle_enable.attr) {
- if (supported_features & UNIWILL_FEATURE_TOUCHPAD_TOGGLE)
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_TOUCHPAD_TOGGLE))
return attr->mode;
}
if (attr == &dev_attr_rainbow_animation.attr ||
attr == &dev_attr_breathing_in_suspend.attr) {
- if (supported_features & UNIWILL_FEATURE_LIGHTBAR)
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR))
+ return attr->mode;
+ }
+
+ if (attr == &dev_attr_ctgp_offset.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
return attr->mode;
}
@@ -944,7 +1047,7 @@ static int uniwill_hwmon_init(struct uniwill_data *data)
{
struct device *hdev;
- if (!(supported_features & UNIWILL_FEATURE_HWMON))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_HWMON))
return 0;
hdev = devm_hwmon_device_register_with_info(data->dev, "uniwill", data,
@@ -1019,7 +1122,7 @@ static int uniwill_led_init(struct uniwill_data *data)
unsigned int value;
int ret;
- if (!(supported_features & UNIWILL_FEATURE_LIGHTBAR))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR))
return 0;
ret = devm_mutex_init(data->dev, &data->led_lock);
@@ -1232,7 +1335,7 @@ static int uniwill_battery_init(struct uniwill_data *data)
{
int ret;
- if (!(supported_features & UNIWILL_FEATURE_BATTERY))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
return 0;
ret = devm_mutex_init(data->dev, &data->battery_lock);
@@ -1361,6 +1464,19 @@ static int uniwill_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
+ data->features = device_descriptor.features;
+
+ /*
+ * Some devices might need to perform some device-specific initialization steps
+ * before the supported features are initialized. Because of this we have to call
+ * this callback just after the EC itself was initialized.
+ */
+ if (device_descriptor.probe) {
+ ret = device_descriptor.probe(data);
+ if (ret < 0)
+ return ret;
+ }
+
ret = uniwill_battery_init(data);
if (ret < 0)
return ret;
@@ -1373,6 +1489,10 @@ static int uniwill_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
+ ret = uniwill_nvidia_ctgp_init(data);
+ if (ret < 0)
+ return ret;
+
return uniwill_input_init(data);
}
@@ -1385,7 +1505,7 @@ static void uniwill_shutdown(struct platform_device *pdev)
static int uniwill_suspend_keyboard(struct uniwill_data *data)
{
- if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
return 0;
/*
@@ -1397,7 +1517,7 @@ static int uniwill_suspend_keyboard(struct uniwill_data *data)
static int uniwill_suspend_battery(struct uniwill_data *data)
{
- if (!(supported_features & UNIWILL_FEATURE_BATTERY))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
return 0;
/*
@@ -1408,6 +1528,15 @@ static int uniwill_suspend_battery(struct uniwill_data *data)
return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl);
}
+static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
+ return 0;
+
+ return regmap_clear_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
+ CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
+}
+
static int uniwill_suspend(struct device *dev)
{
struct uniwill_data *data = dev_get_drvdata(dev);
@@ -1421,6 +1550,10 @@ static int uniwill_suspend(struct device *dev)
if (ret < 0)
return ret;
+ ret = uniwill_suspend_nvidia_ctgp(data);
+ if (ret < 0)
+ return ret;
+
regcache_cache_only(data->regmap, true);
regcache_mark_dirty(data->regmap);
@@ -1432,7 +1565,7 @@ static int uniwill_resume_keyboard(struct uniwill_data *data)
unsigned int value;
int ret;
- if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
return 0;
ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value);
@@ -1448,13 +1581,22 @@ static int uniwill_resume_keyboard(struct uniwill_data *data)
static int uniwill_resume_battery(struct uniwill_data *data)
{
- if (!(supported_features & UNIWILL_FEATURE_BATTERY))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
return 0;
return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK,
data->last_charge_ctrl);
}
+static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
+ return 0;
+
+ return regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
+ CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
+}
+
static int uniwill_resume(struct device *dev)
{
struct uniwill_data *data = dev_get_drvdata(dev);
@@ -1470,7 +1612,11 @@ static int uniwill_resume(struct device *dev)
if (ret < 0)
return ret;
- return uniwill_resume_battery(data);
+ ret = uniwill_resume_battery(data);
+ if (ret < 0)
+ return ret;
+
+ return uniwill_resume_nvidia_ctgp(data);
}
static DEFINE_SIMPLE_DEV_PM_OPS(uniwill_pm_ops, uniwill_suspend, uniwill_resume);
@@ -1496,6 +1642,48 @@ static struct platform_driver uniwill_driver = {
.shutdown = uniwill_shutdown,
};
+static struct uniwill_device_descriptor lapac71h_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK_TOGGLE |
+ UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
+ UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
+ UNIWILL_FEATURE_BATTERY |
+ UNIWILL_FEATURE_HWMON,
+};
+
+static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK_TOGGLE |
+ UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
+ UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
+ UNIWILL_FEATURE_LIGHTBAR |
+ UNIWILL_FEATURE_BATTERY |
+ UNIWILL_FEATURE_HWMON,
+};
+
+static int phxarx1_phxaqf1_probe(struct uniwill_data *data)
+{
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_SYSTEM_ID, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value & HAS_GPU)
+ data->features |= UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL;
+
+ return 0;
+};
+
+static struct uniwill_device_descriptor phxarx1_phxaqf1_descriptor __initdata = {
+ .probe = phxarx1_phxaqf1_probe,
+};
+
+static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
+};
+
+static struct uniwill_device_descriptor empty_descriptor __initdata = {};
+
static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
{
.ident = "XMG FUSION 15",
@@ -1503,6 +1691,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "XMG FUSION 15",
@@ -1510,6 +1699,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "Intel NUC x15",
@@ -1517,11 +1707,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPAC71H"),
},
- .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE |
- UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
- UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
- UNIWILL_FEATURE_BATTERY |
- UNIWILL_FEATURE_HWMON),
+ .driver_data = &lapac71h_descriptor,
},
{
.ident = "Intel NUC x15",
@@ -1529,12 +1715,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"),
},
- .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE |
- UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
- UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
- UNIWILL_FEATURE_LIGHTBAR |
- UNIWILL_FEATURE_BATTERY |
- UNIWILL_FEATURE_HWMON),
+ .driver_data = &lapkc71f_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel",
@@ -1542,6 +1723,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel",
@@ -1549,6 +1731,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel",
@@ -1556,6 +1739,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"),
},
+ .driver_data = &phxarx1_phxaqf1_descriptor,
},
{
.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 = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.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 = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"),
},
+ .driver_data = &empty_descriptor,
},
{
.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 = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel",
@@ -1584,6 +1771,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD",
@@ -1591,6 +1779,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"),
},
+ .driver_data = &empty_descriptor,
},
{
.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 = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -1605,6 +1795,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -1612,6 +1803,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel",
@@ -1619,6 +1811,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 15 Gen10 AMD",
@@ -1626,6 +1819,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -1633,6 +1827,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -1640,6 +1835,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 15 Gen10 Intel",
@@ -1647,6 +1843,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 Intel",
@@ -1654,6 +1851,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 AMD",
@@ -1661,6 +1859,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 AMD",
@@ -1668,6 +1867,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 AMD",
@@ -1675,6 +1875,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 AMD",
@@ -1682,6 +1883,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 Intel",
@@ -1689,6 +1891,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 Intel",
@@ -1696,6 +1899,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 Intel",
@@ -1703,6 +1907,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 Intel",
@@ -1710,6 +1915,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Trinity 15 Intel Gen1",
@@ -1717,6 +1923,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Trinity 17 Intel Gen1",
@@ -1724,6 +1931,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 15/17 Gen2 AMD",
@@ -1731,6 +1939,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Polaris 15/17 Gen2 Intel",
@@ -1738,6 +1947,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD",
@@ -1745,6 +1955,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel",
@@ -1752,6 +1963,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD",
@@ -1759,6 +1971,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 15 Gen4 Intel",
@@ -1766,6 +1979,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Polaris 15/17 Gen5 AMD",
@@ -1773,6 +1987,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen5 AMD",
@@ -1780,6 +1995,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.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 = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris Slim 15 Gen6 AMD",
@@ -1794,6 +2011,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.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 = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -1808,6 +2027,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -1815,6 +2035,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6",
@@ -1822,6 +2043,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 AMD",
@@ -1829,6 +2051,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -1836,6 +2059,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -1843,6 +2067,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Book BA15 Gen10 AMD",
@@ -1850,6 +2075,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Pulse 14 Gen1 AMD",
@@ -1857,6 +2083,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Pulse 15 Gen1 AMD",
@@ -1864,6 +2091,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Pulse 15 Gen2 AMD",
@@ -1871,6 +2099,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"),
},
+ .driver_data = &empty_descriptor,
},
{ }
};
@@ -1878,6 +2107,7 @@ MODULE_DEVICE_TABLE(dmi, uniwill_dmi_table);
static int __init uniwill_init(void)
{
+ const struct uniwill_device_descriptor *descriptor;
const struct dmi_system_id *id;
int ret;
@@ -1887,10 +2117,22 @@ static int __init uniwill_init(void)
return -ENODEV;
/* Assume that the device supports all features */
- supported_features = UINT_MAX;
+ device_descriptor.features = UINT_MAX;
pr_warn("Loading on a potentially unsupported device\n");
} else {
- supported_features = (uintptr_t)id->driver_data;
+ /*
+ * Some devices might support additional features depending on
+ * the BIOS version/date, so we call this callback to let them
+ * modify their device descriptor accordingly.
+ */
+ if (id->callback) {
+ ret = id->callback(id);
+ if (ret < 0)
+ return ret;
+ }
+
+ descriptor = id->driver_data;
+ device_descriptor = *descriptor;
}
ret = platform_driver_register(&uniwill_driver);
diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/x86/wmi-bmof.c
index 5b00370a9a22..e3a126de421b 100644
--- a/drivers/platform/x86/wmi-bmof.c
+++ b/drivers/platform/x86/wmi-bmof.c
@@ -8,7 +8,6 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kernel.h>
@@ -24,9 +23,9 @@ static ssize_t bmof_read(struct file *filp, struct kobject *kobj, const struct b
char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
- union acpi_object *obj = dev_get_drvdata(dev);
+ struct wmi_buffer *buffer = dev_get_drvdata(dev);
- return memory_read_from_buffer(buf, count, &off, obj->buffer.pointer, obj->buffer.length);
+ return memory_read_from_buffer(buf, count, &off, buffer->data, buffer->length);
}
static const BIN_ATTR_ADMIN_RO(bmof, 0);
@@ -39,9 +38,9 @@ static const struct bin_attribute * const bmof_attrs[] = {
static size_t bmof_bin_size(struct kobject *kobj, const struct bin_attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
- union acpi_object *obj = dev_get_drvdata(dev);
+ struct wmi_buffer *buffer = dev_get_drvdata(dev);
- return obj->buffer.length;
+ return buffer->length;
}
static const struct attribute_group bmof_group = {
@@ -56,30 +55,27 @@ static const struct attribute_group *bmof_groups[] = {
static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
{
- union acpi_object *obj;
+ struct wmi_buffer *buffer;
+ int ret;
- obj = wmidev_block_query(wdev, 0);
- if (!obj) {
- dev_err(&wdev->dev, "failed to read Binary MOF\n");
- return -EIO;
- }
+ buffer = devm_kzalloc(&wdev->dev, sizeof(*buffer), GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
- if (obj->type != ACPI_TYPE_BUFFER) {
- dev_err(&wdev->dev, "Binary MOF is not a buffer\n");
- kfree(obj);
- return -EIO;
- }
+ ret = wmidev_query_block(wdev, 0, buffer);
+ if (ret < 0)
+ return ret;
- dev_set_drvdata(&wdev->dev, obj);
+ dev_set_drvdata(&wdev->dev, buffer);
return 0;
}
static void wmi_bmof_remove(struct wmi_device *wdev)
{
- union acpi_object *obj = dev_get_drvdata(&wdev->dev);
+ struct wmi_buffer *buffer = dev_get_drvdata(&wdev->dev);
- kfree(obj);
+ kfree(buffer->data);
}
static const struct wmi_device_id wmi_bmof_id_table[] = {
diff --git a/drivers/platform/x86/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c
index b892007b9863..badf9e42e015 100644
--- a/drivers/platform/x86/xiaomi-wmi.c
+++ b/drivers/platform/x86/xiaomi-wmi.c
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/* WMI driver for Xiaomi Laptops */
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/input.h>
#include <linux/module.h>
@@ -56,7 +55,7 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
return input_register_device(data->input_dev);
}
-static void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy)
+static void xiaomi_wmi_notify(struct wmi_device *wdev, const struct wmi_buffer *dummy)
{
struct xiaomi_wmi *data = dev_get_drvdata(&wdev->dev);
@@ -85,7 +84,7 @@ static struct wmi_driver xiaomi_wmi_driver = {
},
.id_table = xiaomi_wmi_id_table,
.probe = xiaomi_wmi_probe,
- .notify = xiaomi_wmi_notify,
+ .notify_new = xiaomi_wmi_notify,
.no_singleton = true,
};
module_wmi_driver(xiaomi_wmi_driver);
diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h
index 6fa510f419c0..55198d2875cc 100644
--- a/include/linux/amd-pmf-io.h
+++ b/include/linux/amd-pmf-io.h
@@ -61,5 +61,26 @@ enum laptop_placement {
LP_UNDEFINED,
};
+/**
+ * struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver
+ * @npuclk_freq: NPU clock frequency [MHz]
+ * @npu_busy: NPU busy % [0-100]
+ * @npu_power: NPU power [mW]
+ * @mpnpuclk_freq: MPNPU [MHz]
+ * @npu_reads: NPU read bandwidth [MB/sec]
+ * @npu_writes: NPU write bandwidth [MB/sec]
+ */
+struct amd_pmf_npu_metrics {
+ u16 npuclk_freq;
+ u16 npu_busy[8];
+ u16 npu_power;
+ u16 mpnpuclk_freq;
+ u16 npu_reads;
+ u16 npu_writes;
+};
+
int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op);
+
+/* AMD PMF and NPU interface */
+int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info);
#endif
diff --git a/include/linux/platform_data/x86/asus-wmi-leds-ids.h b/include/linux/platform_data/x86/asus-wmi-leds-ids.h
deleted file mode 100644
index 034a039c4e37..000000000000
--- a/include/linux/platform_data/x86/asus-wmi-leds-ids.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H
-#define __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H
-
-#include <linux/dmi.h>
-#include <linux/types.h>
-
-/* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */
-#if IS_REACHABLE(CONFIG_ASUS_WMI) || IS_REACHABLE(CONFIG_HID_ASUS)
-static const struct dmi_system_id asus_use_hid_led_dmi_ids[] = {
- {
- .matches = {
- DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Zephyrus"),
- },
- },
- {
- .matches = {
- DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Strix"),
- },
- },
- {
- .matches = {
- DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Flow"),
- },
- },
- {
- .matches = {
- DMI_MATCH(DMI_PRODUCT_FAMILY, "ProArt P16"),
- },
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_NAME, "GA403U"),
- },
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_NAME, "GU605M"),
- },
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_NAME, "RC71L"),
- },
- },
- { },
-};
-#endif
-
-#endif /* __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H */
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index 516538b5a527..554f41b827e1 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -173,12 +173,29 @@ enum asus_ally_mcu_hack {
ASUS_WMI_ALLY_MCU_HACK_DISABLED,
};
+/* Used to notify hid-asus when asus-wmi changes keyboard backlight */
+struct asus_hid_listener {
+ struct list_head list;
+ void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
+};
+
+enum asus_hid_event {
+ ASUS_EV_BRTUP,
+ ASUS_EV_BRTDOWN,
+ ASUS_EV_BRTTOGGLE,
+};
+
+#define ASUS_EV_MAX_BRIGHTNESS 3
+
#if IS_REACHABLE(CONFIG_ASUS_WMI)
void set_ally_mcu_hack(enum asus_ally_mcu_hack status);
void set_ally_mcu_powersave(bool enabled);
int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval);
int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval);
int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
+int asus_hid_register_listener(struct asus_hid_listener *cdev);
+void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
+int asus_hid_event(enum asus_hid_event event);
#else
static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status)
{
@@ -199,6 +216,17 @@ static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
{
return -ENODEV;
}
+static inline int asus_hid_register_listener(struct asus_hid_listener *bdev)
+{
+ return -ENODEV;
+}
+static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
+{
+}
+static inline int asus_hid_event(enum asus_hid_event event)
+{
+ return -ENODEV;
+}
#endif
#endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */
diff --git a/include/linux/psp.h b/include/linux/psp.h
index 92e60aeef21e..b337dcce1e99 100644
--- a/include/linux/psp.h
+++ b/include/linux/psp.h
@@ -18,6 +18,7 @@
* and should include an appropriate local definition in their source file.
*/
#define PSP_CMDRESP_STS GENMASK(15, 0)
+#define PSP_TEE_STS_RING_BUSY 0x0000000d /* Ring already initialized */
#define PSP_CMDRESP_CMD GENMASK(23, 16)
#define PSP_CMDRESP_RESERVED GENMASK(29, 24)
#define PSP_CMDRESP_RECOVERY BIT(30)
diff --git a/include/linux/wmi.h b/include/linux/wmi.h
index 665ea7dc8a92..75cb0c7cfe57 100644
--- a/include/linux/wmi.h
+++ b/include/linux/wmi.h
@@ -8,9 +8,11 @@
#ifndef _LINUX_WMI_H
#define _LINUX_WMI_H
+#include <linux/compiler_attributes.h>
#include <linux/device.h>
#include <linux/acpi.h>
#include <linux/mod_devicetable.h>
+#include <linux/types.h>
/**
* struct wmi_device - WMI device structure
@@ -36,6 +38,42 @@ struct wmi_device {
*/
#define to_wmi_device(device) container_of_const(device, struct wmi_device, dev)
+/**
+ * struct wmi_buffer - WMI data buffer
+ * @length: Buffer length in bytes
+ * @data: Pointer to the buffer content
+ *
+ * This structure is used to exchange data with the WMI driver core.
+ */
+struct wmi_buffer {
+ size_t length;
+ void *data;
+};
+
+/**
+ * struct wmi_string - WMI string representation
+ * @length: Size of @chars in bytes
+ * @chars: UTF16-LE characters with optional nul termination and padding
+ *
+ * This structure is used when exchanging string data over the WMI interface.
+ */
+struct wmi_string {
+ __le16 length;
+ __le16 chars[];
+} __packed;
+
+ssize_t wmi_string_to_utf8s(const struct wmi_string *str, u8 *dst, size_t length);
+
+ssize_t wmi_string_from_utf8s(struct wmi_string *str, size_t max_chars, const u8 *src,
+ size_t src_length);
+
+int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id,
+ const struct wmi_buffer *in, struct wmi_buffer *out);
+
+int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out);
+
+int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in);
+
acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id,
const struct acpi_buffer *in, struct acpi_buffer *out);
@@ -54,9 +92,11 @@ u8 wmidev_instance_count(struct wmi_device *wdev);
* @probe: Callback for device binding
* @remove: Callback for device unbinding
* @shutdown: Callback for device shutdown
- * @notify: Callback for receiving WMI events
+ * @notify: Callback for receiving WMI events (deprecated)
+ * @notify_new: Callback for receiving WMI events
*
- * This represents WMI drivers which handle WMI devices.
+ * This represents WMI drivers which handle WMI devices. The data inside the buffer
+ * passed to the @notify_new callback is guaranteed to be aligned on a 8-byte boundary.
*/
struct wmi_driver {
struct device_driver driver;
@@ -68,6 +108,7 @@ struct wmi_driver {
void (*remove)(struct wmi_device *wdev);
void (*shutdown)(struct wmi_device *wdev);
void (*notify)(struct wmi_device *device, union acpi_object *data);
+ void (*notify_new)(struct wmi_device *device, const struct wmi_buffer *data);
};
/**
diff --git a/tools/power/x86/intel-speed-select/Makefile b/tools/power/x86/intel-speed-select/Makefile
index 8d3a02a20f3d..6b299aae2ded 100644
--- a/tools/power/x86/intel-speed-select/Makefile
+++ b/tools/power/x86/intel-speed-select/Makefile
@@ -13,7 +13,13 @@ endif
# Do not use make's built-in rules
# (this improves performance and avoids hard-to-debug behaviour);
MAKEFLAGS += -r
-override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include -I$(shell $(CC) -print-sysroot)/usr/include/libnl3
+
+NL3_CFLAGS = $(shell pkg-config --cflags libnl-3.0 2>/dev/null)
+ifeq ($(NL3_CFLAGS),)
+NL3_CFLAGS = -I/usr/include/libnl3
+endif
+
+override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include $(NL3_CFLAGS)
override LDFLAGS += -lnl-genl-3 -lnl-3
ALL_TARGETS := intel-speed-select
diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c
index 558138eea75e..dd9056ddb016 100644
--- a/tools/power/x86/intel-speed-select/isst-config.c
+++ b/tools/power/x86/intel-speed-select/isst-config.c
@@ -16,7 +16,7 @@ struct process_cmd_struct {
int arg;
};
-static const char *version_str = "v1.24";
+static const char *version_str = "v1.25";
static const int supported_api_ver = 3;
static struct isst_if_platform_info isst_platform_info;
@@ -80,6 +80,18 @@ struct cpu_topology {
short die_id;
};
+static int read_only;
+
+static void check_privilege(void)
+{
+ if (!read_only)
+ return;
+
+ isst_display_error_info_message(1, "Insufficient privileges", 0, 0);
+ isst_ctdp_display_information_end(outf);
+ exit(1);
+}
+
FILE *get_output_file(void)
{
return outf;
@@ -950,9 +962,11 @@ int isolate_cpus(struct isst_id *id, int mask_size, cpu_set_t *cpu_mask, int lev
ret = write(fd, "member", strlen("member"));
if (ret == -1) {
printf("Can't update to member\n");
+ close(fd);
return ret;
}
+ close(fd);
return 0;
}
@@ -1578,6 +1592,8 @@ free_mask:
static void set_tdp_level(int arg)
{
+ check_privilege();
+
if (cmd_help) {
fprintf(stderr, "Set Config TDP level\n");
fprintf(stderr,
@@ -2046,6 +2062,8 @@ static void set_pbf_enable(int arg)
{
int enable = arg;
+ check_privilege();
+
if (cmd_help) {
if (enable) {
fprintf(stderr,
@@ -2212,6 +2230,8 @@ static void set_fact_enable(int arg)
int i, ret, enable = arg;
struct isst_id id;
+ check_privilege();
+
if (cmd_help) {
if (enable) {
fprintf(stderr,
@@ -2361,6 +2381,8 @@ static void set_clos_enable(int arg)
{
int enable = arg;
+ check_privilege();
+
if (cmd_help) {
if (enable) {
fprintf(stderr,
@@ -2491,6 +2513,8 @@ static void set_clos_config_for_cpu(struct isst_id *id, void *arg1, void *arg2,
static void set_clos_config(int arg)
{
+ check_privilege();
+
if (cmd_help) {
fprintf(stderr,
"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
static void set_clos_assoc(int arg)
{
+ check_privilege();
+
if (cmd_help) {
fprintf(stderr, "Associate a clos id to a CPU\n");
fprintf(stderr,
@@ -2637,6 +2663,8 @@ static void set_turbo_mode(int arg)
int i, disable = arg;
struct isst_id id;
+ check_privilege();
+
if (cmd_help) {
if (disable)
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,
}
if (set) {
+ check_privilege();
ret = isst_set_trl(id, fact_trl);
isst_display_result(id, outf, "turbo-mode", "set-trl", ret);
return;
@@ -3204,8 +3233,16 @@ static void cmdline(int argc, char **argv)
};
if (geteuid() != 0) {
- fprintf(stderr, "Must run as root\n");
- exit(0);
+ int fd;
+
+ fd = open(pathname, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Must run as root\n");
+ exit(0);
+ }
+ fprintf(stderr, "\nNot running as root, Only read only operations are supported\n");
+ close(fd);
+ read_only = 1;
}
ret = update_cpu_model();