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() ...
Unified · Split
| @@ -54,6 +54,7 @@ detailed description): · 53 unmodified lines | ||
| 54 | 54 | - Setting keyboard language |
| 55 | 55 | - WWAN Antenna type |
| 56 | 56 | - Auxmac |
| 57 | + - Hardware damage detection capability | |
| 57 | 58 | |
| 58 | 59 | A compatibility table by model and feature is maintained on the web |
| 59 | 60 | site, http://ibm-acpi.sf.net/. I appreciate any success or failure |
| @@ -1576,6 +1577,42 @@ percentage level, above which charging will stop. · 1516 unmodified lines | ||
| 1576 | 1577 | The exact semantics of the attributes may be found in |
| 1577 | 1578 | Documentation/ABI/testing/sysfs-class-power. |
| 1578 | 1579 | |
| 1580 | +Hardware damage detection capability | |
| 1581 | +------------------------------------ | |
| 1582 | + | |
| 1583 | +sysfs attributes: hwdd_status, hwdd_detail | |
| 1584 | + | |
| 1585 | +Thinkpads are adding the ability to detect and report hardware damage. | |
| 1586 | +Add new sysfs interface to identify the damaged device status. | |
| 1587 | +Initial support is available for the USB-C replaceable connector. | |
| 1588 | + | |
| 1589 | +The command to check device damaged status is:: | |
| 1590 | + | |
| 1591 | + cat /sys/devices/platform/thinkpad_acpi/hwdd_status | |
| 1592 | + | |
| 1593 | +This value displays status of device damaged. | |
| 1594 | + | |
| 1595 | +- 0 = Not Damaged | |
| 1596 | +- 1 = Damaged | |
| 1597 | + | |
| 1598 | +The command to check location of damaged device is:: | |
| 1599 | + | |
| 1600 | + cat /sys/devices/platform/thinkpad_acpi/hwdd_detail | |
| 1601 | + | |
| 1602 | +This value displays location of damaged device having 1 line per damaged "item". | |
| 1603 | +For example: | |
| 1604 | + | |
| 1605 | +if no damage is detected: | |
| 1606 | + | |
| 1607 | +- No damage detected | |
| 1608 | + | |
| 1609 | +if damage detected: | |
| 1610 | + | |
| 1611 | +- TYPE-C: Base, Right side, Center port | |
| 1612 | + | |
| 1613 | +The property is read-only. If feature is not supported then sysfs | |
| 1614 | +attribute is not created. | |
| 1615 | + | |
| 1579 | 1616 | Multiple Commands, Module Parameters |
| 1580 | 1617 | ------------------------------------ |
| 1581 | 1618 | |
| @@ -16,5 +16,8 @@ which will be bound to compatible WMI devices by the driver core. · 15 unmodified lines | ||
| 16 | 16 | .. kernel-doc:: include/linux/wmi.h |
| 17 | 17 | :internal: |
| 18 | 18 | |
| 19 | +.. kernel-doc:: drivers/platform/wmi/string.c | |
| 20 | + :export: | |
| 21 | + | |
| 19 | 22 | .. kernel-doc:: drivers/platform/wmi/core.c |
| 20 | 23 | :export: |
| @@ -104,3 +104,71 @@ holding the notification ID of the event. This method should be evaluated every · 103 unmodified lines | ||
| 104 | 104 | time an ACPI notification is received, since some ACPI implementations use a |
| 105 | 105 | queue to store WMI event data items. This queue will overflow after a couple |
| 106 | 106 | of WMI events are received without retrieving the associated WMI event data. |
| 107 | + | |
| 108 | +Conversion rules for ACPI data types | |
| 109 | +------------------------------------ | |
| 110 | + | |
| 111 | +Consumers of the ACPI-WMI interface use binary buffers to exchange data with the WMI driver core, | |
| 112 | +with the internal structure of the buffer being only know to the consumers. The WMI driver core is | |
| 113 | +thus responsible for converting the data inside the buffer into an appropriate ACPI data type for | |
| 114 | +consumption by the ACPI firmware. Additionally, any data returned by the various ACPI methods needs | |
| 115 | +to be converted back into a binary buffer. | |
| 116 | + | |
| 117 | +The layout of said buffers is defined by the MOF description of the WMI method or data block in | |
| 118 | +question [1]_: | |
| 119 | + | |
| 120 | +=============== ======================================================================= ========= | |
| 121 | +Data Type Layout Alignment | |
| 122 | +=============== ======================================================================= ========= | |
| 123 | +``string`` Starts with an unsigned 16-bit little endian integer specifying 2 bytes | |
| 124 | + the length of the string data in bytes, followed by the string data | |
| 125 | + encoded as UTF-16LE with **optional** NULL termination and padding. | |
| 126 | + Keep in mind that some firmware implementations might depend on the | |
| 127 | + terminating NULL character to be present. Also the padding should | |
| 128 | + always be performed with NULL characters. | |
| 129 | +``boolean`` Single byte where 0 means ``false`` and nonzero means ``true``. 1 byte | |
| 130 | +``sint8`` Signed 8-bit integer. 1 byte | |
| 131 | +``uint8`` Unsigned 8-bit integer. 1 byte | |
| 132 | +``sint16`` Signed 16-bit little endian integer. 2 bytes | |
| 133 | +``uint16`` Unsigned 16-bit little endian integer. 2 bytes | |
| 134 | +``sint32`` Signed 32-bit little endian integer. 4 bytes | |
| 135 | +``uint32`` Unsigned 32-bit little endian integer. 4 bytes | |
| 136 | +``sint64`` Signed 64-bit little endian integer. 8 bytes | |
| 137 | +``uint64`` Unsigned 64-bit little endian integer. 8 bytes | |
| 138 | +``datetime`` A fixed-length 25-character UTF-16LE string with the format 2 bytes | |
| 139 | + *yyyymmddhhmmss.mmmmmmsutc* where *yyyy* is the 4-digit year, *mm* is | |
| 140 | + the 2-digit month, *dd* is the 2-digit day, *hh* is the 2-digit hour | |
| 141 | + based on a 24-hour clock, *mm* is the 2-digit minute, *ss* is the | |
| 142 | + 2-digit second, *mmmmmm* is the 6-digit microsecond, *s* is a plus or | |
| 143 | + minus character depending on whether *utc* is a positive or negative | |
| 144 | + offset from UTC (or a colon if the date is an interval). Unpopulated | |
| 145 | + fields should be filled with asterisks. | |
| 146 | +=============== ======================================================================= ========= | |
| 147 | + | |
| 148 | +Arrays should be aligned based on the alignment of their base type, while objects should be | |
| 149 | +aligned based on the largest alignment of an element inside them. | |
| 150 | + | |
| 151 | +All buffers returned by the WMI driver core are 8-byte aligned. When converting ACPI data types | |
| 152 | +into such buffers the following conversion rules apply: | |
| 153 | + | |
| 154 | +=============== ============================================================ | |
| 155 | +ACPI Data Type Converted into | |
| 156 | +=============== ============================================================ | |
| 157 | +Buffer Copied as-is. | |
| 158 | +Integer Converted into a ``uint32``. | |
| 159 | +String Converted into a ``string`` with a terminating NULL character | |
| 160 | + to match the behavior the of the Windows driver. | |
| 161 | +Package Each element inside the package is converted with alignment | |
| 162 | + of the resulting data types being respected. Nested packages | |
| 163 | + are not allowed. | |
| 164 | +=============== ============================================================ | |
| 165 | + | |
| 166 | +The Windows driver does attempt to handle nested packages, but this results in internal data | |
| 167 | +structures (``_ACPI_METHOD_ARGUMENT_V1``) erroneously being copied into the resulting buffer. | |
| 168 | +ACPI firmware implementations should thus not return nested packages from ACPI methods | |
| 169 | +associated with the ACPI-WMI interface. | |
| 170 | + | |
| 171 | +References | |
| 172 | +========== | |
| 173 | + | |
| 174 | +.. [1] https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items | |
| @@ -31,13 +31,32 @@ under the following path: · 30 unmodified lines | ||
| 31 | 31 | |
| 32 | 32 | /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/ |
| 33 | 33 | |
| 34 | +Additionally, this driver also exports attributes to HWMON. | |
| 35 | + | |
| 36 | +LENOVO_CAPABILITY_DATA_00 | |
| 37 | +------------------------- | |
| 38 | + | |
| 39 | +WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E`` | |
| 40 | + | |
| 41 | +The LENOVO_CAPABILITY_DATA_00 interface provides various information that | |
| 42 | +does not rely on the gamezone thermal mode. | |
| 43 | + | |
| 44 | +The following HWMON attributes are implemented: | |
| 45 | + - fanX_div: internal RPM divisor | |
| 46 | + - fanX_input: current RPM | |
| 47 | + - fanX_target: target RPM (tunable, 0=auto) | |
| 48 | + | |
| 49 | +Due to the internal RPM divisor, the current/target RPMs are rounded down to | |
| 50 | +its nearest multiple. The divisor itself is not necessary to be a power of two. | |
| 51 | + | |
| 34 | 52 | LENOVO_CAPABILITY_DATA_01 |
| 35 | 53 | ------------------------- |
| 36 | 54 | |
| 37 | 55 | WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154`` |
| 38 | 56 | |
| 39 | −The LENOVO_CAPABILITY_DATA_01 interface provides information on various | |
| 40 | −power limits of integrated CPU and GPU components. | |
| 57 | +The LENOVO_CAPABILITY_DATA_01 interface provides various information that | |
| 58 | +relies on the gamezone thermal mode, including power limits of integrated | |
| 59 | +CPU and GPU components. | |
| 41 | 60 | |
| 42 | 61 | Each attribute has the following properties: |
| 43 | 62 | - current_value |
| @@ -48,11 +67,22 @@ Each attribute has the following properties: · 4 unmodified lines | ||
| 48 | 67 | - scalar_increment |
| 49 | 68 | - type |
| 50 | 69 | |
| 51 | −The following attributes are implemented: | |
| 70 | +The following firmware-attributes are implemented: | |
| 52 | 71 | - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit |
| 53 | 72 | - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking |
| 54 | 73 | - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking |
| 55 | 74 | |
| 75 | +LENOVO_FAN_TEST_DATA | |
| 76 | +------------------------- | |
| 77 | + | |
| 78 | +WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21`` | |
| 79 | + | |
| 80 | +The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of | |
| 81 | +cooling fans. | |
| 82 | + | |
| 83 | +The following HWMON attributes are implemented: | |
| 84 | + - fanX_max: maximum RPM | |
| 85 | + - fanX_min: minimum RPM | |
| 56 | 86 | |
| 57 | 87 | WMI interface description |
| 58 | 88 | ========================= |
| @@ -106,3 +136,13 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility: · 47 unmodified lines | ||
| 106 | 136 | [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize; |
| 107 | 137 | [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[]; |
| 108 | 138 | }; |
| 139 | + | |
| 140 | + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of Fan Test Data"), guid("{B642801B-3D21-45DE-90AE-6E86F164FB21}")] | |
| 141 | + class LENOVO_FAN_TEST_DATA { | |
| 142 | + [key, read] string InstanceName; | |
| 143 | + [read] boolean Active; | |
| 144 | + [WmiDataId(1), read, Description("Mode.")] uint32 NumOfFans; | |
| 145 | + [WmiDataId(2), read, Description("Fan ID."), WmiSizeIs("NumOfFans")] uint32 FanId[]; | |
| 146 | + [WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMaxSpeed[]; | |
| 147 | + [WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[]; | |
| 148 | + }; | |
| @@ -70,7 +70,7 @@ to matching WMI devices using a struct wmi_device_id table: · 69 unmodified lines | ||
| 70 | 70 | .probe = foo_probe, |
| 71 | 71 | .remove = foo_remove, /* optional, devres is preferred */ |
| 72 | 72 | .shutdown = foo_shutdown, /* optional, called during shutdown */ |
| 73 | − .notify = foo_notify, /* optional, for event handling */ | |
| 73 | + .notify_new = foo_notify, /* optional, for event handling */ | |
| 74 | 74 | .no_notify_data = true, /* optional, enables events containing no additional data */ |
| 75 | 75 | .no_singleton = true, /* required for new WMI drivers */ |
| 76 | 76 | }; |
| @@ -90,9 +90,9 @@ the WMI device and put it in a well-known state for the WMI driver to pick up la · 13 unmodified lines | ||
| 90 | 90 | or kexec. Most WMI drivers need no special shutdown handling and can thus omit this callback. |
| 91 | 91 | |
| 92 | 92 | Please note that new WMI drivers are required to be able to be instantiated multiple times, |
| 93 | −and are forbidden from using any deprecated GUID-based WMI functions. This means that the | |
| 94 | −WMI driver should be prepared for the scenario that multiple matching WMI devices are present | |
| 95 | −on a given machine. | |
| 93 | +and are forbidden from using any deprecated GUID-based or ACPI-based WMI functions. This means | |
| 94 | +that the WMI driver should be prepared for the scenario that multiple matching WMI devices are | |
| 95 | +present on a given machine. | |
| 96 | 96 | |
| 97 | 97 | Because of this, WMI drivers should use the state container design pattern as described in |
| 98 | 98 | Documentation/driver-api/driver-model/design-patterns.rst. |
| @@ -104,38 +104,37 @@ Documentation/driver-api/driver-model/design-patterns.rst. · 5 unmodified lines | ||
| 104 | 104 | WMI method drivers |
| 105 | 105 | ------------------ |
| 106 | 106 | |
| 107 | −WMI drivers can call WMI device methods using wmidev_evaluate_method(), the | |
| 108 | −structure of the ACPI buffer passed to this function is device-specific and usually | |
| 109 | −needs some tinkering to get right. Looking at the ACPI tables containing the WMI | |
| 110 | −device usually helps here. The method id and instance number passed to this function | |
| 111 | −are also device-specific, looking at the decoded Binary MOF is usually enough to | |
| 112 | −find the right values. | |
| 107 | +WMI drivers can call WMI device methods using wmidev_invoke_method(). For each WMI method | |
| 108 | +invocation the WMI driver needs to provide the instance number and the method ID, as well as | |
| 109 | +a buffer with the method arguments and optionally a buffer for the results. | |
| 113 | 110 | |
| 114 | −The maximum instance number can be retrieved during runtime using wmidev_instance_count(). | |
| 111 | +The layout of said buffers is device-specific and described by the Binary MOF data associated | |
| 112 | +with a given WMI device. Said Binary MOF data also describes the method ID of a given WMI method | |
| 113 | +with the ``WmiMethodId`` qualifier. WMI devices exposing WMI methods usually expose only a single | |
| 114 | +instance (instance number 0), but in theory can expose multiple instances as well. In such a case | |
| 115 | +the number of instances can be retrieved using wmidev_instance_count(). | |
| 115 | 116 | |
| 116 | −Take a look at drivers/platform/x86/inspur_platform_profile.c for an example WMI method driver. | |
| 117 | +Take a look at drivers/platform/x86/intel/wmi/thunderbolt.c for an example WMI method driver. | |
| 117 | 118 | |
| 118 | 119 | WMI data block drivers |
| 119 | 120 | ---------------------- |
| 120 | 121 | |
| 121 | −WMI drivers can query WMI device data blocks using wmidev_block_query(), the | |
| 122 | −structure of the returned ACPI object is again device-specific. Some WMI devices | |
| 123 | −also allow for setting data blocks using wmidev_block_set(). | |
| 122 | +WMI drivers can query WMI data blocks using wmidev_query_block(), the layout of the returned | |
| 123 | +buffer is again device-specific and described by the Binary MOF data. Some WMI data blocks are | |
| 124 | +also writeable and can be set using wmidev_set_block(). The number of data block instances can | |
| 125 | +again be retrieved using wmidev_instance_count(). | |
| 124 | 126 | |
| 125 | −The maximum instance number can also be retrieved using wmidev_instance_count(). | |
| 126 | − | |
| 127 | −Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example | |
| 128 | −WMI data block driver. | |
| 127 | +Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example WMI data block driver. | |
| 129 | 128 | |
| 130 | 129 | WMI event drivers |
| 131 | 130 | ----------------- |
| 132 | 131 | |
| 133 | −WMI drivers can receive WMI events via the notify() callback inside the struct wmi_driver. | |
| 132 | +WMI drivers can receive WMI events via the notify_new() callback inside the struct wmi_driver. | |
| 134 | 133 | The WMI subsystem will then take care of setting up the WMI event accordingly. Please note that |
| 135 | −the structure of the ACPI object passed to this callback is device-specific, and freeing the | |
| 136 | −ACPI object is being done by the WMI subsystem, not the driver. | |
| 134 | +the layout of the buffer passed to this callback is device-specific, and freeing of the buffer | |
| 135 | +is done by the WMI subsystem itself, not the driver. | |
| 137 | 136 | |
| 138 | −The WMI driver core will take care that the notify() callback will only be called after | |
| 137 | +The WMI driver core will take care that the notify_new() callback will only be called after | |
| 139 | 138 | the probe() callback has been called, and that no events are being received by the driver |
| 140 | 139 | right before and after calling its remove() or shutdown() callback. |
| 141 | 140 | |
| @@ -147,6 +146,36 @@ the ``no_notify_data`` flag inside struct wmi_driver should be set to ``true``. · 5 unmodified lines | ||
| 147 | 146 | |
| 148 | 147 | Take a look at drivers/platform/x86/xiaomi-wmi.c for an example WMI event driver. |
| 149 | 148 | |
| 149 | +Exchanging data with the WMI driver core | |
| 150 | +---------------------------------------- | |
| 151 | + | |
| 152 | +WMI drivers can exchange data with the WMI driver core using struct wmi_buffer. The internal | |
| 153 | +structure of those buffers is device-specific and only known by the WMI driver. Because of this | |
| 154 | +the WMI driver itself is responsible for parsing and validating the data received from its | |
| 155 | +WMI device. | |
| 156 | + | |
| 157 | +The structure of said buffers is described by the MOF data associated with the WMI device in | |
| 158 | +question. When such a buffer contains multiple data items it usually makes sense to define a | |
| 159 | +C structure and use it during parsing. Since the WMI driver core guarantees that all buffers | |
| 160 | +received from a WMI device are aligned on an 8-byte boundary, WMI drivers can simply perform | |
| 161 | +a cast between the WMI buffer data and this C structure. | |
| 162 | + | |
| 163 | +This however should only be done after the size of the buffer was verified to be large enough | |
| 164 | +to hold the whole C structure. WMI drivers should reject undersized buffers as they are usually | |
| 165 | +sent by the WMI device to signal an internal error. Oversized buffers however should be accepted | |
| 166 | +to emulate the behavior of the Windows WMI implementation. | |
| 167 | + | |
| 168 | +When defining a C structure for parsing WMI buffers the alignment of the data items should be | |
| 169 | +respected. This is especially important for 64-bit integers as those have different alignments | |
| 170 | +on 64-bit (8-byte alignment) and 32-bit (4-byte alignment) architectures. It is thus a good idea | |
| 171 | +to manually specify the alignment of such data items or mark the whole structure as packed when | |
| 172 | +appropriate. Integer data items in general are little-endian integers and should be marked as | |
| 173 | +such using ``__le64`` and friends. When parsing WMI string data items the struct wmi_string should | |
| 174 | +be used as WMI strings have a different layout than C strings. | |
| 175 | + | |
| 176 | +See Documentation/wmi/acpi-interface.rst for more information regarding the binary format | |
| 177 | +of WMI data items. | |
| 178 | + | |
| 150 | 179 | Handling multiple WMI devices at once |
| 151 | 180 | ------------------------------------- |
| 152 | 181 | |
| @@ -171,6 +200,7 @@ Things to avoid · 18 unmodified lines | ||
| 171 | 200 | When developing WMI drivers, there are a couple of things which should be avoided: |
| 172 | 201 | |
| 173 | 202 | - usage of the deprecated GUID-based WMI interface which uses GUIDs instead of WMI device structs |
| 203 | +- usage of the deprecated ACPI-based WMI interface which uses ACPI objects instead of plain buffers | |
| 174 | 204 | - bypassing of the WMI subsystem when talking to WMI devices |
| 175 | 205 | - WMI drivers which cannot be instantiated multiple times. |
| 176 | 206 | |
| @@ -351,6 +351,17 @@ struct psp_device *psp_get_master_device(void) · 350 unmodified lines | ||
| 351 | 351 | return sp ? sp->psp_data : NULL; |
| 352 | 352 | } |
| 353 | 353 | |
| 354 | +int psp_restore(struct sp_device *sp) | |
| 355 | +{ | |
| 356 | + struct psp_device *psp = sp->psp_data; | |
| 357 | + int ret = 0; | |
| 358 | + | |
| 359 | + if (psp->tee_data) | |
| 360 | + ret = tee_restore(psp); | |
| 361 | + | |
| 362 | + return ret; | |
| 363 | +} | |
| 364 | + | |
| 354 | 365 | void psp_pci_init(void) |
| 355 | 366 | { |
| 356 | 367 | psp_master = psp_get_master_device(); |
| @@ -230,6 +230,18 @@ int sp_resume(struct sp_device *sp) · 229 unmodified lines | ||
| 230 | 230 | return 0; |
| 231 | 231 | } |
| 232 | 232 | |
| 233 | +int sp_restore(struct sp_device *sp) | |
| 234 | +{ | |
| 235 | + if (sp->psp_data) { | |
| 236 | + int ret = psp_restore(sp); | |
| 237 | + | |
| 238 | + if (ret) | |
| 239 | + return ret; | |
| 240 | + } | |
| 241 | + | |
| 242 | + return sp_resume(sp); | |
| 243 | +} | |
| 244 | + | |
| 233 | 245 | struct sp_device *sp_get_psp_master_device(void) |
| 234 | 246 | { |
| 235 | 247 | struct sp_device *i, *ret = NULL; |
| @@ -141,6 +141,7 @@ void sp_destroy(struct sp_device *sp); · 140 unmodified lines | ||
| 141 | 141 | |
| 142 | 142 | int sp_suspend(struct sp_device *sp); |
| 143 | 143 | int sp_resume(struct sp_device *sp); |
| 144 | +int sp_restore(struct sp_device *sp); | |
| 144 | 145 | int sp_request_ccp_irq(struct sp_device *sp, irq_handler_t handler, |
| 145 | 146 | const char *name, void *data); |
| 146 | 147 | void sp_free_ccp_irq(struct sp_device *sp, void *data); |
| @@ -174,6 +175,7 @@ int psp_dev_init(struct sp_device *sp); · 27 unmodified lines | ||
| 174 | 175 | void psp_pci_init(void); |
| 175 | 176 | void psp_dev_destroy(struct sp_device *sp); |
| 176 | 177 | void psp_pci_exit(void); |
| 178 | +int psp_restore(struct sp_device *sp); | |
| 177 | 179 | |
| 178 | 180 | #else /* !CONFIG_CRYPTO_DEV_SP_PSP */ |
| 179 | 181 | |
| @@ -181,6 +183,7 @@ static inline int psp_dev_init(struct sp_device *sp) { return 0; } · 1 unmodified line | ||
| 181 | 183 | static inline void psp_pci_init(void) { } |
| 182 | 184 | static inline void psp_dev_destroy(struct sp_device *sp) { } |
| 183 | 185 | static inline void psp_pci_exit(void) { } |
| 186 | +static inline int psp_restore(struct sp_device *sp) { return 0; } | |
| 184 | 187 | |
| 185 | 188 | #endif /* CONFIG_CRYPTO_DEV_SP_PSP */ |
| 186 | 189 | |
| @@ -353,6 +353,13 @@ static int __maybe_unused sp_pci_resume(struct device *dev) · 352 unmodified lines | ||
| 353 | 353 | return sp_resume(sp); |
| 354 | 354 | } |
| 355 | 355 | |
| 356 | +static int __maybe_unused sp_pci_restore(struct device *dev) | |
| 357 | +{ | |
| 358 | + struct sp_device *sp = dev_get_drvdata(dev); | |
| 359 | + | |
| 360 | + return sp_restore(sp); | |
| 361 | +} | |
| 362 | + | |
| 356 | 363 | #ifdef CONFIG_CRYPTO_DEV_SP_PSP |
| 357 | 364 | static const struct sev_vdata sevv1 = { |
| 358 | 365 | .cmdresp_reg = 0x10580, /* C2PMSG_32 */ |
| @@ -563,7 +570,14 @@ static const struct pci_device_id sp_pci_table[] = { · 204 unmodified lines | ||
| 563 | 570 | }; |
| 564 | 571 | MODULE_DEVICE_TABLE(pci, sp_pci_table); |
| 565 | 572 | |
| 566 | −static SIMPLE_DEV_PM_OPS(sp_pci_pm_ops, sp_pci_suspend, sp_pci_resume); | |
| 573 | +static const struct dev_pm_ops sp_pci_pm_ops = { | |
| 574 | + .suspend = pm_sleep_ptr(sp_pci_suspend), | |
| 575 | + .resume = pm_sleep_ptr(sp_pci_resume), | |
| 576 | + .freeze = pm_sleep_ptr(sp_pci_suspend), | |
| 577 | + .thaw = pm_sleep_ptr(sp_pci_resume), | |
| 578 | + .poweroff = pm_sleep_ptr(sp_pci_suspend), | |
| 579 | + .restore_early = pm_sleep_ptr(sp_pci_restore), | |
| 580 | +}; | |
| 567 | 581 | |
| 568 | 582 | static struct pci_driver sp_pci_driver = { |
| 569 | 583 | .name = "ccp", |
| @@ -86,10 +86,34 @@ static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd) · 85 unmodified lines | ||
| 86 | 86 | kfree(cmd); |
| 87 | 87 | } |
| 88 | 88 | |
| 89 | +static bool tee_send_destroy_cmd(struct psp_tee_device *tee) | |
| 90 | +{ | |
| 91 | + unsigned int reg; | |
| 92 | + int ret; | |
| 93 | + | |
| 94 | + ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL, | |
| 95 | + TEE_DEFAULT_CMD_TIMEOUT, ®); | |
| 96 | + if (ret) { | |
| 97 | + dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n"); | |
| 98 | + psp_dead = true; | |
| 99 | + return false; | |
| 100 | + } | |
| 101 | + | |
| 102 | + if (FIELD_GET(PSP_CMDRESP_STS, reg)) { | |
| 103 | + dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n", | |
| 104 | + FIELD_GET(PSP_CMDRESP_STS, reg)); | |
| 105 | + psp_dead = true; | |
| 106 | + return false; | |
| 107 | + } | |
| 108 | + | |
| 109 | + return true; | |
| 110 | +} | |
| 111 | + | |
| 89 | 112 | static int tee_init_ring(struct psp_tee_device *tee) |
| 90 | 113 | { |
| 91 | 114 | int ring_size = MAX_RING_BUFFER_ENTRIES * sizeof(struct tee_ring_cmd); |
| 92 | 115 | struct tee_init_ring_cmd *cmd; |
| 116 | + bool retry = false; | |
| 93 | 117 | unsigned int reg; |
| 94 | 118 | int ret; |
| 95 | 119 | |
| @@ -112,6 +136,7 @@ static int tee_init_ring(struct psp_tee_device *tee) · 16 unmodified lines | ||
| 112 | 136 | /* Send command buffer details to Trusted OS by writing to |
| 113 | 137 | * CPU-PSP message registers |
| 114 | 138 | */ |
| 139 | +retry_init: | |
| 115 | 140 | ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_INIT, cmd, |
| 116 | 141 | TEE_DEFAULT_CMD_TIMEOUT, ®); |
| 117 | 142 | if (ret) { |
| @@ -122,9 +147,22 @@ static int tee_init_ring(struct psp_tee_device *tee) · 4 unmodified lines | ||
| 122 | 147 | } |
| 123 | 148 | |
| 124 | 149 | if (FIELD_GET(PSP_CMDRESP_STS, reg)) { |
| 150 | + /* | |
| 151 | + * During the hibernate resume sequence driver may have gotten loaded | |
| 152 | + * but the ring not properly destroyed. If the ring doesn't work, try | |
| 153 | + * to destroy and re-init once. | |
| 154 | + */ | |
| 155 | + if (!retry && FIELD_GET(PSP_CMDRESP_STS, reg) == PSP_TEE_STS_RING_BUSY) { | |
| 156 | + dev_info(tee->dev, "tee: ring init command failed with busy status, retrying\n"); | |
| 157 | + if (tee_send_destroy_cmd(tee)) { | |
| 158 | + retry = true; | |
| 159 | + goto retry_init; | |
| 160 | + } | |
| 161 | + } | |
| 125 | 162 | dev_err(tee->dev, "tee: ring init command failed (%#010lx)\n", |
| 126 | 163 | FIELD_GET(PSP_CMDRESP_STS, reg)); |
| 127 | 164 | tee_free_ring(tee); |
| 165 | + psp_dead = true; | |
| 128 | 166 | ret = -EIO; |
| 129 | 167 | } |
| 130 | 168 | |
| @@ -136,24 +174,13 @@ free_buf: · 5 unmodified lines | ||
| 136 | 174 | |
| 137 | 175 | static void tee_destroy_ring(struct psp_tee_device *tee) |
| 138 | 176 | { |
| 139 | − unsigned int reg; | |
| 140 | − int ret; | |
| 141 | − | |
| 142 | 177 | if (!tee->rb_mgr.ring_start) |
| 143 | 178 | return; |
| 144 | 179 | |
| 145 | 180 | if (psp_dead) |
| 146 | 181 | goto free_ring; |
| 147 | 182 | |
| 148 | − ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL, | |
| 149 | − TEE_DEFAULT_CMD_TIMEOUT, ®); | |
| 150 | − if (ret) { | |
| 151 | − dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n"); | |
| 152 | − psp_dead = true; | |
| 153 | − } else if (FIELD_GET(PSP_CMDRESP_STS, reg)) { | |
| 154 | − dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n", | |
| 155 | − FIELD_GET(PSP_CMDRESP_STS, reg)); | |
| 156 | − } | |
| 183 | + tee_send_destroy_cmd(tee); | |
| 157 | 184 | |
| 158 | 185 | free_ring: |
| 159 | 186 | tee_free_ring(tee); |
| @@ -365,3 +392,8 @@ int psp_check_tee_status(void) · 205 unmodified lines | ||
| 365 | 392 | return 0; |
| 366 | 393 | } |
| 367 | 394 | EXPORT_SYMBOL(psp_check_tee_status); |
| 395 | + | |
| 396 | +int tee_restore(struct psp_device *psp) | |
| 397 | +{ | |
| 398 | + return tee_init_ring(psp->tee_data); | |
| 399 | +} | |
| @@ -111,5 +111,6 @@ struct tee_ring_cmd { · 110 unmodified lines | ||
| 111 | 111 | |
| 112 | 112 | int tee_dev_init(struct psp_device *psp); |
| 113 | 113 | void tee_dev_destroy(struct psp_device *psp); |
| 114 | +int tee_restore(struct psp_device *psp); | |
| 114 | 115 | |
| 115 | 116 | #endif /* __TEE_DEV_H__ */ |
| @@ -28,7 +28,6 @@ · 27 unmodified lines | ||
| 28 | 28 | #include <linux/hid.h> |
| 29 | 29 | #include <linux/module.h> |
| 30 | 30 | #include <linux/platform_data/x86/asus-wmi.h> |
| 31 | −#include <linux/platform_data/x86/asus-wmi-leds-ids.h> | |
| 32 | 31 | #include <linux/input/mt.h> |
| 33 | 32 | #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */ |
| 34 | 33 | #include <linux/power_supply.h> |
| @@ -50,7 +49,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); · 15 unmodified lines | ||
| 50 | 49 | #define FEATURE_REPORT_ID 0x0d |
| 51 | 50 | #define INPUT_REPORT_ID 0x5d |
| 52 | 51 | #define FEATURE_KBD_REPORT_ID 0x5a |
| 53 | −#define FEATURE_KBD_REPORT_SIZE 16 | |
| 52 | +#define FEATURE_KBD_REPORT_SIZE 64 | |
| 54 | 53 | #define FEATURE_KBD_LED_REPORT_ID1 0x5d |
| 55 | 54 | #define FEATURE_KBD_LED_REPORT_ID2 0x5e |
| 56 | 55 | |
| @@ -99,9 +98,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); · 42 unmodified lines | ||
| 99 | 98 | #define QUIRK_T90CHI BIT(9) |
| 100 | 99 | #define QUIRK_MEDION_E1239T BIT(10) |
| 101 | 100 | #define QUIRK_ROG_NKEY_KEYBOARD BIT(11) |
| 102 | −#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12) | |
| 101 | +#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12) | |
| 103 | 102 | #define QUIRK_ROG_ALLY_XPAD BIT(13) |
| 104 | 103 | #define QUIRK_HID_FN_LOCK BIT(14) |
| 104 | +#define QUIRK_ROG_NKEY_ID1ID2_INIT BIT(15) | |
| 105 | 105 | |
| 106 | 106 | #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ |
| 107 | 107 | QUIRK_NO_INIT_REPORTS | \ |
| @@ -113,7 +113,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); · 5 unmodified lines | ||
| 113 | 113 | #define TRKID_SGN ((TRKID_MAX + 1) >> 1) |
| 114 | 114 | |
| 115 | 115 | struct asus_kbd_leds { |
| 116 | − struct led_classdev cdev; | |
| 116 | + struct asus_hid_listener listener; | |
| 117 | 117 | struct hid_device *hdev; |
| 118 | 118 | struct work_struct work; |
| 119 | 119 | unsigned int brightness; |
| @@ -138,7 +138,6 @@ struct asus_drvdata { · 18 unmodified lines | ||
| 138 | 138 | struct input_dev *tp_kbd_input; |
| 139 | 139 | struct asus_kbd_leds *kbd_backlight; |
| 140 | 140 | const struct asus_touchpad_info *tp; |
| 141 | − bool enable_backlight; | |
| 142 | 141 | struct power_supply *battery; |
| 143 | 142 | struct power_supply_desc battery_desc; |
| 144 | 143 | int battery_capacity; |
| @@ -363,10 +362,21 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field, · 218 unmodified lines | ||
| 363 | 362 | usage->hid & HID_USAGE); |
| 364 | 363 | } |
| 365 | 364 | |
| 366 | − if (drvdata->quirks & QUIRK_HID_FN_LOCK && | |
| 367 | − usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) { | |
| 368 | − drvdata->fn_lock = !drvdata->fn_lock; | |
| 369 | − schedule_work(&drvdata->fn_lock_sync_work); | |
| 365 | + if (usage->type == EV_KEY && value) { | |
| 366 | + switch (usage->code) { | |
| 367 | + case KEY_KBDILLUMUP: | |
| 368 | + return !asus_hid_event(ASUS_EV_BRTUP); | |
| 369 | + case KEY_KBDILLUMDOWN: | |
| 370 | + return !asus_hid_event(ASUS_EV_BRTDOWN); | |
| 371 | + case KEY_KBDILLUMTOGGLE: | |
| 372 | + return !asus_hid_event(ASUS_EV_BRTTOGGLE); | |
| 373 | + case KEY_FN_ESC: | |
| 374 | + if (drvdata->quirks & QUIRK_HID_FN_LOCK) { | |
| 375 | + drvdata->fn_lock = !drvdata->fn_lock; | |
| 376 | + schedule_work(&drvdata->fn_lock_sync_work); | |
| 377 | + } | |
| 378 | + break; | |
| 379 | + } | |
| 370 | 380 | } |
| 371 | 381 | |
| 372 | 382 | return 0; |
| @@ -476,15 +486,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu · 103 unmodified lines | ||
| 476 | 486 | |
| 477 | 487 | static int asus_kbd_init(struct hid_device *hdev, u8 report_id) |
| 478 | 488 | { |
| 489 | + /* | |
| 490 | + * The handshake is first sent as a set_report, then retrieved | |
| 491 | + * from a get_report. They should be equal. | |
| 492 | + */ | |
| 479 | 493 | const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54, |
| 480 | 494 | 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 }; |
| 481 | 495 | int ret; |
| 482 | 496 | |
| 483 | 497 | ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); |
| 484 | − if (ret < 0) | |
| 485 | − hid_err(hdev, "Asus failed to send init command: %d\n", ret); | |
| 498 | + if (ret < 0) { | |
| 499 | + hid_err(hdev, "Asus handshake %02x failed to send: %d\n", | |
| 500 | + report_id, ret); | |
| 501 | + return ret; | |
| 502 | + } | |
| 486 | 503 | |
| 487 | − return ret; | |
| 504 | + u8 *readbuf __free(kfree) = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL); | |
| 505 | + if (!readbuf) | |
| 506 | + return -ENOMEM; | |
| 507 | + | |
| 508 | + ret = hid_hw_raw_request(hdev, report_id, readbuf, | |
| 509 | + FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT, | |
| 510 | + HID_REQ_GET_REPORT); | |
| 511 | + if (ret < 0) { | |
| 512 | + hid_warn(hdev, "Asus handshake %02x failed to receive ack: %d\n", | |
| 513 | + report_id, ret); | |
| 514 | + } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) { | |
| 515 | + hid_warn(hdev, "Asus handshake %02x returned invalid response: %*ph\n", | |
| 516 | + report_id, FEATURE_KBD_REPORT_SIZE, readbuf); | |
| 517 | + } | |
| 518 | + | |
| 519 | + /* | |
| 520 | + * Do not return error if handshake is wrong until this is | |
| 521 | + * verified to work for all devices. | |
| 522 | + */ | |
| 523 | + return 0; | |
| 488 | 524 | } |
| 489 | 525 | |
| 490 | 526 | static int asus_kbd_get_functions(struct hid_device *hdev, |
| @@ -505,7 +541,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev, · 14 unmodified lines | ||
| 505 | 541 | if (!readbuf) |
| 506 | 542 | return -ENOMEM; |
| 507 | 543 | |
| 508 | − ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf, | |
| 544 | + ret = hid_hw_raw_request(hdev, report_id, readbuf, | |
| 509 | 545 | FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT, |
| 510 | 546 | HID_REQ_GET_REPORT); |
| 511 | 547 | if (ret < 0) { |
| @@ -565,11 +601,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led) · 53 unmodified lines | ||
| 565 | 601 | spin_unlock_irqrestore(&led->lock, flags); |
| 566 | 602 | } |
| 567 | 603 | |
| 568 | −static void asus_kbd_backlight_set(struct led_classdev *led_cdev, | |
| 569 | − enum led_brightness brightness) | |
| 604 | +static void asus_kbd_backlight_set(struct asus_hid_listener *listener, | |
| 605 | + int brightness) | |
| 570 | 606 | { |
| 571 | − struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, | |
| 572 | − cdev); | |
| 607 | + struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds, | |
| 608 | + listener); | |
| 573 | 609 | unsigned long flags; |
| 574 | 610 | |
| 575 | 611 | spin_lock_irqsave(&led->lock, flags); |
| @@ -579,20 +615,6 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev, · 3 unmodified lines | ||
| 579 | 615 | asus_schedule_work(led); |
| 580 | 616 | } |
| 581 | 617 | |
| 582 | −static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev) | |
| 583 | −{ | |
| 584 | − struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, | |
| 585 | − cdev); | |
| 586 | − enum led_brightness brightness; | |
| 587 | − unsigned long flags; | |
| 588 | − | |
| 589 | − spin_lock_irqsave(&led->lock, flags); | |
| 590 | − brightness = led->brightness; | |
| 591 | − spin_unlock_irqrestore(&led->lock, flags); | |
| 592 | − | |
| 593 | − return brightness; | |
| 594 | −} | |
| 595 | − | |
| 596 | 618 | static void asus_kbd_backlight_work(struct work_struct *work) |
| 597 | 619 | { |
| 598 | 620 | struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work); |
| @@ -609,34 +631,6 @@ static void asus_kbd_backlight_work(struct work_struct *work) · 10 unmodified lines | ||
| 609 | 631 | hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret); |
| 610 | 632 | } |
| 611 | 633 | |
| 612 | −/* WMI-based keyboard backlight LED control (via asus-wmi driver) takes | |
| 613 | − * precedence. We only activate HID-based backlight control when the | |
| 614 | − * WMI control is not available. | |
| 615 | − */ | |
| 616 | −static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev) | |
| 617 | −{ | |
| 618 | − struct asus_drvdata *drvdata = hid_get_drvdata(hdev); | |
| 619 | − u32 value; | |
| 620 | − int ret; | |
| 621 | − | |
| 622 | − if (!IS_ENABLED(CONFIG_ASUS_WMI)) | |
| 623 | − return false; | |
| 624 | − | |
| 625 | − if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD && | |
| 626 | − dmi_check_system(asus_use_hid_led_dmi_ids)) { | |
| 627 | − hid_info(hdev, "using HID for asus::kbd_backlight\n"); | |
| 628 | − return false; | |
| 629 | − } | |
| 630 | − | |
| 631 | − ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, | |
| 632 | − ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value); | |
| 633 | − hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value); | |
| 634 | − if (ret) | |
| 635 | − return false; | |
| 636 | − | |
| 637 | − return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT); | |
| 638 | −} | |
| 639 | − | |
| 640 | 634 | /* |
| 641 | 635 | * We don't care about any other part of the string except the version section. |
| 642 | 636 | * Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01 |
| @@ -736,48 +730,35 @@ static int asus_kbd_register_leds(struct hid_device *hdev) · 93 unmodified lines | ||
| 736 | 730 | unsigned char kbd_func; |
| 737 | 731 | int ret; |
| 738 | 732 | |
| 739 | − if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) { | |
| 740 | − /* Initialize keyboard */ | |
| 741 | − ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); | |
| 742 | − if (ret < 0) | |
| 743 | − return ret; | |
| 744 | − | |
| 745 | − /* The LED endpoint is initialised in two HID */ | |
| 746 | − ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1); | |
| 747 | − if (ret < 0) | |
| 748 | − return ret; | |
| 749 | − | |
| 750 | − ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2); | |
| 751 | − if (ret < 0) | |
| 752 | − return ret; | |
| 733 | + ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); | |
| 734 | + if (ret < 0) | |
| 735 | + return ret; | |
| 753 | 736 | |
| 754 | − if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) { | |
| 755 | − ret = asus_kbd_disable_oobe(hdev); | |
| 756 | − if (ret < 0) | |
| 757 | − return ret; | |
| 758 | − } | |
| 737 | + /* Get keyboard functions */ | |
| 738 | + ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID); | |
| 739 | + if (ret < 0) | |
| 740 | + return ret; | |
| 759 | 741 | |
| 760 | − if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) { | |
| 761 | − intf = to_usb_interface(hdev->dev.parent); | |
| 762 | − udev = interface_to_usbdev(intf); | |
| 763 | − validate_mcu_fw_version(hdev, | |
| 764 | − le16_to_cpu(udev->descriptor.idProduct)); | |
| 765 | − } | |
| 742 | + /* Check for backlight support */ | |
| 743 | + if (!(kbd_func & SUPPORT_KBD_BACKLIGHT)) | |
| 744 | + return -ENODEV; | |
| 766 | 745 | |
| 767 | − } else { | |
| 768 | − /* Initialize keyboard */ | |
| 769 | − ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); | |
| 770 | − if (ret < 0) | |
| 771 | − return ret; | |
| 746 | + if (drvdata->quirks & QUIRK_ROG_NKEY_ID1ID2_INIT) { | |
| 747 | + asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1); | |
| 748 | + asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2); | |
| 749 | + } | |
| 772 | 750 | |
| 773 | − /* Get keyboard functions */ | |
| 774 | − ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID); | |
| 751 | + if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) { | |
| 752 | + ret = asus_kbd_disable_oobe(hdev); | |
| 775 | 753 | if (ret < 0) |
| 776 | 754 | return ret; |
| 755 | + } | |
| 777 | 756 | |
| 778 | − /* Check for backlight support */ | |
| 779 | − if (!(kbd_func & SUPPORT_KBD_BACKLIGHT)) | |
| 780 | − return -ENODEV; | |
| 757 | + if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) { | |
| 758 | + intf = to_usb_interface(hdev->dev.parent); | |
| 759 | + udev = interface_to_usbdev(intf); | |
| 760 | + validate_mcu_fw_version(hdev, | |
| 761 | + le16_to_cpu(udev->descriptor.idProduct)); | |
| 781 | 762 | } |
| 782 | 763 | |
| 783 | 764 | drvdata->kbd_backlight = devm_kzalloc(&hdev->dev, |
| @@ -789,14 +770,11 @@ static int asus_kbd_register_leds(struct hid_device *hdev) · 5 unmodified lines | ||
| 789 | 770 | drvdata->kbd_backlight->removed = false; |
| 790 | 771 | drvdata->kbd_backlight->brightness = 0; |
| 791 | 772 | drvdata->kbd_backlight->hdev = hdev; |
| 792 | − drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight"; | |
| 793 | − drvdata->kbd_backlight->cdev.max_brightness = 3; | |
| 794 | − drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set; | |
| 795 | − drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get; | |
| 773 | + drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set; | |
| 796 | 774 | INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work); |
| 797 | 775 | spin_lock_init(&drvdata->kbd_backlight->lock); |
| 798 | 776 | |
| 799 | − ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev); | |
| 777 | + ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener); | |
| 800 | 778 | if (ret < 0) { |
| 801 | 779 | /* No need to have this still around */ |
| 802 | 780 | devm_kfree(&hdev->dev, drvdata->kbd_backlight); |
| @@ -1021,11 +999,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) · 218 unmodified lines | ||
| 1021 | 999 | |
| 1022 | 1000 | drvdata->input = input; |
| 1023 | 1001 | |
| 1024 | − if (drvdata->enable_backlight && | |
| 1025 | − !asus_kbd_wmi_led_control_present(hdev) && | |
| 1026 | − asus_kbd_register_leds(hdev)) | |
| 1027 | − hid_warn(hdev, "Failed to initialize backlight.\n"); | |
| 1028 | − | |
| 1029 | 1002 | if (drvdata->quirks & QUIRK_HID_FN_LOCK) { |
| 1030 | 1003 | drvdata->fn_lock = true; |
| 1031 | 1004 | INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock); |
| @@ -1104,15 +1077,6 @@ static int asus_input_mapping(struct hid_device *hdev, · 72 unmodified lines | ||
| 1104 | 1077 | return -1; |
| 1105 | 1078 | } |
| 1106 | 1079 | |
| 1107 | − /* | |
| 1108 | − * Check and enable backlight only on devices with UsagePage == | |
| 1109 | − * 0xff31 to avoid initializing the keyboard firmware multiple | |
| 1110 | − * times on devices with multiple HID descriptors but same | |
| 1111 | − * PID/VID. | |
| 1112 | − */ | |
| 1113 | − if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) | |
| 1114 | − drvdata->enable_backlight = true; | |
| 1115 | − | |
| 1116 | 1080 | set_bit(EV_REP, hi->input->evbit); |
| 1117 | 1081 | return 1; |
| 1118 | 1082 | } |
| @@ -1205,7 +1169,7 @@ static int __maybe_unused asus_resume(struct hid_device *hdev) { · 86 unmodified lines | ||
| 1205 | 1169 | |
| 1206 | 1170 | if (drvdata->kbd_backlight) { |
| 1207 | 1171 | const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, |
| 1208 | − drvdata->kbd_backlight->cdev.brightness }; | |
| 1172 | + drvdata->kbd_backlight->brightness }; | |
| 1209 | 1173 | ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); |
| 1210 | 1174 | if (ret < 0) { |
| 1211 | 1175 | hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret); |
| @@ -1229,8 +1193,11 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev) · 17 unmodified lines | ||
| 1229 | 1193 | |
| 1230 | 1194 | static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) |
| 1231 | 1195 | { |
| 1232 | − int ret; | |
| 1196 | + struct hid_report_enum *rep_enum; | |
| 1233 | 1197 | struct asus_drvdata *drvdata; |
| 1198 | + struct hid_report *rep; | |
| 1199 | + bool is_vendor = false; | |
| 1200 | + int ret; | |
| 1234 | 1201 | |
| 1235 | 1202 | drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); |
| 1236 | 1203 | if (drvdata == NULL) { |
| @@ -1314,12 +1281,30 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) · 77 unmodified lines | ||
| 1314 | 1281 | return ret; |
| 1315 | 1282 | } |
| 1316 | 1283 | |
| 1284 | + /* Check for vendor for RGB init and handle generic devices properly. */ | |
| 1285 | + rep_enum = &hdev->report_enum[HID_INPUT_REPORT]; | |
| 1286 | + list_for_each_entry(rep, &rep_enum->report_list, list) { | |
| 1287 | + if ((rep->application & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR) | |
| 1288 | + is_vendor = true; | |
| 1289 | + } | |
| 1290 | + | |
| 1317 | 1291 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); |
| 1318 | 1292 | if (ret) { |
| 1319 | 1293 | hid_err(hdev, "Asus hw start failed: %d\n", ret); |
| 1320 | 1294 | return ret; |
| 1321 | 1295 | } |
| 1322 | 1296 | |
| 1297 | + if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) && | |
| 1298 | + asus_kbd_register_leds(hdev)) | |
| 1299 | + hid_warn(hdev, "Failed to initialize backlight.\n"); | |
| 1300 | + | |
| 1301 | + /* | |
| 1302 | + * For ROG keyboards, skip rename for consistency and ->input check as | |
| 1303 | + * some devices do not have inputs. | |
| 1304 | + */ | |
| 1305 | + if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) | |
| 1306 | + return 0; | |
| 1307 | + | |
| 1323 | 1308 | /* |
| 1324 | 1309 | * Check that input registration succeeded. Checking that |
| 1325 | 1310 | * HID_CLAIMED_INPUT is set prevents a UAF when all input devices |
| @@ -1356,6 +1341,8 @@ static void asus_remove(struct hid_device *hdev) · 30 unmodified lines | ||
| 1356 | 1341 | unsigned long flags; |
| 1357 | 1342 | |
| 1358 | 1343 | if (drvdata->kbd_backlight) { |
| 1344 | + asus_hid_unregister_listener(&drvdata->kbd_backlight->listener); | |
| 1345 | + | |
| 1359 | 1346 | spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags); |
| 1360 | 1347 | drvdata->kbd_backlight->removed = true; |
| 1361 | 1348 | spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags); |
| @@ -1490,10 +1477,10 @@ static const struct hid_device_id asus_devices[] = { · 128 unmodified lines | ||
| 1490 | 1477 | QUIRK_USE_KBD_BACKLIGHT }, |
| 1491 | 1478 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, |
| 1492 | 1479 | USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD), |
| 1493 | − QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, | |
| 1480 | + QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT }, | |
| 1494 | 1481 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, |
| 1495 | 1482 | USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2), |
| 1496 | − QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK }, | |
| 1483 | + QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK | QUIRK_ROG_NKEY_ID1ID2_INIT }, | |
| 1497 | 1484 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, |
| 1498 | 1485 | USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR), |
| 1499 | 1486 | QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, |
| @@ -6,6 +6,8 @@ · 5 unmodified lines | ||
| 6 | 6 | * Copyright (C) 2016-2018 Vadim Pasternak <vadimp@mellanox.com> |
| 7 | 7 | */ |
| 8 | 8 | |
| 9 | +#include <linux/array_size.h> | |
| 10 | +#include <linux/bits.h> | |
| 9 | 11 | #include <linux/device.h> |
| 10 | 12 | #include <linux/dmi.h> |
| 11 | 13 | #include <linux/i2c.h> |
| @@ -727,6 +729,16 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = { · 715 unmodified lines | ||
| 727 | 729 | }, |
| 728 | 730 | }; |
| 729 | 731 | |
| 732 | +/* Platform hotplug dgx data */ | |
| 733 | +static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pdb_items_data[] = { | |
| 734 | + { | |
| 735 | + .label = "pdb1", | |
| 736 | + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, | |
| 737 | + .mask = BIT(0), | |
| 738 | + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, | |
| 739 | + }, | |
| 740 | +}; | |
| 741 | + | |
| 730 | 742 | static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_items_data[] = { |
| 731 | 743 | { |
| 732 | 744 | .label = "pwr1", |
| @@ -776,6 +788,15 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_ng800_items_data[] = · 43 unmodified lines | ||
| 776 | 788 | }, |
| 777 | 789 | }; |
| 778 | 790 | |
| 791 | +static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pwr_items_data[] = { | |
| 792 | + { | |
| 793 | + .label = "pwr1", | |
| 794 | + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, | |
| 795 | + .mask = BIT(0), | |
| 796 | + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, | |
| 797 | + }, | |
| 798 | +}; | |
| 799 | + | |
| 779 | 800 | static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_items_data[] = { |
| 780 | 801 | { |
| 781 | 802 | .label = "fan1", |
| @@ -1399,6 +1420,45 @@ static struct mlxreg_core_item mlxplat_mlxcpld_ext_items[] = { · 617 unmodified lines | ||
| 1399 | 1420 | } |
| 1400 | 1421 | }; |
| 1401 | 1422 | |
| 1423 | +static struct mlxreg_core_item mlxplat_mlxcpld_ext_dgx_items[] = { | |
| 1424 | + { | |
| 1425 | + .data = mlxplat_mlxcpld_dgx_pdb_items_data, | |
| 1426 | + .aggr_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF, | |
| 1427 | + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, | |
| 1428 | + .mask = MLXPLAT_CPLD_PSU_MASK, | |
| 1429 | + .count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pdb_items_data), | |
| 1430 | + .inversed = 1, | |
| 1431 | + .health = false, | |
| 1432 | + }, | |
| 1433 | + { | |
| 1434 | + .data = mlxplat_mlxcpld_dgx_pwr_items_data, | |
| 1435 | + .aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF, | |
| 1436 | + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, | |
| 1437 | + .mask = MLXPLAT_CPLD_PWR_MASK, | |
| 1438 | + .count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pwr_items_data), | |
| 1439 | + .inversed = 0, | |
| 1440 | + .health = false, | |
| 1441 | + }, | |
| 1442 | + { | |
| 1443 | + .data = mlxplat_mlxcpld_default_ng_fan_items_data, | |
| 1444 | + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, | |
| 1445 | + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, | |
| 1446 | + .mask = MLXPLAT_CPLD_FAN_NG_MASK, | |
| 1447 | + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_fan_items_data), | |
| 1448 | + .inversed = 1, | |
| 1449 | + .health = false, | |
| 1450 | + }, | |
| 1451 | + { | |
| 1452 | + .data = mlxplat_mlxcpld_default_asic_items_data, | |
| 1453 | + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, | |
| 1454 | + .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, | |
| 1455 | + .mask = MLXPLAT_CPLD_ASIC_MASK, | |
| 1456 | + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_asic_items_data), | |
| 1457 | + .inversed = 0, | |
| 1458 | + .health = true, | |
| 1459 | + }, | |
| 1460 | +}; | |
| 1461 | + | |
| 1402 | 1462 | static struct mlxreg_core_item mlxplat_mlxcpld_ng800_items[] = { |
| 1403 | 1463 | { |
| 1404 | 1464 | .data = mlxplat_mlxcpld_default_ng_psu_items_data, |
| @@ -1450,6 +1510,16 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ext_data = { · 45 unmodified lines | ||
| 1450 | 1510 | .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2, |
| 1451 | 1511 | }; |
| 1452 | 1512 | |
| 1513 | +static | |
| 1514 | +struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_dgx_ext_data = { | |
| 1515 | + .items = mlxplat_mlxcpld_ext_dgx_items, | |
| 1516 | + .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_dgx_items), | |
| 1517 | + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, | |
| 1518 | + .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, | |
| 1519 | + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, | |
| 1520 | + .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2, | |
| 1521 | +}; | |
| 1522 | + | |
| 1453 | 1523 | static |
| 1454 | 1524 | struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_data = { |
| 1455 | 1525 | .items = mlxplat_mlxcpld_ng800_items, |
| @@ -4625,6 +4695,359 @@ static struct mlxreg_core_platform_data mlxplat_default_ng_regs_io_data = { · 3169 unmodified lines | ||
| 4625 | 4695 | .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_regs_io_data), |
| 4626 | 4696 | }; |
| 4627 | 4697 | |
| 4698 | +/* Platform register access for next generation systems families data */ | |
| 4699 | +static struct mlxreg_core_data mlxplat_mlxcpld_dgx_ng_regs_io_data[] = { | |
| 4700 | + { | |
| 4701 | + .label = "cpld1_version", | |
| 4702 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, | |
| 4703 | + .bit = GENMASK(7, 0), | |
| 4704 | + .mode = 0444, | |
| 4705 | + }, | |
| 4706 | + { | |
| 4707 | + .label = "cpld2_version", | |
| 4708 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, | |
| 4709 | + .bit = GENMASK(7, 0), | |
| 4710 | + .mode = 0444, | |
| 4711 | + }, | |
| 4712 | + { | |
| 4713 | + .label = "cpld3_version", | |
| 4714 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET, | |
| 4715 | + .bit = GENMASK(7, 0), | |
| 4716 | + .mode = 0444, | |
| 4717 | + }, | |
| 4718 | + { | |
| 4719 | + .label = "cpld4_version", | |
| 4720 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET, | |
| 4721 | + .bit = GENMASK(7, 0), | |
| 4722 | + .mode = 0444, | |
| 4723 | + }, | |
| 4724 | + { | |
| 4725 | + .label = "cpld1_pn", | |
| 4726 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, | |
| 4727 | + .bit = GENMASK(15, 0), | |
| 4728 | + .mode = 0444, | |
| 4729 | + .regnum = 2, | |
| 4730 | + }, | |
| 4731 | + { | |
| 4732 | + .label = "cpld2_pn", | |
| 4733 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, | |
| 4734 | + .bit = GENMASK(15, 0), | |
| 4735 | + .mode = 0444, | |
| 4736 | + .regnum = 2, | |
| 4737 | + }, | |
| 4738 | + { | |
| 4739 | + .label = "cpld3_pn", | |
| 4740 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET, | |
| 4741 | + .bit = GENMASK(15, 0), | |
| 4742 | + .mode = 0444, | |
| 4743 | + .regnum = 2, | |
| 4744 | + }, | |
| 4745 | + { | |
| 4746 | + .label = "cpld4_pn", | |
| 4747 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET, | |
| 4748 | + .bit = GENMASK(15, 0), | |
| 4749 | + .mode = 0444, | |
| 4750 | + .regnum = 2, | |
| 4751 | + }, | |
| 4752 | + { | |
| 4753 | + .label = "cpld1_version_min", | |
| 4754 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, | |
| 4755 | + .bit = GENMASK(7, 0), | |
| 4756 | + .mode = 0444, | |
| 4757 | + }, | |
| 4758 | + { | |
| 4759 | + .label = "cpld2_version_min", | |
| 4760 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, | |
| 4761 | + .bit = GENMASK(7, 0), | |
| 4762 | + .mode = 0444, | |
| 4763 | + }, | |
| 4764 | + { | |
| 4765 | + .label = "cpld3_version_min", | |
| 4766 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET, | |
| 4767 | + .bit = GENMASK(7, 0), | |
| 4768 | + .mode = 0444, | |
| 4769 | + }, | |
| 4770 | + { | |
| 4771 | + .label = "cpld4_version_min", | |
| 4772 | + .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET, | |
| 4773 | + .bit = GENMASK(7, 0), | |
| 4774 | + .mode = 0444, | |
| 4775 | + }, | |
| 4776 | + { | |
| 4777 | + .label = "asic_reset", | |
| 4778 | + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, | |
| 4779 | + .mask = GENMASK(7, 0) & ~BIT(3), | |
| 4780 | + .mode = 0200, | |
| 4781 | + }, | |
| 4782 | + { | |
| 4783 | + .label = "reset_long_pb", | |
| 4784 | + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, | |
| 4785 | + .mask = GENMASK(7, 0) & ~BIT(0), | |
| 4786 | + .mode = 0444, | |
| 4787 | + }, | |
| 4788 | + { | |
| 4789 | + .label = "reset_short_pb", | |
| 4790 | + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, | |
| 4791 | + .mask = GENMASK(7, 0) & ~BIT(1), | |
| 4792 | + .mode = 0444, | |
| 4793 | + }, | |
| 4794 | + { | |
| 4795 | + .label = "reset_aux_pwr_or_ref", | |
| 4796 | + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, | |
| 4797 | + .mask = GENMASK(7, 0) & ~BIT(2), | |
| 4798 | + .mode = 0444, | |
| 4799 | + }, | |
| 4800 | + { | |
| 4801 | + .label = "reset_swb_dc_dc_pwr_fail", | |
| 4802 | + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, | |
| 4803 | + .mask = GENMASK(7, 0) & ~BIT(3), | |
| 4804 | + .mode = 0444, | |
| 4805 | + }, | |
| 4806 | + { | |
| 4807 | + .label = "reset_from_asic", | |
| 4808 | + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, | |
| 4809 | + .mask = GENMASK(7, 0) & ~BIT(5), | |
| 4810 | + .mode = 0444, | |
| 4811 | + }, | |
| 4812 | + { | |
| 4813 | + .label = "reset_swb_wd", | |
| 4814 | + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, | |
| 4815 | + .mask = GENMASK(7, 0) & ~BIT(6), | |
| 4816 | + .mode = 0444, | |
| 4817 | + }, | |
| 4818 | + { | |
| 4819 | + .label = "reset_asic_thermal", | |
| 4820 | + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, | |
| 4821 | + .mask = GENMASK(7, 0) & ~BIT(7), | |
| 4822 | + .mode = 0444, | |
| 4823 | + }, | |
| 4824 | + { | |
| 4825 | + .label = "reset_sw_reset", | |
| 4826 | + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, | |
| 4827 | + .mask = GENMASK(7, 0) & ~BIT(0), | |
| 4828 | + .mode = 0444, | |
| 4829 | + }, | |
| 4830 | + { | |
| 4831 | + .label = "reset_comex_pwr_fail", | |
| 4832 | + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, | |
| 4833 | + .mask = GENMASK(7, 0) & ~BIT(3), | |
| 4834 | + .mode = 0444, | |
| 4835 | + }, | |
| 4836 | + { | |
| 4837 | + .label = "reset_platform", | |
| 4838 | + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, | |
| 4839 | + .mask = GENMASK(7, 0) & ~BIT(4), | |
| 4840 | + .mode = 0444, | |
| 4841 | + }, | |
| 4842 | + { | |
| 4843 | + .label = "reset_soc", | |
| 4844 | + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, | |
| 4845 | + .mask = GENMASK(7, 0) & ~BIT(5), | |
| 4846 | + .mode = 0444, | |
| 4847 | + }, | |
| 4848 | + { | |
| 4849 | + .label = "reset_comex_wd", | |
| 4850 | + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, | |
| 4851 | + .mask = GENMASK(7, 0) & ~BIT(6), | |
| 4852 | + .mode = 0444, | |
| 4853 | + }, | |
| 4854 | + { | |
| 4855 | + .label = "reset_system", | |
| 4856 | + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, | |
| 4857 | + .mask = GENMASK(7, 0) & ~BIT(1), | |
| 4858 | + .mode = 0444, | |
| 4859 | + }, | |
| 4860 | + { | |
| 4861 | + .label = "reset_sw_pwr_off", | |
| 4862 | + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, | |
| 4863 | + .mask = GENMASK(7, 0) & ~BIT(2), | |
| 4864 | + .mode = 0444, | |
| 4865 | + }, | |
| 4866 | + { | |
| 4867 | + .label = "reset_comex_thermal", | |
| 4868 | + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, | |
| 4869 | + .mask = GENMASK(7, 0) & ~BIT(3), | |
| 4870 | + .mode = 0444, | |
| 4871 | + }, | |
| 4872 | + { | |
| 4873 | + .label = "reset_reload_bios", | |
| 4874 | + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, | |
| 4875 | + .mask = GENMASK(7, 0) & ~BIT(5), | |
| 4876 | + .mode = 0444, | |
| 4877 | + }, | |
| 4878 | + { | |
| 4879 | + .label = "reset_pdb_pwr_fail", | |
| 4880 | + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, | |
| 4881 | + .mask = GENMASK(7, 0) & ~BIT(6), | |
| 4882 | + .mode = 0444, | |
| 4883 | + }, | |
| 4884 | + { | |
| 4885 | + .label = "pdb_reset_stby", | |
| 4886 | + .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, | |
| 4887 | + .mask = GENMASK(7, 0) & ~BIT(0), | |
| 4888 | + .mode = 0200, | |
| 4889 | + }, | |
| 4890 | + { | |
| 4891 | + .label = "pwr_cycle", | |
| 4892 | + .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, | |
| 4893 | + .mask = GENMASK(7, 0) & ~BIT(2), | |
| 4894 | + .mode = 0200, | |
| 4895 | + }, | |
| 4896 | + { | |
| 4897 | + .label = "pwr_down", | |
| 4898 | + .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, | |
| 4899 | + .mask = GENMASK(7, 0) & ~BIT(3), | |
| 4900 | + .mode = 0200, | |
| 4901 | + }, | |
| 4902 | + { | |
| 4903 | + .label = "deep_pwr_cycle", | |
| 4904 | + .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, | |
| 4905 | + .mask = GENMASK(7, 0) & ~BIT(5), | |
| 4906 | + .mode = 0200, | |
| 4907 | + }, | |
| 4908 | + { | |
| 4909 | + .label = "latch_reset", | |
| 4910 | + .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, | |
| 4911 | + .mask = GENMASK(7, 0) & ~BIT(6), | |
| 4912 | + .mode = 0200, | |
| 4913 | + }, | |
| 4914 | + { | |
| 4915 | + .label = "jtag_cap", | |
| 4916 | + .reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET, | |
| 4917 | + .mask = MLXPLAT_CPLD_FU_CAP_MASK, | |
| 4918 | + .bit = 1, | |
| 4919 | + .mode = 0444, | |
| 4920 | + }, | |
| 4921 | + { | |
| 4922 | + .label = "jtag_enable", | |
| 4923 | + .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, | |
| 4924 | + .mask = GENMASK(7, 0) & ~BIT(4), | |
| 4925 | + .mode = 0644, | |
| 4926 | + }, | |
| 4927 | + { | |
| 4928 | + .label = "dbg1", | |
| 4929 | + .reg = MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET, | |
| 4930 | + .bit = GENMASK(7, 0), | |
| 4931 | + .mode = 0644, | |
| 4932 | + }, | |
| 4933 | + { | |
| 4934 | + .label = "dbg2", | |
| 4935 | + .reg = MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET, | |
| 4936 | + .bit = GENMASK(7, 0), | |
| 4937 | + .mode = 0644, | |
| 4938 | + }, | |
| 4939 | + { | |
| 4940 | + .label = "dbg3", | |
| 4941 | + .reg = MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET, | |
| 4942 | + .bit = GENMASK(7, 0), | |
| 4943 | + .mode = 0644, | |
| 4944 | + }, | |
| 4945 | + { | |
| 4946 | + .label = "dbg4", | |
| 4947 | + .reg = MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET, | |
| 4948 | + .bit = GENMASK(7, 0), | |
| 4949 | + .mode = 0644, | |
| 4950 | + }, | |
| 4951 | + { | |
| 4952 | + .label = "asic_health", | |
| 4953 | + .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, | |
| 4954 | + .mask = MLXPLAT_CPLD_ASIC_MASK, | |
| 4955 | + .bit = 1, | |
| 4956 | + .mode = 0444, | |
| 4957 | + }, | |
| 4958 | + { | |
| 4959 | + .label = "fan_dir", | |
| 4960 | + .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION, | |
| 4961 | + .bit = GENMASK(7, 0), | |
| 4962 | + .mode = 0444, | |
| 4963 | + }, | |
| 4964 | + { | |
| 4965 | + .label = "bios_safe_mode", | |
| 4966 | + .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, | |
| 4967 | + .mask = GENMASK(7, 0) & ~BIT(4), | |
| 4968 | + .mode = 0444, | |
| 4969 | + }, | |
| 4970 | + { | |
| 4971 | + .label = "bios_active_image", | |
| 4972 | + .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, | |
| 4973 | + .mask = GENMASK(7, 0) & ~BIT(5), | |
| 4974 | + .mode = 0444, | |
| 4975 | + }, | |
| 4976 | + { | |
| 4977 | + .label = "bios_auth_fail", | |
| 4978 | + .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, | |
| 4979 | + .mask = GENMASK(7, 0) & ~BIT(6), | |
| 4980 | + .mode = 0444, | |
| 4981 | + }, | |
| 4982 | + { | |
| 4983 | + .label = "bios_upgrade_fail", | |
| 4984 | + .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, | |
| 4985 | + .mask = GENMASK(7, 0) & ~BIT(7), | |
| 4986 | + .mode = 0444, | |
| 4987 | + }, | |
| 4988 | + { | |
| 4989 | + .label = "voltreg_update_status", | |
| 4990 | + .reg = MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET, | |
| 4991 | + .mask = MLXPLAT_CPLD_VOLTREG_UPD_MASK, | |
| 4992 | + .bit = 5, | |
| 4993 | + .mode = 0444, | |
| 4994 | + }, | |
| 4995 | + { | |
| 4996 | + .label = "pwr_converter_prog_en", | |
| 4997 | + .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, | |
| 4998 | + .mask = GENMASK(7, 0) & ~BIT(0), | |
| 4999 | + .mode = 0644, | |
| 5000 | + .secured = 1, | |
| 5001 | + }, | |
| 5002 | + { | |
| 5003 | + .label = "vpd_wp", | |
| 5004 | + .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, | |
| 5005 | + .mask = GENMASK(7, 0) & ~BIT(3), | |
| 5006 | + .mode = 0644, | |
| 5007 | + }, | |
| 5008 | + { | |
| 5009 | + .label = "pcie_asic_reset_dis", | |
| 5010 | + .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, | |
| 5011 | + .mask = GENMASK(7, 0) & ~BIT(4), | |
| 5012 | + .mode = 0644, | |
| 5013 | + }, | |
| 5014 | + { | |
| 5015 | + .label = "shutdown_unlock", | |
| 5016 | + .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, | |
| 5017 | + .mask = GENMASK(7, 0) & ~BIT(5), | |
| 5018 | + .mode = 0644, | |
| 5019 | + }, | |
| 5020 | + { | |
| 5021 | + .label = "config1", | |
| 5022 | + .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET, | |
| 5023 | + .bit = GENMASK(7, 0), | |
| 5024 | + .mode = 0444, | |
| 5025 | + }, | |
| 5026 | + { | |
| 5027 | + .label = "config2", | |
| 5028 | + .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET, | |
| 5029 | + .bit = GENMASK(7, 0), | |
| 5030 | + .mode = 0444, | |
| 5031 | + }, | |
| 5032 | + { | |
| 5033 | + .label = "config3", | |
| 5034 | + .reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET, | |
| 5035 | + .bit = GENMASK(7, 0), | |
| 5036 | + .mode = 0444, | |
| 5037 | + }, | |
| 5038 | + { | |
| 5039 | + .label = "ufm_version", | |
| 5040 | + .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET, | |
| 5041 | + .bit = GENMASK(7, 0), | |
| 5042 | + .mode = 0444, | |
| 5043 | + }, | |
| 5044 | +}; | |
| 5045 | + | |
| 5046 | +static struct mlxreg_core_platform_data mlxplat_dgx_ng_regs_io_data = { | |
| 5047 | + .data = mlxplat_mlxcpld_dgx_ng_regs_io_data, | |
| 5048 | + .counter = ARRAY_SIZE(mlxplat_mlxcpld_dgx_ng_regs_io_data), | |
| 5049 | +}; | |
| 5050 | + | |
| 4628 | 5051 | /* Platform register access for modular systems families data */ |
| 4629 | 5052 | static struct mlxreg_core_data mlxplat_mlxcpld_modular_regs_io_data[] = { |
| 4630 | 5053 | { |
| @@ -7239,6 +7662,32 @@ static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi) · 2608 unmodified lines | ||
| 7239 | 7662 | return mlxplat_register_platform_device(); |
| 7240 | 7663 | } |
| 7241 | 7664 | |
| 7665 | +static int __init mlxplat_dmi_ng400_dgx_matched(const struct dmi_system_id *dmi) | |
| 7666 | +{ | |
| 7667 | + int i; | |
| 7668 | + | |
| 7669 | + mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; | |
| 7670 | + mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data); | |
| 7671 | + mlxplat_mux_data = mlxplat_default_mux_data; | |
| 7672 | + for (i = 0; i < mlxplat_mux_num; i++) { | |
| 7673 | + mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; | |
| 7674 | + mlxplat_mux_data[i].n_values = | |
| 7675 | + ARRAY_SIZE(mlxplat_msn21xx_channels); | |
| 7676 | + } | |
| 7677 | + mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data; | |
| 7678 | + mlxplat_hotplug->deferred_nr = | |
| 7679 | + mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; | |
| 7680 | + mlxplat_led = &mlxplat_default_ng_led_data; | |
| 7681 | + mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data; | |
| 7682 | + mlxplat_fan = &mlxplat_default_fan_data; | |
| 7683 | + for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) | |
| 7684 | + mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; | |
| 7685 | + mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; | |
| 7686 | + mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400; | |
| 7687 | + | |
| 7688 | + return mlxplat_register_platform_device(); | |
| 7689 | +} | |
| 7690 | + | |
| 7242 | 7691 | static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi) |
| 7243 | 7692 | { |
| 7244 | 7693 | int i; |
| @@ -7323,6 +7772,27 @@ static int __init mlxplat_dmi_ng800_matched(const struct dmi_system_id *dmi) · 78 unmodified lines | ||
| 7323 | 7772 | return mlxplat_register_platform_device(); |
| 7324 | 7773 | } |
| 7325 | 7774 | |
| 7775 | +static int __init mlxplat_dmi_ng800_dgx_matched(const struct dmi_system_id *dmi) | |
| 7776 | +{ | |
| 7777 | + int i; | |
| 7778 | + | |
| 7779 | + mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; | |
| 7780 | + mlxplat_mux_num = ARRAY_SIZE(mlxplat_ng800_mux_data); | |
| 7781 | + mlxplat_mux_data = mlxplat_ng800_mux_data; | |
| 7782 | + mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data; | |
| 7783 | + mlxplat_hotplug->deferred_nr = | |
| 7784 | + mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; | |
| 7785 | + mlxplat_led = &mlxplat_default_ng_led_data; | |
| 7786 | + mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data; | |
| 7787 | + mlxplat_fan = &mlxplat_default_fan_data; | |
| 7788 | + for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) | |
| 7789 | + mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; | |
| 7790 | + mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; | |
| 7791 | + mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400; | |
| 7792 | + | |
| 7793 | + return mlxplat_register_platform_device(); | |
| 7794 | +} | |
| 7795 | + | |
| 7326 | 7796 | static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi) |
| 7327 | 7797 | { |
| 7328 | 7798 | int i; |
| @@ -7458,6 +7928,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { · 129 unmodified lines | ||
| 7458 | 7928 | DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI142"), |
| 7459 | 7929 | }, |
| 7460 | 7930 | }, |
| 7931 | + { | |
| 7932 | + .callback = mlxplat_dmi_ng400_dgx_matched, | |
| 7933 | + .matches = { | |
| 7934 | + DMI_MATCH(DMI_BOARD_NAME, "VMOD0010"), | |
| 7935 | + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI173"), | |
| 7936 | + }, | |
| 7937 | + }, | |
| 7461 | 7938 | { |
| 7462 | 7939 | .callback = mlxplat_dmi_ng400_matched, |
| 7463 | 7940 | .matches = { |
| @@ -7470,6 +7947,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { · 6 unmodified lines | ||
| 7470 | 7947 | DMI_MATCH(DMI_BOARD_NAME, "VMOD0011"), |
| 7471 | 7948 | }, |
| 7472 | 7949 | }, |
| 7950 | + { | |
| 7951 | + .callback = mlxplat_dmi_ng800_dgx_matched, | |
| 7952 | + .matches = { | |
| 7953 | + DMI_MATCH(DMI_BOARD_NAME, "VMOD0013"), | |
| 7954 | + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI174"), | |
| 7955 | + }, | |
| 7956 | + }, | |
| 7473 | 7957 | { |
| 7474 | 7958 | .callback = mlxplat_dmi_ng800_matched, |
| 7475 | 7959 | .matches = { |
| @@ -406,6 +406,22 @@ static const struct software_node *ssam_node_group_sp9_5g[] = { · 405 unmodified lines | ||
| 406 | 406 | NULL, |
| 407 | 407 | }; |
| 408 | 408 | |
| 409 | +/* Devices for Surface Pro 11 (ARM/QCOM) */ | |
| 410 | +static const struct software_node *ssam_node_group_sp11[] = { | |
| 411 | + &ssam_node_root, | |
| 412 | + &ssam_node_hub_kip, | |
| 413 | + &ssam_node_bat_ac, | |
| 414 | + &ssam_node_bat_main, | |
| 415 | + &ssam_node_tmp_sensors, | |
| 416 | + &ssam_node_hid_kip_keyboard, | |
| 417 | + &ssam_node_hid_kip_penstash, | |
| 418 | + &ssam_node_hid_kip_touchpad, | |
| 419 | + &ssam_node_hid_kip_fwupd, | |
| 420 | + &ssam_node_hid_sam_sensors, | |
| 421 | + &ssam_node_kip_tablet_switch, | |
| 422 | + NULL, | |
| 423 | +}; | |
| 424 | + | |
| 409 | 425 | /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ |
| 410 | 426 | |
| 411 | 427 | static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { |
| @@ -482,6 +498,8 @@ MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_acpi_match); · 70 unmodified lines | ||
| 482 | 498 | static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = { |
| 483 | 499 | /* Surface Pro 9 5G (ARM/QCOM) */ |
| 484 | 500 | { .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g }, |
| 501 | + /* Surface Pro 11 (ARM/QCOM) */ | |
| 502 | + { .compatible = "microsoft,denali", (void *)ssam_node_group_sp11 }, | |
| 485 | 503 | /* Surface Laptop 7 */ |
| 486 | 504 | { .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 }, |
| 487 | 505 | { .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 }, |
| @@ -10,6 +10,7 @@ · 9 unmodified lines | ||
| 10 | 10 | #include <linux/kernel.h> |
| 11 | 11 | #include <linux/module.h> |
| 12 | 12 | #include <linux/init.h> |
| 13 | +#include <linux/string.h> | |
| 13 | 14 | #include <linux/types.h> |
| 14 | 15 | #include <linux/input.h> |
| 15 | 16 | #include <linux/acpi.h> |
| @@ -189,7 +190,6 @@ static int surface_button_add(struct acpi_device *device) · 173 unmodified lines | ||
| 189 | 190 | struct surface_button *button; |
| 190 | 191 | struct input_dev *input; |
| 191 | 192 | const char *hid = acpi_device_hid(device); |
| 192 | − char *name; | |
| 193 | 193 | int error; |
| 194 | 194 | |
| 195 | 195 | if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME, |
| @@ -210,11 +210,10 @@ static int surface_button_add(struct acpi_device *device) · 14 unmodified lines | ||
| 210 | 210 | goto err_free_button; |
| 211 | 211 | } |
| 212 | 212 | |
| 213 | − name = acpi_device_name(device); | |
| 214 | − strcpy(name, SURFACE_BUTTON_DEVICE_NAME); | |
| 213 | + strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME); | |
| 215 | 214 | snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid); |
| 216 | 215 | |
| 217 | − input->name = name; | |
| 216 | + input->name = acpi_device_name(device); | |
| 218 | 217 | input->phys = button->phys; |
| 219 | 218 | input->id.bustype = BUS_HOST; |
| 220 | 219 | input->dev.parent = &device->dev; |
| @@ -228,8 +227,8 @@ static int surface_button_add(struct acpi_device *device) · 7 unmodified lines | ||
| 228 | 227 | goto err_free_input; |
| 229 | 228 | |
| 230 | 229 | device_init_wakeup(&device->dev, true); |
| 231 | − dev_info(&device->dev, | |
| 232 | − "%s [%s]\n", name, acpi_device_bid(device)); | |
| 230 | + dev_info(&device->dev, "%s [%s]\n", acpi_device_name(device), | |
| 231 | + acpi_device_bid(device)); | |
| 233 | 232 | return 0; |
| 234 | 233 | |
| 235 | 234 | err_free_input: |
| @@ -6,6 +6,7 @@ · 5 unmodified lines | ||
| 6 | 6 | menuconfig ACPI_WMI |
| 7 | 7 | tristate "ACPI-WMI support" |
| 8 | 8 | depends on ACPI && X86 |
| 9 | + select NLS | |
| 9 | 10 | help |
| 10 | 11 | This option enables support for the ACPI-WMI driver core. |
| 11 | 12 | |
| @@ -31,4 +32,6 @@ config ACPI_WMI_LEGACY_DEVICE_NAMES · 19 unmodified lines | ||
| 31 | 32 | userspace applications but will cause the registration of WMI devices with |
| 32 | 33 | the same GUID to fail in some corner cases. |
| 33 | 34 | |
| 35 | +source "drivers/platform/wmi/tests/Kconfig" | |
| 36 | + | |
| 34 | 37 | endif # ACPI_WMI |
| @@ -4,5 +4,8 @@ · 3 unmodified lines | ||
| 4 | 4 | # ACPI WMI core |
| 5 | 5 | # |
| 6 | 6 | |
| 7 | −wmi-y := core.o | |
| 7 | +wmi-y := core.o marshalling.o string.o | |
| 8 | 8 | obj-$(CONFIG_ACPI_WMI) += wmi.o |
| 9 | + | |
| 10 | +# Unit tests | |
| 11 | +obj-y += tests/ | |
| @@ -23,6 +23,7 @@ · 22 unmodified lines | ||
| 23 | 23 | #include <linux/idr.h> |
| 24 | 24 | #include <linux/init.h> |
| 25 | 25 | #include <linux/kernel.h> |
| 26 | +#include <linux/limits.h> | |
| 26 | 27 | #include <linux/module.h> |
| 27 | 28 | #include <linux/platform_device.h> |
| 28 | 29 | #include <linux/rwsem.h> |
| @@ -33,6 +34,8 @@ · 4 unmodified lines | ||
| 33 | 34 | #include <linux/wmi.h> |
| 34 | 35 | #include <linux/fs.h> |
| 35 | 36 | |
| 37 | +#include "internal.h" | |
| 38 | + | |
| 36 | 39 | MODULE_AUTHOR("Carlos Corbacho"); |
| 37 | 40 | MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); |
| 38 | 41 | MODULE_LICENSE("GPL"); |
| @@ -302,7 +305,7 @@ acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method · 263 unmodified lines | ||
| 302 | 305 | EXPORT_SYMBOL_GPL(wmi_evaluate_method); |
| 303 | 306 | |
| 304 | 307 | /** |
| 305 | − * wmidev_evaluate_method - Evaluate a WMI method | |
| 308 | + * wmidev_evaluate_method - Evaluate a WMI method (deprecated) | |
| 306 | 309 | * @wdev: A wmi bus device from a driver |
| 307 | 310 | * @instance: Instance index |
| 308 | 311 | * @method_id: Method ID to call |
| @@ -360,6 +363,70 @@ acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 met · 51 unmodified lines | ||
| 360 | 363 | } |
| 361 | 364 | EXPORT_SYMBOL_GPL(wmidev_evaluate_method); |
| 362 | 365 | |
| 366 | +/** | |
| 367 | + * wmidev_invoke_method - Invoke a WMI method | |
| 368 | + * @wdev: A wmi bus device from a driver | |
| 369 | + * @instance: Instance index | |
| 370 | + * @method_id: Method ID to call | |
| 371 | + * @in: Mandatory WMI buffer containing input for the method call | |
| 372 | + * @out: Optional WMI buffer to return the method results | |
| 373 | + * | |
| 374 | + * Invoke a WMI method, the caller must free the resulting data inside @out. | |
| 375 | + * Said data is guaranteed to be aligned on a 8-byte boundary. | |
| 376 | + * | |
| 377 | + * Return: 0 on success or negative error code on failure. | |
| 378 | + */ | |
| 379 | +int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id, | |
| 380 | + const struct wmi_buffer *in, struct wmi_buffer *out) | |
| 381 | +{ | |
| 382 | + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); | |
| 383 | + struct acpi_buffer aout = { ACPI_ALLOCATE_BUFFER, NULL }; | |
| 384 | + struct acpi_buffer ain; | |
| 385 | + union acpi_object *obj; | |
| 386 | + acpi_status status; | |
| 387 | + int ret; | |
| 388 | + | |
| 389 | + if (wblock->gblock.flags & ACPI_WMI_STRING) { | |
| 390 | + ret = wmi_marshal_string(in, &ain); | |
| 391 | + if (ret < 0) | |
| 392 | + return ret; | |
| 393 | + } else { | |
| 394 | + if (in->length > U32_MAX) | |
| 395 | + return -E2BIG; | |
| 396 | + | |
| 397 | + ain.length = in->length; | |
| 398 | + ain.pointer = in->data; | |
| 399 | + } | |
| 400 | + | |
| 401 | + if (out) | |
| 402 | + status = wmidev_evaluate_method(wdev, instance, method_id, &ain, &aout); | |
| 403 | + else | |
| 404 | + status = wmidev_evaluate_method(wdev, instance, method_id, &ain, NULL); | |
| 405 | + | |
| 406 | + if (wblock->gblock.flags & ACPI_WMI_STRING) | |
| 407 | + kfree(ain.pointer); | |
| 408 | + | |
| 409 | + if (ACPI_FAILURE(status)) | |
| 410 | + return -EIO; | |
| 411 | + | |
| 412 | + if (!out) | |
| 413 | + return 0; | |
| 414 | + | |
| 415 | + obj = aout.pointer; | |
| 416 | + if (!obj) { | |
| 417 | + out->length = 0; | |
| 418 | + out->data = ZERO_SIZE_PTR; | |
| 419 | + | |
| 420 | + return 0; | |
| 421 | + } | |
| 422 | + | |
| 423 | + ret = wmi_unmarshal_acpi_object(obj, out); | |
| 424 | + kfree(obj); | |
| 425 | + | |
| 426 | + return ret; | |
| 427 | +} | |
| 428 | +EXPORT_SYMBOL_GPL(wmidev_invoke_method); | |
| 429 | + | |
| 363 | 430 | static acpi_status __query_block(struct wmi_block *wblock, u8 instance, |
| 364 | 431 | struct acpi_buffer *out) |
| 365 | 432 | { |
| @@ -432,7 +499,7 @@ acpi_status wmi_query_block(const char *guid_string, u8 instance, · 66 unmodified lines | ||
| 432 | 499 | EXPORT_SYMBOL_GPL(wmi_query_block); |
| 433 | 500 | |
| 434 | 501 | /** |
| 435 | − * wmidev_block_query - Return contents of a WMI block | |
| 502 | + * wmidev_block_query - Return contents of a WMI block (deprectated) | |
| 436 | 503 | * @wdev: A wmi bus device from a driver |
| 437 | 504 | * @instance: Instance index |
| 438 | 505 | * |
| @@ -452,6 +519,33 @@ union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance) · 13 unmodified lines | ||
| 452 | 519 | } |
| 453 | 520 | EXPORT_SYMBOL_GPL(wmidev_block_query); |
| 454 | 521 | |
| 522 | +/** | |
| 523 | + * wmidev_query_block - Return contents of a WMI data block | |
| 524 | + * @wdev: A wmi bus device from a driver | |
| 525 | + * @instance: Instance index | |
| 526 | + * @out: WMI buffer to fill | |
| 527 | + * | |
| 528 | + * Query a WMI data block, the caller must free the resulting data inside @out. | |
| 529 | + * Said data is guaranteed to be aligned on a 8-byte boundary. | |
| 530 | + * | |
| 531 | + * Return: 0 on success or a negative error code on failure. | |
| 532 | + */ | |
| 533 | +int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out) | |
| 534 | +{ | |
| 535 | + union acpi_object *obj; | |
| 536 | + int ret; | |
| 537 | + | |
| 538 | + obj = wmidev_block_query(wdev, instance); | |
| 539 | + if (!obj) | |
| 540 | + return -EIO; | |
| 541 | + | |
| 542 | + ret = wmi_unmarshal_acpi_object(obj, out); | |
| 543 | + kfree(obj); | |
| 544 | + | |
| 545 | + return ret; | |
| 546 | +} | |
| 547 | +EXPORT_SYMBOL_GPL(wmidev_query_block); | |
| 548 | + | |
| 455 | 549 | /** |
| 456 | 550 | * wmi_set_block - Write to a WMI block (deprecated) |
| 457 | 551 | * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba |
| @@ -486,7 +580,7 @@ acpi_status wmi_set_block(const char *guid_string, u8 instance, const struct acp · 28 unmodified lines | ||
| 486 | 580 | EXPORT_SYMBOL_GPL(wmi_set_block); |
| 487 | 581 | |
| 488 | 582 | /** |
| 489 | − * wmidev_block_set - Write to a WMI block | |
| 583 | + * wmidev_block_set - Write to a WMI block (deprecated) | |
| 490 | 584 | * @wdev: A wmi bus device from a driver |
| 491 | 585 | * @instance: Instance index |
| 492 | 586 | * @in: Buffer containing new values for the data block |
| @@ -535,6 +629,46 @@ acpi_status wmidev_block_set(struct wmi_device *wdev, u8 instance, const struct · 42 unmodified lines | ||
| 535 | 629 | } |
| 536 | 630 | EXPORT_SYMBOL_GPL(wmidev_block_set); |
| 537 | 631 | |
| 632 | +/** | |
| 633 | + * wmidev_set_block - Write to a WMI data block | |
| 634 | + * @wdev: A wmi bus device from a driver | |
| 635 | + * @instance: Instance index | |
| 636 | + * @in: WMI buffer containing new values for the data block | |
| 637 | + * | |
| 638 | + * Write the content of @in into a WMI data block. | |
| 639 | + * | |
| 640 | + * Return: 0 on success or negative error code on failure. | |
| 641 | + */ | |
| 642 | +int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in) | |
| 643 | +{ | |
| 644 | + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); | |
| 645 | + struct acpi_buffer buffer; | |
| 646 | + acpi_status status; | |
| 647 | + int ret; | |
| 648 | + | |
| 649 | + if (wblock->gblock.flags & ACPI_WMI_STRING) { | |
| 650 | + ret = wmi_marshal_string(in, &buffer); | |
| 651 | + if (ret < 0) | |
| 652 | + return ret; | |
| 653 | + } else { | |
| 654 | + if (in->length > U32_MAX) | |
| 655 | + return -E2BIG; | |
| 656 | + | |
| 657 | + buffer.length = in->length; | |
| 658 | + buffer.pointer = in->data; | |
| 659 | + } | |
| 660 | + | |
| 661 | + status = wmidev_block_set(wdev, instance, &buffer); | |
| 662 | + if (wblock->gblock.flags & ACPI_WMI_STRING) | |
| 663 | + kfree(buffer.pointer); | |
| 664 | + | |
| 665 | + if (ACPI_FAILURE(status)) | |
| 666 | + return -EIO; | |
| 667 | + | |
| 668 | + return 0; | |
| 669 | +} | |
| 670 | +EXPORT_SYMBOL_GPL(wmidev_set_block); | |
| 671 | + | |
| 538 | 672 | /** |
| 539 | 673 | * wmi_install_notify_handler - Register handler for WMI events (deprecated) |
| 540 | 674 | * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba |
| @@ -862,7 +996,7 @@ static int wmi_dev_probe(struct device *dev) · 321 unmodified lines | ||
| 862 | 996 | return -ENODEV; |
| 863 | 997 | } |
| 864 | 998 | |
| 865 | − if (wdriver->notify) { | |
| 999 | + if (wdriver->notify || wdriver->notify_new) { | |
| 866 | 1000 | if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data) |
| 867 | 1001 | return -ENODEV; |
| 868 | 1002 | } |
| @@ -1221,6 +1355,8 @@ static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj · 352 unmodified lines | ||
| 1221 | 1355 | static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj) |
| 1222 | 1356 | { |
| 1223 | 1357 | struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver); |
| 1358 | + struct wmi_buffer buffer; | |
| 1359 | + int ret; | |
| 1224 | 1360 | |
| 1225 | 1361 | if (!obj && !driver->no_notify_data) { |
| 1226 | 1362 | dev_warn(&wblock->dev.dev, "Event contains no event data\n"); |
| @@ -1229,6 +1365,22 @@ static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj) · 2 unmodified lines | ||
| 1229 | 1365 | |
| 1230 | 1366 | if (driver->notify) |
| 1231 | 1367 | driver->notify(&wblock->dev, obj); |
| 1368 | + | |
| 1369 | + if (driver->notify_new) { | |
| 1370 | + if (!obj) { | |
| 1371 | + driver->notify_new(&wblock->dev, NULL); | |
| 1372 | + return; | |
| 1373 | + } | |
| 1374 | + | |
| 1375 | + ret = wmi_unmarshal_acpi_object(obj, &buffer); | |
| 1376 | + if (ret < 0) { | |
| 1377 | + dev_warn(&wblock->dev.dev, "Failed to unmarshal event data: %d\n", ret); | |
| 1378 | + return; | |
| 1379 | + } | |
| 1380 | + | |
| 1381 | + driver->notify_new(&wblock->dev, &buffer); | |
| 1382 | + kfree(buffer.data); | |
| 1383 | + } | |
| 1232 | 1384 | } |
| 1233 | 1385 | |
| 1234 | 1386 | static int wmi_notify_device(struct device *dev, void *data) |
| @@ -0,0 +1,17 @@ | ||
| 1 | +/* SPDX-License-Identifier: GPL-2.0-or-later */ | |
| 2 | +/* | |
| 3 | + * Internal interfaces used by the WMI core. | |
| 4 | + * | |
| 5 | + * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de> | |
| 6 | + */ | |
| 7 | + | |
| 8 | +#ifndef _WMI_INTERNAL_H_ | |
| 9 | +#define _WMI_INTERNAL_H_ | |
| 10 | + | |
| 11 | +union acpi_object; | |
| 12 | +struct wmi_buffer; | |
| 13 | + | |
| 14 | +int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer); | |
| 15 | +int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out); | |
| 16 | + | |
| 17 | +#endif /* _WMI_INTERNAL_H_ */ | |
| @@ -0,0 +1,247 @@ | ||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | |
| 2 | +/* | |
| 3 | + * ACPI-WMI buffer marshalling. | |
| 4 | + * | |
| 5 | + * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de> | |
| 6 | + */ | |
| 7 | + | |
| 8 | +#include <linux/acpi.h> | |
| 9 | +#include <linux/align.h> | |
| 10 | +#include <linux/math.h> | |
| 11 | +#include <linux/overflow.h> | |
| 12 | +#include <linux/slab.h> | |
| 13 | +#include <linux/unaligned.h> | |
| 14 | +#include <linux/wmi.h> | |
| 15 | + | |
| 16 | +#include <kunit/visibility.h> | |
| 17 | + | |
| 18 | +#include "internal.h" | |
| 19 | + | |
| 20 | +static int wmi_adjust_buffer_length(size_t *length, const union acpi_object *obj) | |
| 21 | +{ | |
| 22 | + size_t alignment, size; | |
| 23 | + | |
| 24 | + switch (obj->type) { | |
| 25 | + case ACPI_TYPE_INTEGER: | |
| 26 | + /* | |
| 27 | + * Integers are threated as 32 bit even if the ACPI DSDT | |
| 28 | + * declares 64 bit integer width. | |
| 29 | + */ | |
| 30 | + alignment = 4; | |
| 31 | + size = sizeof(u32); | |
| 32 | + break; | |
| 33 | + case ACPI_TYPE_STRING: | |
| 34 | + /* | |
| 35 | + * Strings begin with a single little-endian 16-bit field containing | |
| 36 | + * the string length in bytes and are encoded as UTF-16LE with a terminating | |
| 37 | + * nul character. | |
| 38 | + */ | |
| 39 | + if (obj->string.length + 1 > U16_MAX / 2) | |
| 40 | + return -EOVERFLOW; | |
| 41 | + | |
| 42 | + alignment = 2; | |
| 43 | + size = struct_size_t(struct wmi_string, chars, obj->string.length + 1); | |
| 44 | + break; | |
| 45 | + case ACPI_TYPE_BUFFER: | |
| 46 | + /* | |
| 47 | + * Buffers are copied as-is. | |
| 48 | + */ | |
| 49 | + alignment = 1; | |
| 50 | + size = obj->buffer.length; | |
| 51 | + break; | |
| 52 | + default: | |
| 53 | + return -EPROTO; | |
| 54 | + } | |
| 55 | + | |
| 56 | + *length = size_add(ALIGN(*length, alignment), size); | |
| 57 | + | |
| 58 | + return 0; | |
| 59 | +} | |
| 60 | + | |
| 61 | +static int wmi_obj_get_buffer_length(const union acpi_object *obj, size_t *length) | |
| 62 | +{ | |
| 63 | + size_t total = 0; | |
| 64 | + int ret; | |
| 65 | + | |
| 66 | + if (obj->type == ACPI_TYPE_PACKAGE) { | |
| 67 | + for (int i = 0; i < obj->package.count; i++) { | |
| 68 | + ret = wmi_adjust_buffer_length(&total, &obj->package.elements[i]); | |
| 69 | + if (ret < 0) | |
| 70 | + return ret; | |
| 71 | + } | |
| 72 | + } else { | |
| 73 | + ret = wmi_adjust_buffer_length(&total, obj); | |
| 74 | + if (ret < 0) | |
| 75 | + return ret; | |
| 76 | + } | |
| 77 | + | |
| 78 | + *length = total; | |
| 79 | + | |
| 80 | + return 0; | |
| 81 | +} | |
| 82 | + | |
| 83 | +static int wmi_obj_transform_simple(const union acpi_object *obj, u8 *buffer, size_t *consumed) | |
| 84 | +{ | |
| 85 | + struct wmi_string *string; | |
| 86 | + size_t length; | |
| 87 | + __le32 value; | |
| 88 | + u8 *aligned; | |
| 89 | + | |
| 90 | + switch (obj->type) { | |
| 91 | + case ACPI_TYPE_INTEGER: | |
| 92 | + aligned = PTR_ALIGN(buffer, 4); | |
| 93 | + length = sizeof(value); | |
| 94 | + | |
| 95 | + value = cpu_to_le32(obj->integer.value); | |
| 96 | + memcpy(aligned, &value, length); | |
| 97 | + break; | |
| 98 | + case ACPI_TYPE_STRING: | |
| 99 | + aligned = PTR_ALIGN(buffer, 2); | |
| 100 | + string = (struct wmi_string *)aligned; | |
| 101 | + length = struct_size(string, chars, obj->string.length + 1); | |
| 102 | + | |
| 103 | + /* We do not have to worry about unaligned accesses here as the WMI | |
| 104 | + * string will already be aligned on a two-byte boundary. | |
| 105 | + */ | |
| 106 | + string->length = cpu_to_le16((obj->string.length + 1) * 2); | |
| 107 | + for (int i = 0; i < obj->string.length; i++) | |
| 108 | + string->chars[i] = cpu_to_le16(obj->string.pointer[i]); | |
| 109 | + | |
| 110 | + /* | |
| 111 | + * The Windows WMI-ACPI driver always emits a terminating nul character, | |
| 112 | + * so we emulate this behavior here as well. | |
| 113 | + */ | |
| 114 | + string->chars[obj->string.length] = '\0'; | |
| 115 | + break; | |
| 116 | + case ACPI_TYPE_BUFFER: | |
| 117 | + aligned = buffer; | |
| 118 | + length = obj->buffer.length; | |
| 119 | + | |
| 120 | + memcpy(aligned, obj->buffer.pointer, length); | |
| 121 | + break; | |
| 122 | + default: | |
| 123 | + return -EPROTO; | |
| 124 | + } | |
| 125 | + | |
| 126 | + *consumed = (aligned - buffer) + length; | |
| 127 | + | |
| 128 | + return 0; | |
| 129 | +} | |
| 130 | + | |
| 131 | +static int wmi_obj_transform(const union acpi_object *obj, u8 *buffer) | |
| 132 | +{ | |
| 133 | + size_t consumed; | |
| 134 | + int ret; | |
| 135 | + | |
| 136 | + if (obj->type == ACPI_TYPE_PACKAGE) { | |
| 137 | + for (int i = 0; i < obj->package.count; i++) { | |
| 138 | + ret = wmi_obj_transform_simple(&obj->package.elements[i], buffer, | |
| 139 | + &consumed); | |
| 140 | + if (ret < 0) | |
| 141 | + return ret; | |
| 142 | + | |
| 143 | + buffer += consumed; | |
| 144 | + } | |
| 145 | + } else { | |
| 146 | + ret = wmi_obj_transform_simple(obj, buffer, &consumed); | |
| 147 | + if (ret < 0) | |
| 148 | + return ret; | |
| 149 | + } | |
| 150 | + | |
| 151 | + return 0; | |
| 152 | +} | |
| 153 | + | |
| 154 | +int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer) | |
| 155 | +{ | |
| 156 | + size_t length, alloc_length; | |
| 157 | + u8 *data; | |
| 158 | + int ret; | |
| 159 | + | |
| 160 | + ret = wmi_obj_get_buffer_length(obj, &length); | |
| 161 | + if (ret < 0) | |
| 162 | + return ret; | |
| 163 | + | |
| 164 | + if (ARCH_KMALLOC_MINALIGN < 8) { | |
| 165 | + /* | |
| 166 | + * kmalloc() guarantees that the alignment of the resulting memory allocation is at | |
| 167 | + * least the largest power-of-two divisor of the allocation size. The WMI buffer | |
| 168 | + * data needs to be aligned on a 8 byte boundary to properly support 64-bit WMI | |
| 169 | + * integers, so we have to round the allocation size to the next multiple of 8. | |
| 170 | + */ | |
| 171 | + alloc_length = round_up(length, 8); | |
| 172 | + } else { | |
| 173 | + alloc_length = length; | |
| 174 | + } | |
| 175 | + | |
| 176 | + data = kzalloc(alloc_length, GFP_KERNEL); | |
| 177 | + if (!data) | |
| 178 | + return -ENOMEM; | |
| 179 | + | |
| 180 | + ret = wmi_obj_transform(obj, data); | |
| 181 | + if (ret < 0) { | |
| 182 | + kfree(data); | |
| 183 | + return ret; | |
| 184 | + } | |
| 185 | + | |
| 186 | + buffer->length = length; | |
| 187 | + buffer->data = data; | |
| 188 | + | |
| 189 | + return 0; | |
| 190 | +} | |
| 191 | +EXPORT_SYMBOL_IF_KUNIT(wmi_unmarshal_acpi_object); | |
| 192 | + | |
| 193 | +int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out) | |
| 194 | +{ | |
| 195 | + const struct wmi_string *string; | |
| 196 | + u16 length, value; | |
| 197 | + size_t chars; | |
| 198 | + char *str; | |
| 199 | + | |
| 200 | + if (buffer->length < sizeof(*string)) | |
| 201 | + return -ENODATA; | |
| 202 | + | |
| 203 | + string = buffer->data; | |
| 204 | + length = get_unaligned_le16(&string->length); | |
| 205 | + if (buffer->length < sizeof(*string) + length) | |
| 206 | + return -ENODATA; | |
| 207 | + | |
| 208 | + /* Each character needs to be 16 bits long */ | |
| 209 | + if (length % 2) | |
| 210 | + return -EINVAL; | |
| 211 | + | |
| 212 | + chars = length / 2; | |
| 213 | + str = kmalloc(chars + 1, GFP_KERNEL); | |
| 214 | + if (!str) | |
| 215 | + return -ENOMEM; | |
| 216 | + | |
| 217 | + for (int i = 0; i < chars; i++) { | |
| 218 | + value = get_unaligned_le16(&string->chars[i]); | |
| 219 | + | |
| 220 | + /* ACPI only accepts ASCII strings */ | |
| 221 | + if (value > 0x7F) { | |
| 222 | + kfree(str); | |
| 223 | + return -EINVAL; | |
| 224 | + } | |
| 225 | + | |
| 226 | + str[i] = value & 0xFF; | |
| 227 | + | |
| 228 | + /* | |
| 229 | + * ACPI strings should only contain a single nul character at the end. | |
| 230 | + * Because of this we must not copy any padding from the WMI string. | |
| 231 | + */ | |
| 232 | + if (!value) { | |
| 233 | + /* ACPICA wants the length of the string without the nul character */ | |
| 234 | + out->length = i; | |
| 235 | + out->pointer = str; | |
| 236 | + return 0; | |
| 237 | + } | |
| 238 | + } | |
| 239 | + | |
| 240 | + str[chars] = '\0'; | |
| 241 | + | |
| 242 | + out->length = chars; | |
| 243 | + out->pointer = str; | |
| 244 | + | |
| 245 | + return 0; | |
| 246 | +} | |
| 247 | +EXPORT_SYMBOL_IF_KUNIT(wmi_marshal_string); | |
| @@ -0,0 +1,92 @@ | ||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | |
| 2 | +/* | |
| 3 | + * WMI string utility functions. | |
| 4 | + * | |
| 5 | + * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de> | |
| 6 | + */ | |
| 7 | + | |
| 8 | +#include <linux/build_bug.h> | |
| 9 | +#include <linux/compiler_types.h> | |
| 10 | +#include <linux/err.h> | |
| 11 | +#include <linux/export.h> | |
| 12 | +#include <linux/nls.h> | |
| 13 | +#include <linux/limits.h> | |
| 14 | +#include <linux/types.h> | |
| 15 | +#include <linux/wmi.h> | |
| 16 | + | |
| 17 | +#include <asm/byteorder.h> | |
| 18 | + | |
| 19 | +static_assert(sizeof(__le16) == sizeof(wchar_t)); | |
| 20 | + | |
| 21 | +/** | |
| 22 | + * wmi_string_to_utf8s - Convert a WMI string into a UTF8 string. | |
| 23 | + * @str: WMI string representation | |
| 24 | + * @dst: Buffer to fill with UTF8 characters | |
| 25 | + * @length: Length of the destination buffer | |
| 26 | + * | |
| 27 | + * Convert as WMI string into a standard UTF8 string. The conversion will stop | |
| 28 | + * once a NUL character is detected or when the buffer is full. Any invalid UTF16 | |
| 29 | + * characters will be ignored. The resulting UTF8 string will always be NUL-terminated | |
| 30 | + * when this function returns successfully. | |
| 31 | + * | |
| 32 | + * Return: Length of the resulting UTF8 string or negative errno code on failure. | |
| 33 | + */ | |
| 34 | +ssize_t wmi_string_to_utf8s(const struct wmi_string *str, u8 *dst, size_t length) | |
| 35 | +{ | |
| 36 | + /* Contains the maximum number of UTF16 code points to read */ | |
| 37 | + int inlen = le16_to_cpu(str->length) / 2; | |
| 38 | + int ret; | |
| 39 | + | |
| 40 | + if (length < 1) | |
| 41 | + return -EINVAL; | |
| 42 | + | |
| 43 | + /* We must leave room for the NUL character at the end of the destination buffer */ | |
| 44 | + ret = utf16s_to_utf8s((__force const wchar_t *)str->chars, inlen, UTF16_LITTLE_ENDIAN, dst, | |
| 45 | + length - 1); | |
| 46 | + if (ret < 0) | |
| 47 | + return ret; | |
| 48 | + | |
| 49 | + dst[ret] = '\0'; | |
| 50 | + | |
| 51 | + return ret; | |
| 52 | +} | |
| 53 | +EXPORT_SYMBOL_GPL(wmi_string_to_utf8s); | |
| 54 | + | |
| 55 | +/** | |
| 56 | + * wmi_string_from_utf8s - Convert a UTF8 string into a WMI string. | |
| 57 | + * @str: WMI string representation | |
| 58 | + * @max_chars: Maximum number of UTF16 code points to store inside the WMI string | |
| 59 | + * @src: UTF8 string to convert | |
| 60 | + * @src_length: Length of the source string without any trailing NUL-characters | |
| 61 | + * | |
| 62 | + * Convert a UTF8 string into a WMI string. The conversion will stop when the WMI string is | |
| 63 | + * full. The resulting WMI string will always be NUL-terminated and have its length field set | |
| 64 | + * to and appropriate value when this function returns successfully. | |
| 65 | + * | |
| 66 | + * Return: Number of UTF16 code points inside the WMI string or negative errno code on failure. | |
| 67 | + */ | |
| 68 | +ssize_t wmi_string_from_utf8s(struct wmi_string *str, size_t max_chars, const u8 *src, | |
| 69 | + size_t src_length) | |
| 70 | +{ | |
| 71 | + size_t str_length; | |
| 72 | + int ret; | |
| 73 | + | |
| 74 | + if (max_chars < 1) | |
| 75 | + return -EINVAL; | |
| 76 | + | |
| 77 | + /* We must leave room for the NUL character at the end of the WMI string */ | |
| 78 | + ret = utf8s_to_utf16s(src, src_length, UTF16_LITTLE_ENDIAN, (__force wchar_t *)str->chars, | |
| 79 | + max_chars - 1); | |
| 80 | + if (ret < 0) | |
| 81 | + return ret; | |
| 82 | + | |
| 83 | + str_length = (ret + 1) * sizeof(u16); | |
| 84 | + if (str_length > U16_MAX) | |
| 85 | + return -EOVERFLOW; | |
| 86 | + | |
| 87 | + str->length = cpu_to_le16(str_length); | |
| 88 | + str->chars[ret] = '\0'; | |
| 89 | + | |
| 90 | + return ret; | |
| 91 | +} | |
| 92 | +EXPORT_SYMBOL_GPL(wmi_string_from_utf8s); | |
| @@ -0,0 +1,27 @@ | ||
| 1 | +# SPDX-License-Identifier: GPL-2.0-or-later | |
| 2 | +# | |
| 3 | +# ACPI WMI KUnit tests | |
| 4 | +# | |
| 5 | + | |
| 6 | +config ACPI_WMI_MARSHALLING_KUNIT_TEST | |
| 7 | + tristate "KUnit Test for ACPI-WMI marshalling" if !KUNIT_ALL_TESTS | |
| 8 | + depends on KUNIT | |
| 9 | + default KUNIT_ALL_TESTS | |
| 10 | + help | |
| 11 | + This builds unit tests for the ACPI-WMI marshalling code. | |
| 12 | + | |
| 13 | + For more information on KUnit and unit tests in general, please refer | |
| 14 | + to the KUnit documentation in Documentation/dev-tools/kunit/. | |
| 15 | + | |
| 16 | + If unsure, say N. | |
| 17 | + | |
| 18 | +config ACPI_WMI_STRING_KUNIT_TEST | |
| 19 | + tristate "KUnit Test for ACPI-WMI string conversion" if !KUNIT_ALL_TESTS | |
| 20 | + depends on KUNIT | |
| 21 | + default KUNIT_ALL_TESTS | |
| 22 | + help | |
| 23 | + This builds unit tests for the ACPI-WMI string conversion code. | |
| 24 | + For more information on KUnit and unit tests in general, please refer | |
| 25 | + to the KUnit documentation in Documentation/dev-tools/kunit/. | |
| 26 | + | |
| 27 | + If unsure, say N. | |
| @@ -0,0 +1,11 @@ | ||
| 1 | +# SPDX-License-Identifier: GPL-2.0-or-later | |
| 2 | +# | |
| 3 | +# Makefile for linux/drivers/platform/x86/wmi/tests | |
| 4 | +# ACPI WMI KUnit tests | |
| 5 | +# | |
| 6 | + | |
| 7 | +wmi_marshalling_kunit-y := marshalling_kunit.o | |
| 8 | +obj-$(CONFIG_ACPI_WMI_MARSHALLING_KUNIT_TEST) += wmi_marshalling_kunit.o | |
| 9 | + | |
| 10 | +wmi_string_kunit-y := string_kunit.o | |
| 11 | +obj-$(CONFIG_ACPI_WMI_STRING_KUNIT_TEST) += wmi_string_kunit.o | |
| @@ -0,0 +1,452 @@ | ||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | |
| 2 | +/* | |
| 3 | + * KUnit test for the ACPI-WMI marshalling code. | |
| 4 | + * | |
| 5 | + * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de> | |
| 6 | + */ | |
| 7 | + | |
| 8 | +#include <linux/acpi.h> | |
| 9 | +#include <linux/align.h> | |
| 10 | +#include <linux/module.h> | |
| 11 | +#include <linux/slab.h> | |
| 12 | +#include <linux/string.h> | |
| 13 | +#include <linux/types.h> | |
| 14 | +#include <linux/wmi.h> | |
| 15 | + | |
| 16 | +#include <kunit/resource.h> | |
| 17 | +#include <kunit/test.h> | |
| 18 | + | |
| 19 | +#include "../internal.h" | |
| 20 | + | |
| 21 | +struct wmi_acpi_param { | |
| 22 | + const char *name; | |
| 23 | + const union acpi_object obj; | |
| 24 | + const struct wmi_buffer buffer; | |
| 25 | +}; | |
| 26 | + | |
| 27 | +struct wmi_string_param { | |
| 28 | + const char *name; | |
| 29 | + const char *string; | |
| 30 | + const struct wmi_buffer buffer; | |
| 31 | +}; | |
| 32 | + | |
| 33 | +struct wmi_invalid_acpi_param { | |
| 34 | + const char *name; | |
| 35 | + const union acpi_object obj; | |
| 36 | +}; | |
| 37 | + | |
| 38 | +struct wmi_invalid_string_param { | |
| 39 | + const char *name; | |
| 40 | + const struct wmi_buffer buffer; | |
| 41 | +}; | |
| 42 | + | |
| 43 | +/* 0xdeadbeef */ | |
| 44 | +static u8 expected_single_integer[] = { | |
| 45 | + 0xef, 0xbe, 0xad, 0xde, | |
| 46 | +}; | |
| 47 | + | |
| 48 | +/* "TEST" */ | |
| 49 | +static u8 expected_single_string[] = { | |
| 50 | + 0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00, | |
| 51 | +}; | |
| 52 | + | |
| 53 | +static u8 test_buffer[] = { | |
| 54 | + 0xab, 0xcd, | |
| 55 | +}; | |
| 56 | + | |
| 57 | +static u8 expected_single_buffer[] = { | |
| 58 | + 0xab, 0xcd, | |
| 59 | +}; | |
| 60 | + | |
| 61 | +static union acpi_object simple_package_elements[] = { | |
| 62 | + { | |
| 63 | + .buffer = { | |
| 64 | + .type = ACPI_TYPE_BUFFER, | |
| 65 | + .length = sizeof(test_buffer), | |
| 66 | + .pointer = test_buffer, | |
| 67 | + }, | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + .integer = { | |
| 71 | + .type = ACPI_TYPE_INTEGER, | |
| 72 | + .value = 0x01020304, | |
| 73 | + }, | |
| 74 | + }, | |
| 75 | +}; | |
| 76 | + | |
| 77 | +static u8 expected_simple_package[] = { | |
| 78 | + 0xab, 0xcd, | |
| 79 | + 0x00, 0x00, | |
| 80 | + 0x04, 0x03, 0x02, 0x01, | |
| 81 | +}; | |
| 82 | + | |
| 83 | +static u8 test_small_buffer[] = { | |
| 84 | + 0xde, | |
| 85 | +}; | |
| 86 | + | |
| 87 | +static union acpi_object complex_package_elements[] = { | |
| 88 | + { | |
| 89 | + .integer = { | |
| 90 | + .type = ACPI_TYPE_INTEGER, | |
| 91 | + .value = 0xdeadbeef, | |
| 92 | + }, | |
| 93 | + }, | |
| 94 | + { | |
| 95 | + .buffer = { | |
| 96 | + .type = ACPI_TYPE_BUFFER, | |
| 97 | + .length = sizeof(test_small_buffer), | |
| 98 | + .pointer = test_small_buffer, | |
| 99 | + }, | |
| 100 | + }, | |
| 101 | + { | |
| 102 | + .string = { | |
| 103 | + .type = ACPI_TYPE_STRING, | |
| 104 | + .length = sizeof("TEST") - 1, | |
| 105 | + .pointer = "TEST", | |
| 106 | + }, | |
| 107 | + }, | |
| 108 | + { | |
| 109 | + .buffer = { | |
| 110 | + .type = ACPI_TYPE_BUFFER, | |
| 111 | + .length = sizeof(test_small_buffer), | |
| 112 | + .pointer = test_small_buffer, | |
| 113 | + }, | |
| 114 | + }, | |
| 115 | + { | |
| 116 | + .integer = { | |
| 117 | + .type = ACPI_TYPE_INTEGER, | |
| 118 | + .value = 0x01020304, | |
| 119 | + }, | |
| 120 | + } | |
| 121 | +}; | |
| 122 | + | |
| 123 | +static u8 expected_complex_package[] = { | |
| 124 | + 0xef, 0xbe, 0xad, 0xde, | |
| 125 | + 0xde, | |
| 126 | + 0x00, | |
| 127 | + 0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00, | |
| 128 | + 0xde, | |
| 129 | + 0x00, | |
| 130 | + 0x04, 0x03, 0x02, 0x01, | |
| 131 | +}; | |
| 132 | + | |
| 133 | +static const struct wmi_acpi_param wmi_acpi_params_array[] = { | |
| 134 | + { | |
| 135 | + .name = "single_integer", | |
| 136 | + .obj = { | |
| 137 | + .integer = { | |
| 138 | + .type = ACPI_TYPE_INTEGER, | |
| 139 | + .value = 0xdeadbeef, | |
| 140 | + }, | |
| 141 | + }, | |
| 142 | + .buffer = { | |
| 143 | + .data = expected_single_integer, | |
| 144 | + .length = sizeof(expected_single_integer), | |
| 145 | + }, | |
| 146 | + }, | |
| 147 | + { | |
| 148 | + .name = "single_string", | |
| 149 | + .obj = { | |
| 150 | + .string = { | |
| 151 | + .type = ACPI_TYPE_STRING, | |
| 152 | + .length = sizeof("TEST") - 1, | |
| 153 | + .pointer = "TEST", | |
| 154 | + }, | |
| 155 | + }, | |
| 156 | + .buffer = { | |
| 157 | + .data = expected_single_string, | |
| 158 | + .length = sizeof(expected_single_string), | |
| 159 | + }, | |
| 160 | + }, | |
| 161 | + { | |
| 162 | + .name = "single_buffer", | |
| 163 | + .obj = { | |
| 164 | + .buffer = { | |
| 165 | + .type = ACPI_TYPE_BUFFER, | |
| 166 | + .length = sizeof(test_buffer), | |
| 167 | + .pointer = test_buffer, | |
| 168 | + }, | |
| 169 | + }, | |
| 170 | + .buffer = { | |
| 171 | + .data = expected_single_buffer, | |
| 172 | + .length = sizeof(expected_single_buffer), | |
| 173 | + }, | |
| 174 | + }, | |
| 175 | + { | |
| 176 | + .name = "simple_package", | |
| 177 | + .obj = { | |
| 178 | + .package = { | |
| 179 | + .type = ACPI_TYPE_PACKAGE, | |
| 180 | + .count = ARRAY_SIZE(simple_package_elements), | |
| 181 | + .elements = simple_package_elements, | |
| 182 | + }, | |
| 183 | + }, | |
| 184 | + .buffer = { | |
| 185 | + .data = expected_simple_package, | |
| 186 | + .length = sizeof(expected_simple_package), | |
| 187 | + }, | |
| 188 | + }, | |
| 189 | + { | |
| 190 | + .name = "complex_package", | |
| 191 | + .obj = { | |
| 192 | + .package = { | |
| 193 | + .type = ACPI_TYPE_PACKAGE, | |
| 194 | + .count = ARRAY_SIZE(complex_package_elements), | |
| 195 | + .elements = complex_package_elements, | |
| 196 | + }, | |
| 197 | + }, | |
| 198 | + .buffer = { | |
| 199 | + .data = expected_complex_package, | |
| 200 | + .length = sizeof(expected_complex_package), | |
| 201 | + }, | |
| 202 | + }, | |
| 203 | +}; | |
| 204 | + | |
| 205 | +static void wmi_acpi_param_get_desc(const struct wmi_acpi_param *param, char *desc) | |
| 206 | +{ | |
| 207 | + strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE); | |
| 208 | +} | |
| 209 | + | |
| 210 | +KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object, wmi_acpi_params_array, wmi_acpi_param_get_desc); | |
| 211 | + | |
| 212 | +/* "WMI\0" */ | |
| 213 | +static u8 padded_wmi_string[] = { | |
| 214 | + 0x0a, 0x00, | |
| 215 | + 0x57, 0x00, | |
| 216 | + 0x4D, 0x00, | |
| 217 | + 0x49, 0x00, | |
| 218 | + 0x00, 0x00, | |
| 219 | + 0x00, 0x00, | |
| 220 | +}; | |
| 221 | + | |
| 222 | +static const struct wmi_string_param wmi_string_params_array[] = { | |
| 223 | + { | |
| 224 | + .name = "test", | |
| 225 | + .string = "TEST", | |
| 226 | + .buffer = { | |
| 227 | + .length = sizeof(expected_single_string), | |
| 228 | + .data = expected_single_string, | |
| 229 | + }, | |
| 230 | + }, | |
| 231 | + { | |
| 232 | + .name = "padded", | |
| 233 | + .string = "WMI", | |
| 234 | + .buffer = { | |
| 235 | + .length = sizeof(padded_wmi_string), | |
| 236 | + .data = padded_wmi_string, | |
| 237 | + }, | |
| 238 | + }, | |
| 239 | +}; | |
| 240 | + | |
| 241 | +static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc) | |
| 242 | +{ | |
| 243 | + strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE); | |
| 244 | +} | |
| 245 | + | |
| 246 | +KUNIT_ARRAY_PARAM(wmi_marshal_string, wmi_string_params_array, wmi_string_param_get_desc); | |
| 247 | + | |
| 248 | +static union acpi_object nested_package_elements[] = { | |
| 249 | + { | |
| 250 | + .package = { | |
| 251 | + .type = ACPI_TYPE_PACKAGE, | |
| 252 | + .count = ARRAY_SIZE(simple_package_elements), | |
| 253 | + .elements = simple_package_elements, | |
| 254 | + }, | |
| 255 | + } | |
| 256 | +}; | |
| 257 | + | |
| 258 | +static const struct wmi_invalid_acpi_param wmi_invalid_acpi_params_array[] = { | |
| 259 | + { | |
| 260 | + .name = "nested_package", | |
| 261 | + .obj = { | |
| 262 | + .package = { | |
| 263 | + .type = ACPI_TYPE_PACKAGE, | |
| 264 | + .count = ARRAY_SIZE(nested_package_elements), | |
| 265 | + .elements = nested_package_elements, | |
| 266 | + }, | |
| 267 | + }, | |
| 268 | + }, | |
| 269 | + { | |
| 270 | + .name = "reference", | |
| 271 | + .obj = { | |
| 272 | + .reference = { | |
| 273 | + .type = ACPI_TYPE_LOCAL_REFERENCE, | |
| 274 | + .actual_type = ACPI_TYPE_ANY, | |
| 275 | + .handle = NULL, | |
| 276 | + }, | |
| 277 | + }, | |
| 278 | + }, | |
| 279 | + { | |
| 280 | + .name = "processor", | |
| 281 | + .obj = { | |
| 282 | + .processor = { | |
| 283 | + .type = ACPI_TYPE_PROCESSOR, | |
| 284 | + .proc_id = 0, | |
| 285 | + .pblk_address = 0, | |
| 286 | + .pblk_length = 0, | |
| 287 | + }, | |
| 288 | + }, | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + .name = "power_resource", | |
| 292 | + .obj = { | |
| 293 | + .power_resource = { | |
| 294 | + .type = ACPI_TYPE_POWER, | |
| 295 | + .system_level = 0, | |
| 296 | + .resource_order = 0, | |
| 297 | + }, | |
| 298 | + }, | |
| 299 | + }, | |
| 300 | +}; | |
| 301 | + | |
| 302 | +static void wmi_invalid_acpi_param_get_desc(const struct wmi_invalid_acpi_param *param, char *desc) | |
| 303 | +{ | |
| 304 | + strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE); | |
| 305 | +} | |
| 306 | + | |
| 307 | +KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object_failure, wmi_invalid_acpi_params_array, | |
| 308 | + wmi_invalid_acpi_param_get_desc); | |
| 309 | + | |
| 310 | +static u8 oversized_wmi_string[] = { | |
| 311 | + 0x04, 0x00, 0x00, 0x00, | |
| 312 | +}; | |
| 313 | + | |
| 314 | +/* | |
| 315 | + * The error is that 3 bytes can not hold UTF-16 characters | |
| 316 | + * without cutting of the last one. | |
| 317 | + */ | |
| 318 | +static u8 undersized_wmi_string[] = { | |
| 319 | + 0x03, 0x00, 0x00, 0x00, 0x00, | |
| 320 | +}; | |
| 321 | + | |
| 322 | +static u8 non_ascii_wmi_string[] = { | |
| 323 | + 0x04, 0x00, 0xC4, 0x00, 0x00, 0x00, | |
| 324 | +}; | |
| 325 | + | |
| 326 | +static const struct wmi_invalid_string_param wmi_invalid_string_params_array[] = { | |
| 327 | + { | |
| 328 | + .name = "empty_buffer", | |
| 329 | + .buffer = { | |
| 330 | + .length = 0, | |
| 331 | + .data = ZERO_SIZE_PTR, | |
| 332 | + }, | |
| 333 | + | |
| 334 | + }, | |
| 335 | + { | |
| 336 | + .name = "oversized", | |
| 337 | + .buffer = { | |
| 338 | + .length = sizeof(oversized_wmi_string), | |
| 339 | + .data = oversized_wmi_string, | |
| 340 | + }, | |
| 341 | + }, | |
| 342 | + { | |
| 343 | + .name = "undersized", | |
| 344 | + .buffer = { | |
| 345 | + .length = sizeof(undersized_wmi_string), | |
| 346 | + .data = undersized_wmi_string, | |
| 347 | + }, | |
| 348 | + }, | |
| 349 | + { | |
| 350 | + .name = "non_ascii", | |
| 351 | + .buffer = { | |
| 352 | + .length = sizeof(non_ascii_wmi_string), | |
| 353 | + .data = non_ascii_wmi_string, | |
| 354 | + }, | |
| 355 | + }, | |
| 356 | +}; | |
| 357 | + | |
| 358 | +static void wmi_invalid_string_param_get_desc(const struct wmi_invalid_string_param *param, | |
| 359 | + char *desc) | |
| 360 | +{ | |
| 361 | + strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE); | |
| 362 | +} | |
| 363 | + | |
| 364 | +KUNIT_ARRAY_PARAM(wmi_marshal_string_failure, wmi_invalid_string_params_array, | |
| 365 | + wmi_invalid_string_param_get_desc); | |
| 366 | + | |
| 367 | +KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *); | |
| 368 | + | |
| 369 | +static void wmi_unmarshal_acpi_object_test(struct kunit *test) | |
| 370 | +{ | |
| 371 | + const struct wmi_acpi_param *param = test->param_value; | |
| 372 | + struct wmi_buffer result; | |
| 373 | + int ret; | |
| 374 | + | |
| 375 | + ret = wmi_unmarshal_acpi_object(¶m->obj, &result); | |
| 376 | + if (ret < 0) | |
| 377 | + KUNIT_FAIL_AND_ABORT(test, "Unmarshalling of ACPI object failed\n"); | |
| 378 | + | |
| 379 | + kunit_add_action(test, kfree_wrapper, result.data); | |
| 380 | + | |
| 381 | + KUNIT_EXPECT_TRUE(test, IS_ALIGNED((uintptr_t)result.data, 8)); | |
| 382 | + KUNIT_EXPECT_EQ(test, result.length, param->buffer.length); | |
| 383 | + KUNIT_EXPECT_MEMEQ(test, result.data, param->buffer.data, result.length); | |
| 384 | +} | |
| 385 | + | |
| 386 | +static void wmi_unmarshal_acpi_object_failure_test(struct kunit *test) | |
| 387 | +{ | |
| 388 | + const struct wmi_invalid_acpi_param *param = test->param_value; | |
| 389 | + struct wmi_buffer result; | |
| 390 | + int ret; | |
| 391 | + | |
| 392 | + ret = wmi_unmarshal_acpi_object(¶m->obj, &result); | |
| 393 | + if (ret < 0) | |
| 394 | + return; | |
| 395 | + | |
| 396 | + kfree(result.data); | |
| 397 | + KUNIT_FAIL(test, "Invalid ACPI object was not rejected\n"); | |
| 398 | +} | |
| 399 | + | |
| 400 | +static void wmi_marshal_string_test(struct kunit *test) | |
| 401 | +{ | |
| 402 | + const struct wmi_string_param *param = test->param_value; | |
| 403 | + struct acpi_buffer result; | |
| 404 | + int ret; | |
| 405 | + | |
| 406 | + ret = wmi_marshal_string(¶m->buffer, &result); | |
| 407 | + if (ret < 0) | |
| 408 | + KUNIT_FAIL_AND_ABORT(test, "Marshalling of WMI string failed\n"); | |
| 409 | + | |
| 410 | + kunit_add_action(test, kfree_wrapper, result.pointer); | |
| 411 | + | |
| 412 | + KUNIT_EXPECT_EQ(test, result.length, strlen(param->string)); | |
| 413 | + KUNIT_EXPECT_STREQ(test, result.pointer, param->string); | |
| 414 | +} | |
| 415 | + | |
| 416 | +static void wmi_marshal_string_failure_test(struct kunit *test) | |
| 417 | +{ | |
| 418 | + const struct wmi_invalid_string_param *param = test->param_value; | |
| 419 | + struct acpi_buffer result; | |
| 420 | + int ret; | |
| 421 | + | |
| 422 | + ret = wmi_marshal_string(¶m->buffer, &result); | |
| 423 | + if (ret < 0) | |
| 424 | + return; | |
| 425 | + | |
| 426 | + kfree(result.pointer); | |
| 427 | + KUNIT_FAIL(test, "Invalid string was not rejected\n"); | |
| 428 | +} | |
| 429 | + | |
| 430 | +static struct kunit_case wmi_marshalling_test_cases[] = { | |
| 431 | + KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_test, | |
| 432 | + wmi_unmarshal_acpi_object_gen_params), | |
| 433 | + KUNIT_CASE_PARAM(wmi_marshal_string_test, | |
| 434 | + wmi_marshal_string_gen_params), | |
| 435 | + KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_failure_test, | |
| 436 | + wmi_unmarshal_acpi_object_failure_gen_params), | |
| 437 | + KUNIT_CASE_PARAM(wmi_marshal_string_failure_test, | |
| 438 | + wmi_marshal_string_failure_gen_params), | |
| 439 | + {} | |
| 440 | +}; | |
| 441 | + | |
| 442 | +static struct kunit_suite wmi_marshalling_test_suite = { | |
| 443 | + .name = "wmi_marshalling", | |
| 444 | + .test_cases = wmi_marshalling_test_cases, | |
| 445 | +}; | |
| 446 | + | |
| 447 | +kunit_test_suite(wmi_marshalling_test_suite); | |
| 448 | + | |
| 449 | +MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>"); | |
| 450 | +MODULE_DESCRIPTION("KUnit test for the ACPI-WMI marshalling code"); | |
| 451 | +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); | |
| 452 | +MODULE_LICENSE("GPL"); | |
| @@ -0,0 +1,296 @@ | ||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | |
| 2 | +/* | |
| 3 | + * KUnit test for the ACPI-WMI string conversion code. | |
| 4 | + * | |
| 5 | + * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de> | |
| 6 | + */ | |
| 7 | + | |
| 8 | +#include <linux/module.h> | |
| 9 | +#include <linux/slab.h> | |
| 10 | +#include <linux/string.h> | |
| 11 | +#include <linux/wmi.h> | |
| 12 | + | |
| 13 | +#include <kunit/resource.h> | |
| 14 | +#include <kunit/test.h> | |
| 15 | + | |
| 16 | +#include <asm/byteorder.h> | |
| 17 | + | |
| 18 | +struct wmi_string_param { | |
| 19 | + const char *name; | |
| 20 | + const struct wmi_string *wmi_string; | |
| 21 | + /* | |
| 22 | + * Remember that using sizeof() on a struct wmi_string will | |
| 23 | + * always return a size of two bytes due to the flexible | |
| 24 | + * array member! | |
| 25 | + */ | |
| 26 | + size_t wmi_string_length; | |
| 27 | + const u8 *utf8_string; | |
| 28 | + size_t utf8_string_length; | |
| 29 | +}; | |
| 30 | + | |
| 31 | +#define TEST_WMI_STRING_LENGTH 12 | |
| 32 | + | |
| 33 | +static const struct wmi_string test_wmi_string = { | |
| 34 | + .length = cpu_to_le16(10), | |
| 35 | + .chars = { | |
| 36 | + cpu_to_le16(u'T'), | |
| 37 | + cpu_to_le16(u'E'), | |
| 38 | + cpu_to_le16(u'S'), | |
| 39 | + cpu_to_le16(u'T'), | |
| 40 | + cpu_to_le16(u'\0'), | |
| 41 | + }, | |
| 42 | +}; | |
| 43 | + | |
| 44 | +static const u8 test_utf8_string[] = "TEST"; | |
| 45 | + | |
| 46 | +#define SPECIAL_WMI_STRING_LENGTH 14 | |
| 47 | + | |
| 48 | +static const struct wmi_string special_wmi_string = { | |
| 49 | + .length = cpu_to_le16(12), | |
| 50 | + .chars = { | |
| 51 | + cpu_to_le16(u'Ä'), | |
| 52 | + cpu_to_le16(u'Ö'), | |
| 53 | + cpu_to_le16(u'Ü'), | |
| 54 | + cpu_to_le16(u'ß'), | |
| 55 | + cpu_to_le16(u'€'), | |
| 56 | + cpu_to_le16(u'\0'), | |
| 57 | + }, | |
| 58 | +}; | |
| 59 | + | |
| 60 | +static const u8 special_utf8_string[] = "ÄÖÜ߀"; | |
| 61 | + | |
| 62 | +#define MULTI_POINT_WMI_STRING_LENGTH 12 | |
| 63 | + | |
| 64 | +static const struct wmi_string multi_point_wmi_string = { | |
| 65 | + .length = cpu_to_le16(10), | |
| 66 | + .chars = { | |
| 67 | + cpu_to_le16(u'K'), | |
| 68 | + /* 🐧 */ | |
| 69 | + cpu_to_le16(0xD83D), | |
| 70 | + cpu_to_le16(0xDC27), | |
| 71 | + cpu_to_le16(u'!'), | |
| 72 | + cpu_to_le16(u'\0'), | |
| 73 | + }, | |
| 74 | +}; | |
| 75 | + | |
| 76 | +static const u8 multi_point_utf8_string[] = "K🐧!"; | |
| 77 | + | |
| 78 | +#define PADDED_TEST_WMI_STRING_LENGTH 14 | |
| 79 | + | |
| 80 | +static const struct wmi_string padded_test_wmi_string = { | |
| 81 | + .length = cpu_to_le16(12), | |
| 82 | + .chars = { | |
| 83 | + cpu_to_le16(u'T'), | |
| 84 | + cpu_to_le16(u'E'), | |
| 85 | + cpu_to_le16(u'S'), | |
| 86 | + cpu_to_le16(u'T'), | |
| 87 | + cpu_to_le16(u'\0'), | |
| 88 | + cpu_to_le16(u'\0'), | |
| 89 | + }, | |
| 90 | +}; | |
| 91 | + | |
| 92 | +static const u8 padded_test_utf8_string[] = "TEST\0"; | |
| 93 | + | |
| 94 | +#define OVERSIZED_TEST_WMI_STRING_LENGTH 14 | |
| 95 | + | |
| 96 | +static const struct wmi_string oversized_test_wmi_string = { | |
| 97 | + .length = cpu_to_le16(8), | |
| 98 | + .chars = { | |
| 99 | + cpu_to_le16(u'T'), | |
| 100 | + cpu_to_le16(u'E'), | |
| 101 | + cpu_to_le16(u'S'), | |
| 102 | + cpu_to_le16(u'T'), | |
| 103 | + cpu_to_le16(u'!'), | |
| 104 | + cpu_to_le16(u'\0'), | |
| 105 | + }, | |
| 106 | +}; | |
| 107 | + | |
| 108 | +static const u8 oversized_test_utf8_string[] = "TEST!"; | |
| 109 | + | |
| 110 | +#define INVALID_TEST_WMI_STRING_LENGTH 14 | |
| 111 | + | |
| 112 | +static const struct wmi_string invalid_test_wmi_string = { | |
| 113 | + .length = cpu_to_le16(12), | |
| 114 | + .chars = { | |
| 115 | + cpu_to_le16(u'T'), | |
| 116 | + /* 🐧, with low surrogate missing */ | |
| 117 | + cpu_to_le16(0xD83D), | |
| 118 | + cpu_to_le16(u'E'), | |
| 119 | + cpu_to_le16(u'S'), | |
| 120 | + cpu_to_le16(u'T'), | |
| 121 | + cpu_to_le16(u'\0'), | |
| 122 | + }, | |
| 123 | +}; | |
| 124 | + | |
| 125 | +/* We have to split the string here to end the hex escape sequence */ | |
| 126 | +static const u8 invalid_test_utf8_string[] = "T" "\xF0\x9F" "EST"; | |
| 127 | + | |
| 128 | +static const struct wmi_string_param wmi_string_params_array[] = { | |
| 129 | + { | |
| 130 | + .name = "ascii_string", | |
| 131 | + .wmi_string = &test_wmi_string, | |
| 132 | + .wmi_string_length = TEST_WMI_STRING_LENGTH, | |
| 133 | + .utf8_string = test_utf8_string, | |
| 134 | + .utf8_string_length = sizeof(test_utf8_string), | |
| 135 | + }, | |
| 136 | + { | |
| 137 | + .name = "special_string", | |
| 138 | + .wmi_string = &special_wmi_string, | |
| 139 | + .wmi_string_length = SPECIAL_WMI_STRING_LENGTH, | |
| 140 | + .utf8_string = special_utf8_string, | |
| 141 | + .utf8_string_length = sizeof(special_utf8_string), | |
| 142 | + }, | |
| 143 | + { | |
| 144 | + .name = "multi_point_string", | |
| 145 | + .wmi_string = &multi_point_wmi_string, | |
| 146 | + .wmi_string_length = MULTI_POINT_WMI_STRING_LENGTH, | |
| 147 | + .utf8_string = multi_point_utf8_string, | |
| 148 | + .utf8_string_length = sizeof(multi_point_utf8_string), | |
| 149 | + }, | |
| 150 | +}; | |
| 151 | + | |
| 152 | +static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc) | |
| 153 | +{ | |
| 154 | + strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE); | |
| 155 | +} | |
| 156 | + | |
| 157 | +KUNIT_ARRAY_PARAM(wmi_string, wmi_string_params_array, wmi_string_param_get_desc); | |
| 158 | + | |
| 159 | +static void wmi_string_to_utf8s_test(struct kunit *test) | |
| 160 | +{ | |
| 161 | + const struct wmi_string_param *param = test->param_value; | |
| 162 | + ssize_t ret; | |
| 163 | + u8 *result; | |
| 164 | + | |
| 165 | + result = kunit_kzalloc(test, param->utf8_string_length, GFP_KERNEL); | |
| 166 | + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result); | |
| 167 | + | |
| 168 | + ret = wmi_string_to_utf8s(param->wmi_string, result, param->utf8_string_length); | |
| 169 | + | |
| 170 | + KUNIT_EXPECT_EQ(test, ret, param->utf8_string_length - 1); | |
| 171 | + KUNIT_EXPECT_MEMEQ(test, result, param->utf8_string, param->utf8_string_length); | |
| 172 | +} | |
| 173 | + | |
| 174 | +static void wmi_string_from_utf8s_test(struct kunit *test) | |
| 175 | +{ | |
| 176 | + const struct wmi_string_param *param = test->param_value; | |
| 177 | + struct wmi_string *result; | |
| 178 | + size_t max_chars; | |
| 179 | + ssize_t ret; | |
| 180 | + | |
| 181 | + max_chars = (param->wmi_string_length - sizeof(*result)) / 2; | |
| 182 | + result = kunit_kzalloc(test, param->wmi_string_length, GFP_KERNEL); | |
| 183 | + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result); | |
| 184 | + | |
| 185 | + ret = wmi_string_from_utf8s(result, max_chars, param->utf8_string, | |
| 186 | + param->utf8_string_length); | |
| 187 | + | |
| 188 | + KUNIT_EXPECT_EQ(test, ret, max_chars - 1); | |
| 189 | + KUNIT_EXPECT_MEMEQ(test, result, param->wmi_string, param->wmi_string_length); | |
| 190 | +} | |
| 191 | + | |
| 192 | +static void wmi_string_to_utf8s_padded_test(struct kunit *test) | |
| 193 | +{ | |
| 194 | + u8 result[sizeof(padded_test_utf8_string)]; | |
| 195 | + ssize_t ret; | |
| 196 | + | |
| 197 | + ret = wmi_string_to_utf8s(&padded_test_wmi_string, result, sizeof(result)); | |
| 198 | + | |
| 199 | + KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1); | |
| 200 | + KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string)); | |
| 201 | +} | |
| 202 | + | |
| 203 | +static void wmi_string_from_utf8s_padded_test(struct kunit *test) | |
| 204 | +{ | |
| 205 | + struct wmi_string *result; | |
| 206 | + size_t max_chars; | |
| 207 | + ssize_t ret; | |
| 208 | + | |
| 209 | + max_chars = (PADDED_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2; | |
| 210 | + result = kunit_kzalloc(test, PADDED_TEST_WMI_STRING_LENGTH, GFP_KERNEL); | |
| 211 | + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result); | |
| 212 | + | |
| 213 | + ret = wmi_string_from_utf8s(result, max_chars, padded_test_utf8_string, | |
| 214 | + sizeof(padded_test_utf8_string)); | |
| 215 | + | |
| 216 | + KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1); | |
| 217 | + KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string)); | |
| 218 | +} | |
| 219 | + | |
| 220 | +static void wmi_string_to_utf8s_oversized_test(struct kunit *test) | |
| 221 | +{ | |
| 222 | + u8 result[sizeof(oversized_test_utf8_string)]; | |
| 223 | + ssize_t ret; | |
| 224 | + | |
| 225 | + ret = wmi_string_to_utf8s(&oversized_test_wmi_string, result, sizeof(result)); | |
| 226 | + | |
| 227 | + KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1); | |
| 228 | + KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string)); | |
| 229 | +} | |
| 230 | + | |
| 231 | +static void wmi_string_from_utf8s_oversized_test(struct kunit *test) | |
| 232 | +{ | |
| 233 | + struct wmi_string *result; | |
| 234 | + size_t max_chars; | |
| 235 | + ssize_t ret; | |
| 236 | + | |
| 237 | + max_chars = (TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2; | |
| 238 | + result = kunit_kzalloc(test, TEST_WMI_STRING_LENGTH, GFP_KERNEL); | |
| 239 | + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result); | |
| 240 | + | |
| 241 | + ret = wmi_string_from_utf8s(result, max_chars, oversized_test_utf8_string, | |
| 242 | + sizeof(oversized_test_utf8_string)); | |
| 243 | + | |
| 244 | + KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1); | |
| 245 | + KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string)); | |
| 246 | +} | |
| 247 | + | |
| 248 | +static void wmi_string_to_utf8s_invalid_test(struct kunit *test) | |
| 249 | +{ | |
| 250 | + u8 result[sizeof(invalid_test_utf8_string)]; | |
| 251 | + ssize_t ret; | |
| 252 | + | |
| 253 | + ret = wmi_string_to_utf8s(&invalid_test_wmi_string, result, sizeof(result)); | |
| 254 | + | |
| 255 | + KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1); | |
| 256 | + KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string)); | |
| 257 | +} | |
| 258 | + | |
| 259 | +static void wmi_string_from_utf8s_invalid_test(struct kunit *test) | |
| 260 | +{ | |
| 261 | + struct wmi_string *result; | |
| 262 | + size_t max_chars; | |
| 263 | + ssize_t ret; | |
| 264 | + | |
| 265 | + max_chars = (INVALID_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2; | |
| 266 | + result = kunit_kzalloc(test, INVALID_TEST_WMI_STRING_LENGTH, GFP_KERNEL); | |
| 267 | + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result); | |
| 268 | + | |
| 269 | + ret = wmi_string_from_utf8s(result, max_chars, invalid_test_utf8_string, | |
| 270 | + sizeof(invalid_test_utf8_string)); | |
| 271 | + | |
| 272 | + KUNIT_EXPECT_EQ(test, ret, -EINVAL); | |
| 273 | +} | |
| 274 | + | |
| 275 | +static struct kunit_case wmi_string_test_cases[] = { | |
| 276 | + KUNIT_CASE_PARAM(wmi_string_to_utf8s_test, wmi_string_gen_params), | |
| 277 | + KUNIT_CASE_PARAM(wmi_string_from_utf8s_test, wmi_string_gen_params), | |
| 278 | + KUNIT_CASE(wmi_string_to_utf8s_padded_test), | |
| 279 | + KUNIT_CASE(wmi_string_from_utf8s_padded_test), | |
| 280 | + KUNIT_CASE(wmi_string_to_utf8s_oversized_test), | |
| 281 | + KUNIT_CASE(wmi_string_from_utf8s_oversized_test), | |
| 282 | + KUNIT_CASE(wmi_string_to_utf8s_invalid_test), | |
| 283 | + KUNIT_CASE(wmi_string_from_utf8s_invalid_test), | |
| 284 | + {} | |
| 285 | +}; | |
| 286 | + | |
| 287 | +static struct kunit_suite wmi_string_test_suite = { | |
| 288 | + .name = "wmi_string", | |
| 289 | + .test_cases = wmi_string_test_cases, | |
| 290 | +}; | |
| 291 | + | |
| 292 | +kunit_test_suite(wmi_string_test_suite); | |
| 293 | + | |
| 294 | +MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>"); | |
| 295 | +MODULE_DESCRIPTION("KUnit test for the ACPI-WMI string conversion code"); | |
| 296 | +MODULE_LICENSE("GPL"); | |
| @@ -9,6 +9,9 @@ · 8 unmodified lines | ||
| 9 | 9 | */ |
| 10 | 10 | |
| 11 | 11 | #include <linux/acpi.h> |
| 12 | +#include <linux/array_size.h> | |
| 13 | +#include <linux/cleanup.h> | |
| 14 | +#include <linux/dev_printk.h> | |
| 12 | 15 | #include "pmf.h" |
| 13 | 16 | |
| 14 | 17 | #define APMF_CQL_NOTIFICATION 2 |
| @@ -331,6 +334,39 @@ int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req · 316 unmodified lines | ||
| 331 | 334 | req, sizeof(*req)); |
| 332 | 335 | } |
| 333 | 336 | |
| 337 | +/* Store custom BIOS inputs data in ring buffer */ | |
| 338 | +static void amd_pmf_custom_bios_inputs_rb(struct amd_pmf_dev *pmf_dev) | |
| 339 | +{ | |
| 340 | + struct pmf_cbi_ring_buffer *rb = &pmf_dev->cbi_buf; | |
| 341 | + int i; | |
| 342 | + | |
| 343 | + guard(mutex)(&pmf_dev->cbi_mutex); | |
| 344 | + | |
| 345 | + switch (pmf_dev->cpu_id) { | |
| 346 | + case AMD_CPU_ID_PS: | |
| 347 | + for (i = 0; i < ARRAY_SIZE(custom_bios_inputs_v1); i++) | |
| 348 | + rb->data[rb->head].val[i] = pmf_dev->req1.custom_policy[i]; | |
| 349 | + rb->data[rb->head].preq = pmf_dev->req1.pending_req; | |
| 350 | + break; | |
| 351 | + case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: | |
| 352 | + case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: | |
| 353 | + for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) | |
| 354 | + rb->data[rb->head].val[i] = pmf_dev->req.custom_policy[i]; | |
| 355 | + rb->data[rb->head].preq = pmf_dev->req.pending_req; | |
| 356 | + break; | |
| 357 | + default: | |
| 358 | + return; | |
| 359 | + } | |
| 360 | + | |
| 361 | + if (CIRC_SPACE(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0) { | |
| 362 | + /* Rare case: ensures the newest BIOS input value is kept */ | |
| 363 | + dev_warn(pmf_dev->dev, "Overwriting BIOS input value, data may be lost\n"); | |
| 364 | + rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1); | |
| 365 | + } | |
| 366 | + | |
| 367 | + rb->head = (rb->head + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1); | |
| 368 | +} | |
| 369 | + | |
| 334 | 370 | static void amd_pmf_handle_early_preq(struct amd_pmf_dev *pdev) |
| 335 | 371 | { |
| 336 | 372 | if (!pdev->cb_flag) |
| @@ -356,6 +392,8 @@ static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data) · 19 unmodified lines | ||
| 356 | 392 | dev_dbg(pmf_dev->dev, "Pending request (preq): 0x%x\n", pmf_dev->req.pending_req); |
| 357 | 393 | |
| 358 | 394 | amd_pmf_handle_early_preq(pmf_dev); |
| 395 | + | |
| 396 | + amd_pmf_custom_bios_inputs_rb(pmf_dev); | |
| 359 | 397 | } |
| 360 | 398 | |
| 361 | 399 | static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data) |
| @@ -374,6 +412,8 @@ static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data) · 12 unmodified lines | ||
| 374 | 412 | dev_dbg(pmf_dev->dev, "Pending request (preq1): 0x%x\n", pmf_dev->req1.pending_req); |
| 375 | 413 | |
| 376 | 414 | amd_pmf_handle_early_preq(pmf_dev); |
| 415 | + | |
| 416 | + amd_pmf_custom_bios_inputs_rb(pmf_dev); | |
| 377 | 417 | } |
| 378 | 418 | |
| 379 | 419 | static void apmf_event_handler(acpi_handle handle, u32 event, void *data) |
| @@ -8,12 +8,16 @@ · 7 unmodified lines | ||
| 8 | 8 | * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> |
| 9 | 9 | */ |
| 10 | 10 | |
| 11 | +#include <linux/array_size.h> | |
| 12 | +#include <linux/cleanup.h> | |
| 11 | 13 | #include <linux/debugfs.h> |
| 12 | 14 | #include <linux/iopoll.h> |
| 13 | 15 | #include <linux/module.h> |
| 16 | +#include <linux/mutex.h> | |
| 14 | 17 | #include <linux/pci.h> |
| 15 | 18 | #include <linux/platform_device.h> |
| 16 | 19 | #include <linux/power_supply.h> |
| 20 | +#include <linux/string.h> | |
| 17 | 21 | #include <asm/amd/node.h> |
| 18 | 22 | #include "pmf.h" |
| 19 | 23 | |
| @@ -53,6 +57,12 @@ static bool force_load; · 33 unmodified lines | ||
| 53 | 57 | module_param(force_load, bool, 0444); |
| 54 | 58 | MODULE_PARM_DESC(force_load, "Force load this driver on supported older platforms (experimental)"); |
| 55 | 59 | |
| 60 | +static bool smart_pc_support = true; | |
| 61 | +module_param(smart_pc_support, bool, 0444); | |
| 62 | +MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)"); | |
| 63 | + | |
| 64 | +static struct device *pmf_device; | |
| 65 | + | |
| 56 | 66 | static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data) |
| 57 | 67 | { |
| 58 | 68 | struct amd_pmf_dev *pmf = container_of(nb, struct amd_pmf_dev, pwr_src_notifier); |
| @@ -314,6 +324,126 @@ int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) · 255 unmodified lines | ||
| 314 | 324 | return 0; |
| 315 | 325 | } |
| 316 | 326 | |
| 327 | +static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) | |
| 328 | +{ | |
| 329 | + switch (pdev->cpu_id) { | |
| 330 | + case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: | |
| 331 | + case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: | |
| 332 | + return 0; | |
| 333 | + default: | |
| 334 | + return -EOPNOTSUPP; | |
| 335 | + } | |
| 336 | +} | |
| 337 | + | |
| 338 | +static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) | |
| 339 | +{ | |
| 340 | + int ret, i; | |
| 341 | + | |
| 342 | + guard(mutex)(&dev->metrics_mutex); | |
| 343 | + | |
| 344 | + ret = is_npu_metrics_supported(dev); | |
| 345 | + if (ret) | |
| 346 | + return ret; | |
| 347 | + | |
| 348 | + ret = amd_pmf_set_dram_addr(dev, true); | |
| 349 | + if (ret) | |
| 350 | + return ret; | |
| 351 | + | |
| 352 | + memset(dev->buf, 0, dev->mtable_size); | |
| 353 | + | |
| 354 | + /* Send SMU command to get NPU metrics */ | |
| 355 | + ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); | |
| 356 | + if (ret) { | |
| 357 | + dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); | |
| 358 | + return ret; | |
| 359 | + } | |
| 360 | + | |
| 361 | + memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); | |
| 362 | + | |
| 363 | + data->npuclk_freq = dev->m_table_v2.npuclk_freq; | |
| 364 | + for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) | |
| 365 | + data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; | |
| 366 | + data->npu_power = dev->m_table_v2.npu_power; | |
| 367 | + data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; | |
| 368 | + data->npu_reads = dev->m_table_v2.npu_reads; | |
| 369 | + data->npu_writes = dev->m_table_v2.npu_writes; | |
| 370 | + | |
| 371 | + return 0; | |
| 372 | +} | |
| 373 | + | |
| 374 | +int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) | |
| 375 | +{ | |
| 376 | + struct amd_pmf_dev *pdev; | |
| 377 | + | |
| 378 | + if (!info) | |
| 379 | + return -EINVAL; | |
| 380 | + | |
| 381 | + if (!pmf_device) | |
| 382 | + return -ENODEV; | |
| 383 | + | |
| 384 | + pdev = dev_get_drvdata(pmf_device); | |
| 385 | + if (!pdev) | |
| 386 | + return -ENODEV; | |
| 387 | + | |
| 388 | + return amd_pmf_get_smu_metrics(pdev, info); | |
| 389 | +} | |
| 390 | +EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); | |
| 391 | + | |
| 392 | +static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev) | |
| 393 | +{ | |
| 394 | + bool status; | |
| 395 | + int ret, i; | |
| 396 | + | |
| 397 | + for (i = 0; i < ARRAY_SIZE(amd_pmf_ta_uuid); i++) { | |
| 398 | + ret = amd_pmf_tee_init(pdev, &amd_pmf_ta_uuid[i]); | |
| 399 | + if (ret) { | |
| 400 | + dev_err(pdev->dev, "TEE init failed for UUID[%d] ret: %d\n", i, ret); | |
| 401 | + return ret; | |
| 402 | + } | |
| 403 | + | |
| 404 | + ret = amd_pmf_start_policy_engine(pdev); | |
| 405 | + dev_dbg(pdev->dev, "start policy engine ret: %d (UUID idx: %d)\n", ret, i); | |
| 406 | + status = ret == TA_PMF_TYPE_SUCCESS; | |
| 407 | + if (status) | |
| 408 | + break; | |
| 409 | + amd_pmf_tee_deinit(pdev); | |
| 410 | + } | |
| 411 | + | |
| 412 | + return 0; | |
| 413 | +} | |
| 414 | + | |
| 415 | +static int amd_pmf_restore_handler(struct device *dev) | |
| 416 | +{ | |
| 417 | + struct amd_pmf_dev *pdev = dev_get_drvdata(dev); | |
| 418 | + int ret; | |
| 419 | + | |
| 420 | + if (pdev->buf) { | |
| 421 | + ret = amd_pmf_set_dram_addr(pdev, false); | |
| 422 | + if (ret) | |
| 423 | + return ret; | |
| 424 | + } | |
| 425 | + | |
| 426 | + if (pdev->smart_pc_enabled) | |
| 427 | + amd_pmf_reinit_ta(pdev); | |
| 428 | + | |
| 429 | + return 0; | |
| 430 | +} | |
| 431 | + | |
| 432 | +static int amd_pmf_freeze_handler(struct device *dev) | |
| 433 | +{ | |
| 434 | + struct amd_pmf_dev *pdev = dev_get_drvdata(dev); | |
| 435 | + | |
| 436 | + if (!pdev->smart_pc_enabled) | |
| 437 | + return 0; | |
| 438 | + | |
| 439 | + cancel_delayed_work_sync(&pdev->pb_work); | |
| 440 | + /* Clear all TEE resources */ | |
| 441 | + amd_pmf_tee_deinit(pdev); | |
| 442 | + pdev->session_id = 0; | |
| 443 | + | |
| 444 | + return 0; | |
| 445 | +} | |
| 446 | + | |
| 317 | 447 | static int amd_pmf_suspend_handler(struct device *dev) |
| 318 | 448 | { |
| 319 | 449 | struct amd_pmf_dev *pdev = dev_get_drvdata(dev); |
| @@ -347,7 +477,12 @@ static int amd_pmf_resume_handler(struct device *dev) · 27 unmodified lines | ||
| 347 | 477 | return 0; |
| 348 | 478 | } |
| 349 | 479 | |
| 350 | −static DEFINE_SIMPLE_DEV_PM_OPS(amd_pmf_pm, amd_pmf_suspend_handler, amd_pmf_resume_handler); | |
| 480 | +static const struct dev_pm_ops amd_pmf_pm = { | |
| 481 | + .suspend = amd_pmf_suspend_handler, | |
| 482 | + .resume = amd_pmf_resume_handler, | |
| 483 | + .freeze = amd_pmf_freeze_handler, | |
| 484 | + .restore = amd_pmf_restore_handler, | |
| 485 | +}; | |
| 351 | 486 | |
| 352 | 487 | static void amd_pmf_init_features(struct amd_pmf_dev *dev) |
| 353 | 488 | { |
| @@ -362,11 +497,15 @@ static void amd_pmf_init_features(struct amd_pmf_dev *dev) · 8 unmodified lines | ||
| 362 | 497 | dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n"); |
| 363 | 498 | } |
| 364 | 499 | |
| 365 | − amd_pmf_init_smart_pc(dev); | |
| 366 | − if (dev->smart_pc_enabled) { | |
| 367 | − dev_dbg(dev->dev, "Smart PC Solution Enabled\n"); | |
| 368 | − /* If Smart PC is enabled, no need to check for other features */ | |
| 369 | − return; | |
| 500 | + if (smart_pc_support) { | |
| 501 | + amd_pmf_init_smart_pc(dev); | |
| 502 | + if (dev->smart_pc_enabled) { | |
| 503 | + dev_dbg(dev->dev, "Smart PC Solution Enabled\n"); | |
| 504 | + /* If Smart PC is enabled, no need to check for other features */ | |
| 505 | + return; | |
| 506 | + } | |
| 507 | + } else { | |
| 508 | + dev->smart_pc_enabled = false; | |
| 370 | 509 | } |
| 371 | 510 | |
| 372 | 511 | if (is_apmf_func_supported(dev, APMF_FUNC_AUTO_MODE)) { |
| @@ -477,6 +616,14 @@ static int amd_pmf_probe(struct platform_device *pdev) · 104 unmodified lines | ||
| 477 | 616 | if (err) |
| 478 | 617 | return err; |
| 479 | 618 | |
| 619 | + err = devm_mutex_init(dev->dev, &dev->cbi_mutex); | |
| 620 | + if (err) | |
| 621 | + return err; | |
| 622 | + | |
| 623 | + err = devm_mutex_init(dev->dev, &dev->metrics_mutex); | |
| 624 | + if (err) | |
| 625 | + return err; | |
| 626 | + | |
| 480 | 627 | apmf_acpi_init(dev); |
| 481 | 628 | platform_set_drvdata(pdev, dev); |
| 482 | 629 | amd_pmf_dbgfs_register(dev); |
| @@ -485,6 +632,8 @@ static int amd_pmf_probe(struct platform_device *pdev) · 2 unmodified lines | ||
| 485 | 632 | if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) |
| 486 | 633 | amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD); |
| 487 | 634 | |
| 635 | + pmf_device = dev->dev; | |
| 636 | + | |
| 488 | 637 | dev_info(dev->dev, "registered PMF device successfully\n"); |
| 489 | 638 | |
| 490 | 639 | return 0; |
| @@ -12,7 +12,10 @@ · 11 unmodified lines | ||
| 12 | 12 | #define PMF_H |
| 13 | 13 | |
| 14 | 14 | #include <linux/acpi.h> |
| 15 | +#include <linux/amd-pmf-io.h> | |
| 16 | +#include <linux/circ_buf.h> | |
| 15 | 17 | #include <linux/input.h> |
| 18 | +#include <linux/mutex_types.h> | |
| 16 | 19 | #include <linux/platform_device.h> |
| 17 | 20 | #include <linux/platform_profile.h> |
| 18 | 21 | |
| @@ -120,6 +123,7 @@ struct cookie_header { · 101 unmodified lines | ||
| 120 | 123 | #define APTS_MAX_STATES 16 |
| 121 | 124 | #define CUSTOM_BIOS_INPUT_BITS GENMASK(16, 7) |
| 122 | 125 | #define BIOS_INPUTS_MAX 10 |
| 126 | +#define CUSTOM_BIOS_INPUT_RING_ENTRIES 64 /* Must be power of two for CIRC_* macros */ | |
| 123 | 127 | |
| 124 | 128 | /* amd_pmf_send_cmd() set/get */ |
| 125 | 129 | #define SET_CMD false |
| @@ -129,6 +133,12 @@ struct cookie_header { · 3 unmodified lines | ||
| 129 | 133 | |
| 130 | 134 | typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); |
| 131 | 135 | |
| 136 | +static const uuid_t amd_pmf_ta_uuid[] __used = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, | |
| 137 | + 0x8a, 0xcc, 0x2b, 0x2b, 0x60, 0xd6), | |
| 138 | + UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d, | |
| 139 | + 0xc5, 0x29, 0xb1, 0x3d, 0x85, 0x43), | |
| 140 | + }; | |
| 141 | + | |
| 132 | 142 | /* APTS PMF BIOS Interface */ |
| 133 | 143 | struct amd_pmf_apts_output { |
| 134 | 144 | u16 table_version; |
| @@ -365,6 +375,22 @@ struct pmf_bios_inputs_prev { · 230 unmodified lines | ||
| 365 | 375 | u32 custom_bios_inputs[BIOS_INPUTS_MAX]; |
| 366 | 376 | }; |
| 367 | 377 | |
| 378 | +/** | |
| 379 | + * struct pmf_bios_input_entry - Snapshot of custom BIOS input event | |
| 380 | + * @val: Array of custom BIOS input values | |
| 381 | + * @preq: Pending request value associated with this event | |
| 382 | + */ | |
| 383 | +struct pmf_bios_input_entry { | |
| 384 | + u32 val[BIOS_INPUTS_MAX]; | |
| 385 | + u32 preq; | |
| 386 | +}; | |
| 387 | + | |
| 388 | +struct pmf_cbi_ring_buffer { | |
| 389 | + struct pmf_bios_input_entry data[CUSTOM_BIOS_INPUT_RING_ENTRIES]; | |
| 390 | + int head; | |
| 391 | + int tail; | |
| 392 | +}; | |
| 393 | + | |
| 368 | 394 | struct amd_pmf_dev { |
| 369 | 395 | void __iomem *regbase; |
| 370 | 396 | void __iomem *smu_virt_addr; |
| @@ -413,6 +439,9 @@ struct amd_pmf_dev { · 42 unmodified lines | ||
| 413 | 439 | struct apmf_sbios_req_v1 req1; |
| 414 | 440 | struct pmf_bios_inputs_prev cb_prev; /* To preserve custom BIOS inputs */ |
| 415 | 441 | bool cb_flag; /* To handle first custom BIOS input */ |
| 442 | + struct pmf_cbi_ring_buffer cbi_buf; | |
| 443 | + struct mutex cbi_mutex; /* Protects ring buffer access */ | |
| 444 | + struct mutex metrics_mutex; | |
| 416 | 445 | }; |
| 417 | 446 | |
| 418 | 447 | struct apmf_sps_prop_granular_v2 { |
| @@ -895,4 +924,8 @@ void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_tab · 476 unmodified lines | ||
| 895 | 924 | void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); |
| 896 | 925 | int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev); |
| 897 | 926 | |
| 927 | +int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid); | |
| 928 | +void amd_pmf_tee_deinit(struct amd_pmf_dev *dev); | |
| 929 | +int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev); | |
| 930 | + | |
| 898 | 931 | #endif /* PMF_H */ |
| @@ -11,6 +11,7 @@ · 10 unmodified lines | ||
| 11 | 11 | |
| 12 | 12 | #include <acpi/button.h> |
| 13 | 13 | #include <linux/amd-pmf-io.h> |
| 14 | +#include <linux/cleanup.h> | |
| 14 | 15 | #include <linux/power_supply.h> |
| 15 | 16 | #include <linux/units.h> |
| 16 | 17 | #include "pmf.h" |
| @@ -132,32 +133,39 @@ static void amd_pmf_set_ta_custom_bios_input(struct ta_pmf_enact_table *in, int · 115 unmodified lines | ||
| 132 | 133 | } |
| 133 | 134 | } |
| 134 | 135 | |
| 135 | −static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, u32 pending_req, | |
| 136 | +static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, struct pmf_bios_input_entry *data, | |
| 136 | 137 | const struct amd_pmf_pb_bitmap *inputs, |
| 137 | − const u32 *custom_policy, struct ta_pmf_enact_table *in) | |
| 138 | + struct ta_pmf_enact_table *in) | |
| 138 | 139 | { |
| 139 | 140 | unsigned int i; |
| 140 | 141 | |
| 141 | 142 | for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) { |
| 142 | − if (!(pending_req & inputs[i].bit_mask)) | |
| 143 | + if (!(data->preq & inputs[i].bit_mask)) | |
| 143 | 144 | continue; |
| 144 | − amd_pmf_set_ta_custom_bios_input(in, i, custom_policy[i]); | |
| 145 | − pdev->cb_prev.custom_bios_inputs[i] = custom_policy[i]; | |
| 146 | − dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, custom_policy[i]); | |
| 145 | + amd_pmf_set_ta_custom_bios_input(in, i, data->val[i]); | |
| 146 | + pdev->cb_prev.custom_bios_inputs[i] = data->val[i]; | |
| 147 | + dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, data->val[i]); | |
| 147 | 148 | } |
| 148 | 149 | } |
| 149 | 150 | |
| 150 | 151 | static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev, |
| 151 | 152 | struct ta_pmf_enact_table *in) |
| 152 | 153 | { |
| 154 | + struct pmf_cbi_ring_buffer *rb = &pdev->cbi_buf; | |
| 153 | 155 | unsigned int i; |
| 154 | 156 | |
| 157 | + guard(mutex)(&pdev->cbi_mutex); | |
| 158 | + | |
| 155 | 159 | for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) |
| 156 | 160 | amd_pmf_set_ta_custom_bios_input(in, i, pdev->cb_prev.custom_bios_inputs[i]); |
| 157 | 161 | |
| 158 | − if (!(pdev->req.pending_req || pdev->req1.pending_req)) | |
| 162 | + if (CIRC_CNT(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0) | |
| 159 | 163 | return; |
| 160 | 164 | |
| 165 | + /* If no active custom BIOS input pending request, do not consume further work */ | |
| 166 | + if (!rb->data[rb->tail].preq) | |
| 167 | + goto out_rbadvance; | |
| 168 | + | |
| 161 | 169 | if (!pdev->smart_pc_enabled) |
| 162 | 170 | return; |
| 163 | 171 | |
| @@ -165,20 +173,17 @@ static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev, · 1 unmodified line | ||
| 165 | 173 | case PMF_IF_V1: |
| 166 | 174 | if (!is_apmf_bios_input_notifications_supported(pdev)) |
| 167 | 175 | return; |
| 168 | − amd_pmf_update_bios_inputs(pdev, pdev->req1.pending_req, custom_bios_inputs_v1, | |
| 169 | − pdev->req1.custom_policy, in); | |
| 176 | + amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs_v1, in); | |
| 170 | 177 | break; |
| 171 | 178 | case PMF_IF_V2: |
| 172 | − amd_pmf_update_bios_inputs(pdev, pdev->req.pending_req, custom_bios_inputs, | |
| 173 | − pdev->req.custom_policy, in); | |
| 179 | + amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs, in); | |
| 174 | 180 | break; |
| 175 | 181 | default: |
| 176 | 182 | break; |
| 177 | 183 | } |
| 178 | 184 | |
| 179 | − /* Clear pending requests after handling */ | |
| 180 | − memset(&pdev->req, 0, sizeof(pdev->req)); | |
| 181 | − memset(&pdev->req1, 0, sizeof(pdev->req1)); | |
| 185 | +out_rbadvance: | |
| 186 | + rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1); | |
| 182 | 187 | } |
| 183 | 188 | |
| 184 | 189 | static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in) |
| @@ -27,12 +27,6 @@ module_param(pb_side_load, bool, 0444); · 26 unmodified lines | ||
| 27 | 27 | MODULE_PARM_DESC(pb_side_load, "Sideload policy binaries debug policy failures"); |
| 28 | 28 | #endif |
| 29 | 29 | |
| 30 | −static const uuid_t amd_pmf_ta_uuid[] = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, 0x8a, | |
| 31 | − 0xcc, 0x2b, 0x2b, 0x60, 0xd6), | |
| 32 | − UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d, 0xc5, | |
| 33 | − 0x29, 0xb1, 0x3d, 0x85, 0x43), | |
| 34 | − }; | |
| 35 | − | |
| 36 | 30 | static const char *amd_pmf_uevent_as_str(unsigned int state) |
| 37 | 31 | { |
| 38 | 32 | switch (state) { |
| @@ -324,7 +318,7 @@ static void amd_pmf_invoke_cmd(struct work_struct *work) · 285 unmodified lines | ||
| 324 | 318 | schedule_delayed_work(&dev->pb_work, msecs_to_jiffies(pb_actions_ms)); |
| 325 | 319 | } |
| 326 | 320 | |
| 327 | −static int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev) | |
| 321 | +int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev) | |
| 328 | 322 | { |
| 329 | 323 | struct cookie_header *header; |
| 330 | 324 | int res; |
| @@ -480,7 +474,7 @@ static int amd_pmf_register_input_device(struct amd_pmf_dev *dev) · 149 unmodified lines | ||
| 480 | 474 | return 0; |
| 481 | 475 | } |
| 482 | 476 | |
| 483 | −static int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid) | |
| 477 | +int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid) | |
| 484 | 478 | { |
| 485 | 479 | u32 size; |
| 486 | 480 | int ret; |
| @@ -528,7 +522,7 @@ out_ctx: · 41 unmodified lines | ||
| 528 | 522 | return ret; |
| 529 | 523 | } |
| 530 | 524 | |
| 531 | −static void amd_pmf_tee_deinit(struct amd_pmf_dev *dev) | |
| 525 | +void amd_pmf_tee_deinit(struct amd_pmf_dev *dev) | |
| 532 | 526 | { |
| 533 | 527 | if (!dev->tee_ctx) |
| 534 | 528 | return; |
| @@ -591,6 +585,8 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev) · 56 unmodified lines | ||
| 591 | 585 | status = ret == TA_PMF_TYPE_SUCCESS; |
| 592 | 586 | if (status) { |
| 593 | 587 | dev->cb_flag = true; |
| 588 | + dev->cbi_buf.head = 0; | |
| 589 | + dev->cbi_buf.tail = 0; | |
| 594 | 590 | break; |
| 595 | 591 | } |
| 596 | 592 | amd_pmf_tee_deinit(dev); |
| @@ -42,8 +42,6 @@ static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head); · 41 unmodified lines | ||
| 42 | 42 | static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ranges_in_out *in) |
| 43 | 43 | { |
| 44 | 44 | union acpi_object argv4; |
| 45 | − union acpi_object *tmp; | |
| 46 | − union acpi_object *obj; | |
| 47 | 45 | u32 num_of_ranges = 0; |
| 48 | 46 | u32 num_of_elements; |
| 49 | 47 | u32 arg_idx = 0; |
| @@ -74,7 +72,7 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran · 24 unmodified lines | ||
| 74 | 72 | */ |
| 75 | 73 | num_of_elements = 2 * num_of_ranges + 2; |
| 76 | 74 | |
| 77 | − tmp = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL); | |
| 75 | + union acpi_object *tmp __free(kfree) = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL); | |
| 78 | 76 | if (!tmp) |
| 79 | 77 | return -ENOMEM; |
| 80 | 78 | |
| @@ -101,26 +99,19 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran · 20 unmodified lines | ||
| 101 | 99 | tmp[arg_idx++].integer.value = in->band_list[i].end; |
| 102 | 100 | } |
| 103 | 101 | |
| 104 | − obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid, | |
| 105 | − WBRF_REVISION, WBRF_RECORD, &argv4); | |
| 102 | + union acpi_object *obj __free(kfree) = | |
| 103 | + acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid, | |
| 104 | + WBRF_REVISION, WBRF_RECORD, &argv4); | |
| 106 | 105 | |
| 107 | − if (!obj) { | |
| 108 | − kfree(tmp); | |
| 106 | + if (!obj) | |
| 109 | 107 | return -EINVAL; |
| 110 | − } | |
| 111 | 108 | |
| 112 | − if (obj->type != ACPI_TYPE_INTEGER) { | |
| 113 | − ret = -EINVAL; | |
| 114 | − goto out; | |
| 115 | − } | |
| 109 | + if (obj->type != ACPI_TYPE_INTEGER) | |
| 110 | + return -EINVAL; | |
| 116 | 111 | |
| 117 | 112 | ret = obj->integer.value; |
| 118 | 113 | if (ret) |
| 119 | − ret = -EINVAL; | |
| 120 | − | |
| 121 | −out: | |
| 122 | − ACPI_FREE(obj); | |
| 123 | − kfree(tmp); | |
| 114 | + return -EINVAL; | |
| 124 | 115 | |
| 125 | 116 | return ret; |
| 126 | 117 | } |
| @@ -31,13 +31,13 @@ · 30 unmodified lines | ||
| 31 | 31 | #include <linux/pci.h> |
| 32 | 32 | #include <linux/pci_hotplug.h> |
| 33 | 33 | #include <linux/platform_data/x86/asus-wmi.h> |
| 34 | −#include <linux/platform_data/x86/asus-wmi-leds-ids.h> | |
| 35 | 34 | #include <linux/platform_device.h> |
| 36 | 35 | #include <linux/platform_profile.h> |
| 37 | 36 | #include <linux/power_supply.h> |
| 38 | 37 | #include <linux/rfkill.h> |
| 39 | 38 | #include <linux/seq_file.h> |
| 40 | 39 | #include <linux/slab.h> |
| 40 | +#include <linux/spinlock.h> | |
| 41 | 41 | #include <linux/types.h> |
| 42 | 42 | #include <linux/units.h> |
| 43 | 43 | |
| @@ -256,6 +256,9 @@ struct asus_wmi { · 212 unmodified lines | ||
| 256 | 256 | int tpd_led_wk; |
| 257 | 257 | struct led_classdev kbd_led; |
| 258 | 258 | int kbd_led_wk; |
| 259 | + bool kbd_led_notify; | |
| 260 | + bool kbd_led_avail; | |
| 261 | + bool kbd_led_registered; | |
| 259 | 262 | struct led_classdev lightbar_led; |
| 260 | 263 | int lightbar_led_wk; |
| 261 | 264 | struct led_classdev micmute_led; |
| @@ -264,6 +267,7 @@ struct asus_wmi { · 2 unmodified lines | ||
| 264 | 267 | struct work_struct tpd_led_work; |
| 265 | 268 | struct work_struct wlan_led_work; |
| 266 | 269 | struct work_struct lightbar_led_work; |
| 270 | + struct work_struct kbd_led_work; | |
| 267 | 271 | |
| 268 | 272 | struct asus_rfkill wlan; |
| 269 | 273 | struct asus_rfkill bluetooth; |
| @@ -1615,6 +1619,144 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus) · 1345 unmodified lines | ||
| 1615 | 1619 | |
| 1616 | 1620 | /* LEDs ***********************************************************************/ |
| 1617 | 1621 | |
| 1622 | +struct asus_hid_ref { | |
| 1623 | + struct list_head listeners; | |
| 1624 | + struct asus_wmi *asus; | |
| 1625 | + /* Protects concurrent access from hid-asus and asus-wmi to leds */ | |
| 1626 | + spinlock_t lock; | |
| 1627 | +}; | |
| 1628 | + | |
| 1629 | +static struct asus_hid_ref asus_ref = { | |
| 1630 | + .listeners = LIST_HEAD_INIT(asus_ref.listeners), | |
| 1631 | + .asus = NULL, | |
| 1632 | + /* | |
| 1633 | + * Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other | |
| 1634 | + * asus variables are read-only after .asus is set. | |
| 1635 | + * | |
| 1636 | + * The led cdev device is not protected because it calls backlight_get | |
| 1637 | + * during initialization, which would result in a nested lock attempt. | |
| 1638 | + * | |
| 1639 | + * The led cdev is safe to access without a lock because if | |
| 1640 | + * kbd_led_avail is true it is initialized before .asus is set and never | |
| 1641 | + * changed until .asus is dropped. If kbd_led_avail is false, the led | |
| 1642 | + * cdev is registered by the workqueue, which is single-threaded and | |
| 1643 | + * cancelled before asus-wmi would access the led cdev to unregister it. | |
| 1644 | + * | |
| 1645 | + * A spinlock is used, because the protected variables can be accessed | |
| 1646 | + * from an IRQ context from asus-hid. | |
| 1647 | + */ | |
| 1648 | + .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock), | |
| 1649 | +}; | |
| 1650 | + | |
| 1651 | +/* | |
| 1652 | + * Allows registering hid-asus listeners that want to be notified of | |
| 1653 | + * keyboard backlight changes. | |
| 1654 | + */ | |
| 1655 | +int asus_hid_register_listener(struct asus_hid_listener *bdev) | |
| 1656 | +{ | |
| 1657 | + struct asus_wmi *asus; | |
| 1658 | + | |
| 1659 | + guard(spinlock_irqsave)(&asus_ref.lock); | |
| 1660 | + list_add_tail(&bdev->list, &asus_ref.listeners); | |
| 1661 | + asus = asus_ref.asus; | |
| 1662 | + if (asus) | |
| 1663 | + queue_work(asus->led_workqueue, &asus->kbd_led_work); | |
| 1664 | + return 0; | |
| 1665 | +} | |
| 1666 | +EXPORT_SYMBOL_GPL(asus_hid_register_listener); | |
| 1667 | + | |
| 1668 | +/* | |
| 1669 | + * Allows unregistering hid-asus listeners that were added with | |
| 1670 | + * asus_hid_register_listener(). | |
| 1671 | + */ | |
| 1672 | +void asus_hid_unregister_listener(struct asus_hid_listener *bdev) | |
| 1673 | +{ | |
| 1674 | + guard(spinlock_irqsave)(&asus_ref.lock); | |
| 1675 | + list_del(&bdev->list); | |
| 1676 | +} | |
| 1677 | +EXPORT_SYMBOL_GPL(asus_hid_unregister_listener); | |
| 1678 | + | |
| 1679 | +static void do_kbd_led_set(struct led_classdev *led_cdev, int value); | |
| 1680 | + | |
| 1681 | +static void kbd_led_update_all(struct work_struct *work) | |
| 1682 | +{ | |
| 1683 | + struct asus_wmi *asus; | |
| 1684 | + bool registered, notify; | |
| 1685 | + int ret, value; | |
| 1686 | + | |
| 1687 | + asus = container_of(work, struct asus_wmi, kbd_led_work); | |
| 1688 | + | |
| 1689 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) { | |
| 1690 | + registered = asus->kbd_led_registered; | |
| 1691 | + value = asus->kbd_led_wk; | |
| 1692 | + notify = asus->kbd_led_notify; | |
| 1693 | + } | |
| 1694 | + | |
| 1695 | + if (!registered) { | |
| 1696 | + /* | |
| 1697 | + * This workqueue runs under asus-wmi, which means probe has | |
| 1698 | + * completed and asus-wmi will keep running until it finishes. | |
| 1699 | + * Therefore, we can safely register the LED without holding | |
| 1700 | + * a spinlock. | |
| 1701 | + */ | |
| 1702 | + ret = devm_led_classdev_register(&asus->platform_device->dev, | |
| 1703 | + &asus->kbd_led); | |
| 1704 | + if (!ret) { | |
| 1705 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) | |
| 1706 | + asus->kbd_led_registered = true; | |
| 1707 | + } else { | |
| 1708 | + pr_warn("Failed to register keyboard backlight LED: %d\n", ret); | |
| 1709 | + return; | |
| 1710 | + } | |
| 1711 | + } | |
| 1712 | + | |
| 1713 | + if (value >= 0) | |
| 1714 | + do_kbd_led_set(&asus->kbd_led, value); | |
| 1715 | + if (notify) { | |
| 1716 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) | |
| 1717 | + asus->kbd_led_notify = false; | |
| 1718 | + led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value); | |
| 1719 | + } | |
| 1720 | +} | |
| 1721 | + | |
| 1722 | +/* | |
| 1723 | + * This function is called from hid-asus to inform asus-wmi of brightness | |
| 1724 | + * changes initiated by the keyboard backlight keys. | |
| 1725 | + */ | |
| 1726 | +int asus_hid_event(enum asus_hid_event event) | |
| 1727 | +{ | |
| 1728 | + struct asus_wmi *asus; | |
| 1729 | + int brightness; | |
| 1730 | + | |
| 1731 | + guard(spinlock_irqsave)(&asus_ref.lock); | |
| 1732 | + asus = asus_ref.asus; | |
| 1733 | + if (!asus || !asus->kbd_led_registered) | |
| 1734 | + return -EBUSY; | |
| 1735 | + | |
| 1736 | + brightness = asus->kbd_led_wk; | |
| 1737 | + | |
| 1738 | + switch (event) { | |
| 1739 | + case ASUS_EV_BRTUP: | |
| 1740 | + brightness += 1; | |
| 1741 | + break; | |
| 1742 | + case ASUS_EV_BRTDOWN: | |
| 1743 | + brightness -= 1; | |
| 1744 | + break; | |
| 1745 | + case ASUS_EV_BRTTOGGLE: | |
| 1746 | + if (brightness >= ASUS_EV_MAX_BRIGHTNESS) | |
| 1747 | + brightness = 0; | |
| 1748 | + else | |
| 1749 | + brightness += 1; | |
| 1750 | + break; | |
| 1751 | + } | |
| 1752 | + | |
| 1753 | + asus->kbd_led_wk = clamp_val(brightness, 0, ASUS_EV_MAX_BRIGHTNESS); | |
| 1754 | + asus->kbd_led_notify = true; | |
| 1755 | + queue_work(asus->led_workqueue, &asus->kbd_led_work); | |
| 1756 | + return 0; | |
| 1757 | +} | |
| 1758 | +EXPORT_SYMBOL_GPL(asus_hid_event); | |
| 1759 | + | |
| 1618 | 1760 | /* |
| 1619 | 1761 | * These functions actually update the LED's, and are called from a |
| 1620 | 1762 | * workqueue. By doing this as separate work rather than when the LED |
| @@ -1661,7 +1803,8 @@ static void kbd_led_update(struct asus_wmi *asus) · 40 unmodified lines | ||
| 1661 | 1803 | { |
| 1662 | 1804 | int ctrl_param = 0; |
| 1663 | 1805 | |
| 1664 | − ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F); | |
| 1806 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) | |
| 1807 | + ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F); | |
| 1665 | 1808 | asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL); |
| 1666 | 1809 | } |
| 1667 | 1810 | |
| @@ -1694,14 +1837,21 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env) · 26 unmodified lines | ||
| 1694 | 1837 | |
| 1695 | 1838 | static void do_kbd_led_set(struct led_classdev *led_cdev, int value) |
| 1696 | 1839 | { |
| 1840 | + struct asus_hid_listener *listener; | |
| 1697 | 1841 | struct asus_wmi *asus; |
| 1698 | − int max_level; | |
| 1699 | 1842 | |
| 1700 | 1843 | asus = container_of(led_cdev, struct asus_wmi, kbd_led); |
| 1701 | − max_level = asus->kbd_led.max_brightness; | |
| 1702 | 1844 | |
| 1703 | − asus->kbd_led_wk = clamp_val(value, 0, max_level); | |
| 1704 | − kbd_led_update(asus); | |
| 1845 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) | |
| 1846 | + asus->kbd_led_wk = clamp_val(value, 0, ASUS_EV_MAX_BRIGHTNESS); | |
| 1847 | + | |
| 1848 | + if (asus->kbd_led_avail) | |
| 1849 | + kbd_led_update(asus); | |
| 1850 | + | |
| 1851 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) { | |
| 1852 | + list_for_each_entry(listener, &asus_ref.listeners, list) | |
| 1853 | + listener->brightness_set(listener, asus->kbd_led_wk); | |
| 1854 | + } | |
| 1705 | 1855 | } |
| 1706 | 1856 | |
| 1707 | 1857 | static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) |
| @@ -1716,10 +1866,11 @@ static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) · 8 unmodified lines | ||
| 1716 | 1866 | |
| 1717 | 1867 | static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value) |
| 1718 | 1868 | { |
| 1719 | − struct led_classdev *led_cdev = &asus->kbd_led; | |
| 1720 | − | |
| 1721 | − do_kbd_led_set(led_cdev, value); | |
| 1722 | − led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk); | |
| 1869 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) { | |
| 1870 | + asus->kbd_led_wk = value; | |
| 1871 | + asus->kbd_led_notify = true; | |
| 1872 | + } | |
| 1873 | + queue_work(asus->led_workqueue, &asus->kbd_led_work); | |
| 1723 | 1874 | } |
| 1724 | 1875 | |
| 1725 | 1876 | static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) |
| @@ -1729,10 +1880,18 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) · 3 unmodified lines | ||
| 1729 | 1880 | |
| 1730 | 1881 | asus = container_of(led_cdev, struct asus_wmi, kbd_led); |
| 1731 | 1882 | |
| 1883 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) { | |
| 1884 | + if (!asus->kbd_led_avail) | |
| 1885 | + return asus->kbd_led_wk; | |
| 1886 | + } | |
| 1887 | + | |
| 1732 | 1888 | retval = kbd_led_read(asus, &value, NULL); |
| 1733 | 1889 | if (retval < 0) |
| 1734 | 1890 | return retval; |
| 1735 | 1891 | |
| 1892 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) | |
| 1893 | + asus->kbd_led_wk = value; | |
| 1894 | + | |
| 1736 | 1895 | return value; |
| 1737 | 1896 | } |
| 1738 | 1897 | |
| @@ -1844,7 +2003,9 @@ static int camera_led_set(struct led_classdev *led_cdev, · 105 unmodified lines | ||
| 1844 | 2003 | |
| 1845 | 2004 | static void asus_wmi_led_exit(struct asus_wmi *asus) |
| 1846 | 2005 | { |
| 1847 | − led_classdev_unregister(&asus->kbd_led); | |
| 2006 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) | |
| 2007 | + asus_ref.asus = NULL; | |
| 2008 | + | |
| 1848 | 2009 | led_classdev_unregister(&asus->tpd_led); |
| 1849 | 2010 | led_classdev_unregister(&asus->wlan_led); |
| 1850 | 2011 | led_classdev_unregister(&asus->lightbar_led); |
| @@ -1882,22 +2043,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus) · 31 unmodified lines | ||
| 1882 | 2043 | goto error; |
| 1883 | 2044 | } |
| 1884 | 2045 | |
| 1885 | − if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) { | |
| 1886 | − pr_info("using asus-wmi for asus::kbd_backlight\n"); | |
| 1887 | − asus->kbd_led_wk = led_val; | |
| 1888 | − asus->kbd_led.name = "asus::kbd_backlight"; | |
| 1889 | − asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED; | |
| 1890 | − asus->kbd_led.brightness_set_blocking = kbd_led_set; | |
| 1891 | − asus->kbd_led.brightness_get = kbd_led_get; | |
| 1892 | − asus->kbd_led.max_brightness = 3; | |
| 2046 | + asus->kbd_led.name = "asus::kbd_backlight"; | |
| 2047 | + asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED; | |
| 2048 | + asus->kbd_led.brightness_set_blocking = kbd_led_set; | |
| 2049 | + asus->kbd_led.brightness_get = kbd_led_get; | |
| 2050 | + asus->kbd_led.max_brightness = ASUS_EV_MAX_BRIGHTNESS; | |
| 2051 | + asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL); | |
| 2052 | + INIT_WORK(&asus->kbd_led_work, kbd_led_update_all); | |
| 1893 | 2053 | |
| 2054 | + if (asus->kbd_led_avail) { | |
| 2055 | + asus->kbd_led_wk = led_val; | |
| 1894 | 2056 | if (num_rgb_groups != 0) |
| 1895 | 2057 | asus->kbd_led.groups = kbd_rgb_mode_groups; |
| 2058 | + } else { | |
| 2059 | + asus->kbd_led_wk = -1; | |
| 2060 | + } | |
| 1896 | 2061 | |
| 1897 | − rv = led_classdev_register(&asus->platform_device->dev, | |
| 1898 | − &asus->kbd_led); | |
| 1899 | − if (rv) | |
| 1900 | − goto error; | |
| 2062 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) { | |
| 2063 | + asus_ref.asus = asus; | |
| 2064 | + if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners)) | |
| 2065 | + queue_work(asus->led_workqueue, &asus->kbd_led_work); | |
| 1901 | 2066 | } |
| 1902 | 2067 | |
| 1903 | 2068 | if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED) |
| @@ -4372,6 +4537,7 @@ static int asus_wmi_get_event_code(union acpi_object *obj) · 2468 unmodified lines | ||
| 4372 | 4537 | |
| 4373 | 4538 | static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) |
| 4374 | 4539 | { |
| 4540 | + enum led_brightness led_value; | |
| 4375 | 4541 | unsigned int key_value = 1; |
| 4376 | 4542 | bool autorelease = 1; |
| 4377 | 4543 | |
| @@ -4388,19 +4554,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) · 10 unmodified lines | ||
| 4388 | 4554 | return; |
| 4389 | 4555 | } |
| 4390 | 4556 | |
| 4557 | + scoped_guard(spinlock_irqsave, &asus_ref.lock) | |
| 4558 | + led_value = asus->kbd_led_wk; | |
| 4559 | + | |
| 4391 | 4560 | if (code == NOTIFY_KBD_BRTUP) { |
| 4392 | − kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); | |
| 4561 | + kbd_led_set_by_kbd(asus, led_value + 1); | |
| 4393 | 4562 | return; |
| 4394 | 4563 | } |
| 4395 | 4564 | if (code == NOTIFY_KBD_BRTDWN) { |
| 4396 | − kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1); | |
| 4565 | + kbd_led_set_by_kbd(asus, led_value - 1); | |
| 4397 | 4566 | return; |
| 4398 | 4567 | } |
| 4399 | 4568 | if (code == NOTIFY_KBD_BRTTOGGLE) { |
| 4400 | − if (asus->kbd_led_wk == asus->kbd_led.max_brightness) | |
| 4569 | + if (led_value >= ASUS_EV_MAX_BRIGHTNESS) | |
| 4401 | 4570 | kbd_led_set_by_kbd(asus, 0); |
| 4402 | 4571 | else |
| 4403 | − kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); | |
| 4572 | + kbd_led_set_by_kbd(asus, led_value + 1); | |
| 4404 | 4573 | return; |
| 4405 | 4574 | } |
| 4406 | 4575 | |
| @@ -13,23 +13,28 @@ · 12 unmodified lines | ||
| 13 | 13 | |
| 14 | 14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 15 | 15 | |
| 16 | −#include <linux/kernel.h> | |
| 17 | −#include <linux/module.h> | |
| 16 | +#include <linux/acpi.h> | |
| 17 | +#include <linux/cleanup.h> | |
| 18 | +#include <linux/compiler_attributes.h> | |
| 19 | +#include <linux/dmi.h> | |
| 20 | +#include <linux/fixp-arith.h> | |
| 21 | +#include <linux/hwmon.h> | |
| 18 | 22 | #include <linux/init.h> |
| 19 | −#include <linux/slab.h> | |
| 20 | −#include <linux/types.h> | |
| 21 | 23 | #include <linux/input.h> |
| 22 | 24 | #include <linux/input/sparse-keymap.h> |
| 25 | +#include <linux/kernel.h> | |
| 26 | +#include <linux/limits.h> | |
| 27 | +#include <linux/minmax.h> | |
| 28 | +#include <linux/module.h> | |
| 29 | +#include <linux/mutex.h> | |
| 23 | 30 | #include <linux/platform_device.h> |
| 24 | 31 | #include <linux/platform_profile.h> |
| 25 | −#include <linux/hwmon.h> | |
| 26 | −#include <linux/acpi.h> | |
| 27 | −#include <linux/mutex.h> | |
| 28 | −#include <linux/cleanup.h> | |
| 29 | 32 | #include <linux/power_supply.h> |
| 30 | 33 | #include <linux/rfkill.h> |
| 34 | +#include <linux/slab.h> | |
| 31 | 35 | #include <linux/string.h> |
| 32 | −#include <linux/dmi.h> | |
| 36 | +#include <linux/types.h> | |
| 37 | +#include <linux/workqueue.h> | |
| 33 | 38 | |
| 34 | 39 | MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>"); |
| 35 | 40 | MODULE_DESCRIPTION("HP laptop WMI driver"); |
| @@ -41,9 +46,13 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4"); · 5 unmodified lines | ||
| 41 | 46 | #define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" |
| 42 | 47 | #define HPWMI_BIOS_GUID "5FB7F034-2C63-45E9-BE91-3D44E2C707E4" |
| 43 | 48 | |
| 44 | −#define HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET 0x62 | |
| 45 | −#define HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET 0x63 | |
| 46 | −#define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95 | |
| 49 | +enum hp_ec_offsets { | |
| 50 | + HP_EC_OFFSET_UNKNOWN = 0x00, | |
| 51 | + HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET = 0x59, | |
| 52 | + HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET = 0x62, | |
| 53 | + HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET = 0x63, | |
| 54 | + HP_OMEN_EC_THERMAL_PROFILE_OFFSET = 0x95, | |
| 55 | +}; | |
| 47 | 56 | |
| 48 | 57 | #define HP_FAN_SPEED_AUTOMATIC 0x00 |
| 49 | 58 | #define HP_POWER_LIMIT_DEFAULT 0x00 |
| @@ -53,6 +62,70 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4"); · 3 unmodified lines | ||
| 53 | 62 | |
| 54 | 63 | #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required |
| 55 | 64 | |
| 65 | +enum hp_thermal_profile_omen_v0 { | |
| 66 | + HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00, | |
| 67 | + HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01, | |
| 68 | + HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02, | |
| 69 | +}; | |
| 70 | + | |
| 71 | +enum hp_thermal_profile_omen_v1 { | |
| 72 | + HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30, | |
| 73 | + HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31, | |
| 74 | + HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50, | |
| 75 | +}; | |
| 76 | + | |
| 77 | +enum hp_thermal_profile_omen_flags { | |
| 78 | + HP_OMEN_EC_FLAGS_TURBO = 0x04, | |
| 79 | + HP_OMEN_EC_FLAGS_NOTIMER = 0x02, | |
| 80 | + HP_OMEN_EC_FLAGS_JUSTSET = 0x01, | |
| 81 | +}; | |
| 82 | + | |
| 83 | +enum hp_thermal_profile_victus { | |
| 84 | + HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00, | |
| 85 | + HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01, | |
| 86 | + HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03, | |
| 87 | +}; | |
| 88 | + | |
| 89 | +enum hp_thermal_profile_victus_s { | |
| 90 | + HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00, | |
| 91 | + HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01, | |
| 92 | +}; | |
| 93 | + | |
| 94 | +enum hp_thermal_profile { | |
| 95 | + HP_THERMAL_PROFILE_PERFORMANCE = 0x00, | |
| 96 | + HP_THERMAL_PROFILE_DEFAULT = 0x01, | |
| 97 | + HP_THERMAL_PROFILE_COOL = 0x02, | |
| 98 | + HP_THERMAL_PROFILE_QUIET = 0x03, | |
| 99 | +}; | |
| 100 | + | |
| 101 | + | |
| 102 | +struct thermal_profile_params { | |
| 103 | + u8 performance; | |
| 104 | + u8 balanced; | |
| 105 | + u8 low_power; | |
| 106 | + u8 ec_tp_offset; | |
| 107 | +}; | |
| 108 | + | |
| 109 | +static const struct thermal_profile_params victus_s_thermal_params = { | |
| 110 | + .performance = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE, | |
| 111 | + .balanced = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT, | |
| 112 | + .low_power = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT, | |
| 113 | + .ec_tp_offset = HP_EC_OFFSET_UNKNOWN, | |
| 114 | +}; | |
| 115 | + | |
| 116 | +static const struct thermal_profile_params omen_v1_thermal_params = { | |
| 117 | + .performance = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE, | |
| 118 | + .balanced = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT, | |
| 119 | + .low_power = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT, | |
| 120 | + .ec_tp_offset = HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET, | |
| 121 | +}; | |
| 122 | + | |
| 123 | +/* | |
| 124 | + * A generic pointer for the currently-active board's thermal profile | |
| 125 | + * parameters. | |
| 126 | + */ | |
| 127 | +static struct thermal_profile_params *active_thermal_profile_params; | |
| 128 | + | |
| 56 | 129 | /* DMI board names of devices that should use the omen specific path for |
| 57 | 130 | * thermal profiles. |
| 58 | 131 | * This was obtained by taking a look in the windows omen command center |
| @@ -99,12 +172,40 @@ static const char * const victus_thermal_profile_boards[] = { · 40 unmodified lines | ||
| 99 | 172 | }; |
| 100 | 173 | |
| 101 | 174 | /* DMI Board names of Victus 16-r and Victus 16-s laptops */ |
| 102 | −static const char * const victus_s_thermal_profile_boards[] = { | |
| 103 | − "8BBE", "8BD4", "8BD5", | |
| 104 | − "8C78", "8C99", "8C9C", | |
| 105 | − "8D41", | |
| 175 | +static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { | |
| 176 | + { | |
| 177 | + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, | |
| 178 | + .driver_data = (void *)&victus_s_thermal_params, | |
| 179 | + }, | |
| 180 | + { | |
| 181 | + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") }, | |
| 182 | + .driver_data = (void *)&victus_s_thermal_params, | |
| 183 | + }, | |
| 184 | + { | |
| 185 | + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") }, | |
| 186 | + .driver_data = (void *)&victus_s_thermal_params, | |
| 187 | + }, | |
| 188 | + { | |
| 189 | + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") }, | |
| 190 | + .driver_data = (void *)&omen_v1_thermal_params, | |
| 191 | + }, | |
| 192 | + { | |
| 193 | + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") }, | |
| 194 | + .driver_data = (void *)&victus_s_thermal_params, | |
| 195 | + }, | |
| 196 | + { | |
| 197 | + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") }, | |
| 198 | + .driver_data = (void *)&victus_s_thermal_params, | |
| 199 | + }, | |
| 200 | + { | |
| 201 | + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, | |
| 202 | + .driver_data = (void *)&victus_s_thermal_params, | |
| 203 | + }, | |
| 204 | + {}, | |
| 106 | 205 | }; |
| 107 | 206 | |
| 207 | +static bool is_victus_s_board; | |
| 208 | + | |
| 108 | 209 | enum hp_wmi_radio { |
| 109 | 210 | HPWMI_WIFI = 0x0, |
| 110 | 211 | HPWMI_BLUETOOTH = 0x1, |
| @@ -190,7 +291,8 @@ enum hp_wmi_gm_commandtype { · 79 unmodified lines | ||
| 190 | 291 | HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22, |
| 191 | 292 | HPWMI_SET_POWER_LIMITS_QUERY = 0x29, |
| 192 | 293 | HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY = 0x2D, |
| 193 | − HPWMI_FAN_SPEED_SET_QUERY = 0x2E, | |
| 294 | + HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY = 0x2E, | |
| 295 | + HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY = 0x2F, | |
| 194 | 296 | }; |
| 195 | 297 | |
| 196 | 298 | enum hp_wmi_command { |
| @@ -225,42 +327,6 @@ enum hp_wireless2_bits { · 28 unmodified lines | ||
| 225 | 327 | HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD, |
| 226 | 328 | }; |
| 227 | 329 | |
| 228 | −enum hp_thermal_profile_omen_v0 { | |
| 229 | − HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00, | |
| 230 | − HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01, | |
| 231 | − HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02, | |
| 232 | −}; | |
| 233 | − | |
| 234 | −enum hp_thermal_profile_omen_v1 { | |
| 235 | − HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30, | |
| 236 | − HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31, | |
| 237 | − HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50, | |
| 238 | −}; | |
| 239 | − | |
| 240 | −enum hp_thermal_profile_omen_flags { | |
| 241 | − HP_OMEN_EC_FLAGS_TURBO = 0x04, | |
| 242 | − HP_OMEN_EC_FLAGS_NOTIMER = 0x02, | |
| 243 | − HP_OMEN_EC_FLAGS_JUSTSET = 0x01, | |
| 244 | −}; | |
| 245 | − | |
| 246 | −enum hp_thermal_profile_victus { | |
| 247 | − HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00, | |
| 248 | − HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01, | |
| 249 | − HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03, | |
| 250 | −}; | |
| 251 | − | |
| 252 | −enum hp_thermal_profile_victus_s { | |
| 253 | − HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00, | |
| 254 | − HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01, | |
| 255 | −}; | |
| 256 | − | |
| 257 | −enum hp_thermal_profile { | |
| 258 | − HP_THERMAL_PROFILE_PERFORMANCE = 0x00, | |
| 259 | − HP_THERMAL_PROFILE_DEFAULT = 0x01, | |
| 260 | − HP_THERMAL_PROFILE_COOL = 0x02, | |
| 261 | − HP_THERMAL_PROFILE_QUIET = 0x03, | |
| 262 | −}; | |
| 263 | − | |
| 264 | 330 | #define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW) |
| 265 | 331 | #define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT) |
| 266 | 332 | |
| @@ -348,6 +414,58 @@ static const char * const tablet_chassis_types[] = { · 81 unmodified lines | ||
| 348 | 414 | |
| 349 | 415 | #define DEVICE_MODE_TABLET 0x06 |
| 350 | 416 | |
| 417 | +#define CPU_FAN 0 | |
| 418 | +#define GPU_FAN 1 | |
| 419 | + | |
| 420 | +enum pwm_modes { | |
| 421 | + PWM_MODE_MAX = 0, | |
| 422 | + PWM_MODE_MANUAL = 1, | |
| 423 | + PWM_MODE_AUTO = 2, | |
| 424 | +}; | |
| 425 | + | |
| 426 | +struct hp_wmi_hwmon_priv { | |
| 427 | + u8 min_rpm; | |
| 428 | + u8 max_rpm; | |
| 429 | + u8 gpu_delta; | |
| 430 | + u8 mode; | |
| 431 | + u8 pwm; | |
| 432 | + struct delayed_work keep_alive_dwork; | |
| 433 | +}; | |
| 434 | + | |
| 435 | +struct victus_s_fan_table_header { | |
| 436 | + u8 unknown; | |
| 437 | + u8 num_entries; | |
| 438 | +} __packed; | |
| 439 | + | |
| 440 | +struct victus_s_fan_table_entry { | |
| 441 | + u8 cpu_rpm; | |
| 442 | + u8 gpu_rpm; | |
| 443 | + u8 unknown; | |
| 444 | +} __packed; | |
| 445 | + | |
| 446 | +struct victus_s_fan_table { | |
| 447 | + struct victus_s_fan_table_header header; | |
| 448 | + struct victus_s_fan_table_entry entries[]; | |
| 449 | +} __packed; | |
| 450 | + | |
| 451 | +/* | |
| 452 | + * 90s delay to prevent the firmware from resetting fan mode after fixed | |
| 453 | + * 120s timeout | |
| 454 | + */ | |
| 455 | +#define KEEP_ALIVE_DELAY_SECS 90 | |
| 456 | + | |
| 457 | +static inline u8 rpm_to_pwm(u8 rpm, struct hp_wmi_hwmon_priv *priv) | |
| 458 | +{ | |
| 459 | + return fixp_linear_interpolate(0, 0, priv->max_rpm, U8_MAX, | |
| 460 | + clamp_val(rpm, 0, priv->max_rpm)); | |
| 461 | +} | |
| 462 | + | |
| 463 | +static inline u8 pwm_to_rpm(u8 pwm, struct hp_wmi_hwmon_priv *priv) | |
| 464 | +{ | |
| 465 | + return fixp_linear_interpolate(0, 0, U8_MAX, priv->max_rpm, | |
| 466 | + clamp_val(pwm, 0, U8_MAX)); | |
| 467 | +} | |
| 468 | + | |
| 351 | 469 | /* map output size to the corresponding WMI method id */ |
| 352 | 470 | static inline int encode_outsize_for_pvsz(int outsize) |
| 353 | 471 | { |
| @@ -637,41 +755,53 @@ static int hp_wmi_fan_speed_max_set(int enabled) · 283 unmodified lines | ||
| 637 | 755 | return enabled; |
| 638 | 756 | } |
| 639 | 757 | |
| 640 | −static int hp_wmi_fan_speed_reset(void) | |
| 758 | +static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) | |
| 641 | 759 | { |
| 642 | − u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC }; | |
| 643 | − int ret; | |
| 760 | + u8 fan_speed[2]; | |
| 761 | + int gpu_speed, ret; | |
| 644 | 762 | |
| 645 | − ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM, | |
| 646 | − &fan_speed, sizeof(fan_speed), 0); | |
| 763 | + fan_speed[CPU_FAN] = speed; | |
| 764 | + fan_speed[GPU_FAN] = speed; | |
| 647 | 765 | |
| 648 | − return ret; | |
| 649 | −} | |
| 650 | − | |
| 651 | −static int hp_wmi_fan_speed_max_reset(void) | |
| 652 | −{ | |
| 653 | − int ret; | |
| 766 | + /* | |
| 767 | + * GPU fan speed is always a little higher than CPU fan speed, we fetch | |
| 768 | + * this delta value from the fan table during hwmon init. | |
| 769 | + * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to | |
| 770 | + * automatic mode. | |
| 771 | + */ | |
| 772 | + if (speed != HP_FAN_SPEED_AUTOMATIC) { | |
| 773 | + gpu_speed = speed + priv->gpu_delta; | |
| 774 | + fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX); | |
| 775 | + } | |
| 654 | 776 | |
| 777 | + ret = hp_wmi_get_fan_count_userdefine_trigger(); | |
| 778 | + if (ret < 0) | |
| 779 | + return ret; | |
| 780 | + /* Max fans need to be explicitly disabled */ | |
| 655 | 781 | ret = hp_wmi_fan_speed_max_set(0); |
| 656 | − if (ret) | |
| 782 | + if (ret < 0) | |
| 657 | 783 | return ret; |
| 784 | + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY, HPWMI_GM, | |
| 785 | + &fan_speed, sizeof(fan_speed), 0); | |
| 658 | 786 | |
| 659 | − /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */ | |
| 660 | − ret = hp_wmi_fan_speed_reset(); | |
| 661 | 787 | return ret; |
| 662 | 788 | } |
| 663 | 789 | |
| 664 | −static int hp_wmi_fan_speed_max_get(void) | |
| 790 | +static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv) | |
| 665 | 791 | { |
| 666 | − int val = 0, ret; | |
| 792 | + return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC); | |
| 793 | +} | |
| 667 | 794 | |
| 668 | − ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM, | |
| 669 | − &val, zero_if_sup(val), sizeof(val)); | |
| 795 | +static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv) | |
| 796 | +{ | |
| 797 | + int ret; | |
| 670 | 798 | |
| 799 | + ret = hp_wmi_fan_speed_max_set(0); | |
| 671 | 800 | if (ret) |
| 672 | − return ret < 0 ? ret : -EINVAL; | |
| 801 | + return ret; | |
| 673 | 802 | |
| 674 | − return val; | |
| 803 | + /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */ | |
| 804 | + return hp_wmi_fan_speed_reset(priv); | |
| 675 | 805 | } |
| 676 | 806 | |
| 677 | 807 | static int __init hp_wmi_bios_2008_later(void) |
| @@ -1581,15 +1711,8 @@ static int platform_profile_victus_set_ec(enum platform_profile_option profile) · 903 unmodified lines | ||
| 1581 | 1711 | |
| 1582 | 1712 | static bool is_victus_s_thermal_profile(void) |
| 1583 | 1713 | { |
| 1584 | − const char *board_name; | |
| 1585 | − | |
| 1586 | − board_name = dmi_get_system_info(DMI_BOARD_NAME); | |
| 1587 | − if (!board_name) | |
| 1588 | − return false; | |
| 1589 | − | |
| 1590 | − return match_string(victus_s_thermal_profile_boards, | |
| 1591 | − ARRAY_SIZE(victus_s_thermal_profile_boards), | |
| 1592 | − board_name) >= 0; | |
| 1714 | + /* Initialised in driver init, hence safe to use here */ | |
| 1715 | + return is_victus_s_board; | |
| 1593 | 1716 | } |
| 1594 | 1717 | |
| 1595 | 1718 | static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, |
| @@ -1670,27 +1793,86 @@ static int victus_s_set_cpu_pl1_pl2(u8 pl1, u8 pl2) · 74 unmodified lines | ||
| 1670 | 1793 | return ret; |
| 1671 | 1794 | } |
| 1672 | 1795 | |
| 1796 | +static int platform_profile_victus_s_get_ec(enum platform_profile_option *profile) | |
| 1797 | +{ | |
| 1798 | + int ret = 0; | |
| 1799 | + bool current_ctgp_state, current_ppab_state; | |
| 1800 | + u8 current_dstate, current_gpu_slowdown_temp, tp; | |
| 1801 | + const struct thermal_profile_params *params; | |
| 1802 | + | |
| 1803 | + params = active_thermal_profile_params; | |
| 1804 | + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { | |
| 1805 | + *profile = active_platform_profile; | |
| 1806 | + return 0; | |
| 1807 | + } | |
| 1808 | + | |
| 1809 | + ret = ec_read(params->ec_tp_offset, &tp); | |
| 1810 | + if (ret) | |
| 1811 | + return ret; | |
| 1812 | + | |
| 1813 | + /* | |
| 1814 | + * We cannot use active_thermal_profile_params here, because boards | |
| 1815 | + * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically | |
| 1816 | + * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen | |
| 1817 | + * Gaming Hub on windows. Hence accept both of these values. | |
| 1818 | + */ | |
| 1819 | + if (tp == victus_s_thermal_params.performance || | |
| 1820 | + tp == omen_v1_thermal_params.performance) { | |
| 1821 | + *profile = PLATFORM_PROFILE_PERFORMANCE; | |
| 1822 | + } else if (tp == victus_s_thermal_params.balanced || | |
| 1823 | + tp == omen_v1_thermal_params.balanced) { | |
| 1824 | + /* | |
| 1825 | + * Since both PLATFORM_PROFILE_LOW_POWER and | |
| 1826 | + * PLATFORM_PROFILE_BALANCED share the same thermal profile | |
| 1827 | + * parameter value, hence to differentiate between them, we | |
| 1828 | + * query the GPU CTGP and PPAB states and compare based off of | |
| 1829 | + * that. | |
| 1830 | + */ | |
| 1831 | + ret = victus_s_gpu_thermal_profile_get(¤t_ctgp_state, | |
| 1832 | + ¤t_ppab_state, | |
| 1833 | + ¤t_dstate, | |
| 1834 | + ¤t_gpu_slowdown_temp); | |
| 1835 | + if (ret < 0) | |
| 1836 | + return ret; | |
| 1837 | + if (current_ctgp_state == 0 && current_ppab_state == 0) | |
| 1838 | + *profile = PLATFORM_PROFILE_LOW_POWER; | |
| 1839 | + else if (current_ctgp_state == 0 && current_ppab_state == 1) | |
| 1840 | + *profile = PLATFORM_PROFILE_BALANCED; | |
| 1841 | + else | |
| 1842 | + return -EINVAL; | |
| 1843 | + } else { | |
| 1844 | + return -EINVAL; | |
| 1845 | + } | |
| 1846 | + | |
| 1847 | + return 0; | |
| 1848 | +} | |
| 1849 | + | |
| 1673 | 1850 | static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) |
| 1674 | 1851 | { |
| 1852 | + struct thermal_profile_params *params; | |
| 1675 | 1853 | bool gpu_ctgp_enable, gpu_ppab_enable; |
| 1676 | 1854 | u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ |
| 1677 | 1855 | int err, tp; |
| 1678 | 1856 | |
| 1857 | + params = active_thermal_profile_params; | |
| 1858 | + if (!params) | |
| 1859 | + return -ENODEV; | |
| 1860 | + | |
| 1679 | 1861 | switch (profile) { |
| 1680 | 1862 | case PLATFORM_PROFILE_PERFORMANCE: |
| 1681 | − tp = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE; | |
| 1863 | + tp = params->performance; | |
| 1682 | 1864 | gpu_ctgp_enable = true; |
| 1683 | 1865 | gpu_ppab_enable = true; |
| 1684 | 1866 | gpu_dstate = 1; |
| 1685 | 1867 | break; |
| 1686 | 1868 | case PLATFORM_PROFILE_BALANCED: |
| 1687 | − tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT; | |
| 1869 | + tp = params->balanced; | |
| 1688 | 1870 | gpu_ctgp_enable = false; |
| 1689 | 1871 | gpu_ppab_enable = true; |
| 1690 | 1872 | gpu_dstate = 1; |
| 1691 | 1873 | break; |
| 1692 | 1874 | case PLATFORM_PROFILE_LOW_POWER: |
| 1693 | − tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT; | |
| 1875 | + tp = params->low_power; | |
| 1694 | 1876 | gpu_ctgp_enable = false; |
| 1695 | 1877 | gpu_ppab_enable = false; |
| 1696 | 1878 | gpu_dstate = 1; |
| @@ -1832,6 +2014,7 @@ static int victus_s_powersource_event(struct notifier_block *nb, · 135 unmodified lines | ||
| 1832 | 2014 | void *data) |
| 1833 | 2015 | { |
| 1834 | 2016 | struct acpi_bus_event *event_entry = data; |
| 2017 | + enum platform_profile_option actual_profile; | |
| 1835 | 2018 | int err; |
| 1836 | 2019 | |
| 1837 | 2020 | if (strcmp(event_entry->device_class, ACPI_AC_CLASS) != 0) |
| @@ -1839,6 +2022,17 @@ static int victus_s_powersource_event(struct notifier_block *nb, · 1 unmodified line | ||
| 1839 | 2022 | |
| 1840 | 2023 | pr_debug("Received power source device event\n"); |
| 1841 | 2024 | |
| 2025 | + guard(mutex)(&active_platform_profile_lock); | |
| 2026 | + err = platform_profile_victus_s_get_ec(&actual_profile); | |
| 2027 | + if (err < 0) { | |
| 2028 | + /* | |
| 2029 | + * Although we failed to get the current platform profile, we | |
| 2030 | + * still want the other event consumers to process it. | |
| 2031 | + */ | |
| 2032 | + pr_warn("Failed to read current platform profile (%d)\n", err); | |
| 2033 | + return NOTIFY_DONE; | |
| 2034 | + } | |
| 2035 | + | |
| 1842 | 2036 | /* |
| 1843 | 2037 | * Switching to battery power source while Performance mode is active |
| 1844 | 2038 | * needs manual triggering of CPU power limits. Same goes when switching |
| @@ -1847,7 +2041,7 @@ static int victus_s_powersource_event(struct notifier_block *nb, · 2 unmodified lines | ||
| 1847 | 2041 | * Seen on HP 16-s1034nf (board 8C9C) with F.11 and F.13 BIOS versions. |
| 1848 | 2042 | */ |
| 1849 | 2043 | |
| 1850 | − if (active_platform_profile == PLATFORM_PROFILE_PERFORMANCE) { | |
| 2044 | + if (actual_profile == PLATFORM_PROFILE_PERFORMANCE) { | |
| 1851 | 2045 | pr_debug("Triggering CPU PL1/PL2 actualization\n"); |
| 1852 | 2046 | err = victus_s_set_cpu_pl1_pl2(HP_POWER_LIMIT_DEFAULT, |
| 1853 | 2047 | HP_POWER_LIMIT_DEFAULT); |
| @@ -1958,11 +2152,22 @@ static int thermal_profile_setup(struct platform_device *device) · 104 unmodified lines | ||
| 1958 | 2152 | ops = &platform_profile_victus_ops; |
| 1959 | 2153 | } else if (is_victus_s_thermal_profile()) { |
| 1960 | 2154 | /* |
| 1961 | − * Being unable to retrieve laptop's current thermal profile, | |
| 1962 | − * during this setup, we set it to Balanced by default. | |
| 2155 | + * For an unknown EC layout board, platform_profile_victus_s_get_ec(), | |
| 2156 | + * behaves like a wrapper around active_platform_profile, to avoid using | |
| 2157 | + * uninitialized data, we default to PLATFORM_PROFILE_BALANCED. | |
| 1963 | 2158 | */ |
| 1964 | − active_platform_profile = PLATFORM_PROFILE_BALANCED; | |
| 2159 | + if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { | |
| 2160 | + active_platform_profile = PLATFORM_PROFILE_BALANCED; | |
| 2161 | + } else { | |
| 2162 | + err = platform_profile_victus_s_get_ec(&active_platform_profile); | |
| 2163 | + if (err < 0) | |
| 2164 | + return err; | |
| 2165 | + } | |
| 1965 | 2166 | |
| 2167 | + /* | |
| 2168 | + * call thermal profile write command to ensure that the | |
| 2169 | + * firmware correctly sets the OEM variables | |
| 2170 | + */ | |
| 1966 | 2171 | err = platform_profile_victus_s_set_ec(active_platform_profile); |
| 1967 | 2172 | if (err < 0) |
| 1968 | 2173 | return err; |
| @@ -2031,6 +2236,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) · 62 unmodified lines | ||
| 2031 | 2236 | static void __exit hp_wmi_bios_remove(struct platform_device *device) |
| 2032 | 2237 | { |
| 2033 | 2238 | int i; |
| 2239 | + struct hp_wmi_hwmon_priv *priv; | |
| 2034 | 2240 | |
| 2035 | 2241 | for (i = 0; i < rfkill2_count; i++) { |
| 2036 | 2242 | rfkill_unregister(rfkill2[i].rfkill); |
| @@ -2049,6 +2255,10 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device) · 12 unmodified lines | ||
| 2049 | 2255 | rfkill_unregister(wwan_rfkill); |
| 2050 | 2256 | rfkill_destroy(wwan_rfkill); |
| 2051 | 2257 | } |
| 2258 | + | |
| 2259 | + priv = platform_get_drvdata(device); | |
| 2260 | + if (priv) | |
| 2261 | + cancel_delayed_work_sync(&priv->keep_alive_dwork); | |
| 2052 | 2262 | } |
| 2053 | 2263 | |
| 2054 | 2264 | static int hp_wmi_resume_handler(struct device *device) |
| @@ -2108,12 +2318,56 @@ static struct platform_driver hp_wmi_driver __refdata = { · 53 unmodified lines | ||
| 2108 | 2318 | .remove = __exit_p(hp_wmi_bios_remove), |
| 2109 | 2319 | }; |
| 2110 | 2320 | |
| 2321 | +static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) | |
| 2322 | +{ | |
| 2323 | + int ret; | |
| 2324 | + | |
| 2325 | + switch (priv->mode) { | |
| 2326 | + case PWM_MODE_MAX: | |
| 2327 | + if (is_victus_s_thermal_profile()) | |
| 2328 | + hp_wmi_get_fan_count_userdefine_trigger(); | |
| 2329 | + ret = hp_wmi_fan_speed_max_set(1); | |
| 2330 | + if (ret < 0) | |
| 2331 | + return ret; | |
| 2332 | + schedule_delayed_work(&priv->keep_alive_dwork, | |
| 2333 | + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); | |
| 2334 | + return 0; | |
| 2335 | + case PWM_MODE_MANUAL: | |
| 2336 | + if (!is_victus_s_thermal_profile()) | |
| 2337 | + return -EOPNOTSUPP; | |
| 2338 | + ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); | |
| 2339 | + if (ret < 0) | |
| 2340 | + return ret; | |
| 2341 | + schedule_delayed_work(&priv->keep_alive_dwork, | |
| 2342 | + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); | |
| 2343 | + return 0; | |
| 2344 | + case PWM_MODE_AUTO: | |
| 2345 | + if (is_victus_s_thermal_profile()) { | |
| 2346 | + hp_wmi_get_fan_count_userdefine_trigger(); | |
| 2347 | + ret = hp_wmi_fan_speed_max_reset(priv); | |
| 2348 | + } else { | |
| 2349 | + ret = hp_wmi_fan_speed_max_set(0); | |
| 2350 | + } | |
| 2351 | + if (ret < 0) | |
| 2352 | + return ret; | |
| 2353 | + cancel_delayed_work_sync(&priv->keep_alive_dwork); | |
| 2354 | + return 0; | |
| 2355 | + default: | |
| 2356 | + /* shouldn't happen */ | |
| 2357 | + return -EINVAL; | |
| 2358 | + } | |
| 2359 | + | |
| 2360 | + return 0; | |
| 2361 | +} | |
| 2362 | + | |
| 2111 | 2363 | static umode_t hp_wmi_hwmon_is_visible(const void *data, |
| 2112 | 2364 | enum hwmon_sensor_types type, |
| 2113 | 2365 | u32 attr, int channel) |
| 2114 | 2366 | { |
| 2115 | 2367 | switch (type) { |
| 2116 | 2368 | case hwmon_pwm: |
| 2369 | + if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) | |
| 2370 | + return 0; | |
| 2117 | 2371 | return 0644; |
| 2118 | 2372 | case hwmon_fan: |
| 2119 | 2373 | if (is_victus_s_thermal_profile()) { |
| @@ -2134,8 +2388,10 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, · 14 unmodified lines | ||
| 2134 | 2388 | static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, |
| 2135 | 2389 | u32 attr, int channel, long *val) |
| 2136 | 2390 | { |
| 2137 | − int ret; | |
| 2391 | + struct hp_wmi_hwmon_priv *priv; | |
| 2392 | + int rpm, ret; | |
| 2138 | 2393 | |
| 2394 | + priv = dev_get_drvdata(dev); | |
| 2139 | 2395 | switch (type) { |
| 2140 | 2396 | case hwmon_fan: |
| 2141 | 2397 | if (is_victus_s_thermal_profile()) |
| @@ -2147,16 +2403,21 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, · 5 unmodified lines | ||
| 2147 | 2403 | *val = ret; |
| 2148 | 2404 | return 0; |
| 2149 | 2405 | case hwmon_pwm: |
| 2150 | − switch (hp_wmi_fan_speed_max_get()) { | |
| 2151 | − case 0: | |
| 2152 | − /* 0 is automatic fan, which is 2 for hwmon */ | |
| 2153 | − *val = 2; | |
| 2406 | + if (attr == hwmon_pwm_input) { | |
| 2407 | + if (!is_victus_s_thermal_profile()) | |
| 2408 | + return -EOPNOTSUPP; | |
| 2409 | + | |
| 2410 | + rpm = hp_wmi_get_fan_speed_victus_s(channel); | |
| 2411 | + if (rpm < 0) | |
| 2412 | + return rpm; | |
| 2413 | + *val = rpm_to_pwm(rpm / 100, priv); | |
| 2154 | 2414 | return 0; |
| 2155 | − case 1: | |
| 2156 | − /* 1 is max fan, which is 0 | |
| 2157 | − * (no fan speed control) for hwmon | |
| 2158 | − */ | |
| 2159 | − *val = 0; | |
| 2415 | + } | |
| 2416 | + switch (priv->mode) { | |
| 2417 | + case PWM_MODE_MAX: | |
| 2418 | + case PWM_MODE_MANUAL: | |
| 2419 | + case PWM_MODE_AUTO: | |
| 2420 | + *val = priv->mode; | |
| 2160 | 2421 | return 0; |
| 2161 | 2422 | default: |
| 2162 | 2423 | /* shouldn't happen */ |
| @@ -2170,23 +2431,46 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, · 7 unmodified lines | ||
| 2170 | 2431 | static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, |
| 2171 | 2432 | u32 attr, int channel, long val) |
| 2172 | 2433 | { |
| 2434 | + struct hp_wmi_hwmon_priv *priv; | |
| 2435 | + int rpm; | |
| 2436 | + | |
| 2437 | + priv = dev_get_drvdata(dev); | |
| 2173 | 2438 | switch (type) { |
| 2174 | 2439 | case hwmon_pwm: |
| 2440 | + if (attr == hwmon_pwm_input) { | |
| 2441 | + if (!is_victus_s_thermal_profile()) | |
| 2442 | + return -EOPNOTSUPP; | |
| 2443 | + /* PWM input is invalid when not in manual mode */ | |
| 2444 | + if (priv->mode != PWM_MODE_MANUAL) | |
| 2445 | + return -EINVAL; | |
| 2446 | + | |
| 2447 | + /* ensure PWM input is within valid fan speeds */ | |
| 2448 | + rpm = pwm_to_rpm(val, priv); | |
| 2449 | + rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm); | |
| 2450 | + priv->pwm = rpm_to_pwm(rpm, priv); | |
| 2451 | + return hp_wmi_apply_fan_settings(priv); | |
| 2452 | + } | |
| 2175 | 2453 | switch (val) { |
| 2176 | − case 0: | |
| 2177 | − if (is_victus_s_thermal_profile()) | |
| 2178 | − hp_wmi_get_fan_count_userdefine_trigger(); | |
| 2179 | − /* 0 is no fan speed control (max), which is 1 for us */ | |
| 2180 | − return hp_wmi_fan_speed_max_set(1); | |
| 2181 | − case 2: | |
| 2182 | − /* 2 is automatic speed control, which is 0 for us */ | |
| 2183 | − if (is_victus_s_thermal_profile()) { | |
| 2184 | − hp_wmi_get_fan_count_userdefine_trigger(); | |
| 2185 | − return hp_wmi_fan_speed_max_reset(); | |
| 2186 | − } else | |
| 2187 | − return hp_wmi_fan_speed_max_set(0); | |
| 2454 | + case PWM_MODE_MAX: | |
| 2455 | + priv->mode = PWM_MODE_MAX; | |
| 2456 | + return hp_wmi_apply_fan_settings(priv); | |
| 2457 | + case PWM_MODE_MANUAL: | |
| 2458 | + if (!is_victus_s_thermal_profile()) | |
| 2459 | + return -EOPNOTSUPP; | |
| 2460 | + /* | |
| 2461 | + * When switching to manual mode, set fan speed to | |
| 2462 | + * current RPM values to ensure a smooth transition. | |
| 2463 | + */ | |
| 2464 | + rpm = hp_wmi_get_fan_speed_victus_s(channel); | |
| 2465 | + if (rpm < 0) | |
| 2466 | + return rpm; | |
| 2467 | + priv->pwm = rpm_to_pwm(rpm / 100, priv); | |
| 2468 | + priv->mode = PWM_MODE_MANUAL; | |
| 2469 | + return hp_wmi_apply_fan_settings(priv); | |
| 2470 | + case PWM_MODE_AUTO: | |
| 2471 | + priv->mode = PWM_MODE_AUTO; | |
| 2472 | + return hp_wmi_apply_fan_settings(priv); | |
| 2188 | 2473 | default: |
| 2189 | − /* we don't support manual fan speed control */ | |
| 2190 | 2474 | return -EINVAL; |
| 2191 | 2475 | } |
| 2192 | 2476 | default: |
| @@ -2196,7 +2480,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, · 3 unmodified lines | ||
| 2196 | 2480 | |
| 2197 | 2481 | static const struct hwmon_channel_info * const info[] = { |
| 2198 | 2482 | HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), |
| 2199 | − HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE), | |
| 2483 | + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT), | |
| 2200 | 2484 | NULL |
| 2201 | 2485 | }; |
| 2202 | 2486 | |
| @@ -2211,12 +2495,70 @@ static const struct hwmon_chip_info chip_info = { · 8 unmodified lines | ||
| 2211 | 2495 | .info = info, |
| 2212 | 2496 | }; |
| 2213 | 2497 | |
| 2498 | +static void hp_wmi_hwmon_keep_alive_handler(struct work_struct *work) | |
| 2499 | +{ | |
| 2500 | + struct delayed_work *dwork; | |
| 2501 | + struct hp_wmi_hwmon_priv *priv; | |
| 2502 | + | |
| 2503 | + dwork = to_delayed_work(work); | |
| 2504 | + priv = container_of(dwork, struct hp_wmi_hwmon_priv, keep_alive_dwork); | |
| 2505 | + /* | |
| 2506 | + * Re-apply the current hwmon context settings. | |
| 2507 | + * NOTE: hp_wmi_apply_fan_settings will handle the re-scheduling. | |
| 2508 | + */ | |
| 2509 | + hp_wmi_apply_fan_settings(priv); | |
| 2510 | +} | |
| 2511 | + | |
| 2512 | +static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) | |
| 2513 | +{ | |
| 2514 | + u8 fan_data[128] = { 0 }; | |
| 2515 | + struct victus_s_fan_table *fan_table; | |
| 2516 | + u8 min_rpm, max_rpm, gpu_delta; | |
| 2517 | + int ret; | |
| 2518 | + | |
| 2519 | + /* Default behaviour on hwmon init is automatic mode */ | |
| 2520 | + priv->mode = PWM_MODE_AUTO; | |
| 2521 | + | |
| 2522 | + /* Bypass all non-Victus S devices */ | |
| 2523 | + if (!is_victus_s_thermal_profile()) | |
| 2524 | + return 0; | |
| 2525 | + | |
| 2526 | + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, | |
| 2527 | + HPWMI_GM, &fan_data, 4, sizeof(fan_data)); | |
| 2528 | + if (ret) | |
| 2529 | + return ret; | |
| 2530 | + | |
| 2531 | + fan_table = (struct victus_s_fan_table *)fan_data; | |
| 2532 | + if (fan_table->header.num_entries == 0 || | |
| 2533 | + sizeof(struct victus_s_fan_table_header) + | |
| 2534 | + sizeof(struct victus_s_fan_table_entry) * fan_table->header.num_entries > sizeof(fan_data)) | |
| 2535 | + return -EINVAL; | |
| 2536 | + | |
| 2537 | + min_rpm = fan_table->entries[0].cpu_rpm; | |
| 2538 | + max_rpm = fan_table->entries[fan_table->header.num_entries - 1].cpu_rpm; | |
| 2539 | + gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm; | |
| 2540 | + priv->min_rpm = min_rpm; | |
| 2541 | + priv->max_rpm = max_rpm; | |
| 2542 | + priv->gpu_delta = gpu_delta; | |
| 2543 | + | |
| 2544 | + return 0; | |
| 2545 | +} | |
| 2546 | + | |
| 2214 | 2547 | static int hp_wmi_hwmon_init(void) |
| 2215 | 2548 | { |
| 2216 | 2549 | struct device *dev = &hp_wmi_platform_dev->dev; |
| 2550 | + struct hp_wmi_hwmon_priv *priv; | |
| 2217 | 2551 | struct device *hwmon; |
| 2552 | + int ret; | |
| 2553 | + | |
| 2554 | + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
| 2555 | + if (!priv) | |
| 2556 | + return -ENOMEM; | |
| 2218 | 2557 | |
| 2219 | − hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver, | |
| 2558 | + ret = hp_wmi_setup_fan_settings(priv); | |
| 2559 | + if (ret) | |
| 2560 | + return ret; | |
| 2561 | + hwmon = devm_hwmon_device_register_with_info(dev, "hp", priv, | |
| 2220 | 2562 | &chip_info, NULL); |
| 2221 | 2563 | |
| 2222 | 2564 | if (IS_ERR(hwmon)) { |
| @@ -2224,9 +2566,37 @@ static int hp_wmi_hwmon_init(void) · 1 unmodified line | ||
| 2224 | 2566 | return PTR_ERR(hwmon); |
| 2225 | 2567 | } |
| 2226 | 2568 | |
| 2569 | + INIT_DELAYED_WORK(&priv->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler); | |
| 2570 | + platform_set_drvdata(hp_wmi_platform_dev, priv); | |
| 2571 | + hp_wmi_apply_fan_settings(priv); | |
| 2572 | + | |
| 2227 | 2573 | return 0; |
| 2228 | 2574 | } |
| 2229 | 2575 | |
| 2576 | +static void __init setup_active_thermal_profile_params(void) | |
| 2577 | +{ | |
| 2578 | + const struct dmi_system_id *id; | |
| 2579 | + | |
| 2580 | + /* | |
| 2581 | + * Currently only victus_s devices use the | |
| 2582 | + * active_thermal_profile_params | |
| 2583 | + */ | |
| 2584 | + id = dmi_first_match(victus_s_thermal_profile_boards); | |
| 2585 | + if (id) { | |
| 2586 | + /* | |
| 2587 | + * Marking this boolean is required to ensure that | |
| 2588 | + * is_victus_s_thermal_profile() behaves like a valid | |
| 2589 | + * wrapper. | |
| 2590 | + */ | |
| 2591 | + is_victus_s_board = true; | |
| 2592 | + active_thermal_profile_params = id->driver_data; | |
| 2593 | + if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { | |
| 2594 | + pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n", | |
| 2595 | + dmi_get_system_info(DMI_BOARD_NAME)); | |
| 2596 | + } | |
| 2597 | + } | |
| 2598 | +} | |
| 2599 | + | |
| 2230 | 2600 | static int __init hp_wmi_init(void) |
| 2231 | 2601 | { |
| 2232 | 2602 | int event_capable = wmi_has_guid(HPWMI_EVENT_GUID); |
| @@ -2254,6 +2624,11 @@ static int __init hp_wmi_init(void) · 21 unmodified lines | ||
| 2254 | 2624 | goto err_destroy_input; |
| 2255 | 2625 | } |
| 2256 | 2626 | |
| 2627 | + /* | |
| 2628 | + * Setup active board's thermal profile parameters before | |
| 2629 | + * starting platform driver probe. | |
| 2630 | + */ | |
| 2631 | + setup_active_thermal_profile_params(); | |
| 2257 | 2632 | err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); |
| 2258 | 2633 | if (err) |
| 2259 | 2634 | goto err_unregister_device; |
| @@ -776,16 +776,26 @@ static inline u64 adjust_lpm_residency(struct pmc *pmc, u32 offset, · 775 unmodified lines | ||
| 776 | 776 | static int pmc_core_substate_res_show(struct seq_file *s, void *unused) |
| 777 | 777 | { |
| 778 | 778 | struct pmc_dev *pmcdev = s->private; |
| 779 | − struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; | |
| 780 | − const int lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2; | |
| 781 | − u32 offset = pmc->map->lpm_residency_offset; | |
| 782 | − int mode; | |
| 779 | + unsigned int pmc_idx; | |
| 780 | + | |
| 781 | + for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) { | |
| 782 | + int lpm_adj_x2; | |
| 783 | + struct pmc *pmc; | |
| 784 | + u32 offset; | |
| 785 | + u8 mode; | |
| 783 | 786 | |
| 784 | − seq_printf(s, "%-10s %-15s\n", "Substate", "Residency"); | |
| 787 | + pmc = pmcdev->pmcs[pmc_idx]; | |
| 788 | + if (!pmc) | |
| 789 | + continue; | |
| 790 | + | |
| 791 | + lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2; | |
| 792 | + offset = pmc->map->lpm_residency_offset; | |
| 785 | 793 | |
| 786 | − pmc_for_each_mode(mode, pmcdev) { | |
| 787 | − seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode], | |
| 788 | − adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2)); | |
| 794 | + seq_printf(s, "pmc%u %10s %15s\n", pmc_idx, "Substate", "Residency"); | |
| 795 | + pmc_for_each_mode(mode, pmc) { | |
| 796 | + seq_printf(s, "%15s %15llu\n", pmc_lpm_modes[mode], | |
| 797 | + adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2)); | |
| 798 | + } | |
| 789 | 799 | } |
| 790 | 800 | |
| 791 | 801 | return 0; |
| @@ -838,10 +848,11 @@ static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index, · 46 unmodified lines | ||
| 838 | 848 | enum header_type type) |
| 839 | 849 | { |
| 840 | 850 | struct pmc_dev *pmcdev = s->private; |
| 841 | − int mode; | |
| 851 | + struct pmc *pmc = pmcdev->pmcs[pmc_index]; | |
| 852 | + u8 mode; | |
| 842 | 853 | |
| 843 | 854 | seq_printf(s, "%40s |", "Element"); |
| 844 | − pmc_for_each_mode(mode, pmcdev) | |
| 855 | + pmc_for_each_mode(mode, pmc) | |
| 845 | 856 | seq_printf(s, " %9s |", pmc_lpm_modes[mode]); |
| 846 | 857 | |
| 847 | 858 | if (type == HEADER_STATUS) { |
| @@ -880,14 +891,14 @@ static int pmc_core_substate_blk_req_show(struct seq_file *s, void *unused) · 32 unmodified lines | ||
| 880 | 891 | const struct pmc_bit_map *map; |
| 881 | 892 | |
| 882 | 893 | for (map = maps[r_idx]; map->name; map++) { |
| 883 | − int mode; | |
| 894 | + u8 mode; | |
| 884 | 895 | |
| 885 | 896 | if (!map->blk) |
| 886 | 897 | continue; |
| 887 | 898 | |
| 888 | 899 | counter = pmc_core_reg_read(pmc, offset); |
| 889 | 900 | seq_printf(s, "pmc%u: %34s |", pmc_idx, map->name); |
| 890 | − pmc_for_each_mode(mode, pmcdev) { | |
| 901 | + pmc_for_each_mode(mode, pmc) { | |
| 891 | 902 | bool required = *lpm_req_regs & BIT(mode); |
| 892 | 903 | |
| 893 | 904 | seq_printf(s, " %9s |", required ? "Required" : " "); |
| @@ -953,14 +964,15 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) · 59 unmodified lines | ||
| 953 | 964 | u32 lpm_status; |
| 954 | 965 | u32 lpm_status_live; |
| 955 | 966 | const struct pmc_bit_map *map; |
| 956 | − int mode, i, len = 32; | |
| 967 | + int i, len = 32; | |
| 968 | + u8 mode; | |
| 957 | 969 | |
| 958 | 970 | /* |
| 959 | 971 | * Capture the requirements and create a mask so that we only |
| 960 | 972 | * show an element if it's required for at least one of the |
| 961 | 973 | * enabled low power modes |
| 962 | 974 | */ |
| 963 | − pmc_for_each_mode(mode, pmcdev) | |
| 975 | + pmc_for_each_mode(mode, pmc) | |
| 964 | 976 | req_mask |= lpm_req_regs[mp + (mode * num_maps)]; |
| 965 | 977 | |
| 966 | 978 | /* Get the last latched status for this map */ |
| @@ -986,7 +998,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) · 19 unmodified lines | ||
| 986 | 998 | seq_printf(s, "pmc%d: %34s |", pmc_idx, map[i].name); |
| 987 | 999 | |
| 988 | 1000 | /* Loop over the enabled states and display if required */ |
| 989 | − pmc_for_each_mode(mode, pmcdev) { | |
| 1001 | + pmc_for_each_mode(mode, pmc) { | |
| 990 | 1002 | bool required = lpm_req_regs[mp + (mode * num_maps)] & |
| 991 | 1003 | bit_mask; |
| 992 | 1004 | seq_printf(s, " %9s |", required ? "Required" : " "); |
| @@ -1065,7 +1077,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused) · 72 unmodified lines | ||
| 1065 | 1077 | struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; |
| 1066 | 1078 | bool c10; |
| 1067 | 1079 | u32 reg; |
| 1068 | − int mode; | |
| 1080 | + u8 mode; | |
| 1069 | 1081 | |
| 1070 | 1082 | reg = pmc_core_reg_read(pmc, pmc->map->lpm_sts_latch_en_offset); |
| 1071 | 1083 | if (reg & LPM_STS_LATCH_MODE) { |
| @@ -1076,7 +1088,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused) · 4 unmodified lines | ||
| 1076 | 1088 | c10 = true; |
| 1077 | 1089 | } |
| 1078 | 1090 | |
| 1079 | − pmc_for_each_mode(mode, pmcdev) { | |
| 1091 | + pmc_for_each_mode(mode, pmc) { | |
| 1080 | 1092 | if ((BIT(mode) & reg) && !c10) |
| 1081 | 1093 | seq_printf(s, " [%s]", pmc_lpm_modes[mode]); |
| 1082 | 1094 | else |
| @@ -1097,8 +1109,9 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, · 14 unmodified lines | ||
| 1097 | 1109 | struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; |
| 1098 | 1110 | bool clear = false, c10 = false; |
| 1099 | 1111 | unsigned char buf[8]; |
| 1100 | − int m, mode; | |
| 1112 | + int mode; | |
| 1101 | 1113 | u32 reg; |
| 1114 | + u8 m; | |
| 1102 | 1115 | |
| 1103 | 1116 | if (count > sizeof(buf) - 1) |
| 1104 | 1117 | return -EINVAL; |
| @@ -1115,7 +1128,7 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, · 10 unmodified lines | ||
| 1115 | 1128 | mode = sysfs_match_string(pmc_lpm_modes, buf); |
| 1116 | 1129 | |
| 1117 | 1130 | /* Check string matches enabled mode */ |
| 1118 | − pmc_for_each_mode(m, pmcdev) | |
| 1131 | + pmc_for_each_mode(m, pmc) | |
| 1119 | 1132 | if (mode == m) |
| 1120 | 1133 | break; |
| 1121 | 1134 | |
| @@ -1211,15 +1224,15 @@ static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order) · 89 unmodified lines | ||
| 1211 | 1224 | return true; |
| 1212 | 1225 | } |
| 1213 | 1226 | |
| 1214 | −void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev) | |
| 1227 | +static void pmc_core_pmc_get_low_power_modes(struct pmc_dev *pmcdev, struct pmc *pmc) | |
| 1215 | 1228 | { |
| 1216 | − struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; | |
| 1217 | 1229 | u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI; |
| 1218 | 1230 | u8 mode_order[LPM_MAX_NUM_MODES]; |
| 1219 | 1231 | u32 lpm_pri; |
| 1220 | 1232 | u32 lpm_en; |
| 1233 | + u8 mode; | |
| 1221 | 1234 | unsigned int i; |
| 1222 | − int mode, p; | |
| 1235 | + int p; | |
| 1223 | 1236 | |
| 1224 | 1237 | /* Use LPM Maps to indicate support for substates */ |
| 1225 | 1238 | if (!pmc->map->lpm_num_maps) |
| @@ -1230,12 +1243,11 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev) · 4 unmodified lines | ||
| 1230 | 1243 | * Lower byte is enough to cover the number of lpm modes for all |
| 1231 | 1244 | * platforms and hence mask the upper 3 bytes. |
| 1232 | 1245 | */ |
| 1233 | − pmcdev->num_lpm_modes = hweight32(lpm_en & 0xFF); | |
| 1246 | + pmc->num_lpm_modes = hweight32(lpm_en & 0xFF); | |
| 1234 | 1247 | |
| 1235 | 1248 | /* Read 32 bit LPM_PRI register */ |
| 1236 | 1249 | lpm_pri = pmc_core_reg_read(pmc, pmc->map->lpm_priority_offset); |
| 1237 | 1250 | |
| 1238 | − | |
| 1239 | 1251 | /* |
| 1240 | 1252 | * If lpm_pri value passes verification, then override the default |
| 1241 | 1253 | * modes here. Otherwise stick with the default. |
| @@ -1254,12 +1266,27 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev) · 12 unmodified lines | ||
| 1254 | 1266 | */ |
| 1255 | 1267 | i = 0; |
| 1256 | 1268 | for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) { |
| 1257 | − int mode = pri_order[p]; | |
| 1269 | + u8 mode = pri_order[p]; | |
| 1258 | 1270 | |
| 1259 | 1271 | if (!(BIT(mode) & lpm_en)) |
| 1260 | 1272 | continue; |
| 1261 | 1273 | |
| 1262 | − pmcdev->lpm_en_modes[i++] = mode; | |
| 1274 | + pmc->lpm_en_modes[i++] = mode; | |
| 1275 | + } | |
| 1276 | +} | |
| 1277 | + | |
| 1278 | +static void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev) | |
| 1279 | +{ | |
| 1280 | + unsigned int pmc_idx; | |
| 1281 | + | |
| 1282 | + for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) { | |
| 1283 | + struct pmc *pmc; | |
| 1284 | + | |
| 1285 | + pmc = pmcdev->pmcs[pmc_idx]; | |
| 1286 | + if (!pmc) | |
| 1287 | + continue; | |
| 1288 | + | |
| 1289 | + pmc_core_pmc_get_low_power_modes(pmcdev, pmc); | |
| 1263 | 1290 | } |
| 1264 | 1291 | } |
| 1265 | 1292 | |
| @@ -1490,8 +1517,8 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel · 224 unmodified lines | ||
| 1490 | 1517 | { |
| 1491 | 1518 | const u8 *lpm_indices; |
| 1492 | 1519 | int num_maps, mode_offset = 0; |
| 1493 | − int ret, mode; | |
| 1494 | − int lpm_size; | |
| 1520 | + int ret, lpm_size; | |
| 1521 | + u8 mode; | |
| 1495 | 1522 | |
| 1496 | 1523 | lpm_indices = pmc->map->lpm_reg_index; |
| 1497 | 1524 | num_maps = pmc->map->lpm_num_maps; |
| @@ -1504,7 +1531,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel · 6 unmodified lines | ||
| 1504 | 1531 | return -ENOMEM; |
| 1505 | 1532 | |
| 1506 | 1533 | mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET; |
| 1507 | − pmc_for_each_mode(mode, pmcdev) { | |
| 1534 | + pmc_for_each_mode(mode, pmc) { | |
| 1508 | 1535 | u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps); |
| 1509 | 1536 | int m; |
| 1510 | 1537 | |
| @@ -423,6 +423,8 @@ struct pmc_info { · 422 unmodified lines | ||
| 423 | 423 | * specific attributes |
| 424 | 424 | * @lpm_req_regs: List of substate requirements |
| 425 | 425 | * @ltr_ign: Holds LTR ignore data while suspended |
| 426 | + * @num_lpm_modes: Count of enabled modes | |
| 427 | + * @lpm_en_modes: Array of enabled modes from lowest to highest priority | |
| 426 | 428 | * |
| 427 | 429 | * pmc contains info about one power management controller device. |
| 428 | 430 | */ |
| @@ -432,6 +434,8 @@ struct pmc { · 3 unmodified lines | ||
| 432 | 434 | const struct pmc_reg_map *map; |
| 433 | 435 | u32 *lpm_req_regs; |
| 434 | 436 | u32 ltr_ign; |
| 437 | + u8 num_lpm_modes; | |
| 438 | + u8 lpm_en_modes[LPM_MAX_NUM_MODES]; | |
| 435 | 439 | }; |
| 436 | 440 | |
| 437 | 441 | /** |
| @@ -446,8 +450,6 @@ struct pmc { · 8 unmodified lines | ||
| 446 | 450 | * @pkgc_res_cnt: Array of PKGC residency counters |
| 447 | 451 | * @num_of_pkgc: Number of PKGC |
| 448 | 452 | * @s0ix_counter: S0ix residency (step adjusted) |
| 449 | − * @num_lpm_modes: Count of enabled modes | |
| 450 | − * @lpm_en_modes: Array of enabled modes from lowest to highest priority | |
| 451 | 453 | * @suspend: Function to perform platform specific suspend |
| 452 | 454 | * @resume: Function to perform platform specific resume |
| 453 | 455 | * |
| @@ -462,8 +464,6 @@ struct pmc_dev { · 8 unmodified lines | ||
| 462 | 464 | struct mutex lock; /* generic mutex lock for PMC Core */ |
| 463 | 465 | |
| 464 | 466 | u64 s0ix_counter; |
| 465 | − int num_lpm_modes; | |
| 466 | − int lpm_en_modes[LPM_MAX_NUM_MODES]; | |
| 467 | 467 | void (*suspend)(struct pmc_dev *pmcdev); |
| 468 | 468 | int (*resume)(struct pmc_dev *pmcdev); |
| 469 | 469 | |
| @@ -535,7 +535,6 @@ int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore); · 65 unmodified lines | ||
| 535 | 535 | |
| 536 | 536 | int pmc_core_resume_common(struct pmc_dev *pmcdev); |
| 537 | 537 | int get_primary_reg_base(struct pmc *pmc); |
| 538 | −void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev); | |
| 539 | 538 | void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids); |
| 540 | 539 | void pmc_core_set_device_d3(unsigned int device); |
| 541 | 540 | |
| @@ -563,10 +562,10 @@ int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc, · 21 unmodified lines | ||
| 563 | 562 | extern const struct file_operations pmc_core_substate_req_regs_fops; |
| 564 | 563 | extern const struct file_operations pmc_core_substate_blk_req_fops; |
| 565 | 564 | |
| 566 | −#define pmc_for_each_mode(mode, pmcdev) \ | |
| 565 | +#define pmc_for_each_mode(mode, pmc) \ | |
| 567 | 566 | for (unsigned int __i = 0, __cond; \ |
| 568 | − __cond = __i < (pmcdev)->num_lpm_modes, \ | |
| 569 | − __cond && ((mode) = (pmcdev)->lpm_en_modes[__i]), \ | |
| 567 | + __cond = __i < (pmc)->num_lpm_modes, \ | |
| 568 | + __cond && ((mode) = (pmc)->lpm_en_modes[__i]), \ | |
| 570 | 569 | __cond; \ |
| 571 | 570 | __i++) |
| 572 | 571 | |
| @@ -140,7 +140,7 @@ guid_show(struct device *dev, struct device_attribute *attr, char *buf) · 139 unmodified lines | ||
| 140 | 140 | { |
| 141 | 141 | struct intel_pmt_entry *entry = dev_get_drvdata(dev); |
| 142 | 142 | |
| 143 | − return sprintf(buf, "0x%x\n", entry->guid); | |
| 143 | + return sysfs_emit(buf, "0x%x\n", entry->guid); | |
| 144 | 144 | } |
| 145 | 145 | static DEVICE_ATTR_RO(guid); |
| 146 | 146 | |
| @@ -149,7 +149,7 @@ static ssize_t size_show(struct device *dev, struct device_attribute *attr, · 2 unmodified lines | ||
| 149 | 149 | { |
| 150 | 150 | struct intel_pmt_entry *entry = dev_get_drvdata(dev); |
| 151 | 151 | |
| 152 | − return sprintf(buf, "%zu\n", entry->size); | |
| 152 | + return sysfs_emit(buf, "%zu\n", entry->size); | |
| 153 | 153 | } |
| 154 | 154 | static DEVICE_ATTR_RO(size); |
| 155 | 155 | |
| @@ -158,7 +158,7 @@ offset_show(struct device *dev, struct device_attribute *attr, char *buf) · 2 unmodified lines | ||
| 158 | 158 | { |
| 159 | 159 | struct intel_pmt_entry *entry = dev_get_drvdata(dev); |
| 160 | 160 | |
| 161 | − return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr)); | |
| 161 | + return sysfs_emit(buf, "%lu\n", offset_in_page(entry->base_addr)); | |
| 162 | 162 | } |
| 163 | 163 | static DEVICE_ATTR_RO(offset); |
| 164 | 164 | |
| @@ -612,6 +612,9 @@ static long isst_if_core_power_state(void __user *argp) · 611 unmodified lines | ||
| 612 | 612 | return -EINVAL; |
| 613 | 613 | |
| 614 | 614 | if (core_power.get_set) { |
| 615 | + if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) | |
| 616 | + return -EPERM; | |
| 617 | + | |
| 615 | 618 | _write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET, |
| 616 | 619 | SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE) |
| 617 | 620 | _write_cp_info("cp_prio_type", core_power.priority_type, SST_CP_CONTROL_OFFSET, |
| @@ -656,7 +659,7 @@ static long isst_if_clos_param(void __user *argp) · 38 unmodified lines | ||
| 656 | 659 | return -EINVAL; |
| 657 | 660 | |
| 658 | 661 | if (clos_param.get_set) { |
| 659 | − if (power_domain_info->write_blocked) | |
| 662 | + if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) | |
| 660 | 663 | return -EPERM; |
| 661 | 664 | |
| 662 | 665 | _write_cp_info("clos.min_freq", clos_param.min_freq_mhz, |
| @@ -748,7 +751,8 @@ static long isst_if_clos_assoc(void __user *argp) · 85 unmodified lines | ||
| 748 | 751 | |
| 749 | 752 | power_domain_info = &sst_inst->power_domain_info[part][punit_id]; |
| 750 | 753 | |
| 751 | − if (assoc_cmds.get_set && power_domain_info->write_blocked) | |
| 754 | + if (assoc_cmds.get_set && (power_domain_info->write_blocked || | |
| 755 | + !capable(CAP_SYS_ADMIN))) | |
| 752 | 756 | return -EPERM; |
| 753 | 757 | |
| 754 | 758 | offset = SST_CLOS_ASSOC_0_OFFSET + |
| @@ -925,7 +929,7 @@ static int isst_if_set_perf_level(void __user *argp) · 170 unmodified lines | ||
| 925 | 929 | if (!power_domain_info) |
| 926 | 930 | return -EINVAL; |
| 927 | 931 | |
| 928 | − if (power_domain_info->write_blocked) | |
| 932 | + if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) | |
| 929 | 933 | return -EPERM; |
| 930 | 934 | |
| 931 | 935 | if (!(power_domain_info->pp_header.allowed_level_mask & BIT(perf_level.level))) |
| @@ -985,7 +989,7 @@ static int isst_if_set_perf_feature(void __user *argp) · 53 unmodified lines | ||
| 985 | 989 | if (!power_domain_info) |
| 986 | 990 | return -EINVAL; |
| 987 | 991 | |
| 988 | − if (power_domain_info->write_blocked) | |
| 992 | + if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) | |
| 989 | 993 | return -EPERM; |
| 990 | 994 | |
| 991 | 995 | _write_pp_info("perf_feature", perf_feature.feature, SST_PP_CONTROL_OFFSET, |
| @@ -1717,58 +1721,87 @@ void tpmi_sst_dev_remove(struct auxiliary_device *auxdev) · 725 unmodified lines | ||
| 1717 | 1721 | } |
| 1718 | 1722 | EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_remove, "INTEL_TPMI_SST"); |
| 1719 | 1723 | |
| 1724 | +#define SST_PP_CAP_CP_ENABLE BIT(0) | |
| 1725 | +#define SST_PP_CAP_PP_ENABLE BIT(1) | |
| 1726 | + | |
| 1720 | 1727 | void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev) |
| 1721 | 1728 | { |
| 1722 | 1729 | struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev); |
| 1723 | − struct tpmi_per_power_domain_info *power_domain_info; | |
| 1730 | + struct tpmi_per_power_domain_info *power_domain_info, *pd_info; | |
| 1724 | 1731 | struct oobmsm_plat_info *plat_info; |
| 1725 | 1732 | void __iomem *cp_base; |
| 1733 | + int num_resources, i; | |
| 1726 | 1734 | |
| 1727 | 1735 | plat_info = tpmi_get_platform_data(auxdev); |
| 1728 | 1736 | if (!plat_info) |
| 1729 | 1737 | return; |
| 1730 | 1738 | |
| 1731 | 1739 | power_domain_info = tpmi_sst->power_domain_info[plat_info->partition]; |
| 1740 | + num_resources = tpmi_sst->number_of_power_domains[plat_info->partition]; | |
| 1732 | 1741 | |
| 1733 | − cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset; | |
| 1734 | − power_domain_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET); | |
| 1742 | + for (i = 0; i < num_resources; i++) { | |
| 1743 | + pd_info = &power_domain_info[i]; | |
| 1744 | + if (!pd_info || !pd_info->sst_base) | |
| 1745 | + continue; | |
| 1746 | + | |
| 1747 | + if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE)) | |
| 1748 | + goto process_pp_suspend; | |
| 1735 | 1749 | |
| 1736 | − memcpy_fromio(power_domain_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET, | |
| 1737 | − sizeof(power_domain_info->saved_clos_configs)); | |
| 1750 | + cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset; | |
| 1751 | + pd_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET); | |
| 1752 | + memcpy_fromio(pd_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET, | |
| 1753 | + sizeof(pd_info->saved_clos_configs)); | |
| 1754 | + memcpy_fromio(pd_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET, | |
| 1755 | + sizeof(pd_info->saved_clos_assocs)); | |
| 1738 | 1756 | |
| 1739 | − memcpy_fromio(power_domain_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET, | |
| 1740 | − sizeof(power_domain_info->saved_clos_assocs)); | |
| 1757 | +process_pp_suspend: | |
| 1758 | + if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE)) | |
| 1759 | + continue; | |
| 1741 | 1760 | |
| 1742 | − power_domain_info->saved_pp_control = readq(power_domain_info->sst_base + | |
| 1743 | − power_domain_info->sst_header.pp_offset + | |
| 1744 | − SST_PP_CONTROL_OFFSET); | |
| 1761 | + pd_info->saved_pp_control = readq(pd_info->sst_base + | |
| 1762 | + pd_info->sst_header.pp_offset + | |
| 1763 | + SST_PP_CONTROL_OFFSET); | |
| 1764 | + } | |
| 1745 | 1765 | } |
| 1746 | 1766 | EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_suspend, "INTEL_TPMI_SST"); |
| 1747 | 1767 | |
| 1748 | 1768 | void tpmi_sst_dev_resume(struct auxiliary_device *auxdev) |
| 1749 | 1769 | { |
| 1750 | 1770 | struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev); |
| 1751 | − struct tpmi_per_power_domain_info *power_domain_info; | |
| 1771 | + struct tpmi_per_power_domain_info *power_domain_info, *pd_info; | |
| 1752 | 1772 | struct oobmsm_plat_info *plat_info; |
| 1753 | 1773 | void __iomem *cp_base; |
| 1774 | + int num_resources, i; | |
| 1754 | 1775 | |
| 1755 | 1776 | plat_info = tpmi_get_platform_data(auxdev); |
| 1756 | 1777 | if (!plat_info) |
| 1757 | 1778 | return; |
| 1758 | 1779 | |
| 1759 | 1780 | power_domain_info = tpmi_sst->power_domain_info[plat_info->partition]; |
| 1781 | + num_resources = tpmi_sst->number_of_power_domains[plat_info->partition]; | |
| 1782 | + | |
| 1783 | + for (i = 0; i < num_resources; i++) { | |
| 1784 | + pd_info = &power_domain_info[i]; | |
| 1785 | + if (!pd_info || !pd_info->sst_base) | |
| 1786 | + continue; | |
| 1760 | 1787 | |
| 1761 | − cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset; | |
| 1762 | − writeq(power_domain_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET); | |
| 1788 | + if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE)) | |
| 1789 | + goto process_pp_resume; | |
| 1763 | 1790 | |
| 1764 | − memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, power_domain_info->saved_clos_configs, | |
| 1765 | − sizeof(power_domain_info->saved_clos_configs)); | |
| 1791 | + cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset; | |
| 1792 | + writeq(pd_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET); | |
| 1793 | + memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, pd_info->saved_clos_configs, | |
| 1794 | + sizeof(pd_info->saved_clos_configs)); | |
| 1795 | + memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, pd_info->saved_clos_assocs, | |
| 1796 | + sizeof(pd_info->saved_clos_assocs)); | |
| 1766 | 1797 | |
| 1767 | − memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, power_domain_info->saved_clos_assocs, | |
| 1768 | − sizeof(power_domain_info->saved_clos_assocs)); | |
| 1798 | +process_pp_resume: | |
| 1799 | + if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE)) | |
| 1800 | + continue; | |
| 1769 | 1801 | |
| 1770 | − writeq(power_domain_info->saved_pp_control, power_domain_info->sst_base + | |
| 1771 | − power_domain_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET); | |
| 1802 | + writeq(pd_info->saved_pp_control, power_domain_info->sst_base + | |
| 1803 | + pd_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET); | |
| 1804 | + } | |
| 1772 | 1805 | } |
| 1773 | 1806 | EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_resume, "INTEL_TPMI_SST"); |
| 1774 | 1807 | |
| @@ -26,21 +26,21 @@ static ssize_t show_domain_id(struct kobject *kobj, struct kobj_attribute *attr, · 25 unmodified lines | ||
| 26 | 26 | { |
| 27 | 27 | struct uncore_data *data = container_of(attr, struct uncore_data, domain_id_kobj_attr); |
| 28 | 28 | |
| 29 | − return sprintf(buf, "%u\n", data->domain_id); | |
| 29 | + return sysfs_emit(buf, "%u\n", data->domain_id); | |
| 30 | 30 | } |
| 31 | 31 | |
| 32 | 32 | static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| 33 | 33 | { |
| 34 | 34 | struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_kobj_attr); |
| 35 | 35 | |
| 36 | − return sprintf(buf, "%u\n", data->cluster_id); | |
| 36 | + return sysfs_emit(buf, "%u\n", data->cluster_id); | |
| 37 | 37 | } |
| 38 | 38 | |
| 39 | 39 | static ssize_t show_package_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| 40 | 40 | { |
| 41 | 41 | struct uncore_data *data = container_of(attr, struct uncore_data, package_id_kobj_attr); |
| 42 | 42 | |
| 43 | − return sprintf(buf, "%u\n", data->package_id); | |
| 43 | + return sysfs_emit(buf, "%u\n", data->package_id); | |
| 44 | 44 | } |
| 45 | 45 | |
| 46 | 46 | #define MAX_UNCORE_AGENT_TYPES 4 |
| @@ -77,7 +77,7 @@ static ssize_t show_attr(struct uncore_data *data, char *buf, enum uncore_index · 30 unmodified lines | ||
| 77 | 77 | if (ret) |
| 78 | 78 | return ret; |
| 79 | 79 | |
| 80 | − return sprintf(buf, "%u\n", value); | |
| 80 | + return sysfs_emit(buf, "%u\n", value); | |
| 81 | 81 | } |
| 82 | 82 | |
| 83 | 83 | static ssize_t store_attr(struct uncore_data *data, const char *buf, ssize_t count, |
| @@ -269,9 +269,10 @@ int uncore_freq_add_entry(struct uncore_data *data, int cpu) · 185 unmodified lines | ||
| 269 | 269 | goto uncore_unlock; |
| 270 | 270 | |
| 271 | 271 | data->instance_id = ret; |
| 272 | − sprintf(data->name, "uncore%02d", ret); | |
| 272 | + scnprintf(data->name, sizeof(data->name), "uncore%02d", ret); | |
| 273 | 273 | } else { |
| 274 | − sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id); | |
| 274 | + scnprintf(data->name, sizeof(data->name), "package_%02d_die_%02d", | |
| 275 | + data->package_id, data->die_id); | |
| 275 | 276 | } |
| 276 | 277 | |
| 277 | 278 | uncore_read(data, &data->initial_min_freq_khz, UNCORE_INDEX_MIN_FREQ); |
| @@ -14,7 +14,6 @@ · 13 unmodified lines | ||
| 14 | 14 | * https://slimbootloader.github.io/security/firmware-update.html |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | −#include <linux/acpi.h> | |
| 18 | 17 | #include <linux/device.h> |
| 19 | 18 | #include <linux/module.h> |
| 20 | 19 | #include <linux/slab.h> |
| @@ -25,41 +24,35 @@ · 4 unmodified lines | ||
| 25 | 24 | |
| 26 | 25 | static int get_fwu_request(struct device *dev, u32 *out) |
| 27 | 26 | { |
| 28 | − union acpi_object *obj; | |
| 27 | + struct wmi_buffer buffer; | |
| 28 | + __le32 *result; | |
| 29 | + int ret; | |
| 29 | 30 | |
| 30 | − obj = wmidev_block_query(to_wmi_device(dev), 0); | |
| 31 | − if (!obj) | |
| 32 | − return -ENODEV; | |
| 31 | + ret = wmidev_query_block(to_wmi_device(dev), 0, &buffer); | |
| 32 | + if (ret < 0) | |
| 33 | + return ret; | |
| 33 | 34 | |
| 34 | − if (obj->type != ACPI_TYPE_INTEGER) { | |
| 35 | − dev_warn(dev, "wmidev_block_query returned invalid value\n"); | |
| 36 | − kfree(obj); | |
| 37 | − return -EINVAL; | |
| 35 | + if (buffer.length < sizeof(*result)) { | |
| 36 | + kfree(buffer.data); | |
| 37 | + return -ENODATA; | |
| 38 | 38 | } |
| 39 | 39 | |
| 40 | − *out = obj->integer.value; | |
| 41 | − kfree(obj); | |
| 40 | + result = buffer.data; | |
| 41 | + *out = le32_to_cpu(*result); | |
| 42 | + kfree(result); | |
| 42 | 43 | |
| 43 | 44 | return 0; |
| 44 | 45 | } |
| 45 | 46 | |
| 46 | 47 | static int set_fwu_request(struct device *dev, u32 in) |
| 47 | 48 | { |
| 48 | − struct acpi_buffer input; | |
| 49 | − acpi_status status; | |
| 50 | − u32 value; | |
| 51 | − | |
| 52 | − value = in; | |
| 53 | − input.length = sizeof(u32); | |
| 54 | − input.pointer = &value; | |
| 55 | − | |
| 56 | − status = wmidev_block_set(to_wmi_device(dev), 0, &input); | |
| 57 | − if (ACPI_FAILURE(status)) { | |
| 58 | − dev_err(dev, "wmidev_block_set failed\n"); | |
| 59 | − return -ENODEV; | |
| 60 | − } | |
| 49 | + __le32 value = cpu_to_le32(in); | |
| 50 | + struct wmi_buffer buffer = { | |
| 51 | + .length = sizeof(value), | |
| 52 | + .data = &value, | |
| 53 | + }; | |
| 61 | 54 | |
| 62 | − return 0; | |
| 55 | + return wmidev_set_block(to_wmi_device(dev), 0, &buffer); | |
| 63 | 56 | } |
| 64 | 57 | |
| 65 | 58 | static ssize_t firmware_update_request_show(struct device *dev, |
| @@ -7,7 +7,6 @@ · 6 unmodified lines | ||
| 7 | 7 | |
| 8 | 8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 9 | 9 | |
| 10 | −#include <linux/acpi.h> | |
| 11 | 10 | #include <linux/device.h> |
| 12 | 11 | #include <linux/fs.h> |
| 13 | 12 | #include <linux/hex.h> |
| @@ -24,24 +23,21 @@ static ssize_t force_power_store(struct device *dev, · 10 unmodified lines | ||
| 24 | 23 | struct device_attribute *attr, |
| 25 | 24 | const char *buf, size_t count) |
| 26 | 25 | { |
| 27 | − struct acpi_buffer input; | |
| 28 | − acpi_status status; | |
| 26 | + struct wmi_buffer buffer; | |
| 27 | + int ret; | |
| 29 | 28 | u8 mode; |
| 30 | 29 | |
| 31 | − input.length = sizeof(u8); | |
| 32 | − input.pointer = &mode; | |
| 30 | + buffer.length = sizeof(mode); | |
| 31 | + buffer.data = &mode; | |
| 32 | + | |
| 33 | 33 | mode = hex_to_bin(buf[0]); |
| 34 | − dev_dbg(dev, "force_power: storing %#x\n", mode); | |
| 35 | − if (mode == 0 || mode == 1) { | |
| 36 | − status = wmidev_evaluate_method(to_wmi_device(dev), 0, 1, &input, NULL); | |
| 37 | − if (ACPI_FAILURE(status)) { | |
| 38 | − dev_dbg(dev, "force_power: failed to evaluate ACPI method\n"); | |
| 39 | − return -ENODEV; | |
| 40 | − } | |
| 41 | − } else { | |
| 42 | − dev_dbg(dev, "force_power: unsupported mode\n"); | |
| 34 | + if (mode > 1) | |
| 43 | 35 | return -EINVAL; |
| 44 | − } | |
| 36 | + | |
| 37 | + ret = wmidev_invoke_method(to_wmi_device(dev), 0, 1, &buffer, NULL); | |
| 38 | + if (ret < 0) | |
| 39 | + return ret; | |
| 40 | + | |
| 45 | 41 | return count; |
| 46 | 42 | } |
| 47 | 43 | |
| @@ -233,7 +233,7 @@ config YT2_1380 · 232 unmodified lines | ||
| 233 | 233 | To compile this driver as a module, choose M here: the module will |
| 234 | 234 | be called lenovo-yogabook. |
| 235 | 235 | |
| 236 | −config LENOVO_WMI_DATA01 | |
| 236 | +config LENOVO_WMI_CAPDATA | |
| 237 | 237 | tristate |
| 238 | 238 | depends on ACPI_WMI |
| 239 | 239 | |
| @@ -263,8 +263,9 @@ config LENOVO_WMI_GAMEZONE · 23 unmodified lines | ||
| 263 | 263 | config LENOVO_WMI_TUNING |
| 264 | 264 | tristate "Lenovo Other Mode WMI Driver" |
| 265 | 265 | depends on ACPI_WMI |
| 266 | + select HWMON | |
| 266 | 267 | select FW_ATTR_CLASS |
| 267 | − select LENOVO_WMI_DATA01 | |
| 268 | + select LENOVO_WMI_CAPDATA | |
| 268 | 269 | select LENOVO_WMI_EVENTS |
| 269 | 270 | select LENOVO_WMI_HELPERS |
| 270 | 271 | help |
| @@ -12,7 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o · 11 unmodified lines | ||
| 12 | 12 | lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o |
| 13 | 13 | lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o |
| 14 | 14 | lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o |
| 15 | −lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) += wmi-capdata01.o | |
| 15 | +lenovo-target-$(CONFIG_LENOVO_WMI_CAPDATA) += wmi-capdata.o | |
| 16 | 16 | lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o |
| 17 | 17 | lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o |
| 18 | 18 | lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += wmi-gamezone.o |
| @@ -219,38 +219,32 @@ MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); · 218 unmodified lines | ||
| 219 | 219 | static bool allow_v4_dytc; |
| 220 | 220 | module_param(allow_v4_dytc, bool, 0444); |
| 221 | 221 | MODULE_PARM_DESC(allow_v4_dytc, |
| 222 | − "Enable DYTC version 4 platform-profile support. " | |
| 223 | − "If you need this please report this to: platform-driver-x86@vger.kernel.org"); | |
| 222 | + "Enable DYTC version 4 platform-profile support. If you need this please report this to: platform-driver-x86@vger.kernel.org"); | |
| 224 | 223 | |
| 225 | 224 | static bool hw_rfkill_switch; |
| 226 | 225 | module_param(hw_rfkill_switch, bool, 0444); |
| 227 | 226 | MODULE_PARM_DESC(hw_rfkill_switch, |
| 228 | − "Enable rfkill support for laptops with a hw on/off wifi switch/slider. " | |
| 229 | − "If you need this please report this to: platform-driver-x86@vger.kernel.org"); | |
| 227 | + "Enable rfkill support for laptops with a hw on/off wifi switch/slider. If you need this please report this to: platform-driver-x86@vger.kernel.org"); | |
| 230 | 228 | |
| 231 | 229 | static bool set_fn_lock_led; |
| 232 | 230 | module_param(set_fn_lock_led, bool, 0444); |
| 233 | 231 | MODULE_PARM_DESC(set_fn_lock_led, |
| 234 | − "Enable driver based updates of the fn-lock LED on fn-lock changes. " | |
| 235 | − "If you need this please report this to: platform-driver-x86@vger.kernel.org"); | |
| 232 | + "Enable driver based updates of the fn-lock LED on fn-lock changes. If you need this please report this to: platform-driver-x86@vger.kernel.org"); | |
| 236 | 233 | |
| 237 | 234 | static bool ctrl_ps2_aux_port; |
| 238 | 235 | module_param(ctrl_ps2_aux_port, bool, 0444); |
| 239 | 236 | MODULE_PARM_DESC(ctrl_ps2_aux_port, |
| 240 | − "Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. " | |
| 241 | − "If you need this please report this to: platform-driver-x86@vger.kernel.org"); | |
| 237 | + "Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. If you need this please report this to: platform-driver-x86@vger.kernel.org"); | |
| 242 | 238 | |
| 243 | 239 | static bool touchpad_ctrl_via_ec; |
| 244 | 240 | module_param(touchpad_ctrl_via_ec, bool, 0444); |
| 245 | 241 | MODULE_PARM_DESC(touchpad_ctrl_via_ec, |
| 246 | − "Enable registering a 'touchpad' sysfs-attribute which can be used to manually " | |
| 247 | − "tell the EC to enable/disable the touchpad. This may not work on all models."); | |
| 242 | + "Enable registering a 'touchpad' sysfs-attribute which can be used to manually tell the EC to enable/disable the touchpad. This may not work on all models."); | |
| 248 | 243 | |
| 249 | 244 | static bool ymc_ec_trigger __read_mostly; |
| 250 | 245 | module_param(ymc_ec_trigger, bool, 0444); |
| 251 | 246 | MODULE_PARM_DESC(ymc_ec_trigger, |
| 252 | − "Enable EC triggering work-around to force emitting tablet mode events. " | |
| 253 | − "If you need this please report this to: platform-driver-x86@vger.kernel.org"); | |
| 247 | + "Enable EC triggering work-around to force emitting tablet mode events. If you need this please report this to: platform-driver-x86@vger.kernel.org"); | |
| 254 | 248 | |
| 255 | 249 | /* |
| 256 | 250 | * shared data |
| @@ -1446,7 +1440,7 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv) · 1189 unmodified lines | ||
| 1446 | 1440 | if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value)) |
| 1447 | 1441 | return; |
| 1448 | 1442 | |
| 1449 | − for_each_set_bit (bit, &value, 16) { | |
| 1443 | + for_each_set_bit(bit, &value, 16) { | |
| 1450 | 1444 | switch (bit) { |
| 1451 | 1445 | case 6: /* Z570 */ |
| 1452 | 1446 | case 0: /* Z580 */ |
| @@ -1706,11 +1700,10 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv) · 253 unmodified lines | ||
| 1706 | 1700 | if (WARN_ON(priv->kbd_bl.initialized)) |
| 1707 | 1701 | return -EEXIST; |
| 1708 | 1702 | |
| 1709 | − if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) { | |
| 1703 | + if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) | |
| 1710 | 1704 | priv->kbd_bl.led.max_brightness = 2; |
| 1711 | − } else { | |
| 1705 | + else | |
| 1712 | 1706 | priv->kbd_bl.led.max_brightness = 1; |
| 1713 | − } | |
| 1714 | 1707 | |
| 1715 | 1708 | brightness = ideapad_kbd_bl_brightness_get(priv); |
| 1716 | 1709 | if (brightness < 0) |
| @@ -1752,7 +1745,7 @@ static enum led_brightness ideapad_fn_lock_led_cdev_get(struct led_classdev *led · 35 unmodified lines | ||
| 1752 | 1745 | } |
| 1753 | 1746 | |
| 1754 | 1747 | static int ideapad_fn_lock_led_cdev_set(struct led_classdev *led_cdev, |
| 1755 | − enum led_brightness brightness) | |
| 1748 | + enum led_brightness brightness) | |
| 1756 | 1749 | { |
| 1757 | 1750 | struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, fn_lock.led); |
| 1758 | 1751 | |
| @@ -1928,7 +1921,7 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) · 169 unmodified lines | ||
| 1928 | 1921 | |
| 1929 | 1922 | vpc1 = (vpc2 << 8) | vpc1; |
| 1930 | 1923 | |
| 1931 | − for_each_set_bit (bit, &vpc1, 16) { | |
| 1924 | + for_each_set_bit(bit, &vpc1, 16) { | |
| 1932 | 1925 | switch (bit) { |
| 1933 | 1926 | case 13: |
| 1934 | 1927 | case 11: |
| @@ -2142,14 +2135,14 @@ static const enum power_supply_property ideapad_power_supply_props[] = { · 207 unmodified lines | ||
| 2142 | 2135 | } |
| 2143 | 2136 | |
| 2144 | 2137 | DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v1, |
| 2145 | − (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | | |
| 2146 | − BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) | |
| 2138 | + (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | | |
| 2139 | + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) | |
| 2147 | 2140 | ); |
| 2148 | 2141 | |
| 2149 | 2142 | DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v2, |
| 2150 | − (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | | |
| 2151 | − BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) | | |
| 2152 | − BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) | |
| 2143 | + (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | | |
| 2144 | + BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) | | |
| 2145 | + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) | |
| 2153 | 2146 | ); |
| 2154 | 2147 | |
| 2155 | 2148 | static int ideapad_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) |
| @@ -36,6 +36,7 @@ · 35 unmodified lines | ||
| 36 | 36 | |
| 37 | 37 | #include <linux/acpi.h> |
| 38 | 38 | #include <linux/backlight.h> |
| 39 | +#include <linux/bitfield.h> | |
| 39 | 40 | #include <linux/bitops.h> |
| 40 | 41 | #include <linux/delay.h> |
| 41 | 42 | #include <linux/dmi.h> |
| @@ -11080,6 +11081,206 @@ static const struct attribute_group auxmac_attr_group = { · 11038 unmodified lines | ||
| 11080 | 11081 | .attrs = auxmac_attributes, |
| 11081 | 11082 | }; |
| 11082 | 11083 | |
| 11084 | +/************************************************************************* | |
| 11085 | + * HWDD subdriver, for the Lenovo Hardware Damage Detection feature. | |
| 11086 | + */ | |
| 11087 | + | |
| 11088 | +#define HWDD_GET_DMG_USBC 0x80000001 | |
| 11089 | +#define HWDD_GET_CAP 0 | |
| 11090 | +#define HWDD_NOT_SUPPORTED BIT(31) | |
| 11091 | +#define HWDD_SUPPORT_USBC BIT(0) | |
| 11092 | + | |
| 11093 | +#define PORT_STATUS GENMASK(7, 4) | |
| 11094 | +#define LID_STATUS GENMASK(11, 8) | |
| 11095 | +#define BASE_STATUS GENMASK(15, 12) | |
| 11096 | +#define POS_STATUS GENMASK(3, 2) | |
| 11097 | +#define PANEL_STATUS GENMASK(1, 0) | |
| 11098 | + | |
| 11099 | +#define PORT_DETAIL_OFFSET 16 | |
| 11100 | + | |
| 11101 | +#define PANEL_TOP 0 | |
| 11102 | +#define PANEL_BASE 1 | |
| 11103 | +#define PANEL_LEFT 2 | |
| 11104 | +#define PANEL_RIGHT 3 | |
| 11105 | + | |
| 11106 | +#define POS_LEFT 0 | |
| 11107 | +#define POS_CENTER 1 | |
| 11108 | +#define POS_RIGHT 2 | |
| 11109 | + | |
| 11110 | +#define NUM_PORTS 4 | |
| 11111 | + | |
| 11112 | +static bool hwdd_support_available; | |
| 11113 | +static bool ucdd_supported; | |
| 11114 | + | |
| 11115 | +static int hwdd_command(int command, int *output) | |
| 11116 | +{ | |
| 11117 | + acpi_handle hwdd_handle; | |
| 11118 | + | |
| 11119 | + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "HWDD", &hwdd_handle))) | |
| 11120 | + return -ENODEV; | |
| 11121 | + | |
| 11122 | + if (!acpi_evalf(hwdd_handle, output, NULL, "dd", command)) | |
| 11123 | + return -EIO; | |
| 11124 | + | |
| 11125 | + return 0; | |
| 11126 | +} | |
| 11127 | + | |
| 11128 | +static bool display_damage(char *buf, int *count, char *type, unsigned int dmg_status) | |
| 11129 | +{ | |
| 11130 | + unsigned char lid_status, base_status, port_status; | |
| 11131 | + unsigned char loc_status, pos_status, panel_status; | |
| 11132 | + bool damage_detected = false; | |
| 11133 | + int i; | |
| 11134 | + | |
| 11135 | + port_status = FIELD_GET(PORT_STATUS, dmg_status); | |
| 11136 | + lid_status = FIELD_GET(LID_STATUS, dmg_status); | |
| 11137 | + base_status = FIELD_GET(BASE_STATUS, dmg_status); | |
| 11138 | + for (i = 0; i < NUM_PORTS; i++) { | |
| 11139 | + if (!(dmg_status & BIT(i)) || !(port_status & BIT(i))) | |
| 11140 | + continue; | |
| 11141 | + | |
| 11142 | + *count += sysfs_emit_at(buf, *count, "%s: ", type); | |
| 11143 | + loc_status = (dmg_status >> (PORT_DETAIL_OFFSET + (4 * i))) & 0xF; | |
| 11144 | + pos_status = FIELD_GET(POS_STATUS, loc_status); | |
| 11145 | + panel_status = FIELD_GET(PANEL_STATUS, loc_status); | |
| 11146 | + | |
| 11147 | + if (lid_status & BIT(i)) | |
| 11148 | + *count += sysfs_emit_at(buf, *count, "Lid, "); | |
| 11149 | + if (base_status & BIT(i)) | |
| 11150 | + *count += sysfs_emit_at(buf, *count, "Base, "); | |
| 11151 | + | |
| 11152 | + switch (pos_status) { | |
| 11153 | + case PANEL_TOP: | |
| 11154 | + *count += sysfs_emit_at(buf, *count, "Top, "); | |
| 11155 | + break; | |
| 11156 | + case PANEL_BASE: | |
| 11157 | + *count += sysfs_emit_at(buf, *count, "Bottom, "); | |
| 11158 | + break; | |
| 11159 | + case PANEL_LEFT: | |
| 11160 | + *count += sysfs_emit_at(buf, *count, "Left, "); | |
| 11161 | + break; | |
| 11162 | + case PANEL_RIGHT: | |
| 11163 | + *count += sysfs_emit_at(buf, *count, "Right, "); | |
| 11164 | + break; | |
| 11165 | + default: | |
| 11166 | + pr_err("Unexpected value %d in switch statement\n", pos_status); | |
| 11167 | + } | |
| 11168 | + | |
| 11169 | + switch (panel_status) { | |
| 11170 | + case POS_LEFT: | |
| 11171 | + *count += sysfs_emit_at(buf, *count, "Left port\n"); | |
| 11172 | + break; | |
| 11173 | + case POS_CENTER: | |
| 11174 | + *count += sysfs_emit_at(buf, *count, "Center port\n"); | |
| 11175 | + break; | |
| 11176 | + case POS_RIGHT: | |
| 11177 | + *count += sysfs_emit_at(buf, *count, "Right port\n"); | |
| 11178 | + break; | |
| 11179 | + default: | |
| 11180 | + *count += sysfs_emit_at(buf, *count, "Undefined\n"); | |
| 11181 | + break; | |
| 11182 | + } | |
| 11183 | + damage_detected = true; | |
| 11184 | + } | |
| 11185 | + return damage_detected; | |
| 11186 | +} | |
| 11187 | + | |
| 11188 | +/* sysfs type-c damage detection detail */ | |
| 11189 | +static ssize_t hwdd_detail_show(struct device *dev, | |
| 11190 | + struct device_attribute *attr, | |
| 11191 | + char *buf) | |
| 11192 | +{ | |
| 11193 | + unsigned int damage_status; | |
| 11194 | + int err, count = 0; | |
| 11195 | + | |
| 11196 | + if (!ucdd_supported) | |
| 11197 | + return -ENODEV; | |
| 11198 | + | |
| 11199 | + /* Get USB TYPE-C damage status */ | |
| 11200 | + err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status); | |
| 11201 | + if (err) | |
| 11202 | + return err; | |
| 11203 | + | |
| 11204 | + if (!display_damage(buf, &count, "Type-C", damage_status)) | |
| 11205 | + count += sysfs_emit_at(buf, count, "No damage detected\n"); | |
| 11206 | + | |
| 11207 | + return count; | |
| 11208 | +} | |
| 11209 | + | |
| 11210 | +/* sysfs type-c damage detection capability */ | |
| 11211 | +static ssize_t hwdd_status_show(struct device *dev, | |
| 11212 | + struct device_attribute *attr, | |
| 11213 | + char *buf) | |
| 11214 | +{ | |
| 11215 | + unsigned int damage_status, port_status; | |
| 11216 | + int err, i; | |
| 11217 | + | |
| 11218 | + if (!ucdd_supported) | |
| 11219 | + return -ENODEV; | |
| 11220 | + | |
| 11221 | + /* Get USB TYPE-C damage status */ | |
| 11222 | + err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status); | |
| 11223 | + if (err) | |
| 11224 | + return err; | |
| 11225 | + | |
| 11226 | + port_status = FIELD_GET(PORT_STATUS, damage_status); | |
| 11227 | + for (i = 0; i < NUM_PORTS; i++) { | |
| 11228 | + if (!(damage_status & BIT(i))) | |
| 11229 | + continue; | |
| 11230 | + if (port_status & BIT(i)) | |
| 11231 | + return sysfs_emit(buf, "1\n"); | |
| 11232 | + } | |
| 11233 | + | |
| 11234 | + return sysfs_emit(buf, "0\n"); | |
| 11235 | +} | |
| 11236 | +static DEVICE_ATTR_RO(hwdd_status); | |
| 11237 | +static DEVICE_ATTR_RO(hwdd_detail); | |
| 11238 | + | |
| 11239 | +static struct attribute *hwdd_attributes[] = { | |
| 11240 | + &dev_attr_hwdd_status.attr, | |
| 11241 | + &dev_attr_hwdd_detail.attr, | |
| 11242 | + NULL | |
| 11243 | +}; | |
| 11244 | + | |
| 11245 | +static umode_t hwdd_attr_is_visible(struct kobject *kobj, | |
| 11246 | + struct attribute *attr, int n) | |
| 11247 | +{ | |
| 11248 | + return hwdd_support_available ? attr->mode : 0; | |
| 11249 | +} | |
| 11250 | + | |
| 11251 | +static const struct attribute_group hwdd_attr_group = { | |
| 11252 | + .is_visible = hwdd_attr_is_visible, | |
| 11253 | + .attrs = hwdd_attributes, | |
| 11254 | +}; | |
| 11255 | + | |
| 11256 | +static int tpacpi_hwdd_init(struct ibm_init_struct *iibm) | |
| 11257 | +{ | |
| 11258 | + int err, output; | |
| 11259 | + | |
| 11260 | + /* Below command checks the HWDD damage capability */ | |
| 11261 | + err = hwdd_command(HWDD_GET_CAP, &output); | |
| 11262 | + if (err) | |
| 11263 | + return err; | |
| 11264 | + | |
| 11265 | + if (!(output & HWDD_NOT_SUPPORTED)) | |
| 11266 | + return -ENODEV; | |
| 11267 | + | |
| 11268 | + hwdd_support_available = true; | |
| 11269 | + | |
| 11270 | + /* | |
| 11271 | + * BIT(0) is assigned to check capability of damage detection is | |
| 11272 | + * supported for USB Type-C port or not. | |
| 11273 | + */ | |
| 11274 | + if (output & HWDD_SUPPORT_USBC) | |
| 11275 | + ucdd_supported = true; | |
| 11276 | + | |
| 11277 | + return err; | |
| 11278 | +} | |
| 11279 | + | |
| 11280 | +static struct ibm_struct hwdd_driver_data = { | |
| 11281 | + .name = "hwdd", | |
| 11282 | +}; | |
| 11283 | + | |
| 11083 | 11284 | /* --------------------------------------------------------------------- */ |
| 11084 | 11285 | |
| 11085 | 11286 | static struct attribute *tpacpi_driver_attributes[] = { |
| @@ -11139,6 +11340,7 @@ static const struct attribute_group *tpacpi_groups[] = { · 53 unmodified lines | ||
| 11139 | 11340 | &kbdlang_attr_group, |
| 11140 | 11341 | &dprc_attr_group, |
| 11141 | 11342 | &auxmac_attr_group, |
| 11343 | + &hwdd_attr_group, | |
| 11142 | 11344 | NULL, |
| 11143 | 11345 | }; |
| 11144 | 11346 | |
| @@ -11752,6 +11954,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { · 607 unmodified lines | ||
| 11752 | 11954 | .init = auxmac_init, |
| 11753 | 11955 | .data = &auxmac_data, |
| 11754 | 11956 | }, |
| 11957 | + { | |
| 11958 | + .init = tpacpi_hwdd_init, | |
| 11959 | + .data = &hwdd_driver_data, | |
| 11960 | + }, | |
| 11755 | 11961 | }; |
| 11756 | 11962 | |
| 11757 | 11963 | static int __init set_ibm_param(const char *val, const struct kernel_param *kp) |
| @@ -0,0 +1,829 @@ | ||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | |
| 2 | +/* | |
| 3 | + * Lenovo Capability Data WMI Data Block driver. | |
| 4 | + * | |
| 5 | + * Lenovo Capability Data provides information on tunable attributes used by | |
| 6 | + * the "Other Mode" WMI interface. | |
| 7 | + * | |
| 8 | + * Capability Data 00 includes if the attribute is supported by the hardware, | |
| 9 | + * and the default_value. All attributes are independent of thermal modes. | |
| 10 | + * | |
| 11 | + * Capability Data 01 includes if the attribute is supported by the hardware, | |
| 12 | + * and the default_value, max_value, min_value, and step increment. Each | |
| 13 | + * attribute has multiple pages, one for each of the thermal modes managed by | |
| 14 | + * the Gamezone interface. | |
| 15 | + * | |
| 16 | + * Fan Test Data includes the max/min fan speed RPM for each fan. This is | |
| 17 | + * reference data for self-test. If the fan is in good condition, it is capable | |
| 18 | + * to spin faster than max RPM or slower than min RPM. | |
| 19 | + * | |
| 20 | + * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> | |
| 21 | + * - Initial implementation (formerly named lenovo-wmi-capdata01) | |
| 22 | + * | |
| 23 | + * Copyright (C) 2025 Rong Zhang <i@rong.moe> | |
| 24 | + * - Unified implementation | |
| 25 | + */ | |
| 26 | + | |
| 27 | +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
| 28 | + | |
| 29 | +#include <linux/acpi.h> | |
| 30 | +#include <linux/bitfield.h> | |
| 31 | +#include <linux/bug.h> | |
| 32 | +#include <linux/cleanup.h> | |
| 33 | +#include <linux/component.h> | |
| 34 | +#include <linux/container_of.h> | |
| 35 | +#include <linux/device.h> | |
| 36 | +#include <linux/dev_printk.h> | |
| 37 | +#include <linux/err.h> | |
| 38 | +#include <linux/export.h> | |
| 39 | +#include <linux/gfp_types.h> | |
| 40 | +#include <linux/limits.h> | |
| 41 | +#include <linux/module.h> | |
| 42 | +#include <linux/mutex.h> | |
| 43 | +#include <linux/mutex_types.h> | |
| 44 | +#include <linux/notifier.h> | |
| 45 | +#include <linux/overflow.h> | |
| 46 | +#include <linux/stddef.h> | |
| 47 | +#include <linux/types.h> | |
| 48 | +#include <linux/wmi.h> | |
| 49 | + | |
| 50 | +#include "wmi-capdata.h" | |
| 51 | + | |
| 52 | +#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E" | |
| 53 | +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" | |
| 54 | +#define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21" | |
| 55 | + | |
| 56 | +#define ACPI_AC_CLASS "ac_adapter" | |
| 57 | +#define ACPI_AC_NOTIFY_STATUS 0x80 | |
| 58 | + | |
| 59 | +#define LWMI_FEATURE_ID_FAN_TEST 0x05 | |
| 60 | + | |
| 61 | +#define LWMI_ATTR_ID_FAN_TEST \ | |
| 62 | + (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ | |
| 63 | + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST)) | |
| 64 | + | |
| 65 | +enum lwmi_cd_type { | |
| 66 | + LENOVO_CAPABILITY_DATA_00, | |
| 67 | + LENOVO_CAPABILITY_DATA_01, | |
| 68 | + LENOVO_FAN_TEST_DATA, | |
| 69 | + CD_TYPE_NONE = -1, | |
| 70 | +}; | |
| 71 | + | |
| 72 | +#define LWMI_CD_TABLE_ITEM(_type) \ | |
| 73 | + [_type] = { \ | |
| 74 | + .name = #_type, \ | |
| 75 | + .type = _type, \ | |
| 76 | + } | |
| 77 | + | |
| 78 | +static const struct lwmi_cd_info { | |
| 79 | + const char *name; | |
| 80 | + enum lwmi_cd_type type; | |
| 81 | +} lwmi_cd_table[] = { | |
| 82 | + LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00), | |
| 83 | + LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01), | |
| 84 | + LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA), | |
| 85 | +}; | |
| 86 | + | |
| 87 | +struct lwmi_cd_priv { | |
| 88 | + struct notifier_block acpi_nb; /* ACPI events */ | |
| 89 | + struct wmi_device *wdev; | |
| 90 | + struct cd_list *list; | |
| 91 | + | |
| 92 | + /* | |
| 93 | + * A capdata device may be a component master of another capdata device. | |
| 94 | + * E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan | |
| 95 | + * |- master |- component | |
| 96 | + * |- sub-master | |
| 97 | + * |- sub-component | |
| 98 | + */ | |
| 99 | + struct lwmi_cd_sub_master_priv { | |
| 100 | + struct device *master_dev; | |
| 101 | + cd_list_cb_t master_cb; | |
| 102 | + struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */ | |
| 103 | + bool registered; /* Has the sub-master been registered? */ | |
| 104 | + } *sub_master; | |
| 105 | +}; | |
| 106 | + | |
| 107 | +struct cd_list { | |
| 108 | + struct mutex list_mutex; /* list R/W mutex */ | |
| 109 | + enum lwmi_cd_type type; | |
| 110 | + u8 count; | |
| 111 | + | |
| 112 | + union { | |
| 113 | + DECLARE_FLEX_ARRAY(struct capdata00, cd00); | |
| 114 | + DECLARE_FLEX_ARRAY(struct capdata01, cd01); | |
| 115 | + DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan); | |
| 116 | + }; | |
| 117 | +}; | |
| 118 | + | |
| 119 | +static struct wmi_driver lwmi_cd_driver; | |
| 120 | + | |
| 121 | +/** | |
| 122 | + * lwmi_cd_match() - Match rule for the master driver. | |
| 123 | + * @dev: Pointer to the capability data parent device. | |
| 124 | + * @type: Pointer to capability data type (enum lwmi_cd_type *) to match. | |
| 125 | + * | |
| 126 | + * Return: int. | |
| 127 | + */ | |
| 128 | +static int lwmi_cd_match(struct device *dev, void *type) | |
| 129 | +{ | |
| 130 | + struct lwmi_cd_priv *priv; | |
| 131 | + | |
| 132 | + if (dev->driver != &lwmi_cd_driver.driver) | |
| 133 | + return false; | |
| 134 | + | |
| 135 | + priv = dev_get_drvdata(dev); | |
| 136 | + return priv->list->type == *(enum lwmi_cd_type *)type; | |
| 137 | +} | |
| 138 | + | |
| 139 | +/** | |
| 140 | + * lwmi_cd_match_add_all() - Add all match rule for the master driver. | |
| 141 | + * @master: Pointer to the master device. | |
| 142 | + * @matchptr: Pointer to the returned component_match pointer. | |
| 143 | + * | |
| 144 | + * Adds all component matches to the list stored in @matchptr for the @master | |
| 145 | + * device. @matchptr must be initialized to NULL. | |
| 146 | + */ | |
| 147 | +void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr) | |
| 148 | +{ | |
| 149 | + int i; | |
| 150 | + | |
| 151 | + if (WARN_ON(*matchptr)) | |
| 152 | + return; | |
| 153 | + | |
| 154 | + for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { | |
| 155 | + /* Skip sub-components. */ | |
| 156 | + if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA) | |
| 157 | + continue; | |
| 158 | + | |
| 159 | + component_match_add(master, matchptr, lwmi_cd_match, | |
| 160 | + (void *)&lwmi_cd_table[i].type); | |
| 161 | + if (IS_ERR(*matchptr)) | |
| 162 | + return; | |
| 163 | + } | |
| 164 | +} | |
| 165 | +EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA"); | |
| 166 | + | |
| 167 | +/** | |
| 168 | + * lwmi_cd_call_master_cb() - Call the master callback for the sub-component. | |
| 169 | + * @priv: Pointer to the capability data private data. | |
| 170 | + * | |
| 171 | + * Call the master callback and pass the sub-component list to it if the | |
| 172 | + * dependency chain (master <-> sub-master <-> sub-component) is complete. | |
| 173 | + */ | |
| 174 | +static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv) | |
| 175 | +{ | |
| 176 | + struct cd_list *sub_component_list = priv->sub_master->sub_component_list; | |
| 177 | + | |
| 178 | + /* | |
| 179 | + * Call the callback only if the dependency chain is ready: | |
| 180 | + * - Binding between master and sub-master: fills master_dev and master_cb | |
| 181 | + * - Binding between sub-master and sub-component: fills sub_component_list | |
| 182 | + * | |
| 183 | + * If a binding has been unbound before the other binding is bound, the | |
| 184 | + * corresponding members filled by the former are guaranteed to be cleared. | |
| 185 | + * | |
| 186 | + * This function is only called in bind callbacks, and the component | |
| 187 | + * framework guarantees bind/unbind callbacks may never execute | |
| 188 | + * simultaneously, which implies that it's impossible to have a race | |
| 189 | + * condition. | |
| 190 | + * | |
| 191 | + * Hence, this check is sufficient to ensure that the callback is called | |
| 192 | + * at most once and with the correct state, without relying on a specific | |
| 193 | + * sequence of binding establishment. | |
| 194 | + */ | |
| 195 | + if (!sub_component_list || | |
| 196 | + !priv->sub_master->master_dev || | |
| 197 | + !priv->sub_master->master_cb) | |
| 198 | + return; | |
| 199 | + | |
| 200 | + if (PTR_ERR(sub_component_list) == -ENODEV) | |
| 201 | + sub_component_list = NULL; | |
| 202 | + else if (WARN_ON(IS_ERR(sub_component_list))) | |
| 203 | + return; | |
| 204 | + | |
| 205 | + priv->sub_master->master_cb(priv->sub_master->master_dev, | |
| 206 | + sub_component_list); | |
| 207 | + | |
| 208 | + /* | |
| 209 | + * Userspace may unbind a device from its driver and bind it again | |
| 210 | + * through sysfs. Let's call this operation "reprobe" to distinguish it | |
| 211 | + * from component "rebind". | |
| 212 | + * | |
| 213 | + * When reprobing capdata00/01 or the master device, the master device | |
| 214 | + * is unbound from us with appropriate cleanup before we bind to it and | |
| 215 | + * call master_cb. Everything is fine in this case. | |
| 216 | + * | |
| 217 | + * When reprobing capdata_fan, the master device has never been unbound | |
| 218 | + * from us (hence no cleanup is done)[1], but we call master_cb the | |
| 219 | + * second time. To solve this issue, we clear master_cb and master_dev | |
| 220 | + * so we won't call master_cb twice while a binding is still complete. | |
| 221 | + * | |
| 222 | + * Note that we can't clear sub_component_list, otherwise reprobing | |
| 223 | + * capdata01 or the master device causes master_cb to be never called | |
| 224 | + * after we rebind to the master device. | |
| 225 | + * | |
| 226 | + * [1]: The master device does not need capdata_fan in run time, so | |
| 227 | + * losing capdata_fan will not break the binding to the master device. | |
| 228 | + */ | |
| 229 | + priv->sub_master->master_cb = NULL; | |
| 230 | + priv->sub_master->master_dev = NULL; | |
| 231 | +} | |
| 232 | + | |
| 233 | +/** | |
| 234 | + * lwmi_cd_component_bind() - Bind component to master device. | |
| 235 | + * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. | |
| 236 | + * @om_dev: Pointer to the lenovo-wmi-other driver parent device. | |
| 237 | + * @data: lwmi_cd_binder object pointer used to return the capability data. | |
| 238 | + * | |
| 239 | + * On lenovo-wmi-other's master bind, provide a pointer to the local capdata | |
| 240 | + * list. This is used to call lwmi_cd*_get_data to look up attribute data | |
| 241 | + * from the lenovo-wmi-other driver. | |
| 242 | + * | |
| 243 | + * If cd_dev is a sub-master, try to call the master callback. | |
| 244 | + * | |
| 245 | + * Return: 0 | |
| 246 | + */ | |
| 247 | +static int lwmi_cd_component_bind(struct device *cd_dev, | |
| 248 | + struct device *om_dev, void *data) | |
| 249 | +{ | |
| 250 | + struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev); | |
| 251 | + struct lwmi_cd_binder *binder = data; | |
| 252 | + | |
| 253 | + switch (priv->list->type) { | |
| 254 | + case LENOVO_CAPABILITY_DATA_00: | |
| 255 | + binder->cd00_list = priv->list; | |
| 256 | + | |
| 257 | + priv->sub_master->master_dev = om_dev; | |
| 258 | + priv->sub_master->master_cb = binder->cd_fan_list_cb; | |
| 259 | + lwmi_cd_call_master_cb(priv); | |
| 260 | + | |
| 261 | + break; | |
| 262 | + case LENOVO_CAPABILITY_DATA_01: | |
| 263 | + binder->cd01_list = priv->list; | |
| 264 | + break; | |
| 265 | + default: | |
| 266 | + return -EINVAL; | |
| 267 | + } | |
| 268 | + | |
| 269 | + return 0; | |
| 270 | +} | |
| 271 | + | |
| 272 | +/** | |
| 273 | + * lwmi_cd_component_unbind() - Unbind component to master device. | |
| 274 | + * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. | |
| 275 | + * @om_dev: Pointer to the lenovo-wmi-other driver parent device. | |
| 276 | + * @data: Unused. | |
| 277 | + * | |
| 278 | + * If cd_dev is a sub-master, clear the collected data from the master device to | |
| 279 | + * prevent the binding establishment between the sub-master and the sub- | |
| 280 | + * component (if it's about to happen) from calling the master callback. | |
| 281 | + */ | |
| 282 | +static void lwmi_cd_component_unbind(struct device *cd_dev, | |
| 283 | + struct device *om_dev, void *data) | |
| 284 | +{ | |
| 285 | + struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev); | |
| 286 | + | |
| 287 | + switch (priv->list->type) { | |
| 288 | + case LENOVO_CAPABILITY_DATA_00: | |
| 289 | + priv->sub_master->master_dev = NULL; | |
| 290 | + priv->sub_master->master_cb = NULL; | |
| 291 | + return; | |
| 292 | + default: | |
| 293 | + return; | |
| 294 | + } | |
| 295 | +} | |
| 296 | + | |
| 297 | +static const struct component_ops lwmi_cd_component_ops = { | |
| 298 | + .bind = lwmi_cd_component_bind, | |
| 299 | + .unbind = lwmi_cd_component_unbind, | |
| 300 | +}; | |
| 301 | + | |
| 302 | +/** | |
| 303 | + * lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device | |
| 304 | + * @dev: The sub-master capdata basic device. | |
| 305 | + * | |
| 306 | + * Call component_bind_all to bind the sub-component device to the sub-master | |
| 307 | + * device. On success, collect the pointer to the sub-component list and try | |
| 308 | + * to call the master callback. | |
| 309 | + * | |
| 310 | + * Return: 0 on success, or an error code. | |
| 311 | + */ | |
| 312 | +static int lwmi_cd_sub_master_bind(struct device *dev) | |
| 313 | +{ | |
| 314 | + struct lwmi_cd_priv *priv = dev_get_drvdata(dev); | |
| 315 | + struct cd_list *sub_component_list; | |
| 316 | + int ret; | |
| 317 | + | |
| 318 | + ret = component_bind_all(dev, &sub_component_list); | |
| 319 | + if (ret) | |
| 320 | + return ret; | |
| 321 | + | |
| 322 | + priv->sub_master->sub_component_list = sub_component_list; | |
| 323 | + lwmi_cd_call_master_cb(priv); | |
| 324 | + | |
| 325 | + return 0; | |
| 326 | +} | |
| 327 | + | |
| 328 | +/** | |
| 329 | + * lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device | |
| 330 | + * @dev: The sub-master capdata basic device | |
| 331 | + * | |
| 332 | + * Clear the collected pointer to the sub-component list to prevent the binding | |
| 333 | + * establishment between the sub-master and the sub-component (if it's about to | |
| 334 | + * happen) from calling the master callback. Then, call component_unbind_all to | |
| 335 | + * unbind the sub-component device from the sub-master device. | |
| 336 | + */ | |
| 337 | +static void lwmi_cd_sub_master_unbind(struct device *dev) | |
| 338 | +{ | |
| 339 | + struct lwmi_cd_priv *priv = dev_get_drvdata(dev); | |
| 340 | + | |
| 341 | + priv->sub_master->sub_component_list = NULL; | |
| 342 | + | |
| 343 | + component_unbind_all(dev, NULL); | |
| 344 | +} | |
| 345 | + | |
| 346 | +static const struct component_master_ops lwmi_cd_sub_master_ops = { | |
| 347 | + .bind = lwmi_cd_sub_master_bind, | |
| 348 | + .unbind = lwmi_cd_sub_master_unbind, | |
| 349 | +}; | |
| 350 | + | |
| 351 | +/** | |
| 352 | + * lwmi_cd_sub_master_add() - Register a sub-master with its sub-component | |
| 353 | + * @priv: Pointer to the sub-master capdata device private data. | |
| 354 | + * @sub_component_type: Type of the sub-component. | |
| 355 | + * | |
| 356 | + * Match the sub-component type and register the current capdata device as a | |
| 357 | + * sub-master. If the given sub-component type is CD_TYPE_NONE, mark the sub- | |
| 358 | + * component as non-existent without registering sub-master. | |
| 359 | + * | |
| 360 | + * Return: 0 on success, or an error code. | |
| 361 | + */ | |
| 362 | +static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv, | |
| 363 | + enum lwmi_cd_type sub_component_type) | |
| 364 | +{ | |
| 365 | + struct component_match *master_match = NULL; | |
| 366 | + int ret; | |
| 367 | + | |
| 368 | + priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL); | |
| 369 | + if (!priv->sub_master) | |
| 370 | + return -ENOMEM; | |
| 371 | + | |
| 372 | + if (sub_component_type == CD_TYPE_NONE) { | |
| 373 | + /* The master callback will be called with NULL on bind. */ | |
| 374 | + priv->sub_master->sub_component_list = ERR_PTR(-ENODEV); | |
| 375 | + priv->sub_master->registered = false; | |
| 376 | + return 0; | |
| 377 | + } | |
| 378 | + | |
| 379 | + /* | |
| 380 | + * lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack | |
| 381 | + * data cannot be used here. Steal one from lwmi_cd_table. | |
| 382 | + */ | |
| 383 | + component_match_add(&priv->wdev->dev, &master_match, lwmi_cd_match, | |
| 384 | + (void *)&lwmi_cd_table[sub_component_type].type); | |
| 385 | + if (IS_ERR(master_match)) | |
| 386 | + return PTR_ERR(master_match); | |
| 387 | + | |
| 388 | + ret = component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_master_ops, | |
| 389 | + master_match); | |
| 390 | + if (ret) | |
| 391 | + return ret; | |
| 392 | + | |
| 393 | + priv->sub_master->registered = true; | |
| 394 | + return 0; | |
| 395 | +} | |
| 396 | + | |
| 397 | +/** | |
| 398 | + * lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered | |
| 399 | + * @priv: Pointer to the sub-master capdata device private data. | |
| 400 | + */ | |
| 401 | +static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv) | |
| 402 | +{ | |
| 403 | + if (!priv->sub_master->registered) | |
| 404 | + return; | |
| 405 | + | |
| 406 | + component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops); | |
| 407 | + priv->sub_master->registered = false; | |
| 408 | +} | |
| 409 | + | |
| 410 | +/** | |
| 411 | + * lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device. | |
| 412 | + * @sc_dev: Pointer to the sub-component capdata parent device. | |
| 413 | + * @sm_dev: Pointer to the sub-master capdata parent device. | |
| 414 | + * @data: Pointer used to return the capability data list pointer. | |
| 415 | + * | |
| 416 | + * On sub-master's bind, provide a pointer to the local capdata list. | |
| 417 | + * This is used by the sub-master to call the master callback. | |
| 418 | + * | |
| 419 | + * Return: 0 | |
| 420 | + */ | |
| 421 | +static int lwmi_cd_sub_component_bind(struct device *sc_dev, | |
| 422 | + struct device *sm_dev, void *data) | |
| 423 | +{ | |
| 424 | + struct lwmi_cd_priv *priv = dev_get_drvdata(sc_dev); | |
| 425 | + struct cd_list **listp = data; | |
| 426 | + | |
| 427 | + *listp = priv->list; | |
| 428 | + | |
| 429 | + return 0; | |
| 430 | +} | |
| 431 | + | |
| 432 | +static const struct component_ops lwmi_cd_sub_component_ops = { | |
| 433 | + .bind = lwmi_cd_sub_component_bind, | |
| 434 | +}; | |
| 435 | + | |
| 436 | +/* | |
| 437 | + * lwmi_cd*_get_data - Get the data of the specified attribute | |
| 438 | + * @list: The lenovo-wmi-capdata pointer to its cd_list struct. | |
| 439 | + * @attribute_id: The capdata attribute ID to be found. | |
| 440 | + * @output: Pointer to a capdata* struct to return the data. | |
| 441 | + * | |
| 442 | + * Retrieves the capability data struct pointer for the given | |
| 443 | + * attribute. | |
| 444 | + * | |
| 445 | + * Return: 0 on success, or -EINVAL. | |
| 446 | + */ | |
| 447 | +#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \ | |
| 448 | + int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output) \ | |
| 449 | + { \ | |
| 450 | + u8 idx; \ | |
| 451 | + \ | |
| 452 | + if (WARN_ON(list->type != _cd_type)) \ | |
| 453 | + return -EINVAL; \ | |
| 454 | + \ | |
| 455 | + guard(mutex)(&list->list_mutex); \ | |
| 456 | + for (idx = 0; idx < list->count; idx++) { \ | |
| 457 | + if (list->_cdxx[idx].id != attribute_id) \ | |
| 458 | + continue; \ | |
| 459 | + memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx])); \ | |
| 460 | + return 0; \ | |
| 461 | + } \ | |
| 462 | + return -EINVAL; \ | |
| 463 | + } | |
| 464 | + | |
| 465 | +DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00); | |
| 466 | +EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA"); | |
| 467 | + | |
| 468 | +DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); | |
| 469 | +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA"); | |
| 470 | + | |
| 471 | +DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan); | |
| 472 | +EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA"); | |
| 473 | + | |
| 474 | +/** | |
| 475 | + * lwmi_cd_cache() - Cache all WMI data block information | |
| 476 | + * @priv: lenovo-wmi-capdata driver data. | |
| 477 | + * | |
| 478 | + * Loop through each WMI data block and cache the data. | |
| 479 | + * | |
| 480 | + * Return: 0 on success, or an error. | |
| 481 | + */ | |
| 482 | +static int lwmi_cd_cache(struct lwmi_cd_priv *priv) | |
| 483 | +{ | |
| 484 | + size_t size; | |
| 485 | + int idx; | |
| 486 | + void *p; | |
| 487 | + | |
| 488 | + switch (priv->list->type) { | |
| 489 | + case LENOVO_CAPABILITY_DATA_00: | |
| 490 | + p = &priv->list->cd00[0]; | |
| 491 | + size = sizeof(priv->list->cd00[0]); | |
| 492 | + break; | |
| 493 | + case LENOVO_CAPABILITY_DATA_01: | |
| 494 | + p = &priv->list->cd01[0]; | |
| 495 | + size = sizeof(priv->list->cd01[0]); | |
| 496 | + break; | |
| 497 | + case LENOVO_FAN_TEST_DATA: | |
| 498 | + /* Done by lwmi_cd_alloc() => lwmi_cd_fan_list_alloc_cache(). */ | |
| 499 | + return 0; | |
| 500 | + default: | |
| 501 | + return -EINVAL; | |
| 502 | + } | |
| 503 | + | |
| 504 | + guard(mutex)(&priv->list->list_mutex); | |
| 505 | + for (idx = 0; idx < priv->list->count; idx++, p += size) { | |
| 506 | + union acpi_object *ret_obj __free(kfree) = NULL; | |
| 507 | + | |
| 508 | + ret_obj = wmidev_block_query(priv->wdev, idx); | |
| 509 | + if (!ret_obj) | |
| 510 | + return -ENODEV; | |
| 511 | + | |
| 512 | + if (ret_obj->type != ACPI_TYPE_BUFFER || | |
| 513 | + ret_obj->buffer.length < size) | |
| 514 | + continue; | |
| 515 | + | |
| 516 | + memcpy(p, ret_obj->buffer.pointer, size); | |
| 517 | + } | |
| 518 | + | |
| 519 | + return 0; | |
| 520 | +} | |
| 521 | + | |
| 522 | +/** | |
| 523 | + * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list | |
| 524 | + * @priv: lenovo-wmi-capdata driver data. | |
| 525 | + * @listptr: Pointer to returned cd_list pointer. | |
| 526 | + * | |
| 527 | + * Return: count of fans found, or an error. | |
| 528 | + */ | |
| 529 | +static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr) | |
| 530 | +{ | |
| 531 | + struct cd_list *list; | |
| 532 | + size_t size; | |
| 533 | + u32 count; | |
| 534 | + int idx; | |
| 535 | + | |
| 536 | + /* Emit unaligned access to u8 buffer with __packed. */ | |
| 537 | + struct cd_fan_block { | |
| 538 | + u32 nr; | |
| 539 | + u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */ | |
| 540 | + } __packed * block; | |
| 541 | + | |
| 542 | + union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0); | |
| 543 | + if (!ret_obj) | |
| 544 | + return -ENODEV; | |
| 545 | + | |
| 546 | + if (ret_obj->type == ACPI_TYPE_BUFFER) { | |
| 547 | + block = (struct cd_fan_block *)ret_obj->buffer.pointer; | |
| 548 | + size = ret_obj->buffer.length; | |
| 549 | + | |
| 550 | + count = size >= sizeof(*block) ? block->nr : 0; | |
| 551 | + if (size < struct_size(block, data, count * 3)) { | |
| 552 | + dev_warn(&priv->wdev->dev, | |
| 553 | + "incomplete fan test data block: %zu < %zu, ignoring\n", | |
| 554 | + size, struct_size(block, data, count * 3)); | |
| 555 | + count = 0; | |
| 556 | + } else if (count > U8_MAX) { | |
| 557 | + dev_warn(&priv->wdev->dev, | |
| 558 | + "too many fans reported: %u > %u, truncating\n", | |
| 559 | + count, U8_MAX); | |
| 560 | + count = U8_MAX; | |
| 561 | + } | |
| 562 | + } else { | |
| 563 | + /* | |
| 564 | + * This is usually caused by a dummy ACPI method. Do not return an error | |
| 565 | + * as failing to probe this device will result in sub-master device being | |
| 566 | + * unbound. This behavior aligns with lwmi_cd_cache(). | |
| 567 | + */ | |
| 568 | + count = 0; | |
| 569 | + } | |
| 570 | + | |
| 571 | + list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL); | |
| 572 | + if (!list) | |
| 573 | + return -ENOMEM; | |
| 574 | + | |
| 575 | + for (idx = 0; idx < count; idx++) { | |
| 576 | + /* Do not calculate array index using count, as it may be truncated. */ | |
| 577 | + list->cd_fan[idx] = (struct capdata_fan) { | |
| 578 | + .id = block->data[idx], | |
| 579 | + .max_rpm = block->data[idx + block->nr], | |
| 580 | + .min_rpm = block->data[idx + (2 * block->nr)], | |
| 581 | + }; | |
| 582 | + } | |
| 583 | + | |
| 584 | + *listptr = list; | |
| 585 | + return count; | |
| 586 | +} | |
| 587 | + | |
| 588 | +/** | |
| 589 | + * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata | |
| 590 | + * @priv: lenovo-wmi-capdata driver data. | |
| 591 | + * @type: The type of capability data. | |
| 592 | + * | |
| 593 | + * Allocate a cd_list struct large enough to contain data from all WMI data | |
| 594 | + * blocks provided by the interface. | |
| 595 | + * | |
| 596 | + * Return: 0 on success, or an error. | |
| 597 | + */ | |
| 598 | +static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) | |
| 599 | +{ | |
| 600 | + struct cd_list *list; | |
| 601 | + size_t list_size; | |
| 602 | + int count, ret; | |
| 603 | + | |
| 604 | + count = wmidev_instance_count(priv->wdev); | |
| 605 | + | |
| 606 | + switch (type) { | |
| 607 | + case LENOVO_CAPABILITY_DATA_00: | |
| 608 | + list_size = struct_size(list, cd00, count); | |
| 609 | + break; | |
| 610 | + case LENOVO_CAPABILITY_DATA_01: | |
| 611 | + list_size = struct_size(list, cd01, count); | |
| 612 | + break; | |
| 613 | + case LENOVO_FAN_TEST_DATA: | |
| 614 | + count = lwmi_cd_fan_list_alloc_cache(priv, &list); | |
| 615 | + if (count < 0) | |
| 616 | + return count; | |
| 617 | + | |
| 618 | + goto got_list; | |
| 619 | + default: | |
| 620 | + return -EINVAL; | |
| 621 | + } | |
| 622 | + | |
| 623 | + list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL); | |
| 624 | + if (!list) | |
| 625 | + return -ENOMEM; | |
| 626 | + | |
| 627 | +got_list: | |
| 628 | + ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex); | |
| 629 | + if (ret) | |
| 630 | + return ret; | |
| 631 | + | |
| 632 | + list->type = type; | |
| 633 | + list->count = count; | |
| 634 | + priv->list = list; | |
| 635 | + | |
| 636 | + return 0; | |
| 637 | +} | |
| 638 | + | |
| 639 | +/** | |
| 640 | + * lwmi_cd_setup() - Cache all WMI data block information | |
| 641 | + * @priv: lenovo-wmi-capdata driver data. | |
| 642 | + * @type: The type of capability data. | |
| 643 | + * | |
| 644 | + * Allocate a cd_list struct large enough to contain data from all WMI data | |
| 645 | + * blocks provided by the interface. Then loop through each data block and | |
| 646 | + * cache the data. | |
| 647 | + * | |
| 648 | + * Return: 0 on success, or an error code. | |
| 649 | + */ | |
| 650 | +static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) | |
| 651 | +{ | |
| 652 | + int ret; | |
| 653 | + | |
| 654 | + ret = lwmi_cd_alloc(priv, type); | |
| 655 | + if (ret) | |
| 656 | + return ret; | |
| 657 | + | |
| 658 | + return lwmi_cd_cache(priv); | |
| 659 | +} | |
| 660 | + | |
| 661 | +/** | |
| 662 | + * lwmi_cd01_notifier_call() - Call method for cd01 notifier. | |
| 663 | + * block call chain. | |
| 664 | + * @nb: The notifier_block registered to lenovo-wmi-events driver. | |
| 665 | + * @action: Unused. | |
| 666 | + * @data: The ACPI event. | |
| 667 | + * | |
| 668 | + * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile | |
| 669 | + * of a change. | |
| 670 | + * | |
| 671 | + * Return: notifier_block status. | |
| 672 | + */ | |
| 673 | +static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action, | |
| 674 | + void *data) | |
| 675 | +{ | |
| 676 | + struct acpi_bus_event *event = data; | |
| 677 | + struct lwmi_cd_priv *priv; | |
| 678 | + int ret; | |
| 679 | + | |
| 680 | + if (strcmp(event->device_class, ACPI_AC_CLASS) != 0) | |
| 681 | + return NOTIFY_DONE; | |
| 682 | + | |
| 683 | + priv = container_of(nb, struct lwmi_cd_priv, acpi_nb); | |
| 684 | + | |
| 685 | + switch (event->type) { | |
| 686 | + case ACPI_AC_NOTIFY_STATUS: | |
| 687 | + ret = lwmi_cd_cache(priv); | |
| 688 | + if (ret) | |
| 689 | + return NOTIFY_BAD; | |
| 690 | + | |
| 691 | + return NOTIFY_OK; | |
| 692 | + default: | |
| 693 | + return NOTIFY_DONE; | |
| 694 | + } | |
| 695 | +} | |
| 696 | + | |
| 697 | +/** | |
| 698 | + * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block. | |
| 699 | + * @data: The ACPI event notifier_block to unregister. | |
| 700 | + */ | |
| 701 | +static void lwmi_cd01_unregister(void *data) | |
| 702 | +{ | |
| 703 | + struct notifier_block *acpi_nb = data; | |
| 704 | + | |
| 705 | + unregister_acpi_notifier(acpi_nb); | |
| 706 | +} | |
| 707 | + | |
| 708 | +static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) | |
| 709 | +{ | |
| 710 | + const struct lwmi_cd_info *info = context; | |
| 711 | + struct lwmi_cd_priv *priv; | |
| 712 | + int ret; | |
| 713 | + | |
| 714 | + if (!info) | |
| 715 | + return -EINVAL; | |
| 716 | + | |
| 717 | + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); | |
| 718 | + if (!priv) | |
| 719 | + return -ENOMEM; | |
| 720 | + | |
| 721 | + priv->wdev = wdev; | |
| 722 | + dev_set_drvdata(&wdev->dev, priv); | |
| 723 | + | |
| 724 | + ret = lwmi_cd_setup(priv, info->type); | |
| 725 | + if (ret) | |
| 726 | + goto out; | |
| 727 | + | |
| 728 | + switch (info->type) { | |
| 729 | + case LENOVO_CAPABILITY_DATA_00: { | |
| 730 | + enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA; | |
| 731 | + struct capdata00 capdata00; | |
| 732 | + | |
| 733 | + ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00); | |
| 734 | + if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) { | |
| 735 | + dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n"); | |
| 736 | + sub_component_type = CD_TYPE_NONE; | |
| 737 | + } | |
| 738 | + | |
| 739 | + /* Sub-master (capdata00) <-> sub-component (capdata_fan) */ | |
| 740 | + ret = lwmi_cd_sub_master_add(priv, sub_component_type); | |
| 741 | + if (ret) | |
| 742 | + goto out; | |
| 743 | + | |
| 744 | + /* Master (lenovo-wmi-other) <-> sub-master (capdata00) */ | |
| 745 | + ret = component_add(&wdev->dev, &lwmi_cd_component_ops); | |
| 746 | + if (ret) | |
| 747 | + lwmi_cd_sub_master_del(priv); | |
| 748 | + | |
| 749 | + goto out; | |
| 750 | + } | |
| 751 | + case LENOVO_CAPABILITY_DATA_01: | |
| 752 | + priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call; | |
| 753 | + | |
| 754 | + ret = register_acpi_notifier(&priv->acpi_nb); | |
| 755 | + if (ret) | |
| 756 | + goto out; | |
| 757 | + | |
| 758 | + ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, | |
| 759 | + &priv->acpi_nb); | |
| 760 | + if (ret) | |
| 761 | + goto out; | |
| 762 | + | |
| 763 | + ret = component_add(&wdev->dev, &lwmi_cd_component_ops); | |
| 764 | + goto out; | |
| 765 | + case LENOVO_FAN_TEST_DATA: | |
| 766 | + ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops); | |
| 767 | + goto out; | |
| 768 | + default: | |
| 769 | + return -EINVAL; | |
| 770 | + } | |
| 771 | +out: | |
| 772 | + if (ret) { | |
| 773 | + dev_err(&wdev->dev, "failed to register %s: %d\n", | |
| 774 | + info->name, ret); | |
| 775 | + } else { | |
| 776 | + dev_dbg(&wdev->dev, "registered %s with %u items\n", | |
| 777 | + info->name, priv->list->count); | |
| 778 | + } | |
| 779 | + return ret; | |
| 780 | +} | |
| 781 | + | |
| 782 | +static void lwmi_cd_remove(struct wmi_device *wdev) | |
| 783 | +{ | |
| 784 | + struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev); | |
| 785 | + | |
| 786 | + switch (priv->list->type) { | |
| 787 | + case LENOVO_CAPABILITY_DATA_00: | |
| 788 | + lwmi_cd_sub_master_del(priv); | |
| 789 | + fallthrough; | |
| 790 | + case LENOVO_CAPABILITY_DATA_01: | |
| 791 | + component_del(&wdev->dev, &lwmi_cd_component_ops); | |
| 792 | + break; | |
| 793 | + case LENOVO_FAN_TEST_DATA: | |
| 794 | + component_del(&wdev->dev, &lwmi_cd_sub_component_ops); | |
| 795 | + break; | |
| 796 | + default: | |
| 797 | + WARN_ON(1); | |
| 798 | + } | |
| 799 | +} | |
| 800 | + | |
| 801 | +#define LWMI_CD_WDEV_ID(_type) \ | |
| 802 | + .guid_string = _type##_GUID, \ | |
| 803 | + .context = &lwmi_cd_table[_type], | |
| 804 | + | |
| 805 | +static const struct wmi_device_id lwmi_cd_id_table[] = { | |
| 806 | + { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) }, | |
| 807 | + { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) }, | |
| 808 | + { LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) }, | |
| 809 | + {} | |
| 810 | +}; | |
| 811 | + | |
| 812 | +static struct wmi_driver lwmi_cd_driver = { | |
| 813 | + .driver = { | |
| 814 | + .name = "lenovo_wmi_capdata", | |
| 815 | + .probe_type = PROBE_PREFER_ASYNCHRONOUS, | |
| 816 | + }, | |
| 817 | + .id_table = lwmi_cd_id_table, | |
| 818 | + .probe = lwmi_cd_probe, | |
| 819 | + .remove = lwmi_cd_remove, | |
| 820 | + .no_singleton = true, | |
| 821 | +}; | |
| 822 | + | |
| 823 | +module_wmi_driver(lwmi_cd_driver); | |
| 824 | + | |
| 825 | +MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table); | |
| 826 | +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); | |
| 827 | +MODULE_AUTHOR("Rong Zhang <i@rong.moe>"); | |
| 828 | +MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver"); | |
| 829 | +MODULE_LICENSE("GPL"); | |
| @@ -0,0 +1,65 @@ | ||
| 1 | +/* SPDX-License-Identifier: GPL-2.0-or-later */ | |
| 2 | + | |
| 3 | +/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */ | |
| 4 | + | |
| 5 | +#ifndef _LENOVO_WMI_CAPDATA_H_ | |
| 6 | +#define _LENOVO_WMI_CAPDATA_H_ | |
| 7 | + | |
| 8 | +#include <linux/bits.h> | |
| 9 | +#include <linux/types.h> | |
| 10 | + | |
| 11 | +#define LWMI_SUPP_VALID BIT(0) | |
| 12 | +#define LWMI_SUPP_GET BIT(1) | |
| 13 | +#define LWMI_SUPP_SET BIT(2) | |
| 14 | + | |
| 15 | +#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) | |
| 16 | +#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) | |
| 17 | +#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) | |
| 18 | +#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) | |
| 19 | + | |
| 20 | +#define LWMI_DEVICE_ID_FAN 0x04 | |
| 21 | + | |
| 22 | +struct component_match; | |
| 23 | +struct device; | |
| 24 | +struct cd_list; | |
| 25 | + | |
| 26 | +struct capdata00 { | |
| 27 | + u32 id; | |
| 28 | + u32 supported; | |
| 29 | + u32 default_value; | |
| 30 | +}; | |
| 31 | + | |
| 32 | +struct capdata01 { | |
| 33 | + u32 id; | |
| 34 | + u32 supported; | |
| 35 | + u32 default_value; | |
| 36 | + u32 step; | |
| 37 | + u32 min_value; | |
| 38 | + u32 max_value; | |
| 39 | +}; | |
| 40 | + | |
| 41 | +struct capdata_fan { | |
| 42 | + u32 id; | |
| 43 | + u32 min_rpm; | |
| 44 | + u32 max_rpm; | |
| 45 | +}; | |
| 46 | + | |
| 47 | +typedef void (*cd_list_cb_t)(struct device *master_dev, struct cd_list *cd_list); | |
| 48 | + | |
| 49 | +struct lwmi_cd_binder { | |
| 50 | + struct cd_list *cd00_list; | |
| 51 | + struct cd_list *cd01_list; | |
| 52 | + /* | |
| 53 | + * May be called during or after the bind callback. | |
| 54 | + * Will be called with NULL if capdata_fan does not exist. | |
| 55 | + * The pointer is only valid in the callback; never keep it for later use! | |
| 56 | + */ | |
| 57 | + cd_list_cb_t cd_fan_list_cb; | |
| 58 | +}; | |
| 59 | + | |
| 60 | +void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr); | |
| 61 | +int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capdata00 *output); | |
| 62 | +int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output); | |
| 63 | +int lwmi_cd_fan_get_data(struct cd_list *list, u32 attribute_id, struct capdata_fan *output); | |
| 64 | + | |
| 65 | +#endif /* !_LENOVO_WMI_CAPDATA_H_ */ | |
| @@ -1,302 +0,0 @@ | ||
| 1 | −// SPDX-License-Identifier: GPL-2.0-or-later | |
| 2 | −/* | |
| 3 | − * Lenovo Capability Data 01 WMI Data Block driver. | |
| 4 | − * | |
| 5 | − * Lenovo Capability Data 01 provides information on tunable attributes used by | |
| 6 | − * the "Other Mode" WMI interface. The data includes if the attribute is | |
| 7 | − * supported by the hardware, the default_value, max_value, min_value, and step | |
| 8 | − * increment. Each attribute has multiple pages, one for each of the thermal | |
| 9 | − * modes managed by the Gamezone interface. | |
| 10 | − * | |
| 11 | − * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> | |
| 12 | − */ | |
| 13 | − | |
| 14 | −#include <linux/acpi.h> | |
| 15 | −#include <linux/cleanup.h> | |
| 16 | −#include <linux/component.h> | |
| 17 | −#include <linux/container_of.h> | |
| 18 | −#include <linux/device.h> | |
| 19 | −#include <linux/export.h> | |
| 20 | −#include <linux/gfp_types.h> | |
| 21 | −#include <linux/module.h> | |
| 22 | −#include <linux/mutex.h> | |
| 23 | −#include <linux/mutex_types.h> | |
| 24 | −#include <linux/notifier.h> | |
| 25 | −#include <linux/overflow.h> | |
| 26 | −#include <linux/types.h> | |
| 27 | −#include <linux/wmi.h> | |
| 28 | − | |
| 29 | −#include "wmi-capdata01.h" | |
| 30 | − | |
| 31 | −#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" | |
| 32 | − | |
| 33 | −#define ACPI_AC_CLASS "ac_adapter" | |
| 34 | −#define ACPI_AC_NOTIFY_STATUS 0x80 | |
| 35 | − | |
| 36 | −struct lwmi_cd01_priv { | |
| 37 | − struct notifier_block acpi_nb; /* ACPI events */ | |
| 38 | − struct wmi_device *wdev; | |
| 39 | − struct cd01_list *list; | |
| 40 | −}; | |
| 41 | − | |
| 42 | −struct cd01_list { | |
| 43 | − struct mutex list_mutex; /* list R/W mutex */ | |
| 44 | − u8 count; | |
| 45 | − struct capdata01 data[]; | |
| 46 | −}; | |
| 47 | − | |
| 48 | −/** | |
| 49 | − * lwmi_cd01_component_bind() - Bind component to master device. | |
| 50 | − * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device. | |
| 51 | − * @om_dev: Pointer to the lenovo-wmi-other driver parent device. | |
| 52 | − * @data: capdata01_list object pointer used to return the capability data. | |
| 53 | − * | |
| 54 | − * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01 | |
| 55 | − * list. This is used to call lwmi_cd01_get_data to look up attribute data | |
| 56 | − * from the lenovo-wmi-other driver. | |
| 57 | − * | |
| 58 | − * Return: 0 | |
| 59 | − */ | |
| 60 | −static int lwmi_cd01_component_bind(struct device *cd01_dev, | |
| 61 | − struct device *om_dev, void *data) | |
| 62 | −{ | |
| 63 | − struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev); | |
| 64 | − struct cd01_list **cd01_list = data; | |
| 65 | − | |
| 66 | − *cd01_list = priv->list; | |
| 67 | − | |
| 68 | − return 0; | |
| 69 | −} | |
| 70 | − | |
| 71 | −static const struct component_ops lwmi_cd01_component_ops = { | |
| 72 | − .bind = lwmi_cd01_component_bind, | |
| 73 | −}; | |
| 74 | − | |
| 75 | −/** | |
| 76 | − * lwmi_cd01_get_data - Get the data of the specified attribute | |
| 77 | − * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct. | |
| 78 | − * @attribute_id: The capdata attribute ID to be found. | |
| 79 | − * @output: Pointer to a capdata01 struct to return the data. | |
| 80 | − * | |
| 81 | − * Retrieves the capability data 01 struct pointer for the given | |
| 82 | − * attribute for its specified thermal mode. | |
| 83 | − * | |
| 84 | − * Return: 0 on success, or -EINVAL. | |
| 85 | − */ | |
| 86 | −int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output) | |
| 87 | −{ | |
| 88 | − u8 idx; | |
| 89 | − | |
| 90 | − guard(mutex)(&list->list_mutex); | |
| 91 | − for (idx = 0; idx < list->count; idx++) { | |
| 92 | − if (list->data[idx].id != attribute_id) | |
| 93 | − continue; | |
| 94 | − memcpy(output, &list->data[idx], sizeof(list->data[idx])); | |
| 95 | − return 0; | |
| 96 | − } | |
| 97 | − | |
| 98 | − return -EINVAL; | |
| 99 | −} | |
| 100 | −EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01"); | |
| 101 | − | |
| 102 | −/** | |
| 103 | − * lwmi_cd01_cache() - Cache all WMI data block information | |
| 104 | − * @priv: lenovo-wmi-capdata01 driver data. | |
| 105 | − * | |
| 106 | − * Loop through each WMI data block and cache the data. | |
| 107 | − * | |
| 108 | − * Return: 0 on success, or an error. | |
| 109 | − */ | |
| 110 | −static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv) | |
| 111 | −{ | |
| 112 | − int idx; | |
| 113 | − | |
| 114 | − guard(mutex)(&priv->list->list_mutex); | |
| 115 | − for (idx = 0; idx < priv->list->count; idx++) { | |
| 116 | − union acpi_object *ret_obj __free(kfree) = NULL; | |
| 117 | − | |
| 118 | − ret_obj = wmidev_block_query(priv->wdev, idx); | |
| 119 | − if (!ret_obj) | |
| 120 | − return -ENODEV; | |
| 121 | − | |
| 122 | − if (ret_obj->type != ACPI_TYPE_BUFFER || | |
| 123 | − ret_obj->buffer.length < sizeof(priv->list->data[idx])) | |
| 124 | − continue; | |
| 125 | − | |
| 126 | − memcpy(&priv->list->data[idx], ret_obj->buffer.pointer, | |
| 127 | − ret_obj->buffer.length); | |
| 128 | − } | |
| 129 | − | |
| 130 | − return 0; | |
| 131 | −} | |
| 132 | − | |
| 133 | −/** | |
| 134 | − * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata | |
| 135 | − * @priv: lenovo-wmi-capdata01 driver data. | |
| 136 | − * | |
| 137 | − * Allocate a cd01_list struct large enough to contain data from all WMI data | |
| 138 | − * blocks provided by the interface. | |
| 139 | − * | |
| 140 | − * Return: 0 on success, or an error. | |
| 141 | − */ | |
| 142 | −static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv) | |
| 143 | −{ | |
| 144 | − struct cd01_list *list; | |
| 145 | − size_t list_size; | |
| 146 | − int count, ret; | |
| 147 | − | |
| 148 | − count = wmidev_instance_count(priv->wdev); | |
| 149 | − list_size = struct_size(list, data, count); | |
| 150 | − | |
| 151 | − list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL); | |
| 152 | − if (!list) | |
| 153 | − return -ENOMEM; | |
| 154 | − | |
| 155 | − ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex); | |
| 156 | − if (ret) | |
| 157 | − return ret; | |
| 158 | − | |
| 159 | − list->count = count; | |
| 160 | − priv->list = list; | |
| 161 | − | |
| 162 | − return 0; | |
| 163 | −} | |
| 164 | − | |
| 165 | −/** | |
| 166 | − * lwmi_cd01_setup() - Cache all WMI data block information | |
| 167 | − * @priv: lenovo-wmi-capdata01 driver data. | |
| 168 | − * | |
| 169 | − * Allocate a cd01_list struct large enough to contain data from all WMI data | |
| 170 | − * blocks provided by the interface. Then loop through each data block and | |
| 171 | − * cache the data. | |
| 172 | − * | |
| 173 | − * Return: 0 on success, or an error code. | |
| 174 | − */ | |
| 175 | −static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv) | |
| 176 | −{ | |
| 177 | − int ret; | |
| 178 | − | |
| 179 | − ret = lwmi_cd01_alloc(priv); | |
| 180 | − if (ret) | |
| 181 | − return ret; | |
| 182 | − | |
| 183 | − return lwmi_cd01_cache(priv); | |
| 184 | −} | |
| 185 | − | |
| 186 | −/** | |
| 187 | − * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier. | |
| 188 | − * block call chain. | |
| 189 | − * @nb: The notifier_block registered to lenovo-wmi-events driver. | |
| 190 | − * @action: Unused. | |
| 191 | − * @data: The ACPI event. | |
| 192 | − * | |
| 193 | − * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile | |
| 194 | − * of a change. | |
| 195 | − * | |
| 196 | − * Return: notifier_block status. | |
| 197 | − */ | |
| 198 | −static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action, | |
| 199 | − void *data) | |
| 200 | −{ | |
| 201 | − struct acpi_bus_event *event = data; | |
| 202 | − struct lwmi_cd01_priv *priv; | |
| 203 | − int ret; | |
| 204 | − | |
| 205 | − if (strcmp(event->device_class, ACPI_AC_CLASS) != 0) | |
| 206 | − return NOTIFY_DONE; | |
| 207 | − | |
| 208 | − priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb); | |
| 209 | − | |
| 210 | − switch (event->type) { | |
| 211 | − case ACPI_AC_NOTIFY_STATUS: | |
| 212 | − ret = lwmi_cd01_cache(priv); | |
| 213 | − if (ret) | |
| 214 | − return NOTIFY_BAD; | |
| 215 | − | |
| 216 | − return NOTIFY_OK; | |
| 217 | − default: | |
| 218 | − return NOTIFY_DONE; | |
| 219 | − } | |
| 220 | −} | |
| 221 | − | |
| 222 | −/** | |
| 223 | − * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block. | |
| 224 | − * @data: The ACPI event notifier_block to unregister. | |
| 225 | − */ | |
| 226 | −static void lwmi_cd01_unregister(void *data) | |
| 227 | −{ | |
| 228 | − struct notifier_block *acpi_nb = data; | |
| 229 | − | |
| 230 | − unregister_acpi_notifier(acpi_nb); | |
| 231 | −} | |
| 232 | − | |
| 233 | −static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context) | |
| 234 | − | |
| 235 | −{ | |
| 236 | − struct lwmi_cd01_priv *priv; | |
| 237 | − int ret; | |
| 238 | − | |
| 239 | − priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); | |
| 240 | − if (!priv) | |
| 241 | − return -ENOMEM; | |
| 242 | − | |
| 243 | − priv->wdev = wdev; | |
| 244 | − dev_set_drvdata(&wdev->dev, priv); | |
| 245 | − | |
| 246 | − ret = lwmi_cd01_setup(priv); | |
| 247 | − if (ret) | |
| 248 | − return ret; | |
| 249 | − | |
| 250 | − priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call; | |
| 251 | − | |
| 252 | − ret = register_acpi_notifier(&priv->acpi_nb); | |
| 253 | − if (ret) | |
| 254 | − return ret; | |
| 255 | − | |
| 256 | − ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb); | |
| 257 | − if (ret) | |
| 258 | − return ret; | |
| 259 | − | |
| 260 | − return component_add(&wdev->dev, &lwmi_cd01_component_ops); | |
| 261 | −} | |
| 262 | − | |
| 263 | −static void lwmi_cd01_remove(struct wmi_device *wdev) | |
| 264 | −{ | |
| 265 | − component_del(&wdev->dev, &lwmi_cd01_component_ops); | |
| 266 | −} | |
| 267 | − | |
| 268 | −static const struct wmi_device_id lwmi_cd01_id_table[] = { | |
| 269 | − { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, | |
| 270 | − {} | |
| 271 | −}; | |
| 272 | − | |
| 273 | −static struct wmi_driver lwmi_cd01_driver = { | |
| 274 | − .driver = { | |
| 275 | − .name = "lenovo_wmi_cd01", | |
| 276 | − .probe_type = PROBE_PREFER_ASYNCHRONOUS, | |
| 277 | − }, | |
| 278 | − .id_table = lwmi_cd01_id_table, | |
| 279 | − .probe = lwmi_cd01_probe, | |
| 280 | − .remove = lwmi_cd01_remove, | |
| 281 | − .no_singleton = true, | |
| 282 | −}; | |
| 283 | − | |
| 284 | −/** | |
| 285 | − * lwmi_cd01_match() - Match rule for the master driver. | |
| 286 | − * @dev: Pointer to the capability data 01 parent device. | |
| 287 | − * @data: Unused void pointer for passing match criteria. | |
| 288 | − * | |
| 289 | − * Return: int. | |
| 290 | − */ | |
| 291 | −int lwmi_cd01_match(struct device *dev, void *data) | |
| 292 | −{ | |
| 293 | − return dev->driver == &lwmi_cd01_driver.driver; | |
| 294 | −} | |
| 295 | −EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01"); | |
| 296 | − | |
| 297 | −module_wmi_driver(lwmi_cd01_driver); | |
| 298 | − | |
| 299 | −MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table); | |
| 300 | −MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); | |
| 301 | −MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); | |
| 302 | −MODULE_LICENSE("GPL"); | |
| @@ -1,25 +0,0 @@ | ||
| 1 | −/* SPDX-License-Identifier: GPL-2.0-or-later */ | |
| 2 | − | |
| 3 | −/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */ | |
| 4 | − | |
| 5 | −#ifndef _LENOVO_WMI_CAPDATA01_H_ | |
| 6 | −#define _LENOVO_WMI_CAPDATA01_H_ | |
| 7 | − | |
| 8 | −#include <linux/types.h> | |
| 9 | − | |
| 10 | −struct device; | |
| 11 | −struct cd01_list; | |
| 12 | − | |
| 13 | −struct capdata01 { | |
| 14 | − u32 id; | |
| 15 | − u32 supported; | |
| 16 | − u32 default_value; | |
| 17 | − u32 step; | |
| 18 | − u32 min_value; | |
| 19 | − u32 max_value; | |
| 20 | −}; | |
| 21 | − | |
| 22 | −int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output); | |
| 23 | −int lwmi_cd01_match(struct device *dev, void *data); | |
| 24 | − | |
| 25 | −#endif /* !_LENOVO_WMI_CAPDATA01_H_ */ | |
| @@ -21,6 +21,7 @@ · 20 unmodified lines | ||
| 21 | 21 | #include <linux/errno.h> |
| 22 | 22 | #include <linux/export.h> |
| 23 | 23 | #include <linux/module.h> |
| 24 | +#include <linux/unaligned.h> | |
| 24 | 25 | #include <linux/wmi.h> |
| 25 | 26 | |
| 26 | 27 | #include "wmi-helpers.h" |
| @@ -59,10 +60,24 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id, · 32 unmodified lines | ||
| 59 | 60 | if (!ret_obj) |
| 60 | 61 | return -ENODATA; |
| 61 | 62 | |
| 62 | − if (ret_obj->type != ACPI_TYPE_INTEGER) | |
| 63 | − return -ENXIO; | |
| 63 | + switch (ret_obj->type) { | |
| 64 | + /* | |
| 65 | + * The ACPI method may simply return a buffer when a u32 | |
| 66 | + * is expected. This is valid on Windows as its WMI-ACPI | |
| 67 | + * driver converts everything to a common buffer. | |
| 68 | + */ | |
| 69 | + case ACPI_TYPE_BUFFER: | |
| 70 | + if (ret_obj->buffer.length < sizeof(u32)) | |
| 71 | + return -ENXIO; | |
| 64 | 72 | |
| 65 | − *retval = (u32)ret_obj->integer.value; | |
| 73 | + *retval = get_unaligned_le32(ret_obj->buffer.pointer); | |
| 74 | + return 0; | |
| 75 | + case ACPI_TYPE_INTEGER: | |
| 76 | + *retval = (u32)ret_obj->integer.value; | |
| 77 | + return 0; | |
| 78 | + default: | |
| 79 | + return -ENXIO; | |
| 80 | + } | |
| 66 | 81 | } |
| 67 | 82 | |
| 68 | 83 | return 0; |
| @@ -14,7 +14,16 @@ · 13 unmodified lines | ||
| 14 | 14 | * These attributes typically don't fit anywhere else in the sysfs and are set |
| 15 | 15 | * in Windows using one of Lenovo's multiple user applications. |
| 16 | 16 | * |
| 17 | + * Additionally, this driver also exports tunable fan speed RPM to HWMON. | |
| 18 | + * Min/max RPM are also provided for reference. | |
| 19 | + * | |
| 17 | 20 | * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> |
| 21 | + * - fw_attributes | |
| 22 | + * - binding to Capability Data 01 | |
| 23 | + * | |
| 24 | + * Copyright (C) 2025 Rong Zhang <i@rong.moe> | |
| 25 | + * - HWMON | |
| 26 | + * - binding to Capability Data 00 and Fan | |
| 18 | 27 | */ |
| 19 | 28 | |
| 20 | 29 | #include <linux/acpi.h> |
| @@ -25,16 +34,18 @@ · 4 unmodified lines | ||
| 25 | 34 | #include <linux/device.h> |
| 26 | 35 | #include <linux/export.h> |
| 27 | 36 | #include <linux/gfp_types.h> |
| 37 | +#include <linux/hwmon.h> | |
| 28 | 38 | #include <linux/idr.h> |
| 29 | 39 | #include <linux/kdev_t.h> |
| 30 | 40 | #include <linux/kobject.h> |
| 41 | +#include <linux/limits.h> | |
| 31 | 42 | #include <linux/module.h> |
| 32 | 43 | #include <linux/notifier.h> |
| 33 | 44 | #include <linux/platform_profile.h> |
| 34 | 45 | #include <linux/types.h> |
| 35 | 46 | #include <linux/wmi.h> |
| 36 | 47 | |
| 37 | −#include "wmi-capdata01.h" | |
| 48 | +#include "wmi-capdata.h" | |
| 38 | 49 | #include "wmi-events.h" |
| 39 | 50 | #include "wmi-gamezone.h" |
| 40 | 51 | #include "wmi-helpers.h" |
| @@ -49,17 +60,26 @@ · 8 unmodified lines | ||
| 49 | 60 | #define LWMI_FEATURE_ID_CPU_SPL 0x02 |
| 50 | 61 | #define LWMI_FEATURE_ID_CPU_FPPT 0x03 |
| 51 | 62 | |
| 63 | +#define LWMI_FEATURE_ID_FAN_RPM 0x03 | |
| 64 | + | |
| 52 | 65 | #define LWMI_TYPE_ID_NONE 0x00 |
| 53 | 66 | |
| 54 | 67 | #define LWMI_FEATURE_VALUE_GET 17 |
| 55 | 68 | #define LWMI_FEATURE_VALUE_SET 18 |
| 56 | 69 | |
| 57 | −#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) | |
| 58 | −#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) | |
| 59 | −#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) | |
| 60 | −#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) | |
| 70 | +#define LWMI_FAN_ID_BASE 1 | |
| 71 | +#define LWMI_FAN_NR 4 | |
| 72 | +#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE) | |
| 73 | + | |
| 74 | +#define LWMI_ATTR_ID_FAN_RPM(x) \ | |
| 75 | + (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ | |
| 76 | + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \ | |
| 77 | + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x))) | |
| 78 | + | |
| 79 | +#define LWMI_FAN_DIV 100 | |
| 61 | 80 | |
| 62 | 81 | #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" |
| 82 | +#define LWMI_OM_HWMON_NAME "lenovo_wmi_other" | |
| 63 | 83 | |
| 64 | 84 | static BLOCKING_NOTIFIER_HEAD(om_chain_head); |
| 65 | 85 | static DEFINE_IDA(lwmi_om_ida); |
| @@ -72,16 +92,459 @@ enum attribute_property { · 6 unmodified lines | ||
| 72 | 92 | SUPPORTED, |
| 73 | 93 | }; |
| 74 | 94 | |
| 95 | +struct lwmi_fan_info { | |
| 96 | + u32 supported; | |
| 97 | + u32 last_target; | |
| 98 | + long min_rpm; | |
| 99 | + long max_rpm; | |
| 100 | +}; | |
| 101 | + | |
| 75 | 102 | struct lwmi_om_priv { |
| 76 | 103 | struct component_master_ops *ops; |
| 77 | − struct cd01_list *cd01_list; /* only valid after capdata01 bind */ | |
| 104 | + | |
| 105 | + /* only valid after capdata bind */ | |
| 106 | + struct cd_list *cd00_list; | |
| 107 | + struct cd_list *cd01_list; | |
| 108 | + | |
| 109 | + struct device *hwmon_dev; | |
| 78 | 110 | struct device *fw_attr_dev; |
| 79 | 111 | struct kset *fw_attr_kset; |
| 80 | 112 | struct notifier_block nb; |
| 81 | 113 | struct wmi_device *wdev; |
| 82 | 114 | int ida_id; |
| 115 | + | |
| 116 | + struct lwmi_fan_info fan_info[LWMI_FAN_NR]; | |
| 117 | + | |
| 118 | + struct { | |
| 119 | + bool capdata00_collected : 1; | |
| 120 | + bool capdata_fan_collected : 1; | |
| 121 | + } fan_flags; | |
| 83 | 122 | }; |
| 84 | 123 | |
| 124 | +/* | |
| 125 | + * Visibility of fan channels: | |
| 126 | + * | |
| 127 | + * +-------------------+---------+------------------+-----------------------+------------+ | |
| 128 | + * | | default | +expose_all_fans | +relax_fan_constraint | +both | | |
| 129 | + * +-------------------+---------+------------------+-----------------------+------------+ | |
| 130 | + * | canonical | RW | RW | RW+relaxed | RW+relaxed | | |
| 131 | + * +-------------------+---------+------------------+-----------------------+------------+ | |
| 132 | + * | -capdata_fan[idx] | N | RO | N | RW+relaxed | | |
| 133 | + * +-------------------+---------+------------------+-----------------------+------------+ | |
| 134 | + * | |
| 135 | + * Note: | |
| 136 | + * 1. LWMI_ATTR_ID_FAN_RPM[idx].supported is always checked before exposing a channel. | |
| 137 | + * 2. -capdata_fan implies -capdata_fan[idx]. | |
| 138 | + */ | |
| 139 | +static bool expose_all_fans; | |
| 140 | +module_param(expose_all_fans, bool, 0444); | |
| 141 | +MODULE_PARM_DESC(expose_all_fans, | |
| 142 | + "This option skips some capability checks and solely relies on per-channel ones " | |
| 143 | + "to expose fan attributes. Use with caution."); | |
| 144 | + | |
| 145 | +static bool relax_fan_constraint; | |
| 146 | +module_param(relax_fan_constraint, bool, 0444); | |
| 147 | +MODULE_PARM_DESC(relax_fan_constraint, | |
| 148 | + "Do not enforce fan RPM constraint (div/min/max) " | |
| 149 | + "and enables fan tuning when such data is missing. " | |
| 150 | + "Enabling this may results in HWMON attributes being out-of-sync, " | |
| 151 | + "and setting a too low RPM stops the fan. Use with caution."); | |
| 152 | + | |
| 153 | +/* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */ | |
| 154 | + | |
| 155 | +/** | |
| 156 | + * lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan | |
| 157 | + * @priv: Driver private data structure | |
| 158 | + * @channel: Fan channel index (0-based) | |
| 159 | + * @val: Pointer to value (input for set, output for get) | |
| 160 | + * @set: True to set value, false to get value | |
| 161 | + * | |
| 162 | + * Communicates with WMI interface to either retrieve current fan RPM | |
| 163 | + * or set target fan RPM. | |
| 164 | + * | |
| 165 | + * Return: 0 on success, or an error code. | |
| 166 | + */ | |
| 167 | +static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val, bool set) | |
| 168 | +{ | |
| 169 | + struct wmi_method_args_32 args; | |
| 170 | + u32 method_id, retval; | |
| 171 | + int err; | |
| 172 | + | |
| 173 | + method_id = set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET; | |
| 174 | + args.arg0 = LWMI_ATTR_ID_FAN_RPM(channel); | |
| 175 | + args.arg1 = set ? *val : 0; | |
| 176 | + | |
| 177 | + err = lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id, | |
| 178 | + (unsigned char *)&args, sizeof(args), &retval); | |
| 179 | + if (err) | |
| 180 | + return err; | |
| 181 | + | |
| 182 | + if (!set) { | |
| 183 | + *val = retval; | |
| 184 | + return 0; | |
| 185 | + } | |
| 186 | + | |
| 187 | + /* | |
| 188 | + * It seems that 0 means "no error" and 1 means "done". Apparently | |
| 189 | + * different firmware teams have different thoughts on indicating | |
| 190 | + * success, so we accepts both. | |
| 191 | + */ | |
| 192 | + return (retval == 0 || retval == 1) ? 0 : -EIO; | |
| 193 | +} | |
| 194 | + | |
| 195 | +/** | |
| 196 | + * lwmi_om_hwmon_is_visible() - Determine visibility of HWMON attributes | |
| 197 | + * @drvdata: Driver private data | |
| 198 | + * @type: Sensor type | |
| 199 | + * @attr: Attribute identifier | |
| 200 | + * @channel: Channel index | |
| 201 | + * | |
| 202 | + * Determines whether an HWMON attribute should be visible in sysfs | |
| 203 | + * based on hardware capabilities and current configuration. | |
| 204 | + * | |
| 205 | + * Return: permission mode, or 0 if invisible. | |
| 206 | + */ | |
| 207 | +static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, | |
| 208 | + u32 attr, int channel) | |
| 209 | +{ | |
| 210 | + struct lwmi_om_priv *priv = (struct lwmi_om_priv *)drvdata; | |
| 211 | + bool visible = false; | |
| 212 | + | |
| 213 | + if (type == hwmon_fan) { | |
| 214 | + if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID)) | |
| 215 | + return 0; | |
| 216 | + | |
| 217 | + switch (attr) { | |
| 218 | + case hwmon_fan_target: | |
| 219 | + if (!(priv->fan_info[channel].supported & LWMI_SUPP_SET)) | |
| 220 | + return 0; | |
| 221 | + | |
| 222 | + if (relax_fan_constraint || | |
| 223 | + (priv->fan_info[channel].min_rpm >= 0 && | |
| 224 | + priv->fan_info[channel].max_rpm >= 0)) | |
| 225 | + return 0644; | |
| 226 | + | |
| 227 | + /* | |
| 228 | + * Reaching here implies expose_all_fans is set. | |
| 229 | + * See lwmi_om_hwmon_add(). | |
| 230 | + */ | |
| 231 | + dev_warn_once(&priv->wdev->dev, | |
| 232 | + "fan tuning disabled due to missing RPM constraint\n"); | |
| 233 | + return 0; | |
| 234 | + case hwmon_fan_div: | |
| 235 | + case hwmon_fan_input: | |
| 236 | + visible = priv->fan_info[channel].supported & LWMI_SUPP_GET; | |
| 237 | + break; | |
| 238 | + case hwmon_fan_min: | |
| 239 | + visible = priv->fan_info[channel].min_rpm >= 0; | |
| 240 | + break; | |
| 241 | + case hwmon_fan_max: | |
| 242 | + visible = priv->fan_info[channel].max_rpm >= 0; | |
| 243 | + break; | |
| 244 | + } | |
| 245 | + } | |
| 246 | + | |
| 247 | + return visible ? 0444 : 0; | |
| 248 | +} | |
| 249 | + | |
| 250 | +/** | |
| 251 | + * lwmi_om_hwmon_read() - Read HWMON sensor data | |
| 252 | + * @dev: Device pointer | |
| 253 | + * @type: Sensor type | |
| 254 | + * @attr: Attribute identifier | |
| 255 | + * @channel: Channel index | |
| 256 | + * @val: Pointer to store value | |
| 257 | + * | |
| 258 | + * Reads current sensor values from hardware through WMI interface. | |
| 259 | + * | |
| 260 | + * Return: 0 on success, or an error code. | |
| 261 | + */ | |
| 262 | +static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types type, | |
| 263 | + u32 attr, int channel, long *val) | |
| 264 | +{ | |
| 265 | + struct lwmi_om_priv *priv = dev_get_drvdata(dev); | |
| 266 | + u32 retval = 0; | |
| 267 | + int err; | |
| 268 | + | |
| 269 | + if (type == hwmon_fan) { | |
| 270 | + switch (attr) { | |
| 271 | + /* | |
| 272 | + * The EC has an internal RPM divisor (i.e., the raw register value is | |
| 273 | + * RPM / fanY_div). For fanY_input, the WMI method reads the register | |
| 274 | + * value and returns raw * fanY_div. For fanY_target, the WMI method | |
| 275 | + * divides the written value by fanY_div before writing it to the EC. | |
| 276 | + * | |
| 277 | + * As a result, reading fanY_input always returns a multiple of fanY_div, | |
| 278 | + * while writing to fanY_target loses the remainder. | |
| 279 | + */ | |
| 280 | + case hwmon_fan_div: | |
| 281 | + *val = LWMI_FAN_DIV; | |
| 282 | + return 0; | |
| 283 | + case hwmon_fan_input: | |
| 284 | + err = lwmi_om_fan_get_set(priv, channel, &retval, false); | |
| 285 | + if (err) | |
| 286 | + return err; | |
| 287 | + | |
| 288 | + *val = retval; | |
| 289 | + return 0; | |
| 290 | + case hwmon_fan_target: | |
| 291 | + *val = priv->fan_info[channel].last_target; | |
| 292 | + return 0; | |
| 293 | + case hwmon_fan_min: | |
| 294 | + *val = priv->fan_info[channel].min_rpm; | |
| 295 | + return 0; | |
| 296 | + case hwmon_fan_max: | |
| 297 | + *val = priv->fan_info[channel].max_rpm; | |
| 298 | + return 0; | |
| 299 | + } | |
| 300 | + } | |
| 301 | + | |
| 302 | + return -EOPNOTSUPP; | |
| 303 | +} | |
| 304 | + | |
| 305 | +/** | |
| 306 | + * lwmi_om_hwmon_write() - Write HWMON sensor data | |
| 307 | + * @dev: Device pointer | |
| 308 | + * @type: Sensor type | |
| 309 | + * @attr: Attribute identifier | |
| 310 | + * @channel: Channel index | |
| 311 | + * @val: Value to write | |
| 312 | + * | |
| 313 | + * Writes configuration values to hardware through WMI interface. | |
| 314 | + * | |
| 315 | + * Return: 0 on success, or an error code. | |
| 316 | + */ | |
| 317 | +static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type, | |
| 318 | + u32 attr, int channel, long val) | |
| 319 | +{ | |
| 320 | + struct lwmi_om_priv *priv = dev_get_drvdata(dev); | |
| 321 | + u32 raw, min_rpm, max_rpm; | |
| 322 | + int err; | |
| 323 | + | |
| 324 | + if (type == hwmon_fan) { | |
| 325 | + switch (attr) { | |
| 326 | + case hwmon_fan_target: | |
| 327 | + if (relax_fan_constraint) { | |
| 328 | + min_rpm = 1; | |
| 329 | + max_rpm = U16_MAX; | |
| 330 | + } else { | |
| 331 | + min_rpm = priv->fan_info[channel].min_rpm; | |
| 332 | + max_rpm = priv->fan_info[channel].max_rpm; | |
| 333 | + } | |
| 334 | + | |
| 335 | + /* 0 means "auto". */ | |
| 336 | + if (val != 0 && (val < min_rpm || val > max_rpm)) | |
| 337 | + return -EINVAL; | |
| 338 | + | |
| 339 | + /* | |
| 340 | + * The effective fanY_target is always a multiple of fanY_div | |
| 341 | + * due to the EC's internal RPM divisor (see lwmi_om_hwmon_read). | |
| 342 | + * | |
| 343 | + * Round down the written value to the nearest multiple of fanY_div | |
| 344 | + * to prevent mismatch between the effective value and last_target. | |
| 345 | + * | |
| 346 | + * For relax_fan_constraint, skip this conversion as setting a | |
| 347 | + * sub-fanY_div value is necessary to completely stop the fan on | |
| 348 | + * some devices. | |
| 349 | + */ | |
| 350 | + if (!relax_fan_constraint) | |
| 351 | + raw = val / LWMI_FAN_DIV * LWMI_FAN_DIV; | |
| 352 | + | |
| 353 | + err = lwmi_om_fan_get_set(priv, channel, &raw, true); | |
| 354 | + if (err) | |
| 355 | + return err; | |
| 356 | + | |
| 357 | + priv->fan_info[channel].last_target = raw; | |
| 358 | + return 0; | |
| 359 | + } | |
| 360 | + } | |
| 361 | + | |
| 362 | + return -EOPNOTSUPP; | |
| 363 | +} | |
| 364 | + | |
| 365 | +static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = { | |
| 366 | + /* Must match LWMI_FAN_NR. */ | |
| 367 | + HWMON_CHANNEL_INFO(fan, | |
| 368 | + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | | |
| 369 | + HWMON_F_MIN | HWMON_F_MAX, | |
| 370 | + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | | |
| 371 | + HWMON_F_MIN | HWMON_F_MAX, | |
| 372 | + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | | |
| 373 | + HWMON_F_MIN | HWMON_F_MAX, | |
| 374 | + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | | |
| 375 | + HWMON_F_MIN | HWMON_F_MAX), | |
| 376 | + NULL | |
| 377 | +}; | |
| 378 | + | |
| 379 | +static const struct hwmon_ops lwmi_om_hwmon_ops = { | |
| 380 | + .is_visible = lwmi_om_hwmon_is_visible, | |
| 381 | + .read = lwmi_om_hwmon_read, | |
| 382 | + .write = lwmi_om_hwmon_write, | |
| 383 | +}; | |
| 384 | + | |
| 385 | +static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = { | |
| 386 | + .ops = &lwmi_om_hwmon_ops, | |
| 387 | + .info = lwmi_om_hwmon_info, | |
| 388 | +}; | |
| 389 | + | |
| 390 | +/** | |
| 391 | + * lwmi_om_hwmon_add() - Register HWMON device if all info is collected | |
| 392 | + * @priv: Driver private data | |
| 393 | + */ | |
| 394 | +static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv) | |
| 395 | +{ | |
| 396 | + int i, valid; | |
| 397 | + | |
| 398 | + if (WARN_ON(priv->hwmon_dev)) | |
| 399 | + return; | |
| 400 | + | |
| 401 | + if (!priv->fan_flags.capdata00_collected || !priv->fan_flags.capdata_fan_collected) { | |
| 402 | + dev_dbg(&priv->wdev->dev, "HWMON registration pending (00: %d, fan: %d)\n", | |
| 403 | + priv->fan_flags.capdata00_collected, | |
| 404 | + priv->fan_flags.capdata_fan_collected); | |
| 405 | + return; | |
| 406 | + } | |
| 407 | + | |
| 408 | + if (expose_all_fans) | |
| 409 | + dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n"); | |
| 410 | + | |
| 411 | + if (relax_fan_constraint) | |
| 412 | + dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution\n"); | |
| 413 | + | |
| 414 | + valid = 0; | |
| 415 | + for (i = 0; i < LWMI_FAN_NR; i++) { | |
| 416 | + if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID)) | |
| 417 | + continue; | |
| 418 | + | |
| 419 | + valid++; | |
| 420 | + | |
| 421 | + if (!expose_all_fans && | |
| 422 | + (priv->fan_info[i].min_rpm < 0 || priv->fan_info[i].max_rpm < 0)) { | |
| 423 | + dev_dbg(&priv->wdev->dev, "missing RPM constraint for fan%d, hiding\n", | |
| 424 | + LWMI_FAN_ID(i)); | |
| 425 | + priv->fan_info[i].supported = 0; | |
| 426 | + valid--; | |
| 427 | + } | |
| 428 | + } | |
| 429 | + | |
| 430 | + if (valid == 0) { | |
| 431 | + dev_warn(&priv->wdev->dev, | |
| 432 | + "fan reporting/tuning is unsupported on this device\n"); | |
| 433 | + return; | |
| 434 | + } | |
| 435 | + | |
| 436 | + priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev, | |
| 437 | + LWMI_OM_HWMON_NAME, priv, | |
| 438 | + &lwmi_om_hwmon_chip_info, | |
| 439 | + NULL); | |
| 440 | + if (IS_ERR(priv->hwmon_dev)) { | |
| 441 | + dev_warn(&priv->wdev->dev, "failed to register HWMON device: %ld\n", | |
| 442 | + PTR_ERR(priv->hwmon_dev)); | |
| 443 | + priv->hwmon_dev = NULL; | |
| 444 | + return; | |
| 445 | + } | |
| 446 | + | |
| 447 | + dev_dbg(&priv->wdev->dev, "registered HWMON device\n"); | |
| 448 | +} | |
| 449 | + | |
| 450 | +/** | |
| 451 | + * lwmi_om_hwmon_remove() - Unregister HWMON device | |
| 452 | + * @priv: Driver private data | |
| 453 | + * | |
| 454 | + * Unregisters the HWMON device if applicable. | |
| 455 | + */ | |
| 456 | +static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv) | |
| 457 | +{ | |
| 458 | + if (!priv->hwmon_dev) | |
| 459 | + return; | |
| 460 | + | |
| 461 | + hwmon_device_unregister(priv->hwmon_dev); | |
| 462 | + priv->hwmon_dev = NULL; | |
| 463 | +} | |
| 464 | + | |
| 465 | +/** | |
| 466 | + * lwmi_om_fan_info_init() - Initialzie fan info | |
| 467 | + * @priv: Driver private data | |
| 468 | + * | |
| 469 | + * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan() may be | |
| 470 | + * called in an arbitrary order. Hence, initializion must be done before. | |
| 471 | + */ | |
| 472 | +static void lwmi_om_fan_info_init(struct lwmi_om_priv *priv) | |
| 473 | +{ | |
| 474 | + int i; | |
| 475 | + | |
| 476 | + for (i = 0; i < LWMI_FAN_NR; i++) { | |
| 477 | + priv->fan_info[i] = (struct lwmi_fan_info) { | |
| 478 | + .supported = 0, | |
| 479 | + /* | |
| 480 | + * Assume 0 on probe as the EC resets all fans to auto mode on (re)boot. | |
| 481 | + * | |
| 482 | + * Note that S0ix (s2idle) preserves the RPM target, so we don't need | |
| 483 | + * suspend/resume callbacks. This behavior has not been tested on S3- | |
| 484 | + * capable devices, but I doubt if such devices even have this interface. | |
| 485 | + */ | |
| 486 | + .last_target = 0, | |
| 487 | + .min_rpm = -ENODATA, | |
| 488 | + .max_rpm = -ENODATA, | |
| 489 | + }; | |
| 490 | + } | |
| 491 | + | |
| 492 | + priv->fan_flags.capdata00_collected = false; | |
| 493 | + priv->fan_flags.capdata_fan_collected = false; | |
| 494 | +} | |
| 495 | + | |
| 496 | +/** | |
| 497 | + * lwmi_om_fan_info_collect_cd00() - Collect fan info from capdata 00 | |
| 498 | + * @priv: Driver private data | |
| 499 | + */ | |
| 500 | +static void lwmi_om_fan_info_collect_cd00(struct lwmi_om_priv *priv) | |
| 501 | +{ | |
| 502 | + struct capdata00 capdata00; | |
| 503 | + int i, err; | |
| 504 | + | |
| 505 | + dev_dbg(&priv->wdev->dev, "Collecting fan info from capdata00\n"); | |
| 506 | + | |
| 507 | + for (i = 0; i < LWMI_FAN_NR; i++) { | |
| 508 | + err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &capdata00); | |
| 509 | + priv->fan_info[i].supported = err ? 0 : capdata00.supported; | |
| 510 | + } | |
| 511 | + | |
| 512 | + priv->fan_flags.capdata00_collected = true; | |
| 513 | + lwmi_om_hwmon_add(priv); | |
| 514 | +} | |
| 515 | + | |
| 516 | +/** | |
| 517 | + * lwmi_om_fan_info_collect_cd_fan() - Collect fan info from capdata fan | |
| 518 | + * @dev: Pointer to the lenovo-wmi-other device | |
| 519 | + * @cd_fan_list: Pointer to the capdata fan list | |
| 520 | + */ | |
| 521 | +static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *cd_fan_list) | |
| 522 | +{ | |
| 523 | + struct lwmi_om_priv *priv = dev_get_drvdata(dev); | |
| 524 | + struct capdata_fan capdata_fan; | |
| 525 | + int i, err; | |
| 526 | + | |
| 527 | + dev_dbg(dev, "Collecting fan info from capdata_fan\n"); | |
| 528 | + | |
| 529 | + if (!cd_fan_list) | |
| 530 | + goto out; | |
| 531 | + | |
| 532 | + for (i = 0; i < LWMI_FAN_NR; i++) { | |
| 533 | + err = lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan); | |
| 534 | + if (err) | |
| 535 | + continue; | |
| 536 | + | |
| 537 | + priv->fan_info[i].min_rpm = capdata_fan.min_rpm; | |
| 538 | + priv->fan_info[i].max_rpm = capdata_fan.max_rpm; | |
| 539 | + } | |
| 540 | + | |
| 541 | +out: | |
| 542 | + priv->fan_flags.capdata_fan_collected = true; | |
| 543 | + lwmi_om_hwmon_add(priv); | |
| 544 | +} | |
| 545 | + | |
| 546 | +/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */ | |
| 547 | + | |
| 85 | 548 | struct tunable_attr_01 { |
| 86 | 549 | struct capdata01 *capdata; |
| 87 | 550 | struct device *dev; |
| @@ -561,32 +1024,45 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) · 473 unmodified lines | ||
| 561 | 1024 | device_unregister(priv->fw_attr_dev); |
| 562 | 1025 | } |
| 563 | 1026 | |
| 1027 | +/* ======== Self (master: lenovo-wmi-other) ======== */ | |
| 1028 | + | |
| 564 | 1029 | /** |
| 565 | 1030 | * lwmi_om_master_bind() - Bind all components of the other mode driver |
| 566 | 1031 | * @dev: The lenovo-wmi-other driver basic device. |
| 567 | 1032 | * |
| 568 | − * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the | |
| 569 | − * lenovo-wmi-other master driver. On success, assign the capability data 01 | |
| 570 | − * list pointer to the driver data struct for later access. This pointer | |
| 571 | − * is only valid while the capdata01 interface exists. Finally, register all | |
| 572 | − * firmware attribute groups. | |
| 1033 | + * Call component_bind_all to bind the lenovo-wmi-capdata devices to the | |
| 1034 | + * lenovo-wmi-other master driver, with a callback to collect fan info from | |
| 1035 | + * capdata_fan. On success, assign the capability data list pointers to the | |
| 1036 | + * driver data struct for later access. These pointers are only valid while the | |
| 1037 | + * capdata interfaces exist. Finally, collect fan info from capdata00 and | |
| 1038 | + * register all firmware attribute groups. Note that the HWMON device is | |
| 1039 | + * registered only if all fan info is collected. Hence, it is not registered | |
| 1040 | + * here. See lwmi_om_fan_info_collect_cd00() and | |
| 1041 | + * lwmi_om_fan_info_collect_cd_fan(). | |
| 573 | 1042 | * |
| 574 | 1043 | * Return: 0 on success, or an error code. |
| 575 | 1044 | */ |
| 576 | 1045 | static int lwmi_om_master_bind(struct device *dev) |
| 577 | 1046 | { |
| 578 | 1047 | struct lwmi_om_priv *priv = dev_get_drvdata(dev); |
| 579 | − struct cd01_list *tmp_list; | |
| 1048 | + struct lwmi_cd_binder binder = { | |
| 1049 | + .cd_fan_list_cb = lwmi_om_fan_info_collect_cd_fan, | |
| 1050 | + }; | |
| 580 | 1051 | int ret; |
| 581 | 1052 | |
| 582 | − ret = component_bind_all(dev, &tmp_list); | |
| 1053 | + lwmi_om_fan_info_init(priv); | |
| 1054 | + | |
| 1055 | + ret = component_bind_all(dev, &binder); | |
| 583 | 1056 | if (ret) |
| 584 | 1057 | return ret; |
| 585 | 1058 | |
| 586 | − priv->cd01_list = tmp_list; | |
| 587 | − if (!priv->cd01_list) | |
| 1059 | + priv->cd00_list = binder.cd00_list; | |
| 1060 | + priv->cd01_list = binder.cd01_list; | |
| 1061 | + if (!priv->cd00_list || !priv->cd01_list) | |
| 588 | 1062 | return -ENODEV; |
| 589 | 1063 | |
| 1064 | + lwmi_om_fan_info_collect_cd00(priv); | |
| 1065 | + | |
| 590 | 1066 | return lwmi_om_fw_attr_add(priv); |
| 591 | 1067 | } |
| 592 | 1068 | |
| @@ -594,15 +1070,18 @@ static int lwmi_om_master_bind(struct device *dev) · 1 unmodified line | ||
| 594 | 1070 | * lwmi_om_master_unbind() - Unbind all components of the other mode driver |
| 595 | 1071 | * @dev: The lenovo-wmi-other driver basic device |
| 596 | 1072 | * |
| 597 | − * Unregister all capability data attribute groups. Then call | |
| 598 | − * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the | |
| 599 | − * lenovo-wmi-other master driver. Finally, free the IDA for this device. | |
| 1073 | + * Unregister all firmware attribute groups and the HWMON device. Then call | |
| 1074 | + * component_unbind_all to unbind lenovo-wmi-capdata devices from the | |
| 1075 | + * lenovo-wmi-other master driver. | |
| 600 | 1076 | */ |
| 601 | 1077 | static void lwmi_om_master_unbind(struct device *dev) |
| 602 | 1078 | { |
| 603 | 1079 | struct lwmi_om_priv *priv = dev_get_drvdata(dev); |
| 604 | 1080 | |
| 605 | 1081 | lwmi_om_fw_attr_remove(priv); |
| 1082 | + | |
| 1083 | + lwmi_om_hwmon_remove(priv); | |
| 1084 | + | |
| 606 | 1085 | component_unbind_all(dev, NULL); |
| 607 | 1086 | } |
| 608 | 1087 | |
| @@ -620,10 +1099,13 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context) · 11 unmodified lines | ||
| 620 | 1099 | if (!priv) |
| 621 | 1100 | return -ENOMEM; |
| 622 | 1101 | |
| 1102 | + /* Sentinel for on-demand ida_free(). */ | |
| 1103 | + priv->ida_id = -EIDRM; | |
| 1104 | + | |
| 623 | 1105 | priv->wdev = wdev; |
| 624 | 1106 | dev_set_drvdata(&wdev->dev, priv); |
| 625 | 1107 | |
| 626 | − component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL); | |
| 1108 | + lwmi_cd_match_add_all(&wdev->dev, &master_match); | |
| 627 | 1109 | if (IS_ERR(master_match)) |
| 628 | 1110 | return PTR_ERR(master_match); |
| 629 | 1111 | |
| @@ -636,7 +1118,10 @@ static void lwmi_other_remove(struct wmi_device *wdev) · 6 unmodified lines | ||
| 636 | 1118 | struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev); |
| 637 | 1119 | |
| 638 | 1120 | component_master_del(&wdev->dev, &lwmi_om_master_ops); |
| 639 | − ida_free(&lwmi_om_ida, priv->ida_id); | |
| 1121 | + | |
| 1122 | + /* No IDA to free if the driver is never bound to its components. */ | |
| 1123 | + if (priv->ida_id >= 0) | |
| 1124 | + ida_free(&lwmi_om_ida, priv->ida_id); | |
| 640 | 1125 | } |
| 641 | 1126 | |
| 642 | 1127 | static const struct wmi_device_id lwmi_other_id_table[] = { |
| @@ -657,9 +1142,10 @@ static struct wmi_driver lwmi_other_driver = { · 14 unmodified lines | ||
| 657 | 1142 | |
| 658 | 1143 | module_wmi_driver(lwmi_other_driver); |
| 659 | 1144 | |
| 660 | −MODULE_IMPORT_NS("LENOVO_WMI_CD01"); | |
| 1145 | +MODULE_IMPORT_NS("LENOVO_WMI_CAPDATA"); | |
| 661 | 1146 | MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); |
| 662 | 1147 | MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); |
| 663 | 1148 | MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); |
| 1149 | +MODULE_AUTHOR("Rong Zhang <i@rong.moe>"); | |
| 664 | 1150 | MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver"); |
| 665 | 1151 | MODULE_LICENSE("GPL"); |
| @@ -57,7 +57,7 @@ struct yogabook_data { · 56 unmodified lines | ||
| 57 | 57 | struct work_struct work; |
| 58 | 58 | struct led_classdev kbd_bl_led; |
| 59 | 59 | unsigned long flags; |
| 60 | − uint8_t brightness; | |
| 60 | + u8 brightness; | |
| 61 | 61 | }; |
| 62 | 62 | |
| 63 | 63 | static void yogabook_work(struct work_struct *work) |
| @@ -338,16 +338,18 @@ static int yogabook_wmi_probe(struct wmi_device *wdev, const void *context) · 274 unmodified lines | ||
| 338 | 338 | int r; |
| 339 | 339 | |
| 340 | 340 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
| 341 | − if (data == NULL) | |
| 341 | + if (!data) | |
| 342 | 342 | return -ENOMEM; |
| 343 | 343 | |
| 344 | 344 | data->kbd_adev = acpi_dev_get_first_match_dev("GDIX1001", NULL, -1); |
| 345 | 345 | if (!data->kbd_adev) |
| 346 | − return dev_err_probe(dev, -ENODEV, "Cannot find the touchpad device in ACPI tables\n"); | |
| 346 | + return dev_err_probe(dev, -ENODEV, | |
| 347 | + "Cannot find the touchpad device in ACPI tables\n"); | |
| 347 | 348 | |
| 348 | 349 | data->dig_adev = acpi_dev_get_first_match_dev("WCOM0019", NULL, -1); |
| 349 | 350 | if (!data->dig_adev) { |
| 350 | − r = dev_err_probe(dev, -ENODEV, "Cannot find the digitizer device in ACPI tables\n"); | |
| 351 | + r = dev_err_probe(dev, -ENODEV, | |
| 352 | + "Cannot find the digitizer device in ACPI tables\n"); | |
| 351 | 353 | goto error_put_devs; |
| 352 | 354 | } |
| 353 | 355 | |
| @@ -453,7 +455,7 @@ static int yogabook_pdev_probe(struct platform_device *pdev) · 99 unmodified lines | ||
| 453 | 455 | int r; |
| 454 | 456 | |
| 455 | 457 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
| 456 | − if (data == NULL) | |
| 458 | + if (!data) | |
| 457 | 459 | return -ENOMEM; |
| 458 | 460 | |
| 459 | 461 | data->kbd_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "i2c-goodix_ts"); |
| @@ -88,6 +88,9 @@ · 87 unmodified lines | ||
| 88 | 88 | |
| 89 | 89 | #define EC_ADDR_GPU_TEMP 0x044F |
| 90 | 90 | |
| 91 | +#define EC_ADDR_SYSTEM_ID 0x0456 | |
| 92 | +#define HAS_GPU BIT(7) | |
| 93 | + | |
| 91 | 94 | #define EC_ADDR_MAIN_FAN_RPM_1 0x0464 |
| 92 | 95 | |
| 93 | 96 | #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 |
| @@ -122,11 +125,11 @@ · 28 unmodified lines | ||
| 122 | 125 | #define CTGP_DB_DB_ENABLE BIT(1) |
| 123 | 126 | #define CTGP_DB_CTGP_ENABLE BIT(2) |
| 124 | 127 | |
| 125 | −#define EC_ADDR_CTGP_OFFSET 0x0744 | |
| 128 | +#define EC_ADDR_CTGP_DB_CTGP_OFFSET 0x0744 | |
| 126 | 129 | |
| 127 | −#define EC_ADDR_TPP_OFFSET 0x0745 | |
| 130 | +#define EC_ADDR_CTGP_DB_TPP_OFFSET 0x0745 | |
| 128 | 131 | |
| 129 | −#define EC_ADDR_MAX_TGP 0x0746 | |
| 132 | +#define EC_ADDR_CTGP_DB_DB_OFFSET 0x0746 | |
| 130 | 133 | |
| 131 | 134 | #define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748 |
| 132 | 135 | #define LIGHTBAR_APP_EXISTS BIT(0) |
| @@ -317,11 +320,13 @@ · 184 unmodified lines | ||
| 317 | 320 | #define UNIWILL_FEATURE_LIGHTBAR BIT(3) |
| 318 | 321 | #define UNIWILL_FEATURE_BATTERY BIT(4) |
| 319 | 322 | #define UNIWILL_FEATURE_HWMON BIT(5) |
| 323 | +#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(6) | |
| 320 | 324 | |
| 321 | 325 | struct uniwill_data { |
| 322 | 326 | struct device *dev; |
| 323 | 327 | acpi_handle handle; |
| 324 | 328 | struct regmap *regmap; |
| 329 | + unsigned int features; | |
| 325 | 330 | struct acpi_battery_hook hook; |
| 326 | 331 | unsigned int last_charge_ctrl; |
| 327 | 332 | struct mutex battery_lock; /* Protects the list of currently registered batteries */ |
| @@ -341,12 +346,21 @@ struct uniwill_battery_entry { · 13 unmodified lines | ||
| 341 | 346 | struct power_supply *battery; |
| 342 | 347 | }; |
| 343 | 348 | |
| 349 | +struct uniwill_device_descriptor { | |
| 350 | + unsigned int features; | |
| 351 | + /* Executed during driver probing */ | |
| 352 | + int (*probe)(struct uniwill_data *data); | |
| 353 | +}; | |
| 354 | + | |
| 344 | 355 | static bool force; |
| 345 | 356 | module_param_unsafe(force, bool, 0); |
| 346 | 357 | MODULE_PARM_DESC(force, "Force loading without checking for supported devices\n"); |
| 347 | 358 | |
| 348 | −/* Feature bitmask since the associated registers are not reliable */ | |
| 349 | −static unsigned int supported_features; | |
| 359 | +/* | |
| 360 | + * Contains device specific data like the feature bitmap since | |
| 361 | + * the associated registers are not always reliable. | |
| 362 | + */ | |
| 363 | +static struct uniwill_device_descriptor device_descriptor __ro_after_init; | |
| 350 | 364 | |
| 351 | 365 | static const char * const uniwill_temp_labels[] = { |
| 352 | 366 | "CPU", |
| @@ -411,6 +425,12 @@ static const struct key_entry uniwill_keymap[] = { · 58 unmodified lines | ||
| 411 | 425 | { KE_END } |
| 412 | 426 | }; |
| 413 | 427 | |
| 428 | +static inline bool uniwill_device_supports(struct uniwill_data *data, | |
| 429 | + unsigned int features) | |
| 430 | +{ | |
| 431 | + return (data->features & features) == features; | |
| 432 | +} | |
| 433 | + | |
| 414 | 434 | static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val) |
| 415 | 435 | { |
| 416 | 436 | union acpi_object params[2] = { |
| @@ -498,6 +518,10 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) · 81 unmodified lines | ||
| 498 | 518 | case EC_ADDR_LIGHTBAR_BAT_RED: |
| 499 | 519 | case EC_ADDR_LIGHTBAR_BAT_GREEN: |
| 500 | 520 | case EC_ADDR_LIGHTBAR_BAT_BLUE: |
| 521 | + case EC_ADDR_CTGP_DB_CTRL: | |
| 522 | + case EC_ADDR_CTGP_DB_CTGP_OFFSET: | |
| 523 | + case EC_ADDR_CTGP_DB_TPP_OFFSET: | |
| 524 | + case EC_ADDR_CTGP_DB_DB_OFFSET: | |
| 501 | 525 | return true; |
| 502 | 526 | default: |
| 503 | 527 | return false; |
| @@ -531,6 +555,11 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) · 27 unmodified lines | ||
| 531 | 555 | case EC_ADDR_LIGHTBAR_BAT_RED: |
| 532 | 556 | case EC_ADDR_LIGHTBAR_BAT_GREEN: |
| 533 | 557 | case EC_ADDR_LIGHTBAR_BAT_BLUE: |
| 558 | + case EC_ADDR_SYSTEM_ID: | |
| 559 | + case EC_ADDR_CTGP_DB_CTRL: | |
| 560 | + case EC_ADDR_CTGP_DB_CTGP_OFFSET: | |
| 561 | + case EC_ADDR_CTGP_DB_TPP_OFFSET: | |
| 562 | + case EC_ADDR_CTGP_DB_DB_OFFSET: | |
| 534 | 563 | return true; |
| 535 | 564 | default: |
| 536 | 565 | return false; |
| @@ -786,6 +815,70 @@ static ssize_t breathing_in_suspend_show(struct device *dev, struct device_attri · 249 unmodified lines | ||
| 786 | 815 | |
| 787 | 816 | static DEVICE_ATTR_RW(breathing_in_suspend); |
| 788 | 817 | |
| 818 | +static ssize_t ctgp_offset_store(struct device *dev, struct device_attribute *attr, | |
| 819 | + const char *buf, size_t count) | |
| 820 | +{ | |
| 821 | + struct uniwill_data *data = dev_get_drvdata(dev); | |
| 822 | + unsigned int value; | |
| 823 | + int ret; | |
| 824 | + | |
| 825 | + ret = kstrtouint(buf, 0, &value); | |
| 826 | + if (ret < 0) | |
| 827 | + return ret; | |
| 828 | + | |
| 829 | + if (value > U8_MAX) | |
| 830 | + return -EINVAL; | |
| 831 | + | |
| 832 | + ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, value); | |
| 833 | + if (ret < 0) | |
| 834 | + return ret; | |
| 835 | + | |
| 836 | + return count; | |
| 837 | +} | |
| 838 | + | |
| 839 | +static ssize_t ctgp_offset_show(struct device *dev, struct device_attribute *attr, | |
| 840 | + char *buf) | |
| 841 | +{ | |
| 842 | + struct uniwill_data *data = dev_get_drvdata(dev); | |
| 843 | + unsigned int value; | |
| 844 | + int ret; | |
| 845 | + | |
| 846 | + ret = regmap_read(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, &value); | |
| 847 | + if (ret < 0) | |
| 848 | + return ret; | |
| 849 | + | |
| 850 | + return sysfs_emit(buf, "%u\n", value); | |
| 851 | +} | |
| 852 | + | |
| 853 | +static DEVICE_ATTR_RW(ctgp_offset); | |
| 854 | + | |
| 855 | +static int uniwill_nvidia_ctgp_init(struct uniwill_data *data) | |
| 856 | +{ | |
| 857 | + int ret; | |
| 858 | + | |
| 859 | + if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) | |
| 860 | + return 0; | |
| 861 | + | |
| 862 | + ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, 0); | |
| 863 | + if (ret < 0) | |
| 864 | + return ret; | |
| 865 | + | |
| 866 | + ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_TPP_OFFSET, 255); | |
| 867 | + if (ret < 0) | |
| 868 | + return ret; | |
| 869 | + | |
| 870 | + ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_DB_OFFSET, 25); | |
| 871 | + if (ret < 0) | |
| 872 | + return ret; | |
| 873 | + | |
| 874 | + ret = regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL, | |
| 875 | + CTGP_DB_GENERAL_ENABLE | CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE); | |
| 876 | + if (ret < 0) | |
| 877 | + return ret; | |
| 878 | + | |
| 879 | + return 0; | |
| 880 | +} | |
| 881 | + | |
| 789 | 882 | static struct attribute *uniwill_attrs[] = { |
| 790 | 883 | /* Keyboard-related */ |
| 791 | 884 | &dev_attr_fn_lock_toggle_enable.attr, |
| @@ -794,29 +887,39 @@ static struct attribute *uniwill_attrs[] = { · 2 unmodified lines | ||
| 794 | 887 | /* Lightbar-related */ |
| 795 | 888 | &dev_attr_rainbow_animation.attr, |
| 796 | 889 | &dev_attr_breathing_in_suspend.attr, |
| 890 | + /* Power-management-related */ | |
| 891 | + &dev_attr_ctgp_offset.attr, | |
| 797 | 892 | NULL |
| 798 | 893 | }; |
| 799 | 894 | |
| 800 | 895 | static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) |
| 801 | 896 | { |
| 897 | + struct device *dev = kobj_to_dev(kobj); | |
| 898 | + struct uniwill_data *data = dev_get_drvdata(dev); | |
| 899 | + | |
| 802 | 900 | if (attr == &dev_attr_fn_lock_toggle_enable.attr) { |
| 803 | − if (supported_features & UNIWILL_FEATURE_FN_LOCK_TOGGLE) | |
| 901 | + if (uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK_TOGGLE)) | |
| 804 | 902 | return attr->mode; |
| 805 | 903 | } |
| 806 | 904 | |
| 807 | 905 | if (attr == &dev_attr_super_key_toggle_enable.attr) { |
| 808 | − if (supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE) | |
| 906 | + if (uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE)) | |
| 809 | 907 | return attr->mode; |
| 810 | 908 | } |
| 811 | 909 | |
| 812 | 910 | if (attr == &dev_attr_touchpad_toggle_enable.attr) { |
| 813 | − if (supported_features & UNIWILL_FEATURE_TOUCHPAD_TOGGLE) | |
| 911 | + if (uniwill_device_supports(data, UNIWILL_FEATURE_TOUCHPAD_TOGGLE)) | |
| 814 | 912 | return attr->mode; |
| 815 | 913 | } |
| 816 | 914 | |
| 817 | 915 | if (attr == &dev_attr_rainbow_animation.attr || |
| 818 | 916 | attr == &dev_attr_breathing_in_suspend.attr) { |
| 819 | − if (supported_features & UNIWILL_FEATURE_LIGHTBAR) | |
| 917 | + if (uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR)) | |
| 918 | + return attr->mode; | |
| 919 | + } | |
| 920 | + | |
| 921 | + if (attr == &dev_attr_ctgp_offset.attr) { | |
| 922 | + if (uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) | |
| 820 | 923 | return attr->mode; |
| 821 | 924 | } |
| 822 | 925 | |
| @@ -944,7 +1047,7 @@ static int uniwill_hwmon_init(struct uniwill_data *data) · 121 unmodified lines | ||
| 944 | 1047 | { |
| 945 | 1048 | struct device *hdev; |
| 946 | 1049 | |
| 947 | − if (!(supported_features & UNIWILL_FEATURE_HWMON)) | |
| 1050 | + if (!uniwill_device_supports(data, UNIWILL_FEATURE_HWMON)) | |
| 948 | 1051 | return 0; |
| 949 | 1052 | |
| 950 | 1053 | hdev = devm_hwmon_device_register_with_info(data->dev, "uniwill", data, |
| @@ -1019,7 +1122,7 @@ static int uniwill_led_init(struct uniwill_data *data) · 68 unmodified lines | ||
| 1019 | 1122 | unsigned int value; |
| 1020 | 1123 | int ret; |
| 1021 | 1124 | |
| 1022 | − if (!(supported_features & UNIWILL_FEATURE_LIGHTBAR)) | |
| 1125 | + if (!uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR)) | |
| 1023 | 1126 | return 0; |
| 1024 | 1127 | |
| 1025 | 1128 | ret = devm_mutex_init(data->dev, &data->led_lock); |
| @@ -1232,7 +1335,7 @@ static int uniwill_battery_init(struct uniwill_data *data) · 206 unmodified lines | ||
| 1232 | 1335 | { |
| 1233 | 1336 | int ret; |
| 1234 | 1337 | |
| 1235 | − if (!(supported_features & UNIWILL_FEATURE_BATTERY)) | |
| 1338 | + if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY)) | |
| 1236 | 1339 | return 0; |
| 1237 | 1340 | |
| 1238 | 1341 | ret = devm_mutex_init(data->dev, &data->battery_lock); |
| @@ -1361,6 +1464,19 @@ static int uniwill_probe(struct platform_device *pdev) · 122 unmodified lines | ||
| 1361 | 1464 | if (ret < 0) |
| 1362 | 1465 | return ret; |
| 1363 | 1466 | |
| 1467 | + data->features = device_descriptor.features; | |
| 1468 | + | |
| 1469 | + /* | |
| 1470 | + * Some devices might need to perform some device-specific initialization steps | |
| 1471 | + * before the supported features are initialized. Because of this we have to call | |
| 1472 | + * this callback just after the EC itself was initialized. | |
| 1473 | + */ | |
| 1474 | + if (device_descriptor.probe) { | |
| 1475 | + ret = device_descriptor.probe(data); | |
| 1476 | + if (ret < 0) | |
| 1477 | + return ret; | |
| 1478 | + } | |
| 1479 | + | |
| 1364 | 1480 | ret = uniwill_battery_init(data); |
| 1365 | 1481 | if (ret < 0) |
| 1366 | 1482 | return ret; |
| @@ -1373,6 +1489,10 @@ static int uniwill_probe(struct platform_device *pdev) · 6 unmodified lines | ||
| 1373 | 1489 | if (ret < 0) |
| 1374 | 1490 | return ret; |
| 1375 | 1491 | |
| 1492 | + ret = uniwill_nvidia_ctgp_init(data); | |
| 1493 | + if (ret < 0) | |
| 1494 | + return ret; | |
| 1495 | + | |
| 1376 | 1496 | return uniwill_input_init(data); |
| 1377 | 1497 | } |
| 1378 | 1498 | |
| @@ -1385,7 +1505,7 @@ static void uniwill_shutdown(struct platform_device *pdev) · 6 unmodified lines | ||
| 1385 | 1505 | |
| 1386 | 1506 | static int uniwill_suspend_keyboard(struct uniwill_data *data) |
| 1387 | 1507 | { |
| 1388 | − if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE)) | |
| 1508 | + if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE)) | |
| 1389 | 1509 | return 0; |
| 1390 | 1510 | |
| 1391 | 1511 | /* |
| @@ -1397,7 +1517,7 @@ static int uniwill_suspend_keyboard(struct uniwill_data *data) · 5 unmodified lines | ||
| 1397 | 1517 | |
| 1398 | 1518 | static int uniwill_suspend_battery(struct uniwill_data *data) |
| 1399 | 1519 | { |
| 1400 | − if (!(supported_features & UNIWILL_FEATURE_BATTERY)) | |
| 1520 | + if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY)) | |
| 1401 | 1521 | return 0; |
| 1402 | 1522 | |
| 1403 | 1523 | /* |
| @@ -1408,6 +1528,15 @@ static int uniwill_suspend_battery(struct uniwill_data *data) · 4 unmodified lines | ||
| 1408 | 1528 | return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); |
| 1409 | 1529 | } |
| 1410 | 1530 | |
| 1531 | +static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data) | |
| 1532 | +{ | |
| 1533 | + if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) | |
| 1534 | + return 0; | |
| 1535 | + | |
| 1536 | + return regmap_clear_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL, | |
| 1537 | + CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE); | |
| 1538 | +} | |
| 1539 | + | |
| 1411 | 1540 | static int uniwill_suspend(struct device *dev) |
| 1412 | 1541 | { |
| 1413 | 1542 | struct uniwill_data *data = dev_get_drvdata(dev); |
| @@ -1421,6 +1550,10 @@ static int uniwill_suspend(struct device *dev) · 7 unmodified lines | ||
| 1421 | 1550 | if (ret < 0) |
| 1422 | 1551 | return ret; |
| 1423 | 1552 | |
| 1553 | + ret = uniwill_suspend_nvidia_ctgp(data); | |
| 1554 | + if (ret < 0) | |
| 1555 | + return ret; | |
| 1556 | + | |
| 1424 | 1557 | regcache_cache_only(data->regmap, true); |
| 1425 | 1558 | regcache_mark_dirty(data->regmap); |
| 1426 | 1559 | |
| @@ -1432,7 +1565,7 @@ static int uniwill_resume_keyboard(struct uniwill_data *data) · 5 unmodified lines | ||
| 1432 | 1565 | unsigned int value; |
| 1433 | 1566 | int ret; |
| 1434 | 1567 | |
| 1435 | − if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE)) | |
| 1568 | + if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE)) | |
| 1436 | 1569 | return 0; |
| 1437 | 1570 | |
| 1438 | 1571 | ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); |
| @@ -1448,13 +1581,22 @@ static int uniwill_resume_keyboard(struct uniwill_data *data) · 9 unmodified lines | ||
| 1448 | 1581 | |
| 1449 | 1582 | static int uniwill_resume_battery(struct uniwill_data *data) |
| 1450 | 1583 | { |
| 1451 | − if (!(supported_features & UNIWILL_FEATURE_BATTERY)) | |
| 1584 | + if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY)) | |
| 1452 | 1585 | return 0; |
| 1453 | 1586 | |
| 1454 | 1587 | return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK, |
| 1455 | 1588 | data->last_charge_ctrl); |
| 1456 | 1589 | } |
| 1457 | 1590 | |
| 1591 | +static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) | |
| 1592 | +{ | |
| 1593 | + if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) | |
| 1594 | + return 0; | |
| 1595 | + | |
| 1596 | + return regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL, | |
| 1597 | + CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE); | |
| 1598 | +} | |
| 1599 | + | |
| 1458 | 1600 | static int uniwill_resume(struct device *dev) |
| 1459 | 1601 | { |
| 1460 | 1602 | struct uniwill_data *data = dev_get_drvdata(dev); |
| @@ -1470,7 +1612,11 @@ static int uniwill_resume(struct device *dev) · 9 unmodified lines | ||
| 1470 | 1612 | if (ret < 0) |
| 1471 | 1613 | return ret; |
| 1472 | 1614 | |
| 1473 | − return uniwill_resume_battery(data); | |
| 1615 | + ret = uniwill_resume_battery(data); | |
| 1616 | + if (ret < 0) | |
| 1617 | + return ret; | |
| 1618 | + | |
| 1619 | + return uniwill_resume_nvidia_ctgp(data); | |
| 1474 | 1620 | } |
| 1475 | 1621 | |
| 1476 | 1622 | static DEFINE_SIMPLE_DEV_PM_OPS(uniwill_pm_ops, uniwill_suspend, uniwill_resume); |
| @@ -1496,6 +1642,48 @@ static struct platform_driver uniwill_driver = { · 19 unmodified lines | ||
| 1496 | 1642 | .shutdown = uniwill_shutdown, |
| 1497 | 1643 | }; |
| 1498 | 1644 | |
| 1645 | +static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { | |
| 1646 | + .features = UNIWILL_FEATURE_FN_LOCK_TOGGLE | | |
| 1647 | + UNIWILL_FEATURE_SUPER_KEY_TOGGLE | | |
| 1648 | + UNIWILL_FEATURE_TOUCHPAD_TOGGLE | | |
| 1649 | + UNIWILL_FEATURE_BATTERY | | |
| 1650 | + UNIWILL_FEATURE_HWMON, | |
| 1651 | +}; | |
| 1652 | + | |
| 1653 | +static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { | |
| 1654 | + .features = UNIWILL_FEATURE_FN_LOCK_TOGGLE | | |
| 1655 | + UNIWILL_FEATURE_SUPER_KEY_TOGGLE | | |
| 1656 | + UNIWILL_FEATURE_TOUCHPAD_TOGGLE | | |
| 1657 | + UNIWILL_FEATURE_LIGHTBAR | | |
| 1658 | + UNIWILL_FEATURE_BATTERY | | |
| 1659 | + UNIWILL_FEATURE_HWMON, | |
| 1660 | +}; | |
| 1661 | + | |
| 1662 | +static int phxarx1_phxaqf1_probe(struct uniwill_data *data) | |
| 1663 | +{ | |
| 1664 | + unsigned int value; | |
| 1665 | + int ret; | |
| 1666 | + | |
| 1667 | + ret = regmap_read(data->regmap, EC_ADDR_SYSTEM_ID, &value); | |
| 1668 | + if (ret < 0) | |
| 1669 | + return ret; | |
| 1670 | + | |
| 1671 | + if (value & HAS_GPU) | |
| 1672 | + data->features |= UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL; | |
| 1673 | + | |
| 1674 | + return 0; | |
| 1675 | +}; | |
| 1676 | + | |
| 1677 | +static struct uniwill_device_descriptor phxarx1_phxaqf1_descriptor __initdata = { | |
| 1678 | + .probe = phxarx1_phxaqf1_probe, | |
| 1679 | +}; | |
| 1680 | + | |
| 1681 | +static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = { | |
| 1682 | + .features = UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, | |
| 1683 | +}; | |
| 1684 | + | |
| 1685 | +static struct uniwill_device_descriptor empty_descriptor __initdata = {}; | |
| 1686 | + | |
| 1499 | 1687 | static const struct dmi_system_id uniwill_dmi_table[] __initconst = { |
| 1500 | 1688 | { |
| 1501 | 1689 | .ident = "XMG FUSION 15", |
| @@ -1503,6 +1691,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1503 | 1691 | DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"), |
| 1504 | 1692 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"), |
| 1505 | 1693 | }, |
| 1694 | + .driver_data = &empty_descriptor, | |
| 1506 | 1695 | }, |
| 1507 | 1696 | { |
| 1508 | 1697 | .ident = "XMG FUSION 15", |
| @@ -1510,6 +1699,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1510 | 1699 | DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"), |
| 1511 | 1700 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"), |
| 1512 | 1701 | }, |
| 1702 | + .driver_data = &empty_descriptor, | |
| 1513 | 1703 | }, |
| 1514 | 1704 | { |
| 1515 | 1705 | .ident = "Intel NUC x15", |
| @@ -1517,11 +1707,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1517 | 1707 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), |
| 1518 | 1708 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPAC71H"), |
| 1519 | 1709 | }, |
| 1520 | − .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE | | |
| 1521 | − UNIWILL_FEATURE_SUPER_KEY_TOGGLE | | |
| 1522 | − UNIWILL_FEATURE_TOUCHPAD_TOGGLE | | |
| 1523 | − UNIWILL_FEATURE_BATTERY | | |
| 1524 | − UNIWILL_FEATURE_HWMON), | |
| 1710 | + .driver_data = &lapac71h_descriptor, | |
| 1525 | 1711 | }, |
| 1526 | 1712 | { |
| 1527 | 1713 | .ident = "Intel NUC x15", |
| @@ -1529,12 +1715,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1529 | 1715 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), |
| 1530 | 1716 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"), |
| 1531 | 1717 | }, |
| 1532 | − .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE | | |
| 1533 | − UNIWILL_FEATURE_SUPER_KEY_TOGGLE | | |
| 1534 | − UNIWILL_FEATURE_TOUCHPAD_TOGGLE | | |
| 1535 | − UNIWILL_FEATURE_LIGHTBAR | | |
| 1536 | − UNIWILL_FEATURE_BATTERY | | |
| 1537 | − UNIWILL_FEATURE_HWMON), | |
| 1718 | + .driver_data = &lapkc71f_descriptor, | |
| 1538 | 1719 | }, |
| 1539 | 1720 | { |
| 1540 | 1721 | .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel", |
| @@ -1542,6 +1723,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1542 | 1723 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1543 | 1724 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"), |
| 1544 | 1725 | }, |
| 1726 | + .driver_data = &empty_descriptor, | |
| 1545 | 1727 | }, |
| 1546 | 1728 | { |
| 1547 | 1729 | .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel", |
| @@ -1549,6 +1731,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1549 | 1731 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1550 | 1732 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"), |
| 1551 | 1733 | }, |
| 1734 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1552 | 1735 | }, |
| 1553 | 1736 | { |
| 1554 | 1737 | .ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel", |
| @@ -1556,6 +1739,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1556 | 1739 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1557 | 1740 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"), |
| 1558 | 1741 | }, |
| 1742 | + .driver_data = &phxarx1_phxaqf1_descriptor, | |
| 1559 | 1743 | }, |
| 1560 | 1744 | { |
| 1561 | 1745 | .ident = "TUXEDO InfinityBook Pro 16 Gen7 Intel/Commodore Omnia-Book Pro Gen 7", |
| @@ -1563,6 +1747,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1563 | 1747 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1564 | 1748 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"), |
| 1565 | 1749 | }, |
| 1750 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1566 | 1751 | }, |
| 1567 | 1752 | { |
| 1568 | 1753 | .ident = "TUXEDO InfinityBook Pro 14/16 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", |
| @@ -1570,6 +1755,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1570 | 1755 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1571 | 1756 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"), |
| 1572 | 1757 | }, |
| 1758 | + .driver_data = &empty_descriptor, | |
| 1573 | 1759 | }, |
| 1574 | 1760 | { |
| 1575 | 1761 | .ident = "TUXEDO InfinityBook Pro 14 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", |
| @@ -1577,6 +1763,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1577 | 1763 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1578 | 1764 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"), |
| 1579 | 1765 | }, |
| 1766 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1580 | 1767 | }, |
| 1581 | 1768 | { |
| 1582 | 1769 | .ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel", |
| @@ -1584,6 +1771,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1584 | 1771 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1585 | 1772 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"), |
| 1586 | 1773 | }, |
| 1774 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1587 | 1775 | }, |
| 1588 | 1776 | { |
| 1589 | 1777 | .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD", |
| @@ -1591,6 +1779,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1591 | 1779 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1592 | 1780 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"), |
| 1593 | 1781 | }, |
| 1782 | + .driver_data = &empty_descriptor, | |
| 1594 | 1783 | }, |
| 1595 | 1784 | { |
| 1596 | 1785 | .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9", |
| @@ -1598,6 +1787,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1598 | 1787 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1599 | 1788 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"), |
| 1600 | 1789 | }, |
| 1790 | + .driver_data = &empty_descriptor, | |
| 1601 | 1791 | }, |
| 1602 | 1792 | { |
| 1603 | 1793 | .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", |
| @@ -1605,6 +1795,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1605 | 1795 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1606 | 1796 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"), |
| 1607 | 1797 | }, |
| 1798 | + .driver_data = &empty_descriptor, | |
| 1608 | 1799 | }, |
| 1609 | 1800 | { |
| 1610 | 1801 | .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", |
| @@ -1612,6 +1803,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1612 | 1803 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1613 | 1804 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), |
| 1614 | 1805 | }, |
| 1806 | + .driver_data = &empty_descriptor, | |
| 1615 | 1807 | }, |
| 1616 | 1808 | { |
| 1617 | 1809 | .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel", |
| @@ -1619,6 +1811,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1619 | 1811 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1620 | 1812 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"), |
| 1621 | 1813 | }, |
| 1814 | + .driver_data = &empty_descriptor, | |
| 1622 | 1815 | }, |
| 1623 | 1816 | { |
| 1624 | 1817 | .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD", |
| @@ -1626,6 +1819,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1626 | 1819 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1627 | 1820 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), |
| 1628 | 1821 | }, |
| 1822 | + .driver_data = &empty_descriptor, | |
| 1629 | 1823 | }, |
| 1630 | 1824 | { |
| 1631 | 1825 | .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", |
| @@ -1633,6 +1827,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1633 | 1827 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1634 | 1828 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"), |
| 1635 | 1829 | }, |
| 1830 | + .driver_data = &empty_descriptor, | |
| 1636 | 1831 | }, |
| 1637 | 1832 | { |
| 1638 | 1833 | .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", |
| @@ -1640,6 +1835,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1640 | 1835 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1641 | 1836 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"), |
| 1642 | 1837 | }, |
| 1838 | + .driver_data = &empty_descriptor, | |
| 1643 | 1839 | }, |
| 1644 | 1840 | { |
| 1645 | 1841 | .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel", |
| @@ -1647,6 +1843,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1647 | 1843 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1648 | 1844 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"), |
| 1649 | 1845 | }, |
| 1846 | + .driver_data = &empty_descriptor, | |
| 1650 | 1847 | }, |
| 1651 | 1848 | { |
| 1652 | 1849 | .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel", |
| @@ -1654,6 +1851,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1654 | 1851 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1655 | 1852 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"), |
| 1656 | 1853 | }, |
| 1854 | + .driver_data = &empty_descriptor, | |
| 1657 | 1855 | }, |
| 1658 | 1856 | { |
| 1659 | 1857 | .ident = "TUXEDO Polaris 15 Gen1 AMD", |
| @@ -1661,6 +1859,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1661 | 1859 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1662 | 1860 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"), |
| 1663 | 1861 | }, |
| 1862 | + .driver_data = &empty_descriptor, | |
| 1664 | 1863 | }, |
| 1665 | 1864 | { |
| 1666 | 1865 | .ident = "TUXEDO Polaris 15 Gen1 AMD", |
| @@ -1668,6 +1867,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1668 | 1867 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1669 | 1868 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"), |
| 1670 | 1869 | }, |
| 1870 | + .driver_data = &empty_descriptor, | |
| 1671 | 1871 | }, |
| 1672 | 1872 | { |
| 1673 | 1873 | .ident = "TUXEDO Polaris 17 Gen1 AMD", |
| @@ -1675,6 +1875,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1675 | 1875 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1676 | 1876 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"), |
| 1677 | 1877 | }, |
| 1878 | + .driver_data = &empty_descriptor, | |
| 1678 | 1879 | }, |
| 1679 | 1880 | { |
| 1680 | 1881 | .ident = "TUXEDO Polaris 17 Gen1 AMD", |
| @@ -1682,6 +1883,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1682 | 1883 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1683 | 1884 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"), |
| 1684 | 1885 | }, |
| 1886 | + .driver_data = &empty_descriptor, | |
| 1685 | 1887 | }, |
| 1686 | 1888 | { |
| 1687 | 1889 | .ident = "TUXEDO Polaris 15 Gen1 Intel", |
| @@ -1689,6 +1891,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1689 | 1891 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1690 | 1892 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"), |
| 1691 | 1893 | }, |
| 1894 | + .driver_data = &empty_descriptor, | |
| 1692 | 1895 | }, |
| 1693 | 1896 | { |
| 1694 | 1897 | .ident = "TUXEDO Polaris 15 Gen1 Intel", |
| @@ -1696,6 +1899,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1696 | 1899 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1697 | 1900 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"), |
| 1698 | 1901 | }, |
| 1902 | + .driver_data = &empty_descriptor, | |
| 1699 | 1903 | }, |
| 1700 | 1904 | { |
| 1701 | 1905 | .ident = "TUXEDO Polaris 17 Gen1 Intel", |
| @@ -1703,6 +1907,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1703 | 1907 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1704 | 1908 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"), |
| 1705 | 1909 | }, |
| 1910 | + .driver_data = &empty_descriptor, | |
| 1706 | 1911 | }, |
| 1707 | 1912 | { |
| 1708 | 1913 | .ident = "TUXEDO Polaris 17 Gen1 Intel", |
| @@ -1710,6 +1915,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1710 | 1915 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1711 | 1916 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"), |
| 1712 | 1917 | }, |
| 1918 | + .driver_data = &empty_descriptor, | |
| 1713 | 1919 | }, |
| 1714 | 1920 | { |
| 1715 | 1921 | .ident = "TUXEDO Trinity 15 Intel Gen1", |
| @@ -1717,6 +1923,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1717 | 1923 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1718 | 1924 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"), |
| 1719 | 1925 | }, |
| 1926 | + .driver_data = &empty_descriptor, | |
| 1720 | 1927 | }, |
| 1721 | 1928 | { |
| 1722 | 1929 | .ident = "TUXEDO Trinity 17 Intel Gen1", |
| @@ -1724,6 +1931,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1724 | 1931 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1725 | 1932 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"), |
| 1726 | 1933 | }, |
| 1934 | + .driver_data = &empty_descriptor, | |
| 1727 | 1935 | }, |
| 1728 | 1936 | { |
| 1729 | 1937 | .ident = "TUXEDO Polaris 15/17 Gen2 AMD", |
| @@ -1731,6 +1939,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1731 | 1939 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1732 | 1940 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"), |
| 1733 | 1941 | }, |
| 1942 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1734 | 1943 | }, |
| 1735 | 1944 | { |
| 1736 | 1945 | .ident = "TUXEDO Polaris 15/17 Gen2 Intel", |
| @@ -1738,6 +1947,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1738 | 1947 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1739 | 1948 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"), |
| 1740 | 1949 | }, |
| 1950 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1741 | 1951 | }, |
| 1742 | 1952 | { |
| 1743 | 1953 | .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD", |
| @@ -1745,6 +1955,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1745 | 1955 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1746 | 1956 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"), |
| 1747 | 1957 | }, |
| 1958 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1748 | 1959 | }, |
| 1749 | 1960 | { |
| 1750 | 1961 | .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel", |
| @@ -1752,6 +1963,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1752 | 1963 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1753 | 1964 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"), |
| 1754 | 1965 | }, |
| 1966 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1755 | 1967 | }, |
| 1756 | 1968 | { |
| 1757 | 1969 | .ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD", |
| @@ -1759,6 +1971,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1759 | 1971 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1760 | 1972 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"), |
| 1761 | 1973 | }, |
| 1974 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1762 | 1975 | }, |
| 1763 | 1976 | { |
| 1764 | 1977 | .ident = "TUXEDO Stellaris 15 Gen4 Intel", |
| @@ -1766,6 +1979,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1766 | 1979 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1767 | 1980 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"), |
| 1768 | 1981 | }, |
| 1982 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1769 | 1983 | }, |
| 1770 | 1984 | { |
| 1771 | 1985 | .ident = "TUXEDO Polaris 15/17 Gen5 AMD", |
| @@ -1773,6 +1987,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1773 | 1987 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1774 | 1988 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"), |
| 1775 | 1989 | }, |
| 1990 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1776 | 1991 | }, |
| 1777 | 1992 | { |
| 1778 | 1993 | .ident = "TUXEDO Stellaris 16 Gen5 AMD", |
| @@ -1780,6 +1995,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1780 | 1995 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1781 | 1996 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"), |
| 1782 | 1997 | }, |
| 1998 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1783 | 1999 | }, |
| 1784 | 2000 | { |
| 1785 | 2001 | .ident = "TUXEDO Stellaris 16/17 Gen5 Intel/Commodore ORION Gen 5", |
| @@ -1787,6 +2003,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1787 | 2003 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1788 | 2004 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"), |
| 1789 | 2005 | }, |
| 2006 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1790 | 2007 | }, |
| 1791 | 2008 | { |
| 1792 | 2009 | .ident = "TUXEDO Stellaris Slim 15 Gen6 AMD", |
| @@ -1794,6 +2011,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1794 | 2011 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1795 | 2012 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"), |
| 1796 | 2013 | }, |
| 2014 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1797 | 2015 | }, |
| 1798 | 2016 | { |
| 1799 | 2017 | .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6", |
| @@ -1801,6 +2019,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1801 | 2019 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1802 | 2020 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"), |
| 1803 | 2021 | }, |
| 2022 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1804 | 2023 | }, |
| 1805 | 2024 | { |
| 1806 | 2025 | .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", |
| @@ -1808,6 +2027,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1808 | 2027 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1809 | 2028 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"), |
| 1810 | 2029 | }, |
| 2030 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1811 | 2031 | }, |
| 1812 | 2032 | { |
| 1813 | 2033 | .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", |
| @@ -1815,6 +2035,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1815 | 2035 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1816 | 2036 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"), |
| 1817 | 2037 | }, |
| 2038 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1818 | 2039 | }, |
| 1819 | 2040 | { |
| 1820 | 2041 | .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6", |
| @@ -1822,6 +2043,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1822 | 2043 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1823 | 2044 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"), |
| 1824 | 2045 | }, |
| 2046 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1825 | 2047 | }, |
| 1826 | 2048 | { |
| 1827 | 2049 | .ident = "TUXEDO Stellaris 16 Gen7 AMD", |
| @@ -1829,6 +2051,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1829 | 2051 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1830 | 2052 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"), |
| 1831 | 2053 | }, |
| 2054 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1832 | 2055 | }, |
| 1833 | 2056 | { |
| 1834 | 2057 | .ident = "TUXEDO Stellaris 16 Gen7 Intel", |
| @@ -1836,6 +2059,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1836 | 2059 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1837 | 2060 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"), |
| 1838 | 2061 | }, |
| 2062 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1839 | 2063 | }, |
| 1840 | 2064 | { |
| 1841 | 2065 | .ident = "TUXEDO Stellaris 16 Gen7 Intel", |
| @@ -1843,6 +2067,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1843 | 2067 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1844 | 2068 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"), |
| 1845 | 2069 | }, |
| 2070 | + .driver_data = &tux_featureset_1_descriptor, | |
| 1846 | 2071 | }, |
| 1847 | 2072 | { |
| 1848 | 2073 | .ident = "TUXEDO Book BA15 Gen10 AMD", |
| @@ -1850,6 +2075,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1850 | 2075 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1851 | 2076 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"), |
| 1852 | 2077 | }, |
| 2078 | + .driver_data = &empty_descriptor, | |
| 1853 | 2079 | }, |
| 1854 | 2080 | { |
| 1855 | 2081 | .ident = "TUXEDO Pulse 14 Gen1 AMD", |
| @@ -1857,6 +2083,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1857 | 2083 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1858 | 2084 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"), |
| 1859 | 2085 | }, |
| 2086 | + .driver_data = &empty_descriptor, | |
| 1860 | 2087 | }, |
| 1861 | 2088 | { |
| 1862 | 2089 | .ident = "TUXEDO Pulse 15 Gen1 AMD", |
| @@ -1864,6 +2091,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1864 | 2091 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1865 | 2092 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"), |
| 1866 | 2093 | }, |
| 2094 | + .driver_data = &empty_descriptor, | |
| 1867 | 2095 | }, |
| 1868 | 2096 | { |
| 1869 | 2097 | .ident = "TUXEDO Pulse 15 Gen2 AMD", |
| @@ -1871,6 +2099,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | ||
| 1871 | 2099 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1872 | 2100 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"), |
| 1873 | 2101 | }, |
| 2102 | + .driver_data = &empty_descriptor, | |
| 1874 | 2103 | }, |
| 1875 | 2104 | { } |
| 1876 | 2105 | }; |
| @@ -1878,6 +2107,7 @@ MODULE_DEVICE_TABLE(dmi, uniwill_dmi_table); · 1 unmodified line | ||
| 1878 | 2107 | |
| 1879 | 2108 | static int __init uniwill_init(void) |
| 1880 | 2109 | { |
| 2110 | + const struct uniwill_device_descriptor *descriptor; | |
| 1881 | 2111 | const struct dmi_system_id *id; |
| 1882 | 2112 | int ret; |
| 1883 | 2113 | |
| @@ -1887,10 +2117,22 @@ static int __init uniwill_init(void) · 3 unmodified lines | ||
| 1887 | 2117 | return -ENODEV; |
| 1888 | 2118 | |
| 1889 | 2119 | /* Assume that the device supports all features */ |
| 1890 | − supported_features = UINT_MAX; | |
| 2120 | + device_descriptor.features = UINT_MAX; | |
| 1891 | 2121 | pr_warn("Loading on a potentially unsupported device\n"); |
| 1892 | 2122 | } else { |
| 1893 | − supported_features = (uintptr_t)id->driver_data; | |
| 2123 | + /* | |
| 2124 | + * Some devices might support additional features depending on | |
| 2125 | + * the BIOS version/date, so we call this callback to let them | |
| 2126 | + * modify their device descriptor accordingly. | |
| 2127 | + */ | |
| 2128 | + if (id->callback) { | |
| 2129 | + ret = id->callback(id); | |
| 2130 | + if (ret < 0) | |
| 2131 | + return ret; | |
| 2132 | + } | |
| 2133 | + | |
| 2134 | + descriptor = id->driver_data; | |
| 2135 | + device_descriptor = *descriptor; | |
| 1894 | 2136 | } |
| 1895 | 2137 | |
| 1896 | 2138 | ret = platform_driver_register(&uniwill_driver); |
| @@ -8,7 +8,6 @@ · 7 unmodified lines | ||
| 8 | 8 | |
| 9 | 9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 10 | 10 | |
| 11 | −#include <linux/acpi.h> | |
| 12 | 11 | #include <linux/device.h> |
| 13 | 12 | #include <linux/fs.h> |
| 14 | 13 | #include <linux/kernel.h> |
| @@ -24,9 +23,9 @@ static ssize_t bmof_read(struct file *filp, struct kobject *kobj, const struct b · 9 unmodified lines | ||
| 24 | 23 | char *buf, loff_t off, size_t count) |
| 25 | 24 | { |
| 26 | 25 | struct device *dev = kobj_to_dev(kobj); |
| 27 | − union acpi_object *obj = dev_get_drvdata(dev); | |
| 26 | + struct wmi_buffer *buffer = dev_get_drvdata(dev); | |
| 28 | 27 | |
| 29 | − return memory_read_from_buffer(buf, count, &off, obj->buffer.pointer, obj->buffer.length); | |
| 28 | + return memory_read_from_buffer(buf, count, &off, buffer->data, buffer->length); | |
| 30 | 29 | } |
| 31 | 30 | |
| 32 | 31 | static const BIN_ATTR_ADMIN_RO(bmof, 0); |
| @@ -39,9 +38,9 @@ static const struct bin_attribute * const bmof_attrs[] = { · 6 unmodified lines | ||
| 39 | 38 | static size_t bmof_bin_size(struct kobject *kobj, const struct bin_attribute *attr, int n) |
| 40 | 39 | { |
| 41 | 40 | struct device *dev = kobj_to_dev(kobj); |
| 42 | − union acpi_object *obj = dev_get_drvdata(dev); | |
| 41 | + struct wmi_buffer *buffer = dev_get_drvdata(dev); | |
| 43 | 42 | |
| 44 | − return obj->buffer.length; | |
| 43 | + return buffer->length; | |
| 45 | 44 | } |
| 46 | 45 | |
| 47 | 46 | static const struct attribute_group bmof_group = { |
| @@ -56,30 +55,27 @@ static const struct attribute_group *bmof_groups[] = { · 8 unmodified lines | ||
| 56 | 55 | |
| 57 | 56 | static int wmi_bmof_probe(struct wmi_device *wdev, const void *context) |
| 58 | 57 | { |
| 59 | − union acpi_object *obj; | |
| 58 | + struct wmi_buffer *buffer; | |
| 59 | + int ret; | |
| 60 | 60 | |
| 61 | − obj = wmidev_block_query(wdev, 0); | |
| 62 | − if (!obj) { | |
| 63 | − dev_err(&wdev->dev, "failed to read Binary MOF\n"); | |
| 64 | − return -EIO; | |
| 65 | − } | |
| 61 | + buffer = devm_kzalloc(&wdev->dev, sizeof(*buffer), GFP_KERNEL); | |
| 62 | + if (!buffer) | |
| 63 | + return -ENOMEM; | |
| 66 | 64 | |
| 67 | − if (obj->type != ACPI_TYPE_BUFFER) { | |
| 68 | − dev_err(&wdev->dev, "Binary MOF is not a buffer\n"); | |
| 69 | − kfree(obj); | |
| 70 | − return -EIO; | |
| 71 | − } | |
| 65 | + ret = wmidev_query_block(wdev, 0, buffer); | |
| 66 | + if (ret < 0) | |
| 67 | + return ret; | |
| 72 | 68 | |
| 73 | − dev_set_drvdata(&wdev->dev, obj); | |
| 69 | + dev_set_drvdata(&wdev->dev, buffer); | |
| 74 | 70 | |
| 75 | 71 | return 0; |
| 76 | 72 | } |
| 77 | 73 | |
| 78 | 74 | static void wmi_bmof_remove(struct wmi_device *wdev) |
| 79 | 75 | { |
| 80 | − union acpi_object *obj = dev_get_drvdata(&wdev->dev); | |
| 76 | + struct wmi_buffer *buffer = dev_get_drvdata(&wdev->dev); | |
| 81 | 77 | |
| 82 | − kfree(obj); | |
| 78 | + kfree(buffer->data); | |
| 83 | 79 | } |
| 84 | 80 | |
| 85 | 81 | static const struct wmi_device_id wmi_bmof_id_table[] = { |
| @@ -1,7 +1,6 @@ | ||
| 1 | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | 2 | /* WMI driver for Xiaomi Laptops */ |
| 3 | 3 | |
| 4 | −#include <linux/acpi.h> | |
| 5 | 4 | #include <linux/device.h> |
| 6 | 5 | #include <linux/input.h> |
| 7 | 6 | #include <linux/module.h> |
| @@ -56,7 +55,7 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context) · 48 unmodified lines | ||
| 56 | 55 | return input_register_device(data->input_dev); |
| 57 | 56 | } |
| 58 | 57 | |
| 59 | −static void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy) | |
| 58 | +static void xiaomi_wmi_notify(struct wmi_device *wdev, const struct wmi_buffer *dummy) | |
| 60 | 59 | { |
| 61 | 60 | struct xiaomi_wmi *data = dev_get_drvdata(&wdev->dev); |
| 62 | 61 | |
| @@ -85,7 +84,7 @@ static struct wmi_driver xiaomi_wmi_driver = { · 22 unmodified lines | ||
| 85 | 84 | }, |
| 86 | 85 | .id_table = xiaomi_wmi_id_table, |
| 87 | 86 | .probe = xiaomi_wmi_probe, |
| 88 | − .notify = xiaomi_wmi_notify, | |
| 87 | + .notify_new = xiaomi_wmi_notify, | |
| 89 | 88 | .no_singleton = true, |
| 90 | 89 | }; |
| 91 | 90 | module_wmi_driver(xiaomi_wmi_driver); |
| @@ -61,5 +61,26 @@ enum laptop_placement { · 60 unmodified lines | ||
| 61 | 61 | LP_UNDEFINED, |
| 62 | 62 | }; |
| 63 | 63 | |
| 64 | +/** | |
| 65 | + * struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver | |
| 66 | + * @npuclk_freq: NPU clock frequency [MHz] | |
| 67 | + * @npu_busy: NPU busy % [0-100] | |
| 68 | + * @npu_power: NPU power [mW] | |
| 69 | + * @mpnpuclk_freq: MPNPU [MHz] | |
| 70 | + * @npu_reads: NPU read bandwidth [MB/sec] | |
| 71 | + * @npu_writes: NPU write bandwidth [MB/sec] | |
| 72 | + */ | |
| 73 | +struct amd_pmf_npu_metrics { | |
| 74 | + u16 npuclk_freq; | |
| 75 | + u16 npu_busy[8]; | |
| 76 | + u16 npu_power; | |
| 77 | + u16 mpnpuclk_freq; | |
| 78 | + u16 npu_reads; | |
| 79 | + u16 npu_writes; | |
| 80 | +}; | |
| 81 | + | |
| 64 | 82 | int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op); |
| 83 | + | |
| 84 | +/* AMD PMF and NPU interface */ | |
| 85 | +int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info); | |
| 65 | 86 | #endif |
| @@ -1,50 +0,0 @@ | ||
| 1 | −/* SPDX-License-Identifier: GPL-2.0 */ | |
| 2 | −#ifndef __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H | |
| 3 | −#define __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H | |
| 4 | − | |
| 5 | −#include <linux/dmi.h> | |
| 6 | −#include <linux/types.h> | |
| 7 | − | |
| 8 | −/* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */ | |
| 9 | −#if IS_REACHABLE(CONFIG_ASUS_WMI) || IS_REACHABLE(CONFIG_HID_ASUS) | |
| 10 | −static const struct dmi_system_id asus_use_hid_led_dmi_ids[] = { | |
| 11 | − { | |
| 12 | − .matches = { | |
| 13 | − DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Zephyrus"), | |
| 14 | − }, | |
| 15 | − }, | |
| 16 | − { | |
| 17 | − .matches = { | |
| 18 | − DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Strix"), | |
| 19 | − }, | |
| 20 | − }, | |
| 21 | − { | |
| 22 | − .matches = { | |
| 23 | − DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Flow"), | |
| 24 | − }, | |
| 25 | − }, | |
| 26 | − { | |
| 27 | − .matches = { | |
| 28 | − DMI_MATCH(DMI_PRODUCT_FAMILY, "ProArt P16"), | |
| 29 | − }, | |
| 30 | − }, | |
| 31 | − { | |
| 32 | − .matches = { | |
| 33 | − DMI_MATCH(DMI_BOARD_NAME, "GA403U"), | |
| 34 | − }, | |
| 35 | − }, | |
| 36 | − { | |
| 37 | − .matches = { | |
| 38 | − DMI_MATCH(DMI_BOARD_NAME, "GU605M"), | |
| 39 | − }, | |
| 40 | − }, | |
| 41 | − { | |
| 42 | − .matches = { | |
| 43 | − DMI_MATCH(DMI_BOARD_NAME, "RC71L"), | |
| 44 | − }, | |
| 45 | − }, | |
| 46 | − { }, | |
| 47 | −}; | |
| 48 | −#endif | |
| 49 | − | |
| 50 | −#endif /* __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H */ | |
| @@ -173,12 +173,29 @@ enum asus_ally_mcu_hack { · 172 unmodified lines | ||
| 173 | 173 | ASUS_WMI_ALLY_MCU_HACK_DISABLED, |
| 174 | 174 | }; |
| 175 | 175 | |
| 176 | +/* Used to notify hid-asus when asus-wmi changes keyboard backlight */ | |
| 177 | +struct asus_hid_listener { | |
| 178 | + struct list_head list; | |
| 179 | + void (*brightness_set)(struct asus_hid_listener *listener, int brightness); | |
| 180 | +}; | |
| 181 | + | |
| 182 | +enum asus_hid_event { | |
| 183 | + ASUS_EV_BRTUP, | |
| 184 | + ASUS_EV_BRTDOWN, | |
| 185 | + ASUS_EV_BRTTOGGLE, | |
| 186 | +}; | |
| 187 | + | |
| 188 | +#define ASUS_EV_MAX_BRIGHTNESS 3 | |
| 189 | + | |
| 176 | 190 | #if IS_REACHABLE(CONFIG_ASUS_WMI) |
| 177 | 191 | void set_ally_mcu_hack(enum asus_ally_mcu_hack status); |
| 178 | 192 | void set_ally_mcu_powersave(bool enabled); |
| 179 | 193 | int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval); |
| 180 | 194 | int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval); |
| 181 | 195 | int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); |
| 196 | +int asus_hid_register_listener(struct asus_hid_listener *cdev); | |
| 197 | +void asus_hid_unregister_listener(struct asus_hid_listener *cdev); | |
| 198 | +int asus_hid_event(enum asus_hid_event event); | |
| 182 | 199 | #else |
| 183 | 200 | static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) |
| 184 | 201 | { |
| @@ -199,6 +216,17 @@ static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, · 14 unmodified lines | ||
| 199 | 216 | { |
| 200 | 217 | return -ENODEV; |
| 201 | 218 | } |
| 219 | +static inline int asus_hid_register_listener(struct asus_hid_listener *bdev) | |
| 220 | +{ | |
| 221 | + return -ENODEV; | |
| 222 | +} | |
| 223 | +static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev) | |
| 224 | +{ | |
| 225 | +} | |
| 226 | +static inline int asus_hid_event(enum asus_hid_event event) | |
| 227 | +{ | |
| 228 | + return -ENODEV; | |
| 229 | +} | |
| 202 | 230 | #endif |
| 203 | 231 | |
| 204 | 232 | #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */ |
| @@ -18,6 +18,7 @@ · 17 unmodified lines | ||
| 18 | 18 | * and should include an appropriate local definition in their source file. |
| 19 | 19 | */ |
| 20 | 20 | #define PSP_CMDRESP_STS GENMASK(15, 0) |
| 21 | +#define PSP_TEE_STS_RING_BUSY 0x0000000d /* Ring already initialized */ | |
| 21 | 22 | #define PSP_CMDRESP_CMD GENMASK(23, 16) |
| 22 | 23 | #define PSP_CMDRESP_RESERVED GENMASK(29, 24) |
| 23 | 24 | #define PSP_CMDRESP_RECOVERY BIT(30) |
| @@ -8,9 +8,11 @@ · 7 unmodified lines | ||
| 8 | 8 | #ifndef _LINUX_WMI_H |
| 9 | 9 | #define _LINUX_WMI_H |
| 10 | 10 | |
| 11 | +#include <linux/compiler_attributes.h> | |
| 11 | 12 | #include <linux/device.h> |
| 12 | 13 | #include <linux/acpi.h> |
| 13 | 14 | #include <linux/mod_devicetable.h> |
| 15 | +#include <linux/types.h> | |
| 14 | 16 | |
| 15 | 17 | /** |
| 16 | 18 | * struct wmi_device - WMI device structure |
| @@ -36,6 +38,42 @@ struct wmi_device { · 19 unmodified lines | ||
| 36 | 38 | */ |
| 37 | 39 | #define to_wmi_device(device) container_of_const(device, struct wmi_device, dev) |
| 38 | 40 | |
| 41 | +/** | |
| 42 | + * struct wmi_buffer - WMI data buffer | |
| 43 | + * @length: Buffer length in bytes | |
| 44 | + * @data: Pointer to the buffer content | |
| 45 | + * | |
| 46 | + * This structure is used to exchange data with the WMI driver core. | |
| 47 | + */ | |
| 48 | +struct wmi_buffer { | |
| 49 | + size_t length; | |
| 50 | + void *data; | |
| 51 | +}; | |
| 52 | + | |
| 53 | +/** | |
| 54 | + * struct wmi_string - WMI string representation | |
| 55 | + * @length: Size of @chars in bytes | |
| 56 | + * @chars: UTF16-LE characters with optional nul termination and padding | |
| 57 | + * | |
| 58 | + * This structure is used when exchanging string data over the WMI interface. | |
| 59 | + */ | |
| 60 | +struct wmi_string { | |
| 61 | + __le16 length; | |
| 62 | + __le16 chars[]; | |
| 63 | +} __packed; | |
| 64 | + | |
| 65 | +ssize_t wmi_string_to_utf8s(const struct wmi_string *str, u8 *dst, size_t length); | |
| 66 | + | |
| 67 | +ssize_t wmi_string_from_utf8s(struct wmi_string *str, size_t max_chars, const u8 *src, | |
| 68 | + size_t src_length); | |
| 69 | + | |
| 70 | +int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id, | |
| 71 | + const struct wmi_buffer *in, struct wmi_buffer *out); | |
| 72 | + | |
| 73 | +int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out); | |
| 74 | + | |
| 75 | +int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in); | |
| 76 | + | |
| 39 | 77 | acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id, |
| 40 | 78 | const struct acpi_buffer *in, struct acpi_buffer *out); |
| 41 | 79 | |
| @@ -54,9 +92,11 @@ u8 wmidev_instance_count(struct wmi_device *wdev); · 12 unmodified lines | ||
| 54 | 92 | * @probe: Callback for device binding |
| 55 | 93 | * @remove: Callback for device unbinding |
| 56 | 94 | * @shutdown: Callback for device shutdown |
| 57 | − * @notify: Callback for receiving WMI events | |
| 95 | + * @notify: Callback for receiving WMI events (deprecated) | |
| 96 | + * @notify_new: Callback for receiving WMI events | |
| 58 | 97 | * |
| 59 | − * This represents WMI drivers which handle WMI devices. | |
| 98 | + * This represents WMI drivers which handle WMI devices. The data inside the buffer | |
| 99 | + * passed to the @notify_new callback is guaranteed to be aligned on a 8-byte boundary. | |
| 60 | 100 | */ |
| 61 | 101 | struct wmi_driver { |
| 62 | 102 | struct device_driver driver; |
| @@ -68,6 +108,7 @@ struct wmi_driver { · 5 unmodified lines | ||
| 68 | 108 | void (*remove)(struct wmi_device *wdev); |
| 69 | 109 | void (*shutdown)(struct wmi_device *wdev); |
| 70 | 110 | void (*notify)(struct wmi_device *device, union acpi_object *data); |
| 111 | + void (*notify_new)(struct wmi_device *device, const struct wmi_buffer *data); | |
| 71 | 112 | }; |
| 72 | 113 | |
| 73 | 114 | /** |
| @@ -13,7 +13,13 @@ endif · 12 unmodified lines | ||
| 13 | 13 | # Do not use make's built-in rules |
| 14 | 14 | # (this improves performance and avoids hard-to-debug behaviour); |
| 15 | 15 | MAKEFLAGS += -r |
| 16 | −override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include -I$(shell $(CC) -print-sysroot)/usr/include/libnl3 | |
| 16 | + | |
| 17 | +NL3_CFLAGS = $(shell pkg-config --cflags libnl-3.0 2>/dev/null) | |
| 18 | +ifeq ($(NL3_CFLAGS),) | |
| 19 | +NL3_CFLAGS = -I/usr/include/libnl3 | |
| 20 | +endif | |
| 21 | + | |
| 22 | +override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include $(NL3_CFLAGS) | |
| 17 | 23 | override LDFLAGS += -lnl-genl-3 -lnl-3 |
| 18 | 24 | |
| 19 | 25 | ALL_TARGETS := intel-speed-select |
| @@ -16,7 +16,7 @@ struct process_cmd_struct { · 15 unmodified lines | ||
| 16 | 16 | int arg; |
| 17 | 17 | }; |
| 18 | 18 | |
| 19 | −static const char *version_str = "v1.24"; | |
| 19 | +static const char *version_str = "v1.25"; | |
| 20 | 20 | |
| 21 | 21 | static const int supported_api_ver = 3; |
| 22 | 22 | static struct isst_if_platform_info isst_platform_info; |
| @@ -80,6 +80,18 @@ struct cpu_topology { · 57 unmodified lines | ||
| 80 | 80 | short die_id; |
| 81 | 81 | }; |
| 82 | 82 | |
| 83 | +static int read_only; | |
| 84 | + | |
| 85 | +static void check_privilege(void) | |
| 86 | +{ | |
| 87 | + if (!read_only) | |
| 88 | + return; | |
| 89 | + | |
| 90 | + isst_display_error_info_message(1, "Insufficient privileges", 0, 0); | |
| 91 | + isst_ctdp_display_information_end(outf); | |
| 92 | + exit(1); | |
| 93 | +} | |
| 94 | + | |
| 83 | 95 | FILE *get_output_file(void) |
| 84 | 96 | { |
| 85 | 97 | return outf; |
| @@ -950,9 +962,11 @@ int isolate_cpus(struct isst_id *id, int mask_size, cpu_set_t *cpu_mask, int lev · 864 unmodified lines | ||
| 950 | 962 | ret = write(fd, "member", strlen("member")); |
| 951 | 963 | if (ret == -1) { |
| 952 | 964 | printf("Can't update to member\n"); |
| 965 | + close(fd); | |
| 953 | 966 | return ret; |
| 954 | 967 | } |
| 955 | 968 | |
| 969 | + close(fd); | |
| 956 | 970 | return 0; |
| 957 | 971 | } |
| 958 | 972 | |
| @@ -1578,6 +1592,8 @@ free_mask: · 619 unmodified lines | ||
| 1578 | 1592 | |
| 1579 | 1593 | static void set_tdp_level(int arg) |
| 1580 | 1594 | { |
| 1595 | + check_privilege(); | |
| 1596 | + | |
| 1581 | 1597 | if (cmd_help) { |
| 1582 | 1598 | fprintf(stderr, "Set Config TDP level\n"); |
| 1583 | 1599 | fprintf(stderr, |
| @@ -2046,6 +2062,8 @@ static void set_pbf_enable(int arg) · 462 unmodified lines | ||
| 2046 | 2062 | { |
| 2047 | 2063 | int enable = arg; |
| 2048 | 2064 | |
| 2065 | + check_privilege(); | |
| 2066 | + | |
| 2049 | 2067 | if (cmd_help) { |
| 2050 | 2068 | if (enable) { |
| 2051 | 2069 | fprintf(stderr, |
| @@ -2212,6 +2230,8 @@ static void set_fact_enable(int arg) · 160 unmodified lines | ||
| 2212 | 2230 | int i, ret, enable = arg; |
| 2213 | 2231 | struct isst_id id; |
| 2214 | 2232 | |
| 2233 | + check_privilege(); | |
| 2234 | + | |
| 2215 | 2235 | if (cmd_help) { |
| 2216 | 2236 | if (enable) { |
| 2217 | 2237 | fprintf(stderr, |
| @@ -2361,6 +2381,8 @@ static void set_clos_enable(int arg) · 143 unmodified lines | ||
| 2361 | 2381 | { |
| 2362 | 2382 | int enable = arg; |
| 2363 | 2383 | |
| 2384 | + check_privilege(); | |
| 2385 | + | |
| 2364 | 2386 | if (cmd_help) { |
| 2365 | 2387 | if (enable) { |
| 2366 | 2388 | fprintf(stderr, |
| @@ -2491,6 +2513,8 @@ static void set_clos_config_for_cpu(struct isst_id *id, void *arg1, void *arg2, · 124 unmodified lines | ||
| 2491 | 2513 | |
| 2492 | 2514 | static void set_clos_config(int arg) |
| 2493 | 2515 | { |
| 2516 | + check_privilege(); | |
| 2517 | + | |
| 2494 | 2518 | if (cmd_help) { |
| 2495 | 2519 | fprintf(stderr, |
| 2496 | 2520 | "Set core-power configuration for one of the four clos ids\n"); |
| @@ -2556,6 +2580,8 @@ static void set_clos_assoc_for_cpu(struct isst_id *id, void *arg1, void *arg2, v · 59 unmodified lines | ||
| 2556 | 2580 | |
| 2557 | 2581 | static void set_clos_assoc(int arg) |
| 2558 | 2582 | { |
| 2583 | + check_privilege(); | |
| 2584 | + | |
| 2559 | 2585 | if (cmd_help) { |
| 2560 | 2586 | fprintf(stderr, "Associate a clos id to a CPU\n"); |
| 2561 | 2587 | fprintf(stderr, |
| @@ -2637,6 +2663,8 @@ static void set_turbo_mode(int arg) · 75 unmodified lines | ||
| 2637 | 2663 | int i, disable = arg; |
| 2638 | 2664 | struct isst_id id; |
| 2639 | 2665 | |
| 2666 | + check_privilege(); | |
| 2667 | + | |
| 2640 | 2668 | if (cmd_help) { |
| 2641 | 2669 | if (disable) |
| 2642 | 2670 | fprintf(stderr, "Set turbo mode disable\n"); |
| @@ -2682,6 +2710,7 @@ static void get_set_trl(struct isst_id *id, void *arg1, void *arg2, void *arg3, · 39 unmodified lines | ||
| 2682 | 2710 | } |
| 2683 | 2711 | |
| 2684 | 2712 | if (set) { |
| 2713 | + check_privilege(); | |
| 2685 | 2714 | ret = isst_set_trl(id, fact_trl); |
| 2686 | 2715 | isst_display_result(id, outf, "turbo-mode", "set-trl", ret); |
| 2687 | 2716 | return; |
| @@ -3204,8 +3233,16 @@ static void cmdline(int argc, char **argv) · 516 unmodified lines | ||
| 3204 | 3233 | }; |
| 3205 | 3234 | |
| 3206 | 3235 | if (geteuid() != 0) { |
| 3207 | − fprintf(stderr, "Must run as root\n"); | |
| 3208 | − exit(0); | |
| 3236 | + int fd; | |
| 3237 | + | |
| 3238 | + fd = open(pathname, O_RDWR); | |
| 3239 | + if (fd < 0) { | |
| 3240 | + fprintf(stderr, "Must run as root\n"); | |
| 3241 | + exit(0); | |
| 3242 | + } | |
| 3243 | + fprintf(stderr, "\nNot running as root, Only read only operations are supported\n"); | |
| 3244 | + close(fd); | |
| 3245 | + read_only = 1; | |
| 3209 | 3246 | } |
| 3210 | 3247 | |
| 3211 | 3248 | ret = update_cpu_model(); |