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 | - Setting keyboard language | 54 | - Setting keyboard language |
| 55 | - WWAN Antenna type | 55 | - WWAN Antenna type |
| 56 | - Auxmac | 56 | - Auxmac |
| 57 | - Hardware damage detection capability | ||
| 57 | 58 | ||
| 58 | A compatibility table by model and feature is maintained on the web | 59 | A compatibility table by model and feature is maintained on the web |
| 59 | site, http://ibm-acpi.sf.net/. I appreciate any success or failure | 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 | The exact semantics of the attributes may be found in | 1577 | The exact semantics of the attributes may be found in |
| 1577 | Documentation/ABI/testing/sysfs-class-power. | 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 | Multiple Commands, Module Parameters | 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 | .. kernel-doc:: include/linux/wmi.h | 16 | .. kernel-doc:: include/linux/wmi.h |
| 17 | :internal: | 17 | :internal: |
| 18 | 18 | ||
| 19 | .. kernel-doc:: drivers/platform/wmi/string.c | ||
| 20 | :export: | ||
| 21 | |||
| 19 | .. kernel-doc:: drivers/platform/wmi/core.c | 22 | .. kernel-doc:: drivers/platform/wmi/core.c |
| 20 | :export: | 23 | :export: |
| @@ -104,3 +104,71 @@ holding the notification ID of the event. This method should be evaluated every · 103 unmodified lines | |||
| 104 | time an ACPI notification is received, since some ACPI implementations use a | 104 | time an ACPI notification is received, since some ACPI implementations use a |
| 105 | queue to store WMI event data items. This queue will overflow after a couple | 105 | queue to store WMI event data items. This queue will overflow after a couple |
| 106 | of WMI events are received without retrieving the associated WMI event data. | 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 | /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/ | 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 | LENOVO_CAPABILITY_DATA_01 | 52 | LENOVO_CAPABILITY_DATA_01 |
| 35 | ------------------------- | 53 | ------------------------- |
| 36 | 54 | ||
| 37 | WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154`` | 55 | WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154`` |
| 38 | 56 | ||
| 39 | The LENOVO_CAPABILITY_DATA_01 interface provides information on various | 57 | The LENOVO_CAPABILITY_DATA_01 interface provides various information that |
| 40 | power limits of integrated CPU and GPU components. | 58 | relies on the gamezone thermal mode, including power limits of integrated |
| 59 | CPU and GPU components. | ||
| 41 | 60 | ||
| 42 | Each attribute has the following properties: | 61 | Each attribute has the following properties: |
| 43 | - current_value | 62 | - current_value |
| @@ -48,11 +67,22 @@ Each attribute has the following properties: · 4 unmodified lines | |||
| 48 | - scalar_increment | 67 | - scalar_increment |
| 49 | - type | 68 | - type |
| 50 | 69 | ||
| 51 | The following attributes are implemented: | 70 | The following firmware-attributes are implemented: |
| 52 | - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit | 71 | - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit |
| 53 | - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking | 72 | - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking |
| 54 | - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking | 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 | WMI interface description | 87 | WMI interface description |
| 58 | ========================= | 88 | ========================= |
| @@ -106,3 +136,13 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility: · 47 unmodified lines | |||
| 106 | [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize; | 136 | [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize; |
| 107 | [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[]; | 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 | .probe = foo_probe, | 70 | .probe = foo_probe, |
| 71 | .remove = foo_remove, /* optional, devres is preferred */ | 71 | .remove = foo_remove, /* optional, devres is preferred */ |
| 72 | .shutdown = foo_shutdown, /* optional, called during shutdown */ | 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 | .no_notify_data = true, /* optional, enables events containing no additional data */ | 74 | .no_notify_data = true, /* optional, enables events containing no additional data */ |
| 75 | .no_singleton = true, /* required for new WMI drivers */ | 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 | or kexec. Most WMI drivers need no special shutdown handling and can thus omit this callback. | 90 | or kexec. Most WMI drivers need no special shutdown handling and can thus omit this callback. |
| 91 | 91 | ||
| 92 | Please note that new WMI drivers are required to be able to be instantiated multiple times, | 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 | 93 | and are forbidden from using any deprecated GUID-based or ACPI-based WMI functions. This means |
| 94 | WMI driver should be prepared for the scenario that multiple matching WMI devices are present | 94 | that the WMI driver should be prepared for the scenario that multiple matching WMI devices are |
| 95 | on a given machine. | 95 | present on a given machine. |
| 96 | 96 | ||
| 97 | Because of this, WMI drivers should use the state container design pattern as described in | 97 | Because of this, WMI drivers should use the state container design pattern as described in |
| 98 | Documentation/driver-api/driver-model/design-patterns.rst. | 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 | WMI method drivers | 104 | WMI method drivers |
| 105 | ------------------ | 105 | ------------------ |
| 106 | 106 | ||
| 107 | WMI drivers can call WMI device methods using wmidev_evaluate_method(), the | 107 | WMI drivers can call WMI device methods using wmidev_invoke_method(). For each WMI method |
| 108 | structure of the ACPI buffer passed to this function is device-specific and usually | 108 | invocation the WMI driver needs to provide the instance number and the method ID, as well as |
| 109 | needs some tinkering to get right. Looking at the ACPI tables containing the WMI | 109 | a buffer with the method arguments and optionally a buffer for the results. |
| 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. | ||
| 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 | WMI data block drivers | 119 | WMI data block drivers |
| 119 | ---------------------- | 120 | ---------------------- |
| 120 | 121 | ||
| 121 | WMI drivers can query WMI device data blocks using wmidev_block_query(), the | 122 | WMI drivers can query WMI data blocks using wmidev_query_block(), the layout of the returned |
| 122 | structure of the returned ACPI object is again device-specific. Some WMI devices | 123 | buffer is again device-specific and described by the Binary MOF data. Some WMI data blocks are |
| 123 | also allow for setting data blocks using wmidev_block_set(). | 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(). | 127 | Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example WMI data block driver. |
| 126 | |||
| 127 | Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example | ||
| 128 | WMI data block driver. | ||
| 129 | 128 | ||
| 130 | WMI event drivers | 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 | The WMI subsystem will then take care of setting up the WMI event accordingly. Please note that | 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 | 134 | the layout of the buffer passed to this callback is device-specific, and freeing of the buffer |
| 136 | ACPI object is being done by the WMI subsystem, not the driver. | 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 | the probe() callback has been called, and that no events are being received by the driver | 138 | the probe() callback has been called, and that no events are being received by the driver |
| 140 | right before and after calling its remove() or shutdown() callback. | 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 | Take a look at drivers/platform/x86/xiaomi-wmi.c for an example WMI event driver. | 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 | Handling multiple WMI devices at once | 179 | Handling multiple WMI devices at once |
| 151 | ------------------------------------- | 180 | ------------------------------------- |
| 152 | 181 | ||
| @@ -171,6 +200,7 @@ Things to avoid · 18 unmodified lines | |||
| 171 | When developing WMI drivers, there are a couple of things which should be avoided: | 200 | When developing WMI drivers, there are a couple of things which should be avoided: |
| 172 | 201 | ||
| 173 | - usage of the deprecated GUID-based WMI interface which uses GUIDs instead of WMI device structs | 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 | - bypassing of the WMI subsystem when talking to WMI devices | 204 | - bypassing of the WMI subsystem when talking to WMI devices |
| 175 | - WMI drivers which cannot be instantiated multiple times. | 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 | return sp ? sp->psp_data : NULL; | 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 | void psp_pci_init(void) | 365 | void psp_pci_init(void) |
| 355 | { | 366 | { |
| 356 | psp_master = psp_get_master_device(); | 367 | psp_master = psp_get_master_device(); |
| @@ -230,6 +230,18 @@ int sp_resume(struct sp_device *sp) · 229 unmodified lines | |||
| 230 | return 0; | 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 | struct sp_device *sp_get_psp_master_device(void) | 245 | struct sp_device *sp_get_psp_master_device(void) |
| 234 | { | 246 | { |
| 235 | struct sp_device *i, *ret = NULL; | 247 | struct sp_device *i, *ret = NULL; |
| @@ -141,6 +141,7 @@ void sp_destroy(struct sp_device *sp); · 140 unmodified lines | |||
| 141 | 141 | ||
| 142 | int sp_suspend(struct sp_device *sp); | 142 | int sp_suspend(struct sp_device *sp); |
| 143 | int sp_resume(struct sp_device *sp); | 143 | int sp_resume(struct sp_device *sp); |
| 144 | int sp_restore(struct sp_device *sp); | ||
| 144 | int sp_request_ccp_irq(struct sp_device *sp, irq_handler_t handler, | 145 | int sp_request_ccp_irq(struct sp_device *sp, irq_handler_t handler, |
| 145 | const char *name, void *data); | 146 | const char *name, void *data); |
| 146 | void sp_free_ccp_irq(struct sp_device *sp, void *data); | 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 | void psp_pci_init(void); | 175 | void psp_pci_init(void); |
| 175 | void psp_dev_destroy(struct sp_device *sp); | 176 | void psp_dev_destroy(struct sp_device *sp); |
| 176 | void psp_pci_exit(void); | 177 | void psp_pci_exit(void); |
| 178 | int psp_restore(struct sp_device *sp); | ||
| 177 | 179 | ||
| 178 | #else /* !CONFIG_CRYPTO_DEV_SP_PSP */ | 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 | static inline void psp_pci_init(void) { } | 183 | static inline void psp_pci_init(void) { } |
| 182 | static inline void psp_dev_destroy(struct sp_device *sp) { } | 184 | static inline void psp_dev_destroy(struct sp_device *sp) { } |
| 183 | static inline void psp_pci_exit(void) { } | 185 | static inline void psp_pci_exit(void) { } |
| 186 | static inline int psp_restore(struct sp_device *sp) { return 0; } | ||
| 184 | 187 | ||
| 185 | #endif /* CONFIG_CRYPTO_DEV_SP_PSP */ | 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 | return sp_resume(sp); | 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 | #ifdef CONFIG_CRYPTO_DEV_SP_PSP | 363 | #ifdef CONFIG_CRYPTO_DEV_SP_PSP |
| 357 | static const struct sev_vdata sevv1 = { | 364 | static const struct sev_vdata sevv1 = { |
| 358 | .cmdresp_reg = 0x10580, /* C2PMSG_32 */ | 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 | MODULE_DEVICE_TABLE(pci, sp_pci_table); | 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 | static struct pci_driver sp_pci_driver = { | 582 | static struct pci_driver sp_pci_driver = { |
| 569 | .name = "ccp", | 583 | .name = "ccp", |
| @@ -86,10 +86,34 @@ static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd) · 85 unmodified lines | |||
| 86 | kfree(cmd); | 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 | static int tee_init_ring(struct psp_tee_device *tee) | 112 | static int tee_init_ring(struct psp_tee_device *tee) |
| 90 | { | 113 | { |
| 91 | int ring_size = MAX_RING_BUFFER_ENTRIES * sizeof(struct tee_ring_cmd); | 114 | int ring_size = MAX_RING_BUFFER_ENTRIES * sizeof(struct tee_ring_cmd); |
| 92 | struct tee_init_ring_cmd *cmd; | 115 | struct tee_init_ring_cmd *cmd; |
| 116 | bool retry = false; | ||
| 93 | unsigned int reg; | 117 | unsigned int reg; |
| 94 | int ret; | 118 | int ret; |
| 95 | 119 | ||
| @@ -112,6 +136,7 @@ static int tee_init_ring(struct psp_tee_device *tee) · 16 unmodified lines | |||
| 112 | /* Send command buffer details to Trusted OS by writing to | 136 | /* Send command buffer details to Trusted OS by writing to |
| 113 | * CPU-PSP message registers | 137 | * CPU-PSP message registers |
| 114 | */ | 138 | */ |
| 139 | retry_init: | ||
| 115 | ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_INIT, cmd, | 140 | ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_INIT, cmd, |
| 116 | TEE_DEFAULT_CMD_TIMEOUT, ®); | 141 | TEE_DEFAULT_CMD_TIMEOUT, ®); |
| 117 | if (ret) { | 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 | if (FIELD_GET(PSP_CMDRESP_STS, reg)) { | 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 | dev_err(tee->dev, "tee: ring init command failed (%#010lx)\n", | 162 | dev_err(tee->dev, "tee: ring init command failed (%#010lx)\n", |
| 126 | FIELD_GET(PSP_CMDRESP_STS, reg)); | 163 | FIELD_GET(PSP_CMDRESP_STS, reg)); |
| 127 | tee_free_ring(tee); | 164 | tee_free_ring(tee); |
| 165 | psp_dead = true; | ||
| 128 | ret = -EIO; | 166 | ret = -EIO; |
| 129 | } | 167 | } |
| 130 | 168 | ||
| @@ -136,24 +174,13 @@ free_buf: · 5 unmodified lines | |||
| 136 | 174 | ||
| 137 | static void tee_destroy_ring(struct psp_tee_device *tee) | 175 | static void tee_destroy_ring(struct psp_tee_device *tee) |
| 138 | { | 176 | { |
| 139 | unsigned int reg; | ||
| 140 | int ret; | ||
| 141 | |||
| 142 | if (!tee->rb_mgr.ring_start) | 177 | if (!tee->rb_mgr.ring_start) |
| 143 | return; | 178 | return; |
| 144 | 179 | ||
| 145 | if (psp_dead) | 180 | if (psp_dead) |
| 146 | goto free_ring; | 181 | goto free_ring; |
| 147 | 182 | ||
| 148 | ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL, | 183 | tee_send_destroy_cmd(tee); |
| 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 | } | ||
| 157 | 184 | ||
| 158 | free_ring: | 185 | free_ring: |
| 159 | tee_free_ring(tee); | 186 | tee_free_ring(tee); |
| @@ -365,3 +392,8 @@ int psp_check_tee_status(void) · 205 unmodified lines | |||
| 365 | return 0; | 392 | return 0; |
| 366 | } | 393 | } |
| 367 | EXPORT_SYMBOL(psp_check_tee_status); | 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 | int tee_dev_init(struct psp_device *psp); | 112 | int tee_dev_init(struct psp_device *psp); |
| 113 | void tee_dev_destroy(struct psp_device *psp); | 113 | void tee_dev_destroy(struct psp_device *psp); |
| 114 | int tee_restore(struct psp_device *psp); | ||
| 114 | 115 | ||
| 115 | #endif /* __TEE_DEV_H__ */ | 116 | #endif /* __TEE_DEV_H__ */ |
| @@ -28,7 +28,6 @@ · 27 unmodified lines | |||
| 28 | #include <linux/hid.h> | 28 | #include <linux/hid.h> |
| 29 | #include <linux/module.h> | 29 | #include <linux/module.h> |
| 30 | #include <linux/platform_data/x86/asus-wmi.h> | 30 | #include <linux/platform_data/x86/asus-wmi.h> |
| 31 | #include <linux/platform_data/x86/asus-wmi-leds-ids.h> | ||
| 32 | #include <linux/input/mt.h> | 31 | #include <linux/input/mt.h> |
| 33 | #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */ | 32 | #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */ |
| 34 | #include <linux/power_supply.h> | 33 | #include <linux/power_supply.h> |
| @@ -50,7 +49,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); · 15 unmodified lines | |||
| 50 | #define FEATURE_REPORT_ID 0x0d | 49 | #define FEATURE_REPORT_ID 0x0d |
| 51 | #define INPUT_REPORT_ID 0x5d | 50 | #define INPUT_REPORT_ID 0x5d |
| 52 | #define FEATURE_KBD_REPORT_ID 0x5a | 51 | #define FEATURE_KBD_REPORT_ID 0x5a |
| 53 | #define FEATURE_KBD_REPORT_SIZE 16 | 52 | #define FEATURE_KBD_REPORT_SIZE 64 |
| 54 | #define FEATURE_KBD_LED_REPORT_ID1 0x5d | 53 | #define FEATURE_KBD_LED_REPORT_ID1 0x5d |
| 55 | #define FEATURE_KBD_LED_REPORT_ID2 0x5e | 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 | #define QUIRK_T90CHI BIT(9) | 98 | #define QUIRK_T90CHI BIT(9) |
| 100 | #define QUIRK_MEDION_E1239T BIT(10) | 99 | #define QUIRK_MEDION_E1239T BIT(10) |
| 101 | #define QUIRK_ROG_NKEY_KEYBOARD BIT(11) | 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 | #define QUIRK_ROG_ALLY_XPAD BIT(13) | 102 | #define QUIRK_ROG_ALLY_XPAD BIT(13) |
| 104 | #define QUIRK_HID_FN_LOCK BIT(14) | 103 | #define QUIRK_HID_FN_LOCK BIT(14) |
| 104 | #define QUIRK_ROG_NKEY_ID1ID2_INIT BIT(15) | ||
| 105 | 105 | ||
| 106 | #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ | 106 | #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ |
| 107 | QUIRK_NO_INIT_REPORTS | \ | 107 | QUIRK_NO_INIT_REPORTS | \ |
| @@ -113,7 +113,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); · 5 unmodified lines | |||
| 113 | #define TRKID_SGN ((TRKID_MAX + 1) >> 1) | 113 | #define TRKID_SGN ((TRKID_MAX + 1) >> 1) |
| 114 | 114 | ||
| 115 | struct asus_kbd_leds { | 115 | struct asus_kbd_leds { |
| 116 | struct led_classdev cdev; | 116 | struct asus_hid_listener listener; |
| 117 | struct hid_device *hdev; | 117 | struct hid_device *hdev; |
| 118 | struct work_struct work; | 118 | struct work_struct work; |
| 119 | unsigned int brightness; | 119 | unsigned int brightness; |
| @@ -138,7 +138,6 @@ struct asus_drvdata { · 18 unmodified lines | |||
| 138 | struct input_dev *tp_kbd_input; | 138 | struct input_dev *tp_kbd_input; |
| 139 | struct asus_kbd_leds *kbd_backlight; | 139 | struct asus_kbd_leds *kbd_backlight; |
| 140 | const struct asus_touchpad_info *tp; | 140 | const struct asus_touchpad_info *tp; |
| 141 | bool enable_backlight; | ||
| 142 | struct power_supply *battery; | 141 | struct power_supply *battery; |
| 143 | struct power_supply_desc battery_desc; | 142 | struct power_supply_desc battery_desc; |
| 144 | int battery_capacity; | 143 | int battery_capacity; |
| @@ -363,10 +362,21 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field, · 218 unmodified lines | |||
| 363 | usage->hid & HID_USAGE); | 362 | usage->hid & HID_USAGE); |
| 364 | } | 363 | } |
| 365 | 364 | ||
| 366 | if (drvdata->quirks & QUIRK_HID_FN_LOCK && | 365 | if (usage->type == EV_KEY && value) { |
| 367 | usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) { | 366 | switch (usage->code) { |
| 368 | drvdata->fn_lock = !drvdata->fn_lock; | 367 | case KEY_KBDILLUMUP: |
| 369 | schedule_work(&drvdata->fn_lock_sync_work); | 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 | return 0; | 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 | static int asus_kbd_init(struct hid_device *hdev, u8 report_id) | 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 | const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54, | 493 | const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54, |
| 480 | 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 }; | 494 | 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 }; |
| 481 | int ret; | 495 | int ret; |
| 482 | 496 | ||
| 483 | ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); | 497 | ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); |
| 484 | if (ret < 0) | 498 | if (ret < 0) { |
| 485 | hid_err(hdev, "Asus failed to send init command: %d\n", ret); | 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 | static int asus_kbd_get_functions(struct hid_device *hdev, | 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 | if (!readbuf) | 541 | if (!readbuf) |
| 506 | return -ENOMEM; | 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 | FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT, | 545 | FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT, |
| 510 | HID_REQ_GET_REPORT); | 546 | HID_REQ_GET_REPORT); |
| 511 | if (ret < 0) { | 547 | if (ret < 0) { |
| @@ -565,11 +601,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led) · 53 unmodified lines | |||
| 565 | spin_unlock_irqrestore(&led->lock, flags); | 601 | spin_unlock_irqrestore(&led->lock, flags); |
| 566 | } | 602 | } |
| 567 | 603 | ||
| 568 | static void asus_kbd_backlight_set(struct led_classdev *led_cdev, | 604 | static void asus_kbd_backlight_set(struct asus_hid_listener *listener, |
| 569 | enum led_brightness brightness) | 605 | int brightness) |
| 570 | { | 606 | { |
| 571 | struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, | 607 | struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds, |
| 572 | cdev); | 608 | listener); |
| 573 | unsigned long flags; | 609 | unsigned long flags; |
| 574 | 610 | ||
| 575 | spin_lock_irqsave(&led->lock, flags); | 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 | asus_schedule_work(led); | 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 | static void asus_kbd_backlight_work(struct work_struct *work) | 618 | static void asus_kbd_backlight_work(struct work_struct *work) |
| 597 | { | 619 | { |
| 598 | struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work); | 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 | hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret); | 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 | * We don't care about any other part of the string except the version section. | 635 | * We don't care about any other part of the string except the version section. |
| 642 | * Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01 | 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 | unsigned char kbd_func; | 730 | unsigned char kbd_func; |
| 737 | int ret; | 731 | int ret; |
| 738 | 732 | ||
| 739 | if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) { | 733 | ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); |
| 740 | /* Initialize keyboard */ | 734 | if (ret < 0) |
| 741 | ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); | 735 | return ret; |
| 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; | ||
| 753 | 736 | ||
| 754 | if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) { | 737 | /* Get keyboard functions */ |
| 755 | ret = asus_kbd_disable_oobe(hdev); | 738 | ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID); |
| 756 | if (ret < 0) | 739 | if (ret < 0) |
| 757 | return ret; | 740 | return ret; |
| 758 | } | ||
| 759 | 741 | ||
| 760 | if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) { | 742 | /* Check for backlight support */ |
| 761 | intf = to_usb_interface(hdev->dev.parent); | 743 | if (!(kbd_func & SUPPORT_KBD_BACKLIGHT)) |
| 762 | udev = interface_to_usbdev(intf); | 744 | return -ENODEV; |
| 763 | validate_mcu_fw_version(hdev, | ||
| 764 | le16_to_cpu(udev->descriptor.idProduct)); | ||
| 765 | } | ||
| 766 | 745 | ||
| 767 | } else { | 746 | if (drvdata->quirks & QUIRK_ROG_NKEY_ID1ID2_INIT) { |
| 768 | /* Initialize keyboard */ | 747 | asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1); |
| 769 | ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); | 748 | asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2); |
| 770 | if (ret < 0) | 749 | } |
| 771 | return ret; | ||
| 772 | 750 | ||
| 773 | /* Get keyboard functions */ | 751 | if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) { |
| 774 | ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID); | 752 | ret = asus_kbd_disable_oobe(hdev); |
| 775 | if (ret < 0) | 753 | if (ret < 0) |
| 776 | return ret; | 754 | return ret; |
| 755 | } | ||
| 777 | 756 | ||
| 778 | /* Check for backlight support */ | 757 | if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) { |
| 779 | if (!(kbd_func & SUPPORT_KBD_BACKLIGHT)) | 758 | intf = to_usb_interface(hdev->dev.parent); |
| 780 | return -ENODEV; | 759 | udev = interface_to_usbdev(intf); |
| 760 | validate_mcu_fw_version(hdev, | ||
| 761 | le16_to_cpu(udev->descriptor.idProduct)); | ||
| 781 | } | 762 | } |
| 782 | 763 | ||
| 783 | drvdata->kbd_backlight = devm_kzalloc(&hdev->dev, | 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 | drvdata->kbd_backlight->removed = false; | 770 | drvdata->kbd_backlight->removed = false; |
| 790 | drvdata->kbd_backlight->brightness = 0; | 771 | drvdata->kbd_backlight->brightness = 0; |
| 791 | drvdata->kbd_backlight->hdev = hdev; | 772 | drvdata->kbd_backlight->hdev = hdev; |
| 792 | drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight"; | 773 | drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set; |
| 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; | ||
| 796 | INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work); | 774 | INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work); |
| 797 | spin_lock_init(&drvdata->kbd_backlight->lock); | 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 | if (ret < 0) { | 778 | if (ret < 0) { |
| 801 | /* No need to have this still around */ | 779 | /* No need to have this still around */ |
| 802 | devm_kfree(&hdev->dev, drvdata->kbd_backlight); | 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 | drvdata->input = input; | 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 | if (drvdata->quirks & QUIRK_HID_FN_LOCK) { | 1002 | if (drvdata->quirks & QUIRK_HID_FN_LOCK) { |
| 1030 | drvdata->fn_lock = true; | 1003 | drvdata->fn_lock = true; |
| 1031 | INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock); | 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 | return -1; | 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 | set_bit(EV_REP, hi->input->evbit); | 1080 | set_bit(EV_REP, hi->input->evbit); |
| 1117 | return 1; | 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 | if (drvdata->kbd_backlight) { | 1170 | if (drvdata->kbd_backlight) { |
| 1207 | const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, | 1171 | const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, |
| 1208 | drvdata->kbd_backlight->cdev.brightness }; | 1172 | drvdata->kbd_backlight->brightness }; |
| 1209 | ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); | 1173 | ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); |
| 1210 | if (ret < 0) { | 1174 | if (ret < 0) { |
| 1211 | hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret); | 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 | static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) | 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 | struct asus_drvdata *drvdata; | 1197 | struct asus_drvdata *drvdata; |
| 1198 | struct hid_report *rep; | ||
| 1199 | bool is_vendor = false; | ||
| 1200 | int ret; | ||
| 1234 | 1201 | ||
| 1235 | drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); | 1202 | drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); |
| 1236 | if (drvdata == NULL) { | 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 | return ret; | 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 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | 1291 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); |
| 1318 | if (ret) { | 1292 | if (ret) { |
| 1319 | hid_err(hdev, "Asus hw start failed: %d\n", ret); | 1293 | hid_err(hdev, "Asus hw start failed: %d\n", ret); |
| 1320 | return ret; | 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 | * Check that input registration succeeded. Checking that | 1309 | * Check that input registration succeeded. Checking that |
| 1325 | * HID_CLAIMED_INPUT is set prevents a UAF when all input devices | 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 | unsigned long flags; | 1341 | unsigned long flags; |
| 1357 | 1342 | ||
| 1358 | if (drvdata->kbd_backlight) { | 1343 | if (drvdata->kbd_backlight) { |
| 1344 | asus_hid_unregister_listener(&drvdata->kbd_backlight->listener); | ||
| 1345 | |||
| 1359 | spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags); | 1346 | spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags); |
| 1360 | drvdata->kbd_backlight->removed = true; | 1347 | drvdata->kbd_backlight->removed = true; |
| 1361 | spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags); | 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 | QUIRK_USE_KBD_BACKLIGHT }, | 1477 | QUIRK_USE_KBD_BACKLIGHT }, |
| 1491 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, | 1478 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, |
| 1492 | USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD), | 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 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, | 1481 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, |
| 1495 | USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2), | 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 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, | 1484 | { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, |
| 1498 | USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR), | 1485 | USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR), |
| 1499 | QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, | 1486 | QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, |
| @@ -6,6 +6,8 @@ · 5 unmodified lines | |||
| 6 | * Copyright (C) 2016-2018 Vadim Pasternak <vadimp@mellanox.com> | 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 | #include <linux/device.h> | 11 | #include <linux/device.h> |
| 10 | #include <linux/dmi.h> | 12 | #include <linux/dmi.h> |
| 11 | #include <linux/i2c.h> | 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 | static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_items_data[] = { | 742 | static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_items_data[] = { |
| 731 | { | 743 | { |
| 732 | .label = "pwr1", | 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 | static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_items_data[] = { | 800 | static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_items_data[] = { |
| 780 | { | 801 | { |
| 781 | .label = "fan1", | 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 | static struct mlxreg_core_item mlxplat_mlxcpld_ng800_items[] = { | 1462 | static struct mlxreg_core_item mlxplat_mlxcpld_ng800_items[] = { |
| 1403 | { | 1463 | { |
| 1404 | .data = mlxplat_mlxcpld_default_ng_psu_items_data, | 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 | .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2, | 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 | static | 1523 | static |
| 1454 | struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_data = { | 1524 | struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_data = { |
| 1455 | .items = mlxplat_mlxcpld_ng800_items, | 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 | .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_regs_io_data), | 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 | /* Platform register access for modular systems families data */ | 5051 | /* Platform register access for modular systems families data */ |
| 4629 | static struct mlxreg_core_data mlxplat_mlxcpld_modular_regs_io_data[] = { | 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 | return mlxplat_register_platform_device(); | 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 | static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi) | 7691 | static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi) |
| 7243 | { | 7692 | { |
| 7244 | int i; | 7693 | int i; |
| @@ -7323,6 +7772,27 @@ static int __init mlxplat_dmi_ng800_matched(const struct dmi_system_id *dmi) · 78 unmodified lines | |||
| 7323 | return mlxplat_register_platform_device(); | 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 | static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi) | 7796 | static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi) |
| 7327 | { | 7797 | { |
| 7328 | int i; | 7798 | int i; |
| @@ -7458,6 +7928,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { · 129 unmodified lines | |||
| 7458 | DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI142"), | 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 | .callback = mlxplat_dmi_ng400_matched, | 7939 | .callback = mlxplat_dmi_ng400_matched, |
| 7463 | .matches = { | 7940 | .matches = { |
| @@ -7470,6 +7947,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { · 6 unmodified lines | |||
| 7470 | DMI_MATCH(DMI_BOARD_NAME, "VMOD0011"), | 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 | .callback = mlxplat_dmi_ng800_matched, | 7958 | .callback = mlxplat_dmi_ng800_matched, |
| 7475 | .matches = { | 7959 | .matches = { |
| @@ -406,6 +406,22 @@ static const struct software_node *ssam_node_group_sp9_5g[] = { · 405 unmodified lines | |||
| 406 | NULL, | 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 | /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ | 425 | /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ |
| 410 | 426 | ||
| 411 | static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { | 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 | static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = { | 498 | static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = { |
| 483 | /* Surface Pro 9 5G (ARM/QCOM) */ | 499 | /* Surface Pro 9 5G (ARM/QCOM) */ |
| 484 | { .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g }, | 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 | /* Surface Laptop 7 */ | 503 | /* Surface Laptop 7 */ |
| 486 | { .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 }, | 504 | { .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 }, |
| 487 | { .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 }, | 505 | { .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 }, |
| @@ -10,6 +10,7 @@ · 9 unmodified lines | |||
| 10 | #include <linux/kernel.h> | 10 | #include <linux/kernel.h> |
| 11 | #include <linux/module.h> | 11 | #include <linux/module.h> |
| 12 | #include <linux/init.h> | 12 | #include <linux/init.h> |
| 13 | #include <linux/string.h> | ||
| 13 | #include <linux/types.h> | 14 | #include <linux/types.h> |
| 14 | #include <linux/input.h> | 15 | #include <linux/input.h> |
| 15 | #include <linux/acpi.h> | 16 | #include <linux/acpi.h> |
| @@ -189,7 +190,6 @@ static int surface_button_add(struct acpi_device *device) · 173 unmodified lines | |||
| 189 | struct surface_button *button; | 190 | struct surface_button *button; |
| 190 | struct input_dev *input; | 191 | struct input_dev *input; |
| 191 | const char *hid = acpi_device_hid(device); | 192 | const char *hid = acpi_device_hid(device); |
| 192 | char *name; | ||
| 193 | int error; | 193 | int error; |
| 194 | 194 | ||
| 195 | if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME, | 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 | goto err_free_button; | 210 | goto err_free_button; |
| 211 | } | 211 | } |
| 212 | 212 | ||
| 213 | name = acpi_device_name(device); | 213 | strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME); |
| 214 | strcpy(name, SURFACE_BUTTON_DEVICE_NAME); | ||
| 215 | snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid); | 214 | snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid); |
| 216 | 215 | ||
| 217 | input->name = name; | 216 | input->name = acpi_device_name(device); |
| 218 | input->phys = button->phys; | 217 | input->phys = button->phys; |
| 219 | input->id.bustype = BUS_HOST; | 218 | input->id.bustype = BUS_HOST; |
| 220 | input->dev.parent = &device->dev; | 219 | input->dev.parent = &device->dev; |
| @@ -228,8 +227,8 @@ static int surface_button_add(struct acpi_device *device) · 7 unmodified lines | |||
| 228 | goto err_free_input; | 227 | goto err_free_input; |
| 229 | 228 | ||
| 230 | device_init_wakeup(&device->dev, true); | 229 | device_init_wakeup(&device->dev, true); |
| 231 | dev_info(&device->dev, | 230 | dev_info(&device->dev, "%s [%s]\n", acpi_device_name(device), |
| 232 | "%s [%s]\n", name, acpi_device_bid(device)); | 231 | acpi_device_bid(device)); |
| 233 | return 0; | 232 | return 0; |
| 234 | 233 | ||
| 235 | err_free_input: | 234 | err_free_input: |
| @@ -6,6 +6,7 @@ · 5 unmodified lines | |||
| 6 | menuconfig ACPI_WMI | 6 | menuconfig ACPI_WMI |
| 7 | tristate "ACPI-WMI support" | 7 | tristate "ACPI-WMI support" |
| 8 | depends on ACPI && X86 | 8 | depends on ACPI && X86 |
| 9 | select NLS | ||
| 9 | help | 10 | help |
| 10 | This option enables support for the ACPI-WMI driver core. | 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 | userspace applications but will cause the registration of WMI devices with | 32 | userspace applications but will cause the registration of WMI devices with |
| 32 | the same GUID to fail in some corner cases. | 33 | the same GUID to fail in some corner cases. |
| 33 | 34 | ||
| 35 | source "drivers/platform/wmi/tests/Kconfig" | ||
| 36 | |||
| 34 | endif # ACPI_WMI | 37 | endif # ACPI_WMI |
| @@ -4,5 +4,8 @@ · 3 unmodified lines | |||
| 4 | # ACPI WMI core | 4 | # ACPI WMI core |
| 5 | # | 5 | # |
| 6 | 6 | ||
| 7 | wmi-y := core.o | 7 | wmi-y := core.o marshalling.o string.o |
| 8 | obj-$(CONFIG_ACPI_WMI) += wmi.o | 8 | obj-$(CONFIG_ACPI_WMI) += wmi.o |
| 9 | |||
| 10 | # Unit tests | ||
| 11 | obj-y += tests/ | ||
| @@ -23,6 +23,7 @@ · 22 unmodified lines | |||
| 23 | #include <linux/idr.h> | 23 | #include <linux/idr.h> |
| 24 | #include <linux/init.h> | 24 | #include <linux/init.h> |
| 25 | #include <linux/kernel.h> | 25 | #include <linux/kernel.h> |
| 26 | #include <linux/limits.h> | ||
| 26 | #include <linux/module.h> | 27 | #include <linux/module.h> |
| 27 | #include <linux/platform_device.h> | 28 | #include <linux/platform_device.h> |
| 28 | #include <linux/rwsem.h> | 29 | #include <linux/rwsem.h> |
| @@ -33,6 +34,8 @@ · 4 unmodified lines | |||
| 33 | #include <linux/wmi.h> | 34 | #include <linux/wmi.h> |
| 34 | #include <linux/fs.h> | 35 | #include <linux/fs.h> |
| 35 | 36 | ||
| 37 | #include "internal.h" | ||
| 38 | |||
| 36 | MODULE_AUTHOR("Carlos Corbacho"); | 39 | MODULE_AUTHOR("Carlos Corbacho"); |
| 37 | MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); | 40 | MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); |
| 38 | MODULE_LICENSE("GPL"); | 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 | EXPORT_SYMBOL_GPL(wmi_evaluate_method); | 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 | * @wdev: A wmi bus device from a driver | 309 | * @wdev: A wmi bus device from a driver |
| 307 | * @instance: Instance index | 310 | * @instance: Instance index |
| 308 | * @method_id: Method ID to call | 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 | EXPORT_SYMBOL_GPL(wmidev_evaluate_method); | 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 | static acpi_status __query_block(struct wmi_block *wblock, u8 instance, | 430 | static acpi_status __query_block(struct wmi_block *wblock, u8 instance, |
| 364 | struct acpi_buffer *out) | 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 | EXPORT_SYMBOL_GPL(wmi_query_block); | 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 | * @wdev: A wmi bus device from a driver | 503 | * @wdev: A wmi bus device from a driver |
| 437 | * @instance: Instance index | 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 | EXPORT_SYMBOL_GPL(wmidev_block_query); | 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 | * wmi_set_block - Write to a WMI block (deprecated) | 550 | * wmi_set_block - Write to a WMI block (deprecated) |
| 457 | * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba | 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 | EXPORT_SYMBOL_GPL(wmi_set_block); | 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 | * @wdev: A wmi bus device from a driver | 584 | * @wdev: A wmi bus device from a driver |
| 491 | * @instance: Instance index | 585 | * @instance: Instance index |
| 492 | * @in: Buffer containing new values for the data block | 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 | EXPORT_SYMBOL_GPL(wmidev_block_set); | 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 | * wmi_install_notify_handler - Register handler for WMI events (deprecated) | 673 | * wmi_install_notify_handler - Register handler for WMI events (deprecated) |
| 540 | * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba | 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 | return -ENODEV; | 996 | return -ENODEV; |
| 863 | } | 997 | } |
| 864 | 998 | ||
| 865 | if (wdriver->notify) { | 999 | if (wdriver->notify || wdriver->notify_new) { |
| 866 | if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data) | 1000 | if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data) |
| 867 | return -ENODEV; | 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 | static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj) | 1355 | static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj) |
| 1222 | { | 1356 | { |
| 1223 | struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver); | 1357 | struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver); |
| 1358 | struct wmi_buffer buffer; | ||
| 1359 | int ret; | ||
| 1224 | 1360 | ||
| 1225 | if (!obj && !driver->no_notify_data) { | 1361 | if (!obj && !driver->no_notify_data) { |
| 1226 | dev_warn(&wblock->dev.dev, "Event contains no event data\n"); | 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 | if (driver->notify) | 1366 | if (driver->notify) |
| 1231 | driver->notify(&wblock->dev, obj); | 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 | static int wmi_notify_device(struct device *dev, void *data) | 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 | #include <linux/acpi.h> | 11 | #include <linux/acpi.h> |
| 12 | #include <linux/array_size.h> | ||
| 13 | #include <linux/cleanup.h> | ||
| 14 | #include <linux/dev_printk.h> | ||
| 12 | #include "pmf.h" | 15 | #include "pmf.h" |
| 13 | 16 | ||
| 14 | #define APMF_CQL_NOTIFICATION 2 | 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 | req, sizeof(*req)); | 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 | static void amd_pmf_handle_early_preq(struct amd_pmf_dev *pdev) | 370 | static void amd_pmf_handle_early_preq(struct amd_pmf_dev *pdev) |
| 335 | { | 371 | { |
| 336 | if (!pdev->cb_flag) | 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 | dev_dbg(pmf_dev->dev, "Pending request (preq): 0x%x\n", pmf_dev->req.pending_req); | 392 | dev_dbg(pmf_dev->dev, "Pending request (preq): 0x%x\n", pmf_dev->req.pending_req); |
| 357 | 393 | ||
| 358 | amd_pmf_handle_early_preq(pmf_dev); | 394 | amd_pmf_handle_early_preq(pmf_dev); |
| 395 | |||
| 396 | amd_pmf_custom_bios_inputs_rb(pmf_dev); | ||
| 359 | } | 397 | } |
| 360 | 398 | ||
| 361 | static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data) | 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 | dev_dbg(pmf_dev->dev, "Pending request (preq1): 0x%x\n", pmf_dev->req1.pending_req); | 412 | dev_dbg(pmf_dev->dev, "Pending request (preq1): 0x%x\n", pmf_dev->req1.pending_req); |
| 375 | 413 | ||
| 376 | amd_pmf_handle_early_preq(pmf_dev); | 414 | amd_pmf_handle_early_preq(pmf_dev); |
| 415 | |||
| 416 | amd_pmf_custom_bios_inputs_rb(pmf_dev); | ||
| 377 | } | 417 | } |
| 378 | 418 | ||
| 379 | static void apmf_event_handler(acpi_handle handle, u32 event, void *data) | 419 | static void apmf_event_handler(acpi_handle handle, u32 event, void *data) |
| @@ -8,12 +8,16 @@ · 7 unmodified lines | |||
| 8 | * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> | 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 | #include <linux/debugfs.h> | 13 | #include <linux/debugfs.h> |
| 12 | #include <linux/iopoll.h> | 14 | #include <linux/iopoll.h> |
| 13 | #include <linux/module.h> | 15 | #include <linux/module.h> |
| 16 | #include <linux/mutex.h> | ||
| 14 | #include <linux/pci.h> | 17 | #include <linux/pci.h> |
| 15 | #include <linux/platform_device.h> | 18 | #include <linux/platform_device.h> |
| 16 | #include <linux/power_supply.h> | 19 | #include <linux/power_supply.h> |
| 20 | #include <linux/string.h> | ||
| 17 | #include <asm/amd/node.h> | 21 | #include <asm/amd/node.h> |
| 18 | #include "pmf.h" | 22 | #include "pmf.h" |
| 19 | 23 | ||
| @@ -53,6 +57,12 @@ static bool force_load; · 33 unmodified lines | |||
| 53 | module_param(force_load, bool, 0444); | 57 | module_param(force_load, bool, 0444); |
| 54 | MODULE_PARM_DESC(force_load, "Force load this driver on supported older platforms (experimental)"); | 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 | static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data) | 66 | static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data) |
| 57 | { | 67 | { |
| 58 | struct amd_pmf_dev *pmf = container_of(nb, struct amd_pmf_dev, pwr_src_notifier); | 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 | return 0; | 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 | static int amd_pmf_suspend_handler(struct device *dev) | 447 | static int amd_pmf_suspend_handler(struct device *dev) |
| 318 | { | 448 | { |
| 319 | struct amd_pmf_dev *pdev = dev_get_drvdata(dev); | 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 | return 0; | 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 | static void amd_pmf_init_features(struct amd_pmf_dev *dev) | 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 | dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n"); | 497 | dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n"); |
| 363 | } | 498 | } |
| 364 | 499 | ||
| 365 | amd_pmf_init_smart_pc(dev); | 500 | if (smart_pc_support) { |
| 366 | if (dev->smart_pc_enabled) { | 501 | amd_pmf_init_smart_pc(dev); |
| 367 | dev_dbg(dev->dev, "Smart PC Solution Enabled\n"); | 502 | if (dev->smart_pc_enabled) { |
| 368 | /* If Smart PC is enabled, no need to check for other features */ | 503 | dev_dbg(dev->dev, "Smart PC Solution Enabled\n"); |
| 369 | return; | 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 | if (is_apmf_func_supported(dev, APMF_FUNC_AUTO_MODE)) { | 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 | if (err) | 616 | if (err) |
| 478 | return err; | 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 | apmf_acpi_init(dev); | 627 | apmf_acpi_init(dev); |
| 481 | platform_set_drvdata(pdev, dev); | 628 | platform_set_drvdata(pdev, dev); |
| 482 | amd_pmf_dbgfs_register(dev); | 629 | amd_pmf_dbgfs_register(dev); |
| @@ -485,6 +632,8 @@ static int amd_pmf_probe(struct platform_device *pdev) · 2 unmodified lines | |||
| 485 | if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) | 632 | if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) |
| 486 | amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD); | 633 | amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD); |
| 487 | 634 | ||
| 635 | pmf_device = dev->dev; | ||
| 636 | |||
| 488 | dev_info(dev->dev, "registered PMF device successfully\n"); | 637 | dev_info(dev->dev, "registered PMF device successfully\n"); |
| 489 | 638 | ||
| 490 | return 0; | 639 | return 0; |
| @@ -12,7 +12,10 @@ · 11 unmodified lines | |||
| 12 | #define PMF_H | 12 | #define PMF_H |
| 13 | 13 | ||
| 14 | #include <linux/acpi.h> | 14 | #include <linux/acpi.h> |
| 15 | #include <linux/amd-pmf-io.h> | ||
| 16 | #include <linux/circ_buf.h> | ||
| 15 | #include <linux/input.h> | 17 | #include <linux/input.h> |
| 18 | #include <linux/mutex_types.h> | ||
| 16 | #include <linux/platform_device.h> | 19 | #include <linux/platform_device.h> |
| 17 | #include <linux/platform_profile.h> | 20 | #include <linux/platform_profile.h> |
| 18 | 21 | ||
| @@ -120,6 +123,7 @@ struct cookie_header { · 101 unmodified lines | |||
| 120 | #define APTS_MAX_STATES 16 | 123 | #define APTS_MAX_STATES 16 |
| 121 | #define CUSTOM_BIOS_INPUT_BITS GENMASK(16, 7) | 124 | #define CUSTOM_BIOS_INPUT_BITS GENMASK(16, 7) |
| 122 | #define BIOS_INPUTS_MAX 10 | 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 | /* amd_pmf_send_cmd() set/get */ | 128 | /* amd_pmf_send_cmd() set/get */ |
| 125 | #define SET_CMD false | 129 | #define SET_CMD false |
| @@ -129,6 +133,12 @@ struct cookie_header { · 3 unmodified lines | |||
| 129 | 133 | ||
| 130 | typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); | 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 | /* APTS PMF BIOS Interface */ | 142 | /* APTS PMF BIOS Interface */ |
| 133 | struct amd_pmf_apts_output { | 143 | struct amd_pmf_apts_output { |
| 134 | u16 table_version; | 144 | u16 table_version; |
| @@ -365,6 +375,22 @@ struct pmf_bios_inputs_prev { · 230 unmodified lines | |||
| 365 | u32 custom_bios_inputs[BIOS_INPUTS_MAX]; | 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 | struct amd_pmf_dev { | 394 | struct amd_pmf_dev { |
| 369 | void __iomem *regbase; | 395 | void __iomem *regbase; |
| 370 | void __iomem *smu_virt_addr; | 396 | void __iomem *smu_virt_addr; |
| @@ -413,6 +439,9 @@ struct amd_pmf_dev { · 42 unmodified lines | |||
| 413 | struct apmf_sbios_req_v1 req1; | 439 | struct apmf_sbios_req_v1 req1; |
| 414 | struct pmf_bios_inputs_prev cb_prev; /* To preserve custom BIOS inputs */ | 440 | struct pmf_bios_inputs_prev cb_prev; /* To preserve custom BIOS inputs */ |
| 415 | bool cb_flag; /* To handle first custom BIOS input */ | 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 | struct apmf_sps_prop_granular_v2 { | 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 | void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); | 924 | void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); |
| 896 | int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev); | 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 | #endif /* PMF_H */ | 931 | #endif /* PMF_H */ |
| @@ -11,6 +11,7 @@ · 10 unmodified lines | |||
| 11 | 11 | ||
| 12 | #include <acpi/button.h> | 12 | #include <acpi/button.h> |
| 13 | #include <linux/amd-pmf-io.h> | 13 | #include <linux/amd-pmf-io.h> |
| 14 | #include <linux/cleanup.h> | ||
| 14 | #include <linux/power_supply.h> | 15 | #include <linux/power_supply.h> |
| 15 | #include <linux/units.h> | 16 | #include <linux/units.h> |
| 16 | #include "pmf.h" | 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 | const struct amd_pmf_pb_bitmap *inputs, | 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 | unsigned int i; | 140 | unsigned int i; |
| 140 | 141 | ||
| 141 | for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) { | 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 | continue; | 144 | continue; |
| 144 | amd_pmf_set_ta_custom_bios_input(in, i, custom_policy[i]); | 145 | amd_pmf_set_ta_custom_bios_input(in, i, data->val[i]); |
| 145 | pdev->cb_prev.custom_bios_inputs[i] = custom_policy[i]; | 146 | pdev->cb_prev.custom_bios_inputs[i] = data->val[i]; |
| 146 | dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, custom_policy[i]); | 147 | dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, data->val[i]); |
| 147 | } | 148 | } |
| 148 | } | 149 | } |
| 149 | 150 | ||
| 150 | static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev, | 151 | static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev, |
| 151 | struct ta_pmf_enact_table *in) | 152 | struct ta_pmf_enact_table *in) |
| 152 | { | 153 | { |
| 154 | struct pmf_cbi_ring_buffer *rb = &pdev->cbi_buf; | ||
| 153 | unsigned int i; | 155 | unsigned int i; |
| 154 | 156 | ||
| 157 | guard(mutex)(&pdev->cbi_mutex); | ||
| 158 | |||
| 155 | for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) | 159 | for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) |
| 156 | amd_pmf_set_ta_custom_bios_input(in, i, pdev->cb_prev.custom_bios_inputs[i]); | 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 | return; | 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 | if (!pdev->smart_pc_enabled) | 169 | if (!pdev->smart_pc_enabled) |
| 162 | return; | 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 | case PMF_IF_V1: | 173 | case PMF_IF_V1: |
| 166 | if (!is_apmf_bios_input_notifications_supported(pdev)) | 174 | if (!is_apmf_bios_input_notifications_supported(pdev)) |
| 167 | return; | 175 | return; |
| 168 | amd_pmf_update_bios_inputs(pdev, pdev->req1.pending_req, custom_bios_inputs_v1, | 176 | amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs_v1, in); |
| 169 | pdev->req1.custom_policy, in); | ||
| 170 | break; | 177 | break; |
| 171 | case PMF_IF_V2: | 178 | case PMF_IF_V2: |
| 172 | amd_pmf_update_bios_inputs(pdev, pdev->req.pending_req, custom_bios_inputs, | 179 | amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs, in); |
| 173 | pdev->req.custom_policy, in); | ||
| 174 | break; | 180 | break; |
| 175 | default: | 181 | default: |
| 176 | break; | 182 | break; |
| 177 | } | 183 | } |
| 178 | 184 | ||
| 179 | /* Clear pending requests after handling */ | 185 | out_rbadvance: |
| 180 | memset(&pdev->req, 0, sizeof(pdev->req)); | 186 | rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1); |
| 181 | memset(&pdev->req1, 0, sizeof(pdev->req1)); | ||
| 182 | } | 187 | } |
| 183 | 188 | ||
| 184 | static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in) | 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 | MODULE_PARM_DESC(pb_side_load, "Sideload policy binaries debug policy failures"); | 27 | MODULE_PARM_DESC(pb_side_load, "Sideload policy binaries debug policy failures"); |
| 28 | #endif | 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 | static const char *amd_pmf_uevent_as_str(unsigned int state) | 30 | static const char *amd_pmf_uevent_as_str(unsigned int state) |
| 37 | { | 31 | { |
| 38 | switch (state) { | 32 | switch (state) { |
| @@ -324,7 +318,7 @@ static void amd_pmf_invoke_cmd(struct work_struct *work) · 285 unmodified lines | |||
| 324 | schedule_delayed_work(&dev->pb_work, msecs_to_jiffies(pb_actions_ms)); | 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 | struct cookie_header *header; | 323 | struct cookie_header *header; |
| 330 | int res; | 324 | int res; |
| @@ -480,7 +474,7 @@ static int amd_pmf_register_input_device(struct amd_pmf_dev *dev) · 149 unmodified lines | |||
| 480 | return 0; | 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 | u32 size; | 479 | u32 size; |
| 486 | int ret; | 480 | int ret; |
| @@ -528,7 +522,7 @@ out_ctx: · 41 unmodified lines | |||
| 528 | return ret; | 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 | if (!dev->tee_ctx) | 527 | if (!dev->tee_ctx) |
| 534 | return; | 528 | return; |
| @@ -591,6 +585,8 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev) · 56 unmodified lines | |||
| 591 | status = ret == TA_PMF_TYPE_SUCCESS; | 585 | status = ret == TA_PMF_TYPE_SUCCESS; |
| 592 | if (status) { | 586 | if (status) { |
| 593 | dev->cb_flag = true; | 587 | dev->cb_flag = true; |
| 588 | dev->cbi_buf.head = 0; | ||
| 589 | dev->cbi_buf.tail = 0; | ||
| 594 | break; | 590 | break; |
| 595 | } | 591 | } |
| 596 | amd_pmf_tee_deinit(dev); | 592 | amd_pmf_tee_deinit(dev); |
| @@ -42,8 +42,6 @@ static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head); · 41 unmodified lines | |||
| 42 | static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ranges_in_out *in) | 42 | static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ranges_in_out *in) |
| 43 | { | 43 | { |
| 44 | union acpi_object argv4; | 44 | union acpi_object argv4; |
| 45 | union acpi_object *tmp; | ||
| 46 | union acpi_object *obj; | ||
| 47 | u32 num_of_ranges = 0; | 45 | u32 num_of_ranges = 0; |
| 48 | u32 num_of_elements; | 46 | u32 num_of_elements; |
| 49 | u32 arg_idx = 0; | 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 | num_of_elements = 2 * num_of_ranges + 2; | 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 | if (!tmp) | 76 | if (!tmp) |
| 79 | return -ENOMEM; | 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 | tmp[arg_idx++].integer.value = in->band_list[i].end; | 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, | 102 | union acpi_object *obj __free(kfree) = |
| 105 | WBRF_REVISION, WBRF_RECORD, &argv4); | 103 | acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid, |
| 104 | WBRF_REVISION, WBRF_RECORD, &argv4); | ||
| 106 | 105 | ||
| 107 | if (!obj) { | 106 | if (!obj) |
| 108 | kfree(tmp); | ||
| 109 | return -EINVAL; | 107 | return -EINVAL; |
| 110 | } | ||
| 111 | 108 | ||
| 112 | if (obj->type != ACPI_TYPE_INTEGER) { | 109 | if (obj->type != ACPI_TYPE_INTEGER) |
| 113 | ret = -EINVAL; | 110 | return -EINVAL; |
| 114 | goto out; | ||
| 115 | } | ||
| 116 | 111 | ||
| 117 | ret = obj->integer.value; | 112 | ret = obj->integer.value; |
| 118 | if (ret) | 113 | if (ret) |
| 119 | ret = -EINVAL; | 114 | return -EINVAL; |
| 120 | |||
| 121 | out: | ||
| 122 | ACPI_FREE(obj); | ||
| 123 | kfree(tmp); | ||
| 124 | 115 | ||
| 125 | return ret; | 116 | return ret; |
| 126 | } | 117 | } |
| @@ -31,13 +31,13 @@ · 30 unmodified lines | |||
| 31 | #include <linux/pci.h> | 31 | #include <linux/pci.h> |
| 32 | #include <linux/pci_hotplug.h> | 32 | #include <linux/pci_hotplug.h> |
| 33 | #include <linux/platform_data/x86/asus-wmi.h> | 33 | #include <linux/platform_data/x86/asus-wmi.h> |
| 34 | #include <linux/platform_data/x86/asus-wmi-leds-ids.h> | ||
| 35 | #include <linux/platform_device.h> | 34 | #include <linux/platform_device.h> |
| 36 | #include <linux/platform_profile.h> | 35 | #include <linux/platform_profile.h> |
| 37 | #include <linux/power_supply.h> | 36 | #include <linux/power_supply.h> |
| 38 | #include <linux/rfkill.h> | 37 | #include <linux/rfkill.h> |
| 39 | #include <linux/seq_file.h> | 38 | #include <linux/seq_file.h> |
| 40 | #include <linux/slab.h> | 39 | #include <linux/slab.h> |
| 40 | #include <linux/spinlock.h> | ||
| 41 | #include <linux/types.h> | 41 | #include <linux/types.h> |
| 42 | #include <linux/units.h> | 42 | #include <linux/units.h> |
| 43 | 43 | ||
| @@ -256,6 +256,9 @@ struct asus_wmi { · 212 unmodified lines | |||
| 256 | int tpd_led_wk; | 256 | int tpd_led_wk; |
| 257 | struct led_classdev kbd_led; | 257 | struct led_classdev kbd_led; |
| 258 | int kbd_led_wk; | 258 | int kbd_led_wk; |
| 259 | bool kbd_led_notify; | ||
| 260 | bool kbd_led_avail; | ||
| 261 | bool kbd_led_registered; | ||
| 259 | struct led_classdev lightbar_led; | 262 | struct led_classdev lightbar_led; |
| 260 | int lightbar_led_wk; | 263 | int lightbar_led_wk; |
| 261 | struct led_classdev micmute_led; | 264 | struct led_classdev micmute_led; |
| @@ -264,6 +267,7 @@ struct asus_wmi { · 2 unmodified lines | |||
| 264 | struct work_struct tpd_led_work; | 267 | struct work_struct tpd_led_work; |
| 265 | struct work_struct wlan_led_work; | 268 | struct work_struct wlan_led_work; |
| 266 | struct work_struct lightbar_led_work; | 269 | struct work_struct lightbar_led_work; |
| 270 | struct work_struct kbd_led_work; | ||
| 267 | 271 | ||
| 268 | struct asus_rfkill wlan; | 272 | struct asus_rfkill wlan; |
| 269 | struct asus_rfkill bluetooth; | 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 | /* LEDs ***********************************************************************/ | 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 | * These functions actually update the LED's, and are called from a | 1761 | * These functions actually update the LED's, and are called from a |
| 1620 | * workqueue. By doing this as separate work rather than when the LED | 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 | int ctrl_param = 0; | 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 | asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL); | 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 | static void do_kbd_led_set(struct led_classdev *led_cdev, int value) | 1838 | static void do_kbd_led_set(struct led_classdev *led_cdev, int value) |
| 1696 | { | 1839 | { |
| 1840 | struct asus_hid_listener *listener; | ||
| 1697 | struct asus_wmi *asus; | 1841 | struct asus_wmi *asus; |
| 1698 | int max_level; | ||
| 1699 | 1842 | ||
| 1700 | asus = container_of(led_cdev, struct asus_wmi, kbd_led); | 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); | 1845 | scoped_guard(spinlock_irqsave, &asus_ref.lock) |
| 1704 | kbd_led_update(asus); | 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 | static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) | 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 | static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value) | 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; | 1869 | scoped_guard(spinlock_irqsave, &asus_ref.lock) { |
| 1720 | 1870 | asus->kbd_led_wk = value; | |
| 1721 | do_kbd_led_set(led_cdev, value); | 1871 | asus->kbd_led_notify = true; |
| 1722 | led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk); | 1872 | } |
| 1873 | queue_work(asus->led_workqueue, &asus->kbd_led_work); | ||
| 1723 | } | 1874 | } |
| 1724 | 1875 | ||
| 1725 | static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) | 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 | asus = container_of(led_cdev, struct asus_wmi, kbd_led); | 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 | retval = kbd_led_read(asus, &value, NULL); | 1888 | retval = kbd_led_read(asus, &value, NULL); |
| 1733 | if (retval < 0) | 1889 | if (retval < 0) |
| 1734 | return retval; | 1890 | return retval; |
| 1735 | 1891 | ||
| 1892 | scoped_guard(spinlock_irqsave, &asus_ref.lock) | ||
| 1893 | asus->kbd_led_wk = value; | ||
| 1894 | |||
| 1736 | return value; | 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 | static void asus_wmi_led_exit(struct asus_wmi *asus) | 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 | led_classdev_unregister(&asus->tpd_led); | 2009 | led_classdev_unregister(&asus->tpd_led); |
| 1849 | led_classdev_unregister(&asus->wlan_led); | 2010 | led_classdev_unregister(&asus->wlan_led); |
| 1850 | led_classdev_unregister(&asus->lightbar_led); | 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 | goto error; | 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)) { | 2046 | asus->kbd_led.name = "asus::kbd_backlight"; |
| 1886 | pr_info("using asus-wmi for asus::kbd_backlight\n"); | 2047 | asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED; |
| 1887 | asus->kbd_led_wk = led_val; | 2048 | asus->kbd_led.brightness_set_blocking = kbd_led_set; |
| 1888 | asus->kbd_led.name = "asus::kbd_backlight"; | 2049 | asus->kbd_led.brightness_get = kbd_led_get; |
| 1889 | asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED; | 2050 | asus->kbd_led.max_brightness = ASUS_EV_MAX_BRIGHTNESS; |
| 1890 | asus->kbd_led.brightness_set_blocking = kbd_led_set; | 2051 | asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL); |
| 1891 | asus->kbd_led.brightness_get = kbd_led_get; | 2052 | INIT_WORK(&asus->kbd_led_work, kbd_led_update_all); |
| 1892 | asus->kbd_led.max_brightness = 3; | ||
| 1893 | 2053 | ||
| 2054 | if (asus->kbd_led_avail) { | ||
| 2055 | asus->kbd_led_wk = led_val; | ||
| 1894 | if (num_rgb_groups != 0) | 2056 | if (num_rgb_groups != 0) |
| 1895 | asus->kbd_led.groups = kbd_rgb_mode_groups; | 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, | 2062 | scoped_guard(spinlock_irqsave, &asus_ref.lock) { |
| 1898 | &asus->kbd_led); | 2063 | asus_ref.asus = asus; |
| 1899 | if (rv) | 2064 | if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners)) |
| 1900 | goto error; | 2065 | queue_work(asus->led_workqueue, &asus->kbd_led_work); |
| 1901 | } | 2066 | } |
| 1902 | 2067 | ||
| 1903 | if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED) | 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 | static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) | 4538 | static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) |
| 4374 | { | 4539 | { |
| 4540 | enum led_brightness led_value; | ||
| 4375 | unsigned int key_value = 1; | 4541 | unsigned int key_value = 1; |
| 4376 | bool autorelease = 1; | 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 | return; | 4554 | return; |
| 4389 | } | 4555 | } |
| 4390 | 4556 | ||
| 4557 | scoped_guard(spinlock_irqsave, &asus_ref.lock) | ||
| 4558 | led_value = asus->kbd_led_wk; | ||
| 4559 | |||
| 4391 | if (code == NOTIFY_KBD_BRTUP) { | 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 | return; | 4562 | return; |
| 4394 | } | 4563 | } |
| 4395 | if (code == NOTIFY_KBD_BRTDWN) { | 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 | return; | 4566 | return; |
| 4398 | } | 4567 | } |
| 4399 | if (code == NOTIFY_KBD_BRTTOGGLE) { | 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 | kbd_led_set_by_kbd(asus, 0); | 4570 | kbd_led_set_by_kbd(asus, 0); |
| 4402 | else | 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 | return; | 4573 | return; |
| 4405 | } | 4574 | } |
| 4406 | 4575 | ||
| @@ -13,23 +13,28 @@ · 12 unmodified lines | |||
| 13 | 13 | ||
| 14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 15 | 15 | ||
| 16 | #include <linux/kernel.h> | 16 | #include <linux/acpi.h> |
| 17 | #include <linux/module.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 | #include <linux/init.h> | 22 | #include <linux/init.h> |
| 19 | #include <linux/slab.h> | ||
| 20 | #include <linux/types.h> | ||
| 21 | #include <linux/input.h> | 23 | #include <linux/input.h> |
| 22 | #include <linux/input/sparse-keymap.h> | 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 | #include <linux/platform_device.h> | 30 | #include <linux/platform_device.h> |
| 24 | #include <linux/platform_profile.h> | 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 | #include <linux/power_supply.h> | 32 | #include <linux/power_supply.h> |
| 30 | #include <linux/rfkill.h> | 33 | #include <linux/rfkill.h> |
| 34 | #include <linux/slab.h> | ||
| 31 | #include <linux/string.h> | 35 | #include <linux/string.h> |
| 32 | #include <linux/dmi.h> | 36 | #include <linux/types.h> |
| 37 | #include <linux/workqueue.h> | ||
| 33 | 38 | ||
| 34 | MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>"); | 39 | MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>"); |
| 35 | MODULE_DESCRIPTION("HP laptop WMI driver"); | 40 | MODULE_DESCRIPTION("HP laptop WMI driver"); |
| @@ -41,9 +46,13 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4"); · 5 unmodified lines | |||
| 41 | #define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" | 46 | #define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" |
| 42 | #define HPWMI_BIOS_GUID "5FB7F034-2C63-45E9-BE91-3D44E2C707E4" | 47 | #define HPWMI_BIOS_GUID "5FB7F034-2C63-45E9-BE91-3D44E2C707E4" |
| 43 | 48 | ||
| 44 | #define HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET 0x62 | 49 | enum hp_ec_offsets { |
| 45 | #define HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET 0x63 | 50 | HP_EC_OFFSET_UNKNOWN = 0x00, |
| 46 | #define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95 | 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 | #define HP_FAN_SPEED_AUTOMATIC 0x00 | 57 | #define HP_FAN_SPEED_AUTOMATIC 0x00 |
| 49 | #define HP_POWER_LIMIT_DEFAULT 0x00 | 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 | #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required | 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 | /* DMI board names of devices that should use the omen specific path for | 129 | /* DMI board names of devices that should use the omen specific path for |
| 57 | * thermal profiles. | 130 | * thermal profiles. |
| 58 | * This was obtained by taking a look in the windows omen command center | 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 | /* DMI Board names of Victus 16-r and Victus 16-s laptops */ | 174 | /* DMI Board names of Victus 16-r and Victus 16-s laptops */ |
| 102 | static const char * const victus_s_thermal_profile_boards[] = { | 175 | static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { |
| 103 | "8BBE", "8BD4", "8BD5", | 176 | { |
| 104 | "8C78", "8C99", "8C9C", | 177 | .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, |
| 105 | "8D41", | 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 | enum hp_wmi_radio { | 209 | enum hp_wmi_radio { |
| 109 | HPWMI_WIFI = 0x0, | 210 | HPWMI_WIFI = 0x0, |
| 110 | HPWMI_BLUETOOTH = 0x1, | 211 | HPWMI_BLUETOOTH = 0x1, |
| @@ -190,7 +291,8 @@ enum hp_wmi_gm_commandtype { · 79 unmodified lines | |||
| 190 | HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22, | 291 | HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22, |
| 191 | HPWMI_SET_POWER_LIMITS_QUERY = 0x29, | 292 | HPWMI_SET_POWER_LIMITS_QUERY = 0x29, |
| 192 | HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY = 0x2D, | 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 | enum hp_wmi_command { | 298 | enum hp_wmi_command { |
| @@ -225,42 +327,6 @@ enum hp_wireless2_bits { · 28 unmodified lines | |||
| 225 | HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD, | 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 | #define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW) | 330 | #define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW) |
| 265 | #define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT) | 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 | #define DEVICE_MODE_TABLET 0x06 | 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 | /* map output size to the corresponding WMI method id */ | 469 | /* map output size to the corresponding WMI method id */ |
| 352 | static inline int encode_outsize_for_pvsz(int outsize) | 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 | return enabled; | 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 }; | 760 | u8 fan_speed[2]; |
| 643 | int ret; | 761 | int gpu_speed, ret; |
| 644 | 762 | ||
| 645 | ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM, | 763 | fan_speed[CPU_FAN] = speed; |
| 646 | &fan_speed, sizeof(fan_speed), 0); | 764 | fan_speed[GPU_FAN] = speed; |
| 647 | 765 | ||
| 648 | return ret; | 766 | /* |
| 649 | } | 767 | * GPU fan speed is always a little higher than CPU fan speed, we fetch |
| 650 | 768 | * this delta value from the fan table during hwmon init. | |
| 651 | static int hp_wmi_fan_speed_max_reset(void) | 769 | * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to |
| 652 | { | 770 | * automatic mode. |
| 653 | int ret; | 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 | ret = hp_wmi_fan_speed_max_set(0); | 781 | ret = hp_wmi_fan_speed_max_set(0); |
| 656 | if (ret) | 782 | if (ret < 0) |
| 657 | return ret; | 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 | return ret; | 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, | 795 | static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv) |
| 669 | &val, zero_if_sup(val), sizeof(val)); | 796 | { |
| 797 | int ret; | ||
| 670 | 798 | ||
| 799 | ret = hp_wmi_fan_speed_max_set(0); | ||
| 671 | if (ret) | 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 | static int __init hp_wmi_bios_2008_later(void) | 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 | static bool is_victus_s_thermal_profile(void) | 1712 | static bool is_victus_s_thermal_profile(void) |
| 1583 | { | 1713 | { |
| 1584 | const char *board_name; | 1714 | /* Initialised in driver init, hence safe to use here */ |
| 1585 | 1715 | return is_victus_s_board; | |
| 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; | ||
| 1593 | } | 1716 | } |
| 1594 | 1717 | ||
| 1595 | static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, | 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 | return ret; | 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 | static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) | 1850 | static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) |
| 1674 | { | 1851 | { |
| 1852 | struct thermal_profile_params *params; | ||
| 1675 | bool gpu_ctgp_enable, gpu_ppab_enable; | 1853 | bool gpu_ctgp_enable, gpu_ppab_enable; |
| 1676 | u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ | 1854 | u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ |
| 1677 | int err, tp; | 1855 | int err, tp; |
| 1678 | 1856 | ||
| 1857 | params = active_thermal_profile_params; | ||
| 1858 | if (!params) | ||
| 1859 | return -ENODEV; | ||
| 1860 | |||
| 1679 | switch (profile) { | 1861 | switch (profile) { |
| 1680 | case PLATFORM_PROFILE_PERFORMANCE: | 1862 | case PLATFORM_PROFILE_PERFORMANCE: |
| 1681 | tp = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE; | 1863 | tp = params->performance; |
| 1682 | gpu_ctgp_enable = true; | 1864 | gpu_ctgp_enable = true; |
| 1683 | gpu_ppab_enable = true; | 1865 | gpu_ppab_enable = true; |
| 1684 | gpu_dstate = 1; | 1866 | gpu_dstate = 1; |
| 1685 | break; | 1867 | break; |
| 1686 | case PLATFORM_PROFILE_BALANCED: | 1868 | case PLATFORM_PROFILE_BALANCED: |
| 1687 | tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT; | 1869 | tp = params->balanced; |
| 1688 | gpu_ctgp_enable = false; | 1870 | gpu_ctgp_enable = false; |
| 1689 | gpu_ppab_enable = true; | 1871 | gpu_ppab_enable = true; |
| 1690 | gpu_dstate = 1; | 1872 | gpu_dstate = 1; |
| 1691 | break; | 1873 | break; |
| 1692 | case PLATFORM_PROFILE_LOW_POWER: | 1874 | case PLATFORM_PROFILE_LOW_POWER: |
| 1693 | tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT; | 1875 | tp = params->low_power; |
| 1694 | gpu_ctgp_enable = false; | 1876 | gpu_ctgp_enable = false; |
| 1695 | gpu_ppab_enable = false; | 1877 | gpu_ppab_enable = false; |
| 1696 | gpu_dstate = 1; | 1878 | gpu_dstate = 1; |
| @@ -1832,6 +2014,7 @@ static int victus_s_powersource_event(struct notifier_block *nb, · 135 unmodified lines | |||
| 1832 | void *data) | 2014 | void *data) |
| 1833 | { | 2015 | { |
| 1834 | struct acpi_bus_event *event_entry = data; | 2016 | struct acpi_bus_event *event_entry = data; |
| 2017 | enum platform_profile_option actual_profile; | ||
| 1835 | int err; | 2018 | int err; |
| 1836 | 2019 | ||
| 1837 | if (strcmp(event_entry->device_class, ACPI_AC_CLASS) != 0) | 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 | pr_debug("Received power source device event\n"); | 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 | * Switching to battery power source while Performance mode is active | 2037 | * Switching to battery power source while Performance mode is active |
| 1844 | * needs manual triggering of CPU power limits. Same goes when switching | 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 | * Seen on HP 16-s1034nf (board 8C9C) with F.11 and F.13 BIOS versions. | 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 | pr_debug("Triggering CPU PL1/PL2 actualization\n"); | 2045 | pr_debug("Triggering CPU PL1/PL2 actualization\n"); |
| 1852 | err = victus_s_set_cpu_pl1_pl2(HP_POWER_LIMIT_DEFAULT, | 2046 | err = victus_s_set_cpu_pl1_pl2(HP_POWER_LIMIT_DEFAULT, |
| 1853 | HP_POWER_LIMIT_DEFAULT); | 2047 | HP_POWER_LIMIT_DEFAULT); |
| @@ -1958,11 +2152,22 @@ static int thermal_profile_setup(struct platform_device *device) · 104 unmodified lines | |||
| 1958 | ops = &platform_profile_victus_ops; | 2152 | ops = &platform_profile_victus_ops; |
| 1959 | } else if (is_victus_s_thermal_profile()) { | 2153 | } else if (is_victus_s_thermal_profile()) { |
| 1960 | /* | 2154 | /* |
| 1961 | * Being unable to retrieve laptop's current thermal profile, | 2155 | * For an unknown EC layout board, platform_profile_victus_s_get_ec(), |
| 1962 | * during this setup, we set it to Balanced by default. | 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 | err = platform_profile_victus_s_set_ec(active_platform_profile); | 2171 | err = platform_profile_victus_s_set_ec(active_platform_profile); |
| 1967 | if (err < 0) | 2172 | if (err < 0) |
| 1968 | return err; | 2173 | return err; |
| @@ -2031,6 +2236,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) · 62 unmodified lines | |||
| 2031 | static void __exit hp_wmi_bios_remove(struct platform_device *device) | 2236 | static void __exit hp_wmi_bios_remove(struct platform_device *device) |
| 2032 | { | 2237 | { |
| 2033 | int i; | 2238 | int i; |
| 2239 | struct hp_wmi_hwmon_priv *priv; | ||
| 2034 | 2240 | ||
| 2035 | for (i = 0; i < rfkill2_count; i++) { | 2241 | for (i = 0; i < rfkill2_count; i++) { |
| 2036 | rfkill_unregister(rfkill2[i].rfkill); | 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 | rfkill_unregister(wwan_rfkill); | 2255 | rfkill_unregister(wwan_rfkill); |
| 2050 | rfkill_destroy(wwan_rfkill); | 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 | static int hp_wmi_resume_handler(struct device *device) | 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 | .remove = __exit_p(hp_wmi_bios_remove), | 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 | static umode_t hp_wmi_hwmon_is_visible(const void *data, | 2363 | static umode_t hp_wmi_hwmon_is_visible(const void *data, |
| 2112 | enum hwmon_sensor_types type, | 2364 | enum hwmon_sensor_types type, |
| 2113 | u32 attr, int channel) | 2365 | u32 attr, int channel) |
| 2114 | { | 2366 | { |
| 2115 | switch (type) { | 2367 | switch (type) { |
| 2116 | case hwmon_pwm: | 2368 | case hwmon_pwm: |
| 2369 | if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) | ||
| 2370 | return 0; | ||
| 2117 | return 0644; | 2371 | return 0644; |
| 2118 | case hwmon_fan: | 2372 | case hwmon_fan: |
| 2119 | if (is_victus_s_thermal_profile()) { | 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 | static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, | 2388 | static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, |
| 2135 | u32 attr, int channel, long *val) | 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 | switch (type) { | 2395 | switch (type) { |
| 2140 | case hwmon_fan: | 2396 | case hwmon_fan: |
| 2141 | if (is_victus_s_thermal_profile()) | 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 | *val = ret; | 2403 | *val = ret; |
| 2148 | return 0; | 2404 | return 0; |
| 2149 | case hwmon_pwm: | 2405 | case hwmon_pwm: |
| 2150 | switch (hp_wmi_fan_speed_max_get()) { | 2406 | if (attr == hwmon_pwm_input) { |
| 2151 | case 0: | 2407 | if (!is_victus_s_thermal_profile()) |
| 2152 | /* 0 is automatic fan, which is 2 for hwmon */ | 2408 | return -EOPNOTSUPP; |
| 2153 | *val = 2; | 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 | return 0; | 2414 | return 0; |
| 2155 | case 1: | 2415 | } |
| 2156 | /* 1 is max fan, which is 0 | 2416 | switch (priv->mode) { |
| 2157 | * (no fan speed control) for hwmon | 2417 | case PWM_MODE_MAX: |
| 2158 | */ | 2418 | case PWM_MODE_MANUAL: |
| 2159 | *val = 0; | 2419 | case PWM_MODE_AUTO: |
| 2420 | *val = priv->mode; | ||
| 2160 | return 0; | 2421 | return 0; |
| 2161 | default: | 2422 | default: |
| 2162 | /* shouldn't happen */ | 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 | static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, | 2431 | static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, |
| 2171 | u32 attr, int channel, long val) | 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 | switch (type) { | 2438 | switch (type) { |
| 2174 | case hwmon_pwm: | 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 | switch (val) { | 2453 | switch (val) { |
| 2176 | case 0: | 2454 | case PWM_MODE_MAX: |
| 2177 | if (is_victus_s_thermal_profile()) | 2455 | priv->mode = PWM_MODE_MAX; |
| 2178 | hp_wmi_get_fan_count_userdefine_trigger(); | 2456 | return hp_wmi_apply_fan_settings(priv); |
| 2179 | /* 0 is no fan speed control (max), which is 1 for us */ | 2457 | case PWM_MODE_MANUAL: |
| 2180 | return hp_wmi_fan_speed_max_set(1); | 2458 | if (!is_victus_s_thermal_profile()) |
| 2181 | case 2: | 2459 | return -EOPNOTSUPP; |
| 2182 | /* 2 is automatic speed control, which is 0 for us */ | 2460 | /* |
| 2183 | if (is_victus_s_thermal_profile()) { | 2461 | * When switching to manual mode, set fan speed to |
| 2184 | hp_wmi_get_fan_count_userdefine_trigger(); | 2462 | * current RPM values to ensure a smooth transition. |
| 2185 | return hp_wmi_fan_speed_max_reset(); | 2463 | */ |
| 2186 | } else | 2464 | rpm = hp_wmi_get_fan_speed_victus_s(channel); |
| 2187 | return hp_wmi_fan_speed_max_set(0); | 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 | default: | 2473 | default: |
| 2189 | /* we don't support manual fan speed control */ | ||
| 2190 | return -EINVAL; | 2474 | return -EINVAL; |
| 2191 | } | 2475 | } |
| 2192 | default: | 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 | static const struct hwmon_channel_info * const info[] = { | 2481 | static const struct hwmon_channel_info * const info[] = { |
| 2198 | HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), | 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 | NULL | 2484 | NULL |
| 2201 | }; | 2485 | }; |
| 2202 | 2486 | ||
| @@ -2211,12 +2495,70 @@ static const struct hwmon_chip_info chip_info = { · 8 unmodified lines | |||
| 2211 | .info = info, | 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 | static int hp_wmi_hwmon_init(void) | 2547 | static int hp_wmi_hwmon_init(void) |
| 2215 | { | 2548 | { |
| 2216 | struct device *dev = &hp_wmi_platform_dev->dev; | 2549 | struct device *dev = &hp_wmi_platform_dev->dev; |
| 2550 | struct hp_wmi_hwmon_priv *priv; | ||
| 2217 | struct device *hwmon; | 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 | &chip_info, NULL); | 2562 | &chip_info, NULL); |
| 2221 | 2563 | ||
| 2222 | if (IS_ERR(hwmon)) { | 2564 | if (IS_ERR(hwmon)) { |
| @@ -2224,9 +2566,37 @@ static int hp_wmi_hwmon_init(void) · 1 unmodified line | |||
| 2224 | return PTR_ERR(hwmon); | 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 | return 0; | 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 | static int __init hp_wmi_init(void) | 2600 | static int __init hp_wmi_init(void) |
| 2231 | { | 2601 | { |
| 2232 | int event_capable = wmi_has_guid(HPWMI_EVENT_GUID); | 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 | goto err_destroy_input; | 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 | err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); | 2632 | err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); |
| 2258 | if (err) | 2633 | if (err) |
| 2259 | goto err_unregister_device; | 2634 | goto err_unregister_device; |
| @@ -776,16 +776,26 @@ static inline u64 adjust_lpm_residency(struct pmc *pmc, u32 offset, · 775 unmodified lines | |||
| 776 | static int pmc_core_substate_res_show(struct seq_file *s, void *unused) | 776 | static int pmc_core_substate_res_show(struct seq_file *s, void *unused) |
| 777 | { | 777 | { |
| 778 | struct pmc_dev *pmcdev = s->private; | 778 | struct pmc_dev *pmcdev = s->private; |
| 779 | struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; | 779 | unsigned int pmc_idx; |
| 780 | const int lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2; | 780 | |
| 781 | u32 offset = pmc->map->lpm_residency_offset; | 781 | for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) { |
| 782 | int mode; | 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) { | 794 | seq_printf(s, "pmc%u %10s %15s\n", pmc_idx, "Substate", "Residency"); |
| 787 | seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode], | 795 | pmc_for_each_mode(mode, pmc) { |
| 788 | adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2)); | 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 | return 0; | 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 | enum header_type type) | 848 | enum header_type type) |
| 839 | { | 849 | { |
| 840 | struct pmc_dev *pmcdev = s->private; | 850 | struct pmc_dev *pmcdev = s->private; |
| 841 | int mode; | 851 | struct pmc *pmc = pmcdev->pmcs[pmc_index]; |
| 852 | u8 mode; | ||
| 842 | 853 | ||
| 843 | seq_printf(s, "%40s |", "Element"); | 854 | seq_printf(s, "%40s |", "Element"); |
| 844 | pmc_for_each_mode(mode, pmcdev) | 855 | pmc_for_each_mode(mode, pmc) |
| 845 | seq_printf(s, " %9s |", pmc_lpm_modes[mode]); | 856 | seq_printf(s, " %9s |", pmc_lpm_modes[mode]); |
| 846 | 857 | ||
| 847 | if (type == HEADER_STATUS) { | 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 | const struct pmc_bit_map *map; | 891 | const struct pmc_bit_map *map; |
| 881 | 892 | ||
| 882 | for (map = maps[r_idx]; map->name; map++) { | 893 | for (map = maps[r_idx]; map->name; map++) { |
| 883 | int mode; | 894 | u8 mode; |
| 884 | 895 | ||
| 885 | if (!map->blk) | 896 | if (!map->blk) |
| 886 | continue; | 897 | continue; |
| 887 | 898 | ||
| 888 | counter = pmc_core_reg_read(pmc, offset); | 899 | counter = pmc_core_reg_read(pmc, offset); |
| 889 | seq_printf(s, "pmc%u: %34s |", pmc_idx, map->name); | 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 | bool required = *lpm_req_regs & BIT(mode); | 902 | bool required = *lpm_req_regs & BIT(mode); |
| 892 | 903 | ||
| 893 | seq_printf(s, " %9s |", required ? "Required" : " "); | 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 | u32 lpm_status; | 964 | u32 lpm_status; |
| 954 | u32 lpm_status_live; | 965 | u32 lpm_status_live; |
| 955 | const struct pmc_bit_map *map; | 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 | * Capture the requirements and create a mask so that we only | 971 | * Capture the requirements and create a mask so that we only |
| 960 | * show an element if it's required for at least one of the | 972 | * show an element if it's required for at least one of the |
| 961 | * enabled low power modes | 973 | * enabled low power modes |
| 962 | */ | 974 | */ |
| 963 | pmc_for_each_mode(mode, pmcdev) | 975 | pmc_for_each_mode(mode, pmc) |
| 964 | req_mask |= lpm_req_regs[mp + (mode * num_maps)]; | 976 | req_mask |= lpm_req_regs[mp + (mode * num_maps)]; |
| 965 | 977 | ||
| 966 | /* Get the last latched status for this map */ | 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 | seq_printf(s, "pmc%d: %34s |", pmc_idx, map[i].name); | 998 | seq_printf(s, "pmc%d: %34s |", pmc_idx, map[i].name); |
| 987 | 999 | ||
| 988 | /* Loop over the enabled states and display if required */ | 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 | bool required = lpm_req_regs[mp + (mode * num_maps)] & | 1002 | bool required = lpm_req_regs[mp + (mode * num_maps)] & |
| 991 | bit_mask; | 1003 | bit_mask; |
| 992 | seq_printf(s, " %9s |", required ? "Required" : " "); | 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 | struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; | 1077 | struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; |
| 1066 | bool c10; | 1078 | bool c10; |
| 1067 | u32 reg; | 1079 | u32 reg; |
| 1068 | int mode; | 1080 | u8 mode; |
| 1069 | 1081 | ||
| 1070 | reg = pmc_core_reg_read(pmc, pmc->map->lpm_sts_latch_en_offset); | 1082 | reg = pmc_core_reg_read(pmc, pmc->map->lpm_sts_latch_en_offset); |
| 1071 | if (reg & LPM_STS_LATCH_MODE) { | 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 | c10 = true; | 1088 | c10 = true; |
| 1077 | } | 1089 | } |
| 1078 | 1090 | ||
| 1079 | pmc_for_each_mode(mode, pmcdev) { | 1091 | pmc_for_each_mode(mode, pmc) { |
| 1080 | if ((BIT(mode) & reg) && !c10) | 1092 | if ((BIT(mode) & reg) && !c10) |
| 1081 | seq_printf(s, " [%s]", pmc_lpm_modes[mode]); | 1093 | seq_printf(s, " [%s]", pmc_lpm_modes[mode]); |
| 1082 | else | 1094 | else |
| @@ -1097,8 +1109,9 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, · 14 unmodified lines | |||
| 1097 | struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; | 1109 | struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; |
| 1098 | bool clear = false, c10 = false; | 1110 | bool clear = false, c10 = false; |
| 1099 | unsigned char buf[8]; | 1111 | unsigned char buf[8]; |
| 1100 | int m, mode; | 1112 | int mode; |
| 1101 | u32 reg; | 1113 | u32 reg; |
| 1114 | u8 m; | ||
| 1102 | 1115 | ||
| 1103 | if (count > sizeof(buf) - 1) | 1116 | if (count > sizeof(buf) - 1) |
| 1104 | return -EINVAL; | 1117 | return -EINVAL; |
| @@ -1115,7 +1128,7 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, · 10 unmodified lines | |||
| 1115 | mode = sysfs_match_string(pmc_lpm_modes, buf); | 1128 | mode = sysfs_match_string(pmc_lpm_modes, buf); |
| 1116 | 1129 | ||
| 1117 | /* Check string matches enabled mode */ | 1130 | /* Check string matches enabled mode */ |
| 1118 | pmc_for_each_mode(m, pmcdev) | 1131 | pmc_for_each_mode(m, pmc) |
| 1119 | if (mode == m) | 1132 | if (mode == m) |
| 1120 | break; | 1133 | break; |
| 1121 | 1134 | ||
| @@ -1211,15 +1224,15 @@ static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order) · 89 unmodified lines | |||
| 1211 | return true; | 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 | u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI; | 1229 | u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI; |
| 1218 | u8 mode_order[LPM_MAX_NUM_MODES]; | 1230 | u8 mode_order[LPM_MAX_NUM_MODES]; |
| 1219 | u32 lpm_pri; | 1231 | u32 lpm_pri; |
| 1220 | u32 lpm_en; | 1232 | u32 lpm_en; |
| 1233 | u8 mode; | ||
| 1221 | unsigned int i; | 1234 | unsigned int i; |
| 1222 | int mode, p; | 1235 | int p; |
| 1223 | 1236 | ||
| 1224 | /* Use LPM Maps to indicate support for substates */ | 1237 | /* Use LPM Maps to indicate support for substates */ |
| 1225 | if (!pmc->map->lpm_num_maps) | 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 | * Lower byte is enough to cover the number of lpm modes for all | 1243 | * Lower byte is enough to cover the number of lpm modes for all |
| 1231 | * platforms and hence mask the upper 3 bytes. | 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 | /* Read 32 bit LPM_PRI register */ | 1248 | /* Read 32 bit LPM_PRI register */ |
| 1236 | lpm_pri = pmc_core_reg_read(pmc, pmc->map->lpm_priority_offset); | 1249 | lpm_pri = pmc_core_reg_read(pmc, pmc->map->lpm_priority_offset); |
| 1237 | 1250 | ||
| 1238 | |||
| 1239 | /* | 1251 | /* |
| 1240 | * If lpm_pri value passes verification, then override the default | 1252 | * If lpm_pri value passes verification, then override the default |
| 1241 | * modes here. Otherwise stick with the default. | 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 | i = 0; | 1267 | i = 0; |
| 1256 | for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) { | 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 | if (!(BIT(mode) & lpm_en)) | 1271 | if (!(BIT(mode) & lpm_en)) |
| 1260 | continue; | 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 | const u8 *lpm_indices; | 1518 | const u8 *lpm_indices; |
| 1492 | int num_maps, mode_offset = 0; | 1519 | int num_maps, mode_offset = 0; |
| 1493 | int ret, mode; | 1520 | int ret, lpm_size; |
| 1494 | int lpm_size; | 1521 | u8 mode; |
| 1495 | 1522 | ||
| 1496 | lpm_indices = pmc->map->lpm_reg_index; | 1523 | lpm_indices = pmc->map->lpm_reg_index; |
| 1497 | num_maps = pmc->map->lpm_num_maps; | 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 | return -ENOMEM; | 1531 | return -ENOMEM; |
| 1505 | 1532 | ||
| 1506 | mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET; | 1533 | mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET; |
| 1507 | pmc_for_each_mode(mode, pmcdev) { | 1534 | pmc_for_each_mode(mode, pmc) { |
| 1508 | u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps); | 1535 | u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps); |
| 1509 | int m; | 1536 | int m; |
| 1510 | 1537 | ||
| @@ -423,6 +423,8 @@ struct pmc_info { · 422 unmodified lines | |||
| 423 | * specific attributes | 423 | * specific attributes |
| 424 | * @lpm_req_regs: List of substate requirements | 424 | * @lpm_req_regs: List of substate requirements |
| 425 | * @ltr_ign: Holds LTR ignore data while suspended | 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 | * pmc contains info about one power management controller device. | 429 | * pmc contains info about one power management controller device. |
| 428 | */ | 430 | */ |
| @@ -432,6 +434,8 @@ struct pmc { · 3 unmodified lines | |||
| 432 | const struct pmc_reg_map *map; | 434 | const struct pmc_reg_map *map; |
| 433 | u32 *lpm_req_regs; | 435 | u32 *lpm_req_regs; |
| 434 | u32 ltr_ign; | 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 | * @pkgc_res_cnt: Array of PKGC residency counters | 450 | * @pkgc_res_cnt: Array of PKGC residency counters |
| 447 | * @num_of_pkgc: Number of PKGC | 451 | * @num_of_pkgc: Number of PKGC |
| 448 | * @s0ix_counter: S0ix residency (step adjusted) | 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 | * @suspend: Function to perform platform specific suspend | 453 | * @suspend: Function to perform platform specific suspend |
| 452 | * @resume: Function to perform platform specific resume | 454 | * @resume: Function to perform platform specific resume |
| 453 | * | 455 | * |
| @@ -462,8 +464,6 @@ struct pmc_dev { · 8 unmodified lines | |||
| 462 | struct mutex lock; /* generic mutex lock for PMC Core */ | 464 | struct mutex lock; /* generic mutex lock for PMC Core */ |
| 463 | 465 | ||
| 464 | u64 s0ix_counter; | 466 | u64 s0ix_counter; |
| 465 | int num_lpm_modes; | ||
| 466 | int lpm_en_modes[LPM_MAX_NUM_MODES]; | ||
| 467 | void (*suspend)(struct pmc_dev *pmcdev); | 467 | void (*suspend)(struct pmc_dev *pmcdev); |
| 468 | int (*resume)(struct pmc_dev *pmcdev); | 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 | int pmc_core_resume_common(struct pmc_dev *pmcdev); | 536 | int pmc_core_resume_common(struct pmc_dev *pmcdev); |
| 537 | int get_primary_reg_base(struct pmc *pmc); | 537 | int get_primary_reg_base(struct pmc *pmc); |
| 538 | void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev); | ||
| 539 | void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids); | 538 | void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids); |
| 540 | void pmc_core_set_device_d3(unsigned int device); | 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 | extern const struct file_operations pmc_core_substate_req_regs_fops; | 562 | extern const struct file_operations pmc_core_substate_req_regs_fops; |
| 564 | extern const struct file_operations pmc_core_substate_blk_req_fops; | 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 | for (unsigned int __i = 0, __cond; \ | 566 | for (unsigned int __i = 0, __cond; \ |
| 568 | __cond = __i < (pmcdev)->num_lpm_modes, \ | 567 | __cond = __i < (pmc)->num_lpm_modes, \ |
| 569 | __cond && ((mode) = (pmcdev)->lpm_en_modes[__i]), \ | 568 | __cond && ((mode) = (pmc)->lpm_en_modes[__i]), \ |
| 570 | __cond; \ | 569 | __cond; \ |
| 571 | __i++) | 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 | struct intel_pmt_entry *entry = dev_get_drvdata(dev); | 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 | static DEVICE_ATTR_RO(guid); | 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 | struct intel_pmt_entry *entry = dev_get_drvdata(dev); | 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 | static DEVICE_ATTR_RO(size); | 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 | struct intel_pmt_entry *entry = dev_get_drvdata(dev); | 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 | static DEVICE_ATTR_RO(offset); | 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 | return -EINVAL; | 612 | return -EINVAL; |
| 613 | 613 | ||
| 614 | if (core_power.get_set) { | 614 | if (core_power.get_set) { |
| 615 | if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) | ||
| 616 | return -EPERM; | ||
| 617 | |||
| 615 | _write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET, | 618 | _write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET, |
| 616 | SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE) | 619 | SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE) |
| 617 | _write_cp_info("cp_prio_type", core_power.priority_type, SST_CP_CONTROL_OFFSET, | 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 | return -EINVAL; | 659 | return -EINVAL; |
| 657 | 660 | ||
| 658 | if (clos_param.get_set) { | 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 | return -EPERM; | 663 | return -EPERM; |
| 661 | 664 | ||
| 662 | _write_cp_info("clos.min_freq", clos_param.min_freq_mhz, | 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 | power_domain_info = &sst_inst->power_domain_info[part][punit_id]; | 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 | return -EPERM; | 756 | return -EPERM; |
| 753 | 757 | ||
| 754 | offset = SST_CLOS_ASSOC_0_OFFSET + | 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 | if (!power_domain_info) | 929 | if (!power_domain_info) |
| 926 | return -EINVAL; | 930 | return -EINVAL; |
| 927 | 931 | ||
| 928 | if (power_domain_info->write_blocked) | 932 | if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) |
| 929 | return -EPERM; | 933 | return -EPERM; |
| 930 | 934 | ||
| 931 | if (!(power_domain_info->pp_header.allowed_level_mask & BIT(perf_level.level))) | 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 | if (!power_domain_info) | 989 | if (!power_domain_info) |
| 986 | return -EINVAL; | 990 | return -EINVAL; |
| 987 | 991 | ||
| 988 | if (power_domain_info->write_blocked) | 992 | if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) |
| 989 | return -EPERM; | 993 | return -EPERM; |
| 990 | 994 | ||
| 991 | _write_pp_info("perf_feature", perf_feature.feature, SST_PP_CONTROL_OFFSET, | 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 | EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_remove, "INTEL_TPMI_SST"); | 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 | void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev) | 1727 | void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev) |
| 1721 | { | 1728 | { |
| 1722 | struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev); | 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 | struct oobmsm_plat_info *plat_info; | 1731 | struct oobmsm_plat_info *plat_info; |
| 1725 | void __iomem *cp_base; | 1732 | void __iomem *cp_base; |
| 1733 | int num_resources, i; | ||
| 1726 | 1734 | ||
| 1727 | plat_info = tpmi_get_platform_data(auxdev); | 1735 | plat_info = tpmi_get_platform_data(auxdev); |
| 1728 | if (!plat_info) | 1736 | if (!plat_info) |
| 1729 | return; | 1737 | return; |
| 1730 | 1738 | ||
| 1731 | power_domain_info = tpmi_sst->power_domain_info[plat_info->partition]; | 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; | 1742 | for (i = 0; i < num_resources; i++) { |
| 1734 | power_domain_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET); | 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, | 1750 | cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset; |
| 1737 | sizeof(power_domain_info->saved_clos_configs)); | 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, | 1757 | process_pp_suspend: |
| 1740 | sizeof(power_domain_info->saved_clos_assocs)); | 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 + | 1761 | pd_info->saved_pp_control = readq(pd_info->sst_base + |
| 1743 | power_domain_info->sst_header.pp_offset + | 1762 | pd_info->sst_header.pp_offset + |
| 1744 | SST_PP_CONTROL_OFFSET); | 1763 | SST_PP_CONTROL_OFFSET); |
| 1764 | } | ||
| 1745 | } | 1765 | } |
| 1746 | EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_suspend, "INTEL_TPMI_SST"); | 1766 | EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_suspend, "INTEL_TPMI_SST"); |
| 1747 | 1767 | ||
| 1748 | void tpmi_sst_dev_resume(struct auxiliary_device *auxdev) | 1768 | void tpmi_sst_dev_resume(struct auxiliary_device *auxdev) |
| 1749 | { | 1769 | { |
| 1750 | struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev); | 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 | struct oobmsm_plat_info *plat_info; | 1772 | struct oobmsm_plat_info *plat_info; |
| 1753 | void __iomem *cp_base; | 1773 | void __iomem *cp_base; |
| 1774 | int num_resources, i; | ||
| 1754 | 1775 | ||
| 1755 | plat_info = tpmi_get_platform_data(auxdev); | 1776 | plat_info = tpmi_get_platform_data(auxdev); |
| 1756 | if (!plat_info) | 1777 | if (!plat_info) |
| 1757 | return; | 1778 | return; |
| 1758 | 1779 | ||
| 1759 | power_domain_info = tpmi_sst->power_domain_info[plat_info->partition]; | 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; | 1788 | if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE)) |
| 1762 | writeq(power_domain_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET); | 1789 | goto process_pp_resume; |
| 1763 | 1790 | ||
| 1764 | memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, power_domain_info->saved_clos_configs, | 1791 | cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset; |
| 1765 | sizeof(power_domain_info->saved_clos_configs)); | 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, | 1798 | process_pp_resume: |
| 1768 | sizeof(power_domain_info->saved_clos_assocs)); | 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 + | 1802 | writeq(pd_info->saved_pp_control, power_domain_info->sst_base + |
| 1771 | power_domain_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET); | 1803 | pd_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET); |
| 1804 | } | ||
| 1772 | } | 1805 | } |
| 1773 | EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_resume, "INTEL_TPMI_SST"); | 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 | struct uncore_data *data = container_of(attr, struct uncore_data, domain_id_kobj_attr); | 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 | static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf) | 32 | static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| 33 | { | 33 | { |
| 34 | struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_kobj_attr); | 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 | static ssize_t show_package_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf) | 39 | static ssize_t show_package_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| 40 | { | 40 | { |
| 41 | struct uncore_data *data = container_of(attr, struct uncore_data, package_id_kobj_attr); | 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 | #define MAX_UNCORE_AGENT_TYPES 4 | 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 | if (ret) | 77 | if (ret) |
| 78 | return ret; | 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 | static ssize_t store_attr(struct uncore_data *data, const char *buf, ssize_t count, | 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 | goto uncore_unlock; | 269 | goto uncore_unlock; |
| 270 | 270 | ||
| 271 | data->instance_id = ret; | 271 | data->instance_id = ret; |
| 272 | sprintf(data->name, "uncore%02d", ret); | 272 | scnprintf(data->name, sizeof(data->name), "uncore%02d", ret); |
| 273 | } else { | 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 | uncore_read(data, &data->initial_min_freq_khz, UNCORE_INDEX_MIN_FREQ); | 278 | uncore_read(data, &data->initial_min_freq_khz, UNCORE_INDEX_MIN_FREQ); |
| @@ -14,7 +14,6 @@ · 13 unmodified lines | |||
| 14 | * https://slimbootloader.github.io/security/firmware-update.html | 14 | * https://slimbootloader.github.io/security/firmware-update.html |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | #include <linux/acpi.h> | ||
| 18 | #include <linux/device.h> | 17 | #include <linux/device.h> |
| 19 | #include <linux/module.h> | 18 | #include <linux/module.h> |
| 20 | #include <linux/slab.h> | 19 | #include <linux/slab.h> |
| @@ -25,41 +24,35 @@ · 4 unmodified lines | |||
| 25 | 24 | ||
| 26 | static int get_fwu_request(struct device *dev, u32 *out) | 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 | ret = wmidev_query_block(to_wmi_device(dev), 0, &buffer); |
| 31 | if (!obj) | 32 | if (ret < 0) |
| 32 | return -ENODEV; | 33 | return ret; |
| 33 | 34 | ||
| 34 | if (obj->type != ACPI_TYPE_INTEGER) { | 35 | if (buffer.length < sizeof(*result)) { |
| 35 | dev_warn(dev, "wmidev_block_query returned invalid value\n"); | 36 | kfree(buffer.data); |
| 36 | kfree(obj); | 37 | return -ENODATA; |
| 37 | return -EINVAL; | ||
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | *out = obj->integer.value; | 40 | result = buffer.data; |
| 41 | kfree(obj); | 41 | *out = le32_to_cpu(*result); |
| 42 | kfree(result); | ||
| 42 | 43 | ||
| 43 | return 0; | 44 | return 0; |
| 44 | } | 45 | } |
| 45 | 46 | ||
| 46 | static int set_fwu_request(struct device *dev, u32 in) | 47 | static int set_fwu_request(struct device *dev, u32 in) |
| 47 | { | 48 | { |
| 48 | struct acpi_buffer input; | 49 | __le32 value = cpu_to_le32(in); |
| 49 | acpi_status status; | 50 | struct wmi_buffer buffer = { |
| 50 | u32 value; | 51 | .length = sizeof(value), |
| 51 | 52 | .data = &value, | |
| 52 | value = in; | 53 | }; |
| 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 | } | ||
| 61 | 54 | ||
| 62 | return 0; | 55 | return wmidev_set_block(to_wmi_device(dev), 0, &buffer); |
| 63 | } | 56 | } |
| 64 | 57 | ||
| 65 | static ssize_t firmware_update_request_show(struct device *dev, | 58 | static ssize_t firmware_update_request_show(struct device *dev, |
| @@ -7,7 +7,6 @@ · 6 unmodified lines | |||
| 7 | 7 | ||
| 8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 9 | 9 | ||
| 10 | #include <linux/acpi.h> | ||
| 11 | #include <linux/device.h> | 10 | #include <linux/device.h> |
| 12 | #include <linux/fs.h> | 11 | #include <linux/fs.h> |
| 13 | #include <linux/hex.h> | 12 | #include <linux/hex.h> |
| @@ -24,24 +23,21 @@ static ssize_t force_power_store(struct device *dev, · 10 unmodified lines | |||
| 24 | struct device_attribute *attr, | 23 | struct device_attribute *attr, |
| 25 | const char *buf, size_t count) | 24 | const char *buf, size_t count) |
| 26 | { | 25 | { |
| 27 | struct acpi_buffer input; | 26 | struct wmi_buffer buffer; |
| 28 | acpi_status status; | 27 | int ret; |
| 29 | u8 mode; | 28 | u8 mode; |
| 30 | 29 | ||
| 31 | input.length = sizeof(u8); | 30 | buffer.length = sizeof(mode); |
| 32 | input.pointer = &mode; | 31 | buffer.data = &mode; |
| 32 | |||
| 33 | mode = hex_to_bin(buf[0]); | 33 | mode = hex_to_bin(buf[0]); |
| 34 | dev_dbg(dev, "force_power: storing %#x\n", mode); | 34 | if (mode > 1) |
| 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"); | ||
| 43 | return -EINVAL; | 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 | return count; | 41 | return count; |
| 46 | } | 42 | } |
| 47 | 43 | ||
| @@ -233,7 +233,7 @@ config YT2_1380 · 232 unmodified lines | |||
| 233 | To compile this driver as a module, choose M here: the module will | 233 | To compile this driver as a module, choose M here: the module will |
| 234 | be called lenovo-yogabook. | 234 | be called lenovo-yogabook. |
| 235 | 235 | ||
| 236 | config LENOVO_WMI_DATA01 | 236 | config LENOVO_WMI_CAPDATA |
| 237 | tristate | 237 | tristate |
| 238 | depends on ACPI_WMI | 238 | depends on ACPI_WMI |
| 239 | 239 | ||
| @@ -263,8 +263,9 @@ config LENOVO_WMI_GAMEZONE · 23 unmodified lines | |||
| 263 | config LENOVO_WMI_TUNING | 263 | config LENOVO_WMI_TUNING |
| 264 | tristate "Lenovo Other Mode WMI Driver" | 264 | tristate "Lenovo Other Mode WMI Driver" |
| 265 | depends on ACPI_WMI | 265 | depends on ACPI_WMI |
| 266 | select HWMON | ||
| 266 | select FW_ATTR_CLASS | 267 | select FW_ATTR_CLASS |
| 267 | select LENOVO_WMI_DATA01 | 268 | select LENOVO_WMI_CAPDATA |
| 268 | select LENOVO_WMI_EVENTS | 269 | select LENOVO_WMI_EVENTS |
| 269 | select LENOVO_WMI_HELPERS | 270 | select LENOVO_WMI_HELPERS |
| 270 | help | 271 | help |
| @@ -12,7 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o · 11 unmodified lines | |||
| 12 | lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o | 12 | lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o |
| 13 | lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o | 13 | lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o |
| 14 | lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o | 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 | lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o | 16 | lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o |
| 17 | lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o | 17 | lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o |
| 18 | lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += wmi-gamezone.o | 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 | static bool allow_v4_dytc; | 219 | static bool allow_v4_dytc; |
| 220 | module_param(allow_v4_dytc, bool, 0444); | 220 | module_param(allow_v4_dytc, bool, 0444); |
| 221 | MODULE_PARM_DESC(allow_v4_dytc, | 221 | MODULE_PARM_DESC(allow_v4_dytc, |
| 222 | "Enable DYTC version 4 platform-profile support. " | 222 | "Enable DYTC version 4 platform-profile support. If you need this please report this to: platform-driver-x86@vger.kernel.org"); |
| 223 | "If you need this please report this to: platform-driver-x86@vger.kernel.org"); | ||
| 224 | 223 | ||
| 225 | static bool hw_rfkill_switch; | 224 | static bool hw_rfkill_switch; |
| 226 | module_param(hw_rfkill_switch, bool, 0444); | 225 | module_param(hw_rfkill_switch, bool, 0444); |
| 227 | MODULE_PARM_DESC(hw_rfkill_switch, | 226 | MODULE_PARM_DESC(hw_rfkill_switch, |
| 228 | "Enable rfkill support for laptops with a hw on/off wifi switch/slider. " | 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"); |
| 229 | "If you need this please report this to: platform-driver-x86@vger.kernel.org"); | ||
| 230 | 228 | ||
| 231 | static bool set_fn_lock_led; | 229 | static bool set_fn_lock_led; |
| 232 | module_param(set_fn_lock_led, bool, 0444); | 230 | module_param(set_fn_lock_led, bool, 0444); |
| 233 | MODULE_PARM_DESC(set_fn_lock_led, | 231 | MODULE_PARM_DESC(set_fn_lock_led, |
| 234 | "Enable driver based updates of the fn-lock LED on fn-lock changes. " | 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"); |
| 235 | "If you need this please report this to: platform-driver-x86@vger.kernel.org"); | ||
| 236 | 233 | ||
| 237 | static bool ctrl_ps2_aux_port; | 234 | static bool ctrl_ps2_aux_port; |
| 238 | module_param(ctrl_ps2_aux_port, bool, 0444); | 235 | module_param(ctrl_ps2_aux_port, bool, 0444); |
| 239 | MODULE_PARM_DESC(ctrl_ps2_aux_port, | 236 | MODULE_PARM_DESC(ctrl_ps2_aux_port, |
| 240 | "Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. " | 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"); |
| 241 | "If you need this please report this to: platform-driver-x86@vger.kernel.org"); | ||
| 242 | 238 | ||
| 243 | static bool touchpad_ctrl_via_ec; | 239 | static bool touchpad_ctrl_via_ec; |
| 244 | module_param(touchpad_ctrl_via_ec, bool, 0444); | 240 | module_param(touchpad_ctrl_via_ec, bool, 0444); |
| 245 | MODULE_PARM_DESC(touchpad_ctrl_via_ec, | 241 | MODULE_PARM_DESC(touchpad_ctrl_via_ec, |
| 246 | "Enable registering a 'touchpad' sysfs-attribute which can be used to manually " | 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."); |
| 247 | "tell the EC to enable/disable the touchpad. This may not work on all models."); | ||
| 248 | 243 | ||
| 249 | static bool ymc_ec_trigger __read_mostly; | 244 | static bool ymc_ec_trigger __read_mostly; |
| 250 | module_param(ymc_ec_trigger, bool, 0444); | 245 | module_param(ymc_ec_trigger, bool, 0444); |
| 251 | MODULE_PARM_DESC(ymc_ec_trigger, | 246 | MODULE_PARM_DESC(ymc_ec_trigger, |
| 252 | "Enable EC triggering work-around to force emitting tablet mode events. " | 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"); |
| 253 | "If you need this please report this to: platform-driver-x86@vger.kernel.org"); | ||
| 254 | 248 | ||
| 255 | /* | 249 | /* |
| 256 | * shared data | 250 | * shared data |
| @@ -1446,7 +1440,7 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv) · 1189 unmodified lines | |||
| 1446 | if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value)) | 1440 | if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value)) |
| 1447 | return; | 1441 | return; |
| 1448 | 1442 | ||
| 1449 | for_each_set_bit (bit, &value, 16) { | 1443 | for_each_set_bit(bit, &value, 16) { |
| 1450 | switch (bit) { | 1444 | switch (bit) { |
| 1451 | case 6: /* Z570 */ | 1445 | case 6: /* Z570 */ |
| 1452 | case 0: /* Z580 */ | 1446 | case 0: /* Z580 */ |
| @@ -1706,11 +1700,10 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv) · 253 unmodified lines | |||
| 1706 | if (WARN_ON(priv->kbd_bl.initialized)) | 1700 | if (WARN_ON(priv->kbd_bl.initialized)) |
| 1707 | return -EEXIST; | 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 | priv->kbd_bl.led.max_brightness = 2; | 1704 | priv->kbd_bl.led.max_brightness = 2; |
| 1711 | } else { | 1705 | else |
| 1712 | priv->kbd_bl.led.max_brightness = 1; | 1706 | priv->kbd_bl.led.max_brightness = 1; |
| 1713 | } | ||
| 1714 | 1707 | ||
| 1715 | brightness = ideapad_kbd_bl_brightness_get(priv); | 1708 | brightness = ideapad_kbd_bl_brightness_get(priv); |
| 1716 | if (brightness < 0) | 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 | static int ideapad_fn_lock_led_cdev_set(struct led_classdev *led_cdev, | 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 | struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, fn_lock.led); | 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 | vpc1 = (vpc2 << 8) | vpc1; | 1922 | vpc1 = (vpc2 << 8) | vpc1; |
| 1930 | 1923 | ||
| 1931 | for_each_set_bit (bit, &vpc1, 16) { | 1924 | for_each_set_bit(bit, &vpc1, 16) { |
| 1932 | switch (bit) { | 1925 | switch (bit) { |
| 1933 | case 13: | 1926 | case 13: |
| 1934 | case 11: | 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 | DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v1, | 2137 | DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v1, |
| 2145 | (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | | 2138 | (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | |
| 2146 | BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) | 2139 | BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) |
| 2147 | ); | 2140 | ); |
| 2148 | 2141 | ||
| 2149 | DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v2, | 2142 | DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v2, |
| 2150 | (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | | 2143 | (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | |
| 2151 | BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) | | 2144 | BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) | |
| 2152 | BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) | 2145 | BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) |
| 2153 | ); | 2146 | ); |
| 2154 | 2147 | ||
| 2155 | static int ideapad_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) | 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 | #include <linux/acpi.h> | 37 | #include <linux/acpi.h> |
| 38 | #include <linux/backlight.h> | 38 | #include <linux/backlight.h> |
| 39 | #include <linux/bitfield.h> | ||
| 39 | #include <linux/bitops.h> | 40 | #include <linux/bitops.h> |
| 40 | #include <linux/delay.h> | 41 | #include <linux/delay.h> |
| 41 | #include <linux/dmi.h> | 42 | #include <linux/dmi.h> |
| @@ -11080,6 +11081,206 @@ static const struct attribute_group auxmac_attr_group = { · 11038 unmodified lines | |||
| 11080 | .attrs = auxmac_attributes, | 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 | static struct attribute *tpacpi_driver_attributes[] = { | 11286 | static struct attribute *tpacpi_driver_attributes[] = { |
| @@ -11139,6 +11340,7 @@ static const struct attribute_group *tpacpi_groups[] = { · 53 unmodified lines | |||
| 11139 | &kbdlang_attr_group, | 11340 | &kbdlang_attr_group, |
| 11140 | &dprc_attr_group, | 11341 | &dprc_attr_group, |
| 11141 | &auxmac_attr_group, | 11342 | &auxmac_attr_group, |
| 11343 | &hwdd_attr_group, | ||
| 11142 | NULL, | 11344 | NULL, |
| 11143 | }; | 11345 | }; |
| 11144 | 11346 | ||
| @@ -11752,6 +11954,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { · 607 unmodified lines | |||
| 11752 | .init = auxmac_init, | 11954 | .init = auxmac_init, |
| 11753 | .data = &auxmac_data, | 11955 | .data = &auxmac_data, |
| 11754 | }, | 11956 | }, |
| 11957 | { | ||
| 11958 | .init = tpacpi_hwdd_init, | ||
| 11959 | .data = &hwdd_driver_data, | ||
| 11960 | }, | ||
| 11755 | }; | 11961 | }; |
| 11756 | 11962 | ||
| 11757 | static int __init set_ibm_param(const char *val, const struct kernel_param *kp) | 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 | #include <linux/errno.h> | 21 | #include <linux/errno.h> |
| 22 | #include <linux/export.h> | 22 | #include <linux/export.h> |
| 23 | #include <linux/module.h> | 23 | #include <linux/module.h> |
| 24 | #include <linux/unaligned.h> | ||
| 24 | #include <linux/wmi.h> | 25 | #include <linux/wmi.h> |
| 25 | 26 | ||
| 26 | #include "wmi-helpers.h" | 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 | if (!ret_obj) | 60 | if (!ret_obj) |
| 60 | return -ENODATA; | 61 | return -ENODATA; |
| 61 | 62 | ||
| 62 | if (ret_obj->type != ACPI_TYPE_INTEGER) | 63 | switch (ret_obj->type) { |
| 63 | return -ENXIO; | 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 | return 0; | 83 | return 0; |
| @@ -14,7 +14,16 @@ · 13 unmodified lines | |||
| 14 | * These attributes typically don't fit anywhere else in the sysfs and are set | 14 | * These attributes typically don't fit anywhere else in the sysfs and are set |
| 15 | * in Windows using one of Lenovo's multiple user applications. | 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 | * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> | 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 | #include <linux/acpi.h> | 29 | #include <linux/acpi.h> |
| @@ -25,16 +34,18 @@ · 4 unmodified lines | |||
| 25 | #include <linux/device.h> | 34 | #include <linux/device.h> |
| 26 | #include <linux/export.h> | 35 | #include <linux/export.h> |
| 27 | #include <linux/gfp_types.h> | 36 | #include <linux/gfp_types.h> |
| 37 | #include <linux/hwmon.h> | ||
| 28 | #include <linux/idr.h> | 38 | #include <linux/idr.h> |
| 29 | #include <linux/kdev_t.h> | 39 | #include <linux/kdev_t.h> |
| 30 | #include <linux/kobject.h> | 40 | #include <linux/kobject.h> |
| 41 | #include <linux/limits.h> | ||
| 31 | #include <linux/module.h> | 42 | #include <linux/module.h> |
| 32 | #include <linux/notifier.h> | 43 | #include <linux/notifier.h> |
| 33 | #include <linux/platform_profile.h> | 44 | #include <linux/platform_profile.h> |
| 34 | #include <linux/types.h> | 45 | #include <linux/types.h> |
| 35 | #include <linux/wmi.h> | 46 | #include <linux/wmi.h> |
| 36 | 47 | ||
| 37 | #include "wmi-capdata01.h" | 48 | #include "wmi-capdata.h" |
| 38 | #include "wmi-events.h" | 49 | #include "wmi-events.h" |
| 39 | #include "wmi-gamezone.h" | 50 | #include "wmi-gamezone.h" |
| 40 | #include "wmi-helpers.h" | 51 | #include "wmi-helpers.h" |
| @@ -49,17 +60,26 @@ · 8 unmodified lines | |||
| 49 | #define LWMI_FEATURE_ID_CPU_SPL 0x02 | 60 | #define LWMI_FEATURE_ID_CPU_SPL 0x02 |
| 50 | #define LWMI_FEATURE_ID_CPU_FPPT 0x03 | 61 | #define LWMI_FEATURE_ID_CPU_FPPT 0x03 |
| 51 | 62 | ||
| 63 | #define LWMI_FEATURE_ID_FAN_RPM 0x03 | ||
| 64 | |||
| 52 | #define LWMI_TYPE_ID_NONE 0x00 | 65 | #define LWMI_TYPE_ID_NONE 0x00 |
| 53 | 66 | ||
| 54 | #define LWMI_FEATURE_VALUE_GET 17 | 67 | #define LWMI_FEATURE_VALUE_GET 17 |
| 55 | #define LWMI_FEATURE_VALUE_SET 18 | 68 | #define LWMI_FEATURE_VALUE_SET 18 |
| 56 | 69 | ||
| 57 | #define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) | 70 | #define LWMI_FAN_ID_BASE 1 |
| 58 | #define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) | 71 | #define LWMI_FAN_NR 4 |
| 59 | #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) | 72 | #define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE) |
| 60 | #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) | 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 | #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" | 81 | #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" |
| 82 | #define LWMI_OM_HWMON_NAME "lenovo_wmi_other" | ||
| 63 | 83 | ||
| 64 | static BLOCKING_NOTIFIER_HEAD(om_chain_head); | 84 | static BLOCKING_NOTIFIER_HEAD(om_chain_head); |
| 65 | static DEFINE_IDA(lwmi_om_ida); | 85 | static DEFINE_IDA(lwmi_om_ida); |
| @@ -72,16 +92,459 @@ enum attribute_property { · 6 unmodified lines | |||
| 72 | SUPPORTED, | 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 | struct lwmi_om_priv { | 102 | struct lwmi_om_priv { |
| 76 | struct component_master_ops *ops; | 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 | struct device *fw_attr_dev; | 110 | struct device *fw_attr_dev; |
| 79 | struct kset *fw_attr_kset; | 111 | struct kset *fw_attr_kset; |
| 80 | struct notifier_block nb; | 112 | struct notifier_block nb; |
| 81 | struct wmi_device *wdev; | 113 | struct wmi_device *wdev; |
| 82 | int ida_id; | 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 | struct tunable_attr_01 { | 548 | struct tunable_attr_01 { |
| 86 | struct capdata01 *capdata; | 549 | struct capdata01 *capdata; |
| 87 | struct device *dev; | 550 | struct device *dev; |
| @@ -561,32 +1024,45 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) · 473 unmodified lines | |||
| 561 | device_unregister(priv->fw_attr_dev); | 1024 | device_unregister(priv->fw_attr_dev); |
| 562 | } | 1025 | } |
| 563 | 1026 | ||
| 1027 | /* ======== Self (master: lenovo-wmi-other) ======== */ | ||
| 1028 | |||
| 564 | /** | 1029 | /** |
| 565 | * lwmi_om_master_bind() - Bind all components of the other mode driver | 1030 | * lwmi_om_master_bind() - Bind all components of the other mode driver |
| 566 | * @dev: The lenovo-wmi-other driver basic device. | 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 | 1033 | * Call component_bind_all to bind the lenovo-wmi-capdata devices to the |
| 569 | * lenovo-wmi-other master driver. On success, assign the capability data 01 | 1034 | * lenovo-wmi-other master driver, with a callback to collect fan info from |
| 570 | * list pointer to the driver data struct for later access. This pointer | 1035 | * capdata_fan. On success, assign the capability data list pointers to the |
| 571 | * is only valid while the capdata01 interface exists. Finally, register all | 1036 | * driver data struct for later access. These pointers are only valid while the |
| 572 | * firmware attribute groups. | 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 | * Return: 0 on success, or an error code. | 1043 | * Return: 0 on success, or an error code. |
| 575 | */ | 1044 | */ |
| 576 | static int lwmi_om_master_bind(struct device *dev) | 1045 | static int lwmi_om_master_bind(struct device *dev) |
| 577 | { | 1046 | { |
| 578 | struct lwmi_om_priv *priv = dev_get_drvdata(dev); | 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 | int ret; | 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 | if (ret) | 1056 | if (ret) |
| 584 | return ret; | 1057 | return ret; |
| 585 | 1058 | ||
| 586 | priv->cd01_list = tmp_list; | 1059 | priv->cd00_list = binder.cd00_list; |
| 587 | if (!priv->cd01_list) | 1060 | priv->cd01_list = binder.cd01_list; |
| 1061 | if (!priv->cd00_list || !priv->cd01_list) | ||
| 588 | return -ENODEV; | 1062 | return -ENODEV; |
| 589 | 1063 | ||
| 1064 | lwmi_om_fan_info_collect_cd00(priv); | ||
| 1065 | |||
| 590 | return lwmi_om_fw_attr_add(priv); | 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 | * lwmi_om_master_unbind() - Unbind all components of the other mode driver | 1070 | * lwmi_om_master_unbind() - Unbind all components of the other mode driver |
| 595 | * @dev: The lenovo-wmi-other driver basic device | 1071 | * @dev: The lenovo-wmi-other driver basic device |
| 596 | * | 1072 | * |
| 597 | * Unregister all capability data attribute groups. Then call | 1073 | * Unregister all firmware attribute groups and the HWMON device. Then call |
| 598 | * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the | 1074 | * component_unbind_all to unbind lenovo-wmi-capdata devices from the |
| 599 | * lenovo-wmi-other master driver. Finally, free the IDA for this device. | 1075 | * lenovo-wmi-other master driver. |
| 600 | */ | 1076 | */ |
| 601 | static void lwmi_om_master_unbind(struct device *dev) | 1077 | static void lwmi_om_master_unbind(struct device *dev) |
| 602 | { | 1078 | { |
| 603 | struct lwmi_om_priv *priv = dev_get_drvdata(dev); | 1079 | struct lwmi_om_priv *priv = dev_get_drvdata(dev); |
| 604 | 1080 | ||
| 605 | lwmi_om_fw_attr_remove(priv); | 1081 | lwmi_om_fw_attr_remove(priv); |
| 1082 | |||
| 1083 | lwmi_om_hwmon_remove(priv); | ||
| 1084 | |||
| 606 | component_unbind_all(dev, NULL); | 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 | if (!priv) | 1099 | if (!priv) |
| 621 | return -ENOMEM; | 1100 | return -ENOMEM; |
| 622 | 1101 | ||
| 1102 | /* Sentinel for on-demand ida_free(). */ | ||
| 1103 | priv->ida_id = -EIDRM; | ||
| 1104 | |||
| 623 | priv->wdev = wdev; | 1105 | priv->wdev = wdev; |
| 624 | dev_set_drvdata(&wdev->dev, priv); | 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 | if (IS_ERR(master_match)) | 1109 | if (IS_ERR(master_match)) |
| 628 | return PTR_ERR(master_match); | 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 | struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev); | 1118 | struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev); |
| 637 | 1119 | ||
| 638 | component_master_del(&wdev->dev, &lwmi_om_master_ops); | 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 | static const struct wmi_device_id lwmi_other_id_table[] = { | 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 | module_wmi_driver(lwmi_other_driver); | 1143 | module_wmi_driver(lwmi_other_driver); |
| 659 | 1144 | ||
| 660 | MODULE_IMPORT_NS("LENOVO_WMI_CD01"); | 1145 | MODULE_IMPORT_NS("LENOVO_WMI_CAPDATA"); |
| 661 | MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); | 1146 | MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); |
| 662 | MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); | 1147 | MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); |
| 663 | MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); | 1148 | MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); |
| 1149 | MODULE_AUTHOR("Rong Zhang <i@rong.moe>"); | ||
| 664 | MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver"); | 1150 | MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver"); |
| 665 | MODULE_LICENSE("GPL"); | 1151 | MODULE_LICENSE("GPL"); |
| @@ -57,7 +57,7 @@ struct yogabook_data { · 56 unmodified lines | |||
| 57 | struct work_struct work; | 57 | struct work_struct work; |
| 58 | struct led_classdev kbd_bl_led; | 58 | struct led_classdev kbd_bl_led; |
| 59 | unsigned long flags; | 59 | unsigned long flags; |
| 60 | uint8_t brightness; | 60 | u8 brightness; |
| 61 | }; | 61 | }; |
| 62 | 62 | ||
| 63 | static void yogabook_work(struct work_struct *work) | 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 | int r; | 338 | int r; |
| 339 | 339 | ||
| 340 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | 340 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
| 341 | if (data == NULL) | 341 | if (!data) |
| 342 | return -ENOMEM; | 342 | return -ENOMEM; |
| 343 | 343 | ||
| 344 | data->kbd_adev = acpi_dev_get_first_match_dev("GDIX1001", NULL, -1); | 344 | data->kbd_adev = acpi_dev_get_first_match_dev("GDIX1001", NULL, -1); |
| 345 | if (!data->kbd_adev) | 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 | data->dig_adev = acpi_dev_get_first_match_dev("WCOM0019", NULL, -1); | 349 | data->dig_adev = acpi_dev_get_first_match_dev("WCOM0019", NULL, -1); |
| 349 | if (!data->dig_adev) { | 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 | goto error_put_devs; | 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 | int r; | 455 | int r; |
| 454 | 456 | ||
| 455 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | 457 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
| 456 | if (data == NULL) | 458 | if (!data) |
| 457 | return -ENOMEM; | 459 | return -ENOMEM; |
| 458 | 460 | ||
| 459 | data->kbd_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "i2c-goodix_ts"); | 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 | #define EC_ADDR_GPU_TEMP 0x044F | 89 | #define EC_ADDR_GPU_TEMP 0x044F |
| 90 | 90 | ||
| 91 | #define EC_ADDR_SYSTEM_ID 0x0456 | ||
| 92 | #define HAS_GPU BIT(7) | ||
| 93 | |||
| 91 | #define EC_ADDR_MAIN_FAN_RPM_1 0x0464 | 94 | #define EC_ADDR_MAIN_FAN_RPM_1 0x0464 |
| 92 | 95 | ||
| 93 | #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 | 96 | #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 |
| @@ -122,11 +125,11 @@ · 28 unmodified lines | |||
| 122 | #define CTGP_DB_DB_ENABLE BIT(1) | 125 | #define CTGP_DB_DB_ENABLE BIT(1) |
| 123 | #define CTGP_DB_CTGP_ENABLE BIT(2) | 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 | #define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748 | 134 | #define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748 |
| 132 | #define LIGHTBAR_APP_EXISTS BIT(0) | 135 | #define LIGHTBAR_APP_EXISTS BIT(0) |
| @@ -317,11 +320,13 @@ · 184 unmodified lines | |||
| 317 | #define UNIWILL_FEATURE_LIGHTBAR BIT(3) | 320 | #define UNIWILL_FEATURE_LIGHTBAR BIT(3) |
| 318 | #define UNIWILL_FEATURE_BATTERY BIT(4) | 321 | #define UNIWILL_FEATURE_BATTERY BIT(4) |
| 319 | #define UNIWILL_FEATURE_HWMON BIT(5) | 322 | #define UNIWILL_FEATURE_HWMON BIT(5) |
| 323 | #define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(6) | ||
| 320 | 324 | ||
| 321 | struct uniwill_data { | 325 | struct uniwill_data { |
| 322 | struct device *dev; | 326 | struct device *dev; |
| 323 | acpi_handle handle; | 327 | acpi_handle handle; |
| 324 | struct regmap *regmap; | 328 | struct regmap *regmap; |
| 329 | unsigned int features; | ||
| 325 | struct acpi_battery_hook hook; | 330 | struct acpi_battery_hook hook; |
| 326 | unsigned int last_charge_ctrl; | 331 | unsigned int last_charge_ctrl; |
| 327 | struct mutex battery_lock; /* Protects the list of currently registered batteries */ | 332 | struct mutex battery_lock; /* Protects the list of currently registered batteries */ |
| @@ -341,12 +346,21 @@ struct uniwill_battery_entry { · 13 unmodified lines | |||
| 341 | struct power_supply *battery; | 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 | static bool force; | 355 | static bool force; |
| 345 | module_param_unsafe(force, bool, 0); | 356 | module_param_unsafe(force, bool, 0); |
| 346 | MODULE_PARM_DESC(force, "Force loading without checking for supported devices\n"); | 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 */ | 359 | /* |
| 349 | static unsigned int supported_features; | 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 | static const char * const uniwill_temp_labels[] = { | 365 | static const char * const uniwill_temp_labels[] = { |
| 352 | "CPU", | 366 | "CPU", |
| @@ -411,6 +425,12 @@ static const struct key_entry uniwill_keymap[] = { · 58 unmodified lines | |||
| 411 | { KE_END } | 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 | static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val) | 434 | static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val) |
| 415 | { | 435 | { |
| 416 | union acpi_object params[2] = { | 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 | case EC_ADDR_LIGHTBAR_BAT_RED: | 518 | case EC_ADDR_LIGHTBAR_BAT_RED: |
| 499 | case EC_ADDR_LIGHTBAR_BAT_GREEN: | 519 | case EC_ADDR_LIGHTBAR_BAT_GREEN: |
| 500 | case EC_ADDR_LIGHTBAR_BAT_BLUE: | 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 | return true; | 525 | return true; |
| 502 | default: | 526 | default: |
| 503 | return false; | 527 | return false; |
| @@ -531,6 +555,11 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) · 27 unmodified lines | |||
| 531 | case EC_ADDR_LIGHTBAR_BAT_RED: | 555 | case EC_ADDR_LIGHTBAR_BAT_RED: |
| 532 | case EC_ADDR_LIGHTBAR_BAT_GREEN: | 556 | case EC_ADDR_LIGHTBAR_BAT_GREEN: |
| 533 | case EC_ADDR_LIGHTBAR_BAT_BLUE: | 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 | return true; | 563 | return true; |
| 535 | default: | 564 | default: |
| 536 | return false; | 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 | static DEVICE_ATTR_RW(breathing_in_suspend); | 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 | static struct attribute *uniwill_attrs[] = { | 882 | static struct attribute *uniwill_attrs[] = { |
| 790 | /* Keyboard-related */ | 883 | /* Keyboard-related */ |
| 791 | &dev_attr_fn_lock_toggle_enable.attr, | 884 | &dev_attr_fn_lock_toggle_enable.attr, |
| @@ -794,29 +887,39 @@ static struct attribute *uniwill_attrs[] = { · 2 unmodified lines | |||
| 794 | /* Lightbar-related */ | 887 | /* Lightbar-related */ |
| 795 | &dev_attr_rainbow_animation.attr, | 888 | &dev_attr_rainbow_animation.attr, |
| 796 | &dev_attr_breathing_in_suspend.attr, | 889 | &dev_attr_breathing_in_suspend.attr, |
| 890 | /* Power-management-related */ | ||
| 891 | &dev_attr_ctgp_offset.attr, | ||
| 797 | NULL | 892 | NULL |
| 798 | }; | 893 | }; |
| 799 | 894 | ||
| 800 | static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) | 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 | if (attr == &dev_attr_fn_lock_toggle_enable.attr) { | 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 | return attr->mode; | 902 | return attr->mode; |
| 805 | } | 903 | } |
| 806 | 904 | ||
| 807 | if (attr == &dev_attr_super_key_toggle_enable.attr) { | 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 | return attr->mode; | 907 | return attr->mode; |
| 810 | } | 908 | } |
| 811 | 909 | ||
| 812 | if (attr == &dev_attr_touchpad_toggle_enable.attr) { | 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 | return attr->mode; | 912 | return attr->mode; |
| 815 | } | 913 | } |
| 816 | 914 | ||
| 817 | if (attr == &dev_attr_rainbow_animation.attr || | 915 | if (attr == &dev_attr_rainbow_animation.attr || |
| 818 | attr == &dev_attr_breathing_in_suspend.attr) { | 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 | return attr->mode; | 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 | struct device *hdev; | 1048 | struct device *hdev; |
| 946 | 1049 | ||
| 947 | if (!(supported_features & UNIWILL_FEATURE_HWMON)) | 1050 | if (!uniwill_device_supports(data, UNIWILL_FEATURE_HWMON)) |
| 948 | return 0; | 1051 | return 0; |
| 949 | 1052 | ||
| 950 | hdev = devm_hwmon_device_register_with_info(data->dev, "uniwill", data, | 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 | unsigned int value; | 1122 | unsigned int value; |
| 1020 | int ret; | 1123 | int ret; |
| 1021 | 1124 | ||
| 1022 | if (!(supported_features & UNIWILL_FEATURE_LIGHTBAR)) | 1125 | if (!uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR)) |
| 1023 | return 0; | 1126 | return 0; |
| 1024 | 1127 | ||
| 1025 | ret = devm_mutex_init(data->dev, &data->led_lock); | 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 | int ret; | 1336 | int ret; |
| 1234 | 1337 | ||
| 1235 | if (!(supported_features & UNIWILL_FEATURE_BATTERY)) | 1338 | if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY)) |
| 1236 | return 0; | 1339 | return 0; |
| 1237 | 1340 | ||
| 1238 | ret = devm_mutex_init(data->dev, &data->battery_lock); | 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 | if (ret < 0) | 1464 | if (ret < 0) |
| 1362 | return ret; | 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 | ret = uniwill_battery_init(data); | 1480 | ret = uniwill_battery_init(data); |
| 1365 | if (ret < 0) | 1481 | if (ret < 0) |
| 1366 | return ret; | 1482 | return ret; |
| @@ -1373,6 +1489,10 @@ static int uniwill_probe(struct platform_device *pdev) · 6 unmodified lines | |||
| 1373 | if (ret < 0) | 1489 | if (ret < 0) |
| 1374 | return ret; | 1490 | return ret; |
| 1375 | 1491 | ||
| 1492 | ret = uniwill_nvidia_ctgp_init(data); | ||
| 1493 | if (ret < 0) | ||
| 1494 | return ret; | ||
| 1495 | |||
| 1376 | return uniwill_input_init(data); | 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 | static int uniwill_suspend_keyboard(struct uniwill_data *data) | 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 | return 0; | 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 | static int uniwill_suspend_battery(struct uniwill_data *data) | 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 | return 0; | 1521 | return 0; |
| 1402 | 1522 | ||
| 1403 | /* | 1523 | /* |
| @@ -1408,6 +1528,15 @@ static int uniwill_suspend_battery(struct uniwill_data *data) · 4 unmodified lines | |||
| 1408 | return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); | 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 | static int uniwill_suspend(struct device *dev) | 1540 | static int uniwill_suspend(struct device *dev) |
| 1412 | { | 1541 | { |
| 1413 | struct uniwill_data *data = dev_get_drvdata(dev); | 1542 | struct uniwill_data *data = dev_get_drvdata(dev); |
| @@ -1421,6 +1550,10 @@ static int uniwill_suspend(struct device *dev) · 7 unmodified lines | |||
| 1421 | if (ret < 0) | 1550 | if (ret < 0) |
| 1422 | return ret; | 1551 | return ret; |
| 1423 | 1552 | ||
| 1553 | ret = uniwill_suspend_nvidia_ctgp(data); | ||
| 1554 | if (ret < 0) | ||
| 1555 | return ret; | ||
| 1556 | |||
| 1424 | regcache_cache_only(data->regmap, true); | 1557 | regcache_cache_only(data->regmap, true); |
| 1425 | regcache_mark_dirty(data->regmap); | 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 | unsigned int value; | 1565 | unsigned int value; |
| 1433 | int ret; | 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 | return 0; | 1569 | return 0; |
| 1437 | 1570 | ||
| 1438 | ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); | 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 | static int uniwill_resume_battery(struct uniwill_data *data) | 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 | return 0; | 1585 | return 0; |
| 1453 | 1586 | ||
| 1454 | return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK, | 1587 | return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK, |
| 1455 | data->last_charge_ctrl); | 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 | static int uniwill_resume(struct device *dev) | 1600 | static int uniwill_resume(struct device *dev) |
| 1459 | { | 1601 | { |
| 1460 | struct uniwill_data *data = dev_get_drvdata(dev); | 1602 | struct uniwill_data *data = dev_get_drvdata(dev); |
| @@ -1470,7 +1612,11 @@ static int uniwill_resume(struct device *dev) · 9 unmodified lines | |||
| 1470 | if (ret < 0) | 1612 | if (ret < 0) |
| 1471 | return ret; | 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 | static DEFINE_SIMPLE_DEV_PM_OPS(uniwill_pm_ops, uniwill_suspend, uniwill_resume); | 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 | .shutdown = uniwill_shutdown, | 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 | static const struct dmi_system_id uniwill_dmi_table[] __initconst = { | 1687 | static const struct dmi_system_id uniwill_dmi_table[] __initconst = { |
| 1500 | { | 1688 | { |
| 1501 | .ident = "XMG FUSION 15", | 1689 | .ident = "XMG FUSION 15", |
| @@ -1503,6 +1691,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | |||
| 1503 | DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"), | 1691 | DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"), |
| 1504 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"), | 1692 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"), |
| 1505 | }, | 1693 | }, |
| 1694 | .driver_data = &empty_descriptor, | ||
| 1506 | }, | 1695 | }, |
| 1507 | { | 1696 | { |
| 1508 | .ident = "XMG FUSION 15", | 1697 | .ident = "XMG FUSION 15", |
| @@ -1510,6 +1699,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | |||
| 1510 | DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"), | 1699 | DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"), |
| 1511 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"), | 1700 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"), |
| 1512 | }, | 1701 | }, |
| 1702 | .driver_data = &empty_descriptor, | ||
| 1513 | }, | 1703 | }, |
| 1514 | { | 1704 | { |
| 1515 | .ident = "Intel NUC x15", | 1705 | .ident = "Intel NUC x15", |
| @@ -1517,11 +1707,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | |||
| 1517 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), | 1707 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), |
| 1518 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPAC71H"), | 1708 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPAC71H"), |
| 1519 | }, | 1709 | }, |
| 1520 | .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE | | 1710 | .driver_data = &lapac71h_descriptor, |
| 1521 | UNIWILL_FEATURE_SUPER_KEY_TOGGLE | | ||
| 1522 | UNIWILL_FEATURE_TOUCHPAD_TOGGLE | | ||
| 1523 | UNIWILL_FEATURE_BATTERY | | ||
| 1524 | UNIWILL_FEATURE_HWMON), | ||
| 1525 | }, | 1711 | }, |
| 1526 | { | 1712 | { |
| 1527 | .ident = "Intel NUC x15", | 1713 | .ident = "Intel NUC x15", |
| @@ -1529,12 +1715,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { · 1 unmodified line | |||
| 1529 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), | 1715 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), |
| 1530 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"), | 1716 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"), |
| 1531 | }, | 1717 | }, |
| 1532 | .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE | | 1718 | .driver_data = &lapkc71f_descriptor, |
| 1533 | UNIWILL_FEATURE_SUPER_KEY_TOGGLE | | ||
| 1534 | UNIWILL_FEATURE_TOUCHPAD_TOGGLE | | ||
| 1535 | UNIWILL_FEATURE_LIGHTBAR | | ||
| 1536 | UNIWILL_FEATURE_BATTERY | | ||
| 1537 | UNIWILL_FEATURE_HWMON), | ||
| 1538 | }, | 1719 | }, |
| 1539 | { | 1720 | { |
| 1540 | .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1723 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1543 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"), | 1724 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"), |
| 1544 | }, | 1725 | }, |
| 1726 | .driver_data = &empty_descriptor, | ||
| 1545 | }, | 1727 | }, |
| 1546 | { | 1728 | { |
| 1547 | .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1731 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1550 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"), | 1732 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"), |
| 1551 | }, | 1733 | }, |
| 1734 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1552 | }, | 1735 | }, |
| 1553 | { | 1736 | { |
| 1554 | .ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1739 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1557 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"), | 1740 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"), |
| 1558 | }, | 1741 | }, |
| 1742 | .driver_data = &phxarx1_phxaqf1_descriptor, | ||
| 1559 | }, | 1743 | }, |
| 1560 | { | 1744 | { |
| 1561 | .ident = "TUXEDO InfinityBook Pro 16 Gen7 Intel/Commodore Omnia-Book Pro Gen 7", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1747 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1564 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"), | 1748 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"), |
| 1565 | }, | 1749 | }, |
| 1750 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1566 | }, | 1751 | }, |
| 1567 | { | 1752 | { |
| 1568 | .ident = "TUXEDO InfinityBook Pro 14/16 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1755 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1571 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"), | 1756 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"), |
| 1572 | }, | 1757 | }, |
| 1758 | .driver_data = &empty_descriptor, | ||
| 1573 | }, | 1759 | }, |
| 1574 | { | 1760 | { |
| 1575 | .ident = "TUXEDO InfinityBook Pro 14 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1763 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1578 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"), | 1764 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"), |
| 1579 | }, | 1765 | }, |
| 1766 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1580 | }, | 1767 | }, |
| 1581 | { | 1768 | { |
| 1582 | .ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1771 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1585 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"), | 1772 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"), |
| 1586 | }, | 1773 | }, |
| 1774 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1587 | }, | 1775 | }, |
| 1588 | { | 1776 | { |
| 1589 | .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1779 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1592 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"), | 1780 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"), |
| 1593 | }, | 1781 | }, |
| 1782 | .driver_data = &empty_descriptor, | ||
| 1594 | }, | 1783 | }, |
| 1595 | { | 1784 | { |
| 1596 | .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1787 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1599 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"), | 1788 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"), |
| 1600 | }, | 1789 | }, |
| 1790 | .driver_data = &empty_descriptor, | ||
| 1601 | }, | 1791 | }, |
| 1602 | { | 1792 | { |
| 1603 | .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1795 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1606 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"), | 1796 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"), |
| 1607 | }, | 1797 | }, |
| 1798 | .driver_data = &empty_descriptor, | ||
| 1608 | }, | 1799 | }, |
| 1609 | { | 1800 | { |
| 1610 | .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1803 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1613 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), | 1804 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), |
| 1614 | }, | 1805 | }, |
| 1806 | .driver_data = &empty_descriptor, | ||
| 1615 | }, | 1807 | }, |
| 1616 | { | 1808 | { |
| 1617 | .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1811 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1620 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"), | 1812 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"), |
| 1621 | }, | 1813 | }, |
| 1814 | .driver_data = &empty_descriptor, | ||
| 1622 | }, | 1815 | }, |
| 1623 | { | 1816 | { |
| 1624 | .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1819 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1627 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), | 1820 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), |
| 1628 | }, | 1821 | }, |
| 1822 | .driver_data = &empty_descriptor, | ||
| 1629 | }, | 1823 | }, |
| 1630 | { | 1824 | { |
| 1631 | .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1827 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1634 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"), | 1828 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"), |
| 1635 | }, | 1829 | }, |
| 1830 | .driver_data = &empty_descriptor, | ||
| 1636 | }, | 1831 | }, |
| 1637 | { | 1832 | { |
| 1638 | .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1835 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1641 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"), | 1836 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"), |
| 1642 | }, | 1837 | }, |
| 1838 | .driver_data = &empty_descriptor, | ||
| 1643 | }, | 1839 | }, |
| 1644 | { | 1840 | { |
| 1645 | .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1843 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1648 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"), | 1844 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"), |
| 1649 | }, | 1845 | }, |
| 1846 | .driver_data = &empty_descriptor, | ||
| 1650 | }, | 1847 | }, |
| 1651 | { | 1848 | { |
| 1652 | .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1851 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1655 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"), | 1852 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"), |
| 1656 | }, | 1853 | }, |
| 1854 | .driver_data = &empty_descriptor, | ||
| 1657 | }, | 1855 | }, |
| 1658 | { | 1856 | { |
| 1659 | .ident = "TUXEDO Polaris 15 Gen1 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1859 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1662 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"), | 1860 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"), |
| 1663 | }, | 1861 | }, |
| 1862 | .driver_data = &empty_descriptor, | ||
| 1664 | }, | 1863 | }, |
| 1665 | { | 1864 | { |
| 1666 | .ident = "TUXEDO Polaris 15 Gen1 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1867 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1669 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"), | 1868 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"), |
| 1670 | }, | 1869 | }, |
| 1870 | .driver_data = &empty_descriptor, | ||
| 1671 | }, | 1871 | }, |
| 1672 | { | 1872 | { |
| 1673 | .ident = "TUXEDO Polaris 17 Gen1 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1875 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1676 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"), | 1876 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"), |
| 1677 | }, | 1877 | }, |
| 1878 | .driver_data = &empty_descriptor, | ||
| 1678 | }, | 1879 | }, |
| 1679 | { | 1880 | { |
| 1680 | .ident = "TUXEDO Polaris 17 Gen1 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1883 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1683 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"), | 1884 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"), |
| 1684 | }, | 1885 | }, |
| 1886 | .driver_data = &empty_descriptor, | ||
| 1685 | }, | 1887 | }, |
| 1686 | { | 1888 | { |
| 1687 | .ident = "TUXEDO Polaris 15 Gen1 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1891 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1690 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"), | 1892 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"), |
| 1691 | }, | 1893 | }, |
| 1894 | .driver_data = &empty_descriptor, | ||
| 1692 | }, | 1895 | }, |
| 1693 | { | 1896 | { |
| 1694 | .ident = "TUXEDO Polaris 15 Gen1 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1899 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1697 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"), | 1900 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"), |
| 1698 | }, | 1901 | }, |
| 1902 | .driver_data = &empty_descriptor, | ||
| 1699 | }, | 1903 | }, |
| 1700 | { | 1904 | { |
| 1701 | .ident = "TUXEDO Polaris 17 Gen1 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1907 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1704 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"), | 1908 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"), |
| 1705 | }, | 1909 | }, |
| 1910 | .driver_data = &empty_descriptor, | ||
| 1706 | }, | 1911 | }, |
| 1707 | { | 1912 | { |
| 1708 | .ident = "TUXEDO Polaris 17 Gen1 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1915 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1711 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"), | 1916 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"), |
| 1712 | }, | 1917 | }, |
| 1918 | .driver_data = &empty_descriptor, | ||
| 1713 | }, | 1919 | }, |
| 1714 | { | 1920 | { |
| 1715 | .ident = "TUXEDO Trinity 15 Intel Gen1", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1923 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1718 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"), | 1924 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"), |
| 1719 | }, | 1925 | }, |
| 1926 | .driver_data = &empty_descriptor, | ||
| 1720 | }, | 1927 | }, |
| 1721 | { | 1928 | { |
| 1722 | .ident = "TUXEDO Trinity 17 Intel Gen1", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1931 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1725 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"), | 1932 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"), |
| 1726 | }, | 1933 | }, |
| 1934 | .driver_data = &empty_descriptor, | ||
| 1727 | }, | 1935 | }, |
| 1728 | { | 1936 | { |
| 1729 | .ident = "TUXEDO Polaris 15/17 Gen2 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1939 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1732 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"), | 1940 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"), |
| 1733 | }, | 1941 | }, |
| 1942 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1734 | }, | 1943 | }, |
| 1735 | { | 1944 | { |
| 1736 | .ident = "TUXEDO Polaris 15/17 Gen2 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1947 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1739 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"), | 1948 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"), |
| 1740 | }, | 1949 | }, |
| 1950 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1741 | }, | 1951 | }, |
| 1742 | { | 1952 | { |
| 1743 | .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1955 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1746 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"), | 1956 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"), |
| 1747 | }, | 1957 | }, |
| 1958 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1748 | }, | 1959 | }, |
| 1749 | { | 1960 | { |
| 1750 | .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1963 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1753 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"), | 1964 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"), |
| 1754 | }, | 1965 | }, |
| 1966 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1755 | }, | 1967 | }, |
| 1756 | { | 1968 | { |
| 1757 | .ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1971 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1760 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"), | 1972 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"), |
| 1761 | }, | 1973 | }, |
| 1974 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1762 | }, | 1975 | }, |
| 1763 | { | 1976 | { |
| 1764 | .ident = "TUXEDO Stellaris 15 Gen4 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1979 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1767 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"), | 1980 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"), |
| 1768 | }, | 1981 | }, |
| 1982 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1769 | }, | 1983 | }, |
| 1770 | { | 1984 | { |
| 1771 | .ident = "TUXEDO Polaris 15/17 Gen5 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1987 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1774 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"), | 1988 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"), |
| 1775 | }, | 1989 | }, |
| 1990 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1776 | }, | 1991 | }, |
| 1777 | { | 1992 | { |
| 1778 | .ident = "TUXEDO Stellaris 16 Gen5 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 1995 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1781 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"), | 1996 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"), |
| 1782 | }, | 1997 | }, |
| 1998 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1783 | }, | 1999 | }, |
| 1784 | { | 2000 | { |
| 1785 | .ident = "TUXEDO Stellaris 16/17 Gen5 Intel/Commodore ORION Gen 5", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2003 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1788 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"), | 2004 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"), |
| 1789 | }, | 2005 | }, |
| 2006 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1790 | }, | 2007 | }, |
| 1791 | { | 2008 | { |
| 1792 | .ident = "TUXEDO Stellaris Slim 15 Gen6 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2011 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1795 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"), | 2012 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"), |
| 1796 | }, | 2013 | }, |
| 2014 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1797 | }, | 2015 | }, |
| 1798 | { | 2016 | { |
| 1799 | .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2019 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1802 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"), | 2020 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"), |
| 1803 | }, | 2021 | }, |
| 2022 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1804 | }, | 2023 | }, |
| 1805 | { | 2024 | { |
| 1806 | .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2027 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1809 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"), | 2028 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"), |
| 1810 | }, | 2029 | }, |
| 2030 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1811 | }, | 2031 | }, |
| 1812 | { | 2032 | { |
| 1813 | .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2035 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1816 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"), | 2036 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"), |
| 1817 | }, | 2037 | }, |
| 2038 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1818 | }, | 2039 | }, |
| 1819 | { | 2040 | { |
| 1820 | .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2043 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1823 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"), | 2044 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"), |
| 1824 | }, | 2045 | }, |
| 2046 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1825 | }, | 2047 | }, |
| 1826 | { | 2048 | { |
| 1827 | .ident = "TUXEDO Stellaris 16 Gen7 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2051 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1830 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"), | 2052 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"), |
| 1831 | }, | 2053 | }, |
| 2054 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1832 | }, | 2055 | }, |
| 1833 | { | 2056 | { |
| 1834 | .ident = "TUXEDO Stellaris 16 Gen7 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2059 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1837 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"), | 2060 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"), |
| 1838 | }, | 2061 | }, |
| 2062 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1839 | }, | 2063 | }, |
| 1840 | { | 2064 | { |
| 1841 | .ident = "TUXEDO Stellaris 16 Gen7 Intel", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2067 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1844 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"), | 2068 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"), |
| 1845 | }, | 2069 | }, |
| 2070 | .driver_data = &tux_featureset_1_descriptor, | ||
| 1846 | }, | 2071 | }, |
| 1847 | { | 2072 | { |
| 1848 | .ident = "TUXEDO Book BA15 Gen10 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2075 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1851 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"), | 2076 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"), |
| 1852 | }, | 2077 | }, |
| 2078 | .driver_data = &empty_descriptor, | ||
| 1853 | }, | 2079 | }, |
| 1854 | { | 2080 | { |
| 1855 | .ident = "TUXEDO Pulse 14 Gen1 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2083 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1858 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"), | 2084 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"), |
| 1859 | }, | 2085 | }, |
| 2086 | .driver_data = &empty_descriptor, | ||
| 1860 | }, | 2087 | }, |
| 1861 | { | 2088 | { |
| 1862 | .ident = "TUXEDO Pulse 15 Gen1 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2091 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1865 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"), | 2092 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"), |
| 1866 | }, | 2093 | }, |
| 2094 | .driver_data = &empty_descriptor, | ||
| 1867 | }, | 2095 | }, |
| 1868 | { | 2096 | { |
| 1869 | .ident = "TUXEDO Pulse 15 Gen2 AMD", | 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 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), | 2099 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), |
| 1872 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"), | 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 | static int __init uniwill_init(void) | 2108 | static int __init uniwill_init(void) |
| 1880 | { | 2109 | { |
| 2110 | const struct uniwill_device_descriptor *descriptor; | ||
| 1881 | const struct dmi_system_id *id; | 2111 | const struct dmi_system_id *id; |
| 1882 | int ret; | 2112 | int ret; |
| 1883 | 2113 | ||
| @@ -1887,10 +2117,22 @@ static int __init uniwill_init(void) · 3 unmodified lines | |||
| 1887 | return -ENODEV; | 2117 | return -ENODEV; |
| 1888 | 2118 | ||
| 1889 | /* Assume that the device supports all features */ | 2119 | /* Assume that the device supports all features */ |
| 1890 | supported_features = UINT_MAX; | 2120 | device_descriptor.features = UINT_MAX; |
| 1891 | pr_warn("Loading on a potentially unsupported device\n"); | 2121 | pr_warn("Loading on a potentially unsupported device\n"); |
| 1892 | } else { | 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 | ret = platform_driver_register(&uniwill_driver); | 2138 | ret = platform_driver_register(&uniwill_driver); |
| @@ -8,7 +8,6 @@ · 7 unmodified lines | |||
| 8 | 8 | ||
| 9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 10 | 10 | ||
| 11 | #include <linux/acpi.h> | ||
| 12 | #include <linux/device.h> | 11 | #include <linux/device.h> |
| 13 | #include <linux/fs.h> | 12 | #include <linux/fs.h> |
| 14 | #include <linux/kernel.h> | 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 | char *buf, loff_t off, size_t count) | 23 | char *buf, loff_t off, size_t count) |
| 25 | { | 24 | { |
| 26 | struct device *dev = kobj_to_dev(kobj); | 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 | static const BIN_ATTR_ADMIN_RO(bmof, 0); | 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 | static size_t bmof_bin_size(struct kobject *kobj, const struct bin_attribute *attr, int n) | 38 | static size_t bmof_bin_size(struct kobject *kobj, const struct bin_attribute *attr, int n) |
| 40 | { | 39 | { |
| 41 | struct device *dev = kobj_to_dev(kobj); | 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 | static const struct attribute_group bmof_group = { | 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 | static int wmi_bmof_probe(struct wmi_device *wdev, const void *context) | 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); | 61 | buffer = devm_kzalloc(&wdev->dev, sizeof(*buffer), GFP_KERNEL); |
| 62 | if (!obj) { | 62 | if (!buffer) |
| 63 | dev_err(&wdev->dev, "failed to read Binary MOF\n"); | 63 | return -ENOMEM; |
| 64 | return -EIO; | ||
| 65 | } | ||
| 66 | 64 | ||
| 67 | if (obj->type != ACPI_TYPE_BUFFER) { | 65 | ret = wmidev_query_block(wdev, 0, buffer); |
| 68 | dev_err(&wdev->dev, "Binary MOF is not a buffer\n"); | 66 | if (ret < 0) |
| 69 | kfree(obj); | 67 | return ret; |
| 70 | return -EIO; | ||
| 71 | } | ||
| 72 | 68 | ||
| 73 | dev_set_drvdata(&wdev->dev, obj); | 69 | dev_set_drvdata(&wdev->dev, buffer); |
| 74 | 70 | ||
| 75 | return 0; | 71 | return 0; |
| 76 | } | 72 | } |
| 77 | 73 | ||
| 78 | static void wmi_bmof_remove(struct wmi_device *wdev) | 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 | static const struct wmi_device_id wmi_bmof_id_table[] = { | 81 | static const struct wmi_device_id wmi_bmof_id_table[] = { |
| @@ -1,7 +1,6 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* WMI driver for Xiaomi Laptops */ | 2 | /* WMI driver for Xiaomi Laptops */ |
| 3 | 3 | ||
| 4 | #include <linux/acpi.h> | ||
| 5 | #include <linux/device.h> | 4 | #include <linux/device.h> |
| 6 | #include <linux/input.h> | 5 | #include <linux/input.h> |
| 7 | #include <linux/module.h> | 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 | return input_register_device(data->input_dev); | 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 | struct xiaomi_wmi *data = dev_get_drvdata(&wdev->dev); | 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 | .id_table = xiaomi_wmi_id_table, | 85 | .id_table = xiaomi_wmi_id_table, |
| 87 | .probe = xiaomi_wmi_probe, | 86 | .probe = xiaomi_wmi_probe, |
| 88 | .notify = xiaomi_wmi_notify, | 87 | .notify_new = xiaomi_wmi_notify, |
| 89 | .no_singleton = true, | 88 | .no_singleton = true, |
| 90 | }; | 89 | }; |
| 91 | module_wmi_driver(xiaomi_wmi_driver); | 90 | module_wmi_driver(xiaomi_wmi_driver); |
| @@ -61,5 +61,26 @@ enum laptop_placement { · 60 unmodified lines | |||
| 61 | LP_UNDEFINED, | 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 | int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op); | 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 | #endif | 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 | ASUS_WMI_ALLY_MCU_HACK_DISABLED, | 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 | #if IS_REACHABLE(CONFIG_ASUS_WMI) | 190 | #if IS_REACHABLE(CONFIG_ASUS_WMI) |
| 177 | void set_ally_mcu_hack(enum asus_ally_mcu_hack status); | 191 | void set_ally_mcu_hack(enum asus_ally_mcu_hack status); |
| 178 | void set_ally_mcu_powersave(bool enabled); | 192 | void set_ally_mcu_powersave(bool enabled); |
| 179 | int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval); | 193 | int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval); |
| 180 | int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval); | 194 | int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval); |
| 181 | int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); | 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 | #else | 199 | #else |
| 183 | static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) | 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 | return -ENODEV; | 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 | #endif | 230 | #endif |
| 203 | 231 | ||
| 204 | #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */ | 232 | #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */ |
| @@ -18,6 +18,7 @@ · 17 unmodified lines | |||
| 18 | * and should include an appropriate local definition in their source file. | 18 | * and should include an appropriate local definition in their source file. |
| 19 | */ | 19 | */ |
| 20 | #define PSP_CMDRESP_STS GENMASK(15, 0) | 20 | #define PSP_CMDRESP_STS GENMASK(15, 0) |
| 21 | #define PSP_TEE_STS_RING_BUSY 0x0000000d /* Ring already initialized */ | ||
| 21 | #define PSP_CMDRESP_CMD GENMASK(23, 16) | 22 | #define PSP_CMDRESP_CMD GENMASK(23, 16) |
| 22 | #define PSP_CMDRESP_RESERVED GENMASK(29, 24) | 23 | #define PSP_CMDRESP_RESERVED GENMASK(29, 24) |
| 23 | #define PSP_CMDRESP_RECOVERY BIT(30) | 24 | #define PSP_CMDRESP_RECOVERY BIT(30) |
| @@ -8,9 +8,11 @@ · 7 unmodified lines | |||
| 8 | #ifndef _LINUX_WMI_H | 8 | #ifndef _LINUX_WMI_H |
| 9 | #define _LINUX_WMI_H | 9 | #define _LINUX_WMI_H |
| 10 | 10 | ||
| 11 | #include <linux/compiler_attributes.h> | ||
| 11 | #include <linux/device.h> | 12 | #include <linux/device.h> |
| 12 | #include <linux/acpi.h> | 13 | #include <linux/acpi.h> |
| 13 | #include <linux/mod_devicetable.h> | 14 | #include <linux/mod_devicetable.h> |
| 15 | #include <linux/types.h> | ||
| 14 | 16 | ||
| 15 | /** | 17 | /** |
| 16 | * struct wmi_device - WMI device structure | 18 | * struct wmi_device - WMI device structure |
| @@ -36,6 +38,42 @@ struct wmi_device { · 19 unmodified lines | |||
| 36 | */ | 38 | */ |
| 37 | #define to_wmi_device(device) container_of_const(device, struct wmi_device, dev) | 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 | acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id, | 77 | acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id, |
| 40 | const struct acpi_buffer *in, struct acpi_buffer *out); | 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 | * @probe: Callback for device binding | 92 | * @probe: Callback for device binding |
| 55 | * @remove: Callback for device unbinding | 93 | * @remove: Callback for device unbinding |
| 56 | * @shutdown: Callback for device shutdown | 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 | struct wmi_driver { | 101 | struct wmi_driver { |
| 62 | struct device_driver driver; | 102 | struct device_driver driver; |
| @@ -68,6 +108,7 @@ struct wmi_driver { · 5 unmodified lines | |||
| 68 | void (*remove)(struct wmi_device *wdev); | 108 | void (*remove)(struct wmi_device *wdev); |
| 69 | void (*shutdown)(struct wmi_device *wdev); | 109 | void (*shutdown)(struct wmi_device *wdev); |
| 70 | void (*notify)(struct wmi_device *device, union acpi_object *data); | 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 | # Do not use make's built-in rules | 13 | # Do not use make's built-in rules |
| 14 | # (this improves performance and avoids hard-to-debug behaviour); | 14 | # (this improves performance and avoids hard-to-debug behaviour); |
| 15 | MAKEFLAGS += -r | 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 | override LDFLAGS += -lnl-genl-3 -lnl-3 | 23 | override LDFLAGS += -lnl-genl-3 -lnl-3 |
| 18 | 24 | ||
| 19 | ALL_TARGETS := intel-speed-select | 25 | ALL_TARGETS := intel-speed-select |
| @@ -16,7 +16,7 @@ struct process_cmd_struct { · 15 unmodified lines | |||
| 16 | int arg; | 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 | static const int supported_api_ver = 3; | 21 | static const int supported_api_ver = 3; |
| 22 | static struct isst_if_platform_info isst_platform_info; | 22 | static struct isst_if_platform_info isst_platform_info; |
| @@ -80,6 +80,18 @@ struct cpu_topology { · 57 unmodified lines | |||
| 80 | short die_id; | 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 | FILE *get_output_file(void) | 95 | FILE *get_output_file(void) |
| 84 | { | 96 | { |
| 85 | return outf; | 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 | ret = write(fd, "member", strlen("member")); | 962 | ret = write(fd, "member", strlen("member")); |
| 951 | if (ret == -1) { | 963 | if (ret == -1) { |
| 952 | printf("Can't update to member\n"); | 964 | printf("Can't update to member\n"); |
| 965 | close(fd); | ||
| 953 | return ret; | 966 | return ret; |
| 954 | } | 967 | } |
| 955 | 968 | ||
| 969 | close(fd); | ||
| 956 | return 0; | 970 | return 0; |
| 957 | } | 971 | } |
| 958 | 972 | ||
| @@ -1578,6 +1592,8 @@ free_mask: · 619 unmodified lines | |||
| 1578 | 1592 | ||
| 1579 | static void set_tdp_level(int arg) | 1593 | static void set_tdp_level(int arg) |
| 1580 | { | 1594 | { |
| 1595 | check_privilege(); | ||
| 1596 | |||
| 1581 | if (cmd_help) { | 1597 | if (cmd_help) { |
| 1582 | fprintf(stderr, "Set Config TDP level\n"); | 1598 | fprintf(stderr, "Set Config TDP level\n"); |
| 1583 | fprintf(stderr, | 1599 | fprintf(stderr, |
| @@ -2046,6 +2062,8 @@ static void set_pbf_enable(int arg) · 462 unmodified lines | |||
| 2046 | { | 2062 | { |
| 2047 | int enable = arg; | 2063 | int enable = arg; |
| 2048 | 2064 | ||
| 2065 | check_privilege(); | ||
| 2066 | |||
| 2049 | if (cmd_help) { | 2067 | if (cmd_help) { |
| 2050 | if (enable) { | 2068 | if (enable) { |
| 2051 | fprintf(stderr, | 2069 | fprintf(stderr, |
| @@ -2212,6 +2230,8 @@ static void set_fact_enable(int arg) · 160 unmodified lines | |||
| 2212 | int i, ret, enable = arg; | 2230 | int i, ret, enable = arg; |
| 2213 | struct isst_id id; | 2231 | struct isst_id id; |
| 2214 | 2232 | ||
| 2233 | check_privilege(); | ||
| 2234 | |||
| 2215 | if (cmd_help) { | 2235 | if (cmd_help) { |
| 2216 | if (enable) { | 2236 | if (enable) { |
| 2217 | fprintf(stderr, | 2237 | fprintf(stderr, |
| @@ -2361,6 +2381,8 @@ static void set_clos_enable(int arg) · 143 unmodified lines | |||
| 2361 | { | 2381 | { |
| 2362 | int enable = arg; | 2382 | int enable = arg; |
| 2363 | 2383 | ||
| 2384 | check_privilege(); | ||
| 2385 | |||
| 2364 | if (cmd_help) { | 2386 | if (cmd_help) { |
| 2365 | if (enable) { | 2387 | if (enable) { |
| 2366 | fprintf(stderr, | 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 | static void set_clos_config(int arg) | 2514 | static void set_clos_config(int arg) |
| 2493 | { | 2515 | { |
| 2516 | check_privilege(); | ||
| 2517 | |||
| 2494 | if (cmd_help) { | 2518 | if (cmd_help) { |
| 2495 | fprintf(stderr, | 2519 | fprintf(stderr, |
| 2496 | "Set core-power configuration for one of the four clos ids\n"); | 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 | static void set_clos_assoc(int arg) | 2581 | static void set_clos_assoc(int arg) |
| 2558 | { | 2582 | { |
| 2583 | check_privilege(); | ||
| 2584 | |||
| 2559 | if (cmd_help) { | 2585 | if (cmd_help) { |
| 2560 | fprintf(stderr, "Associate a clos id to a CPU\n"); | 2586 | fprintf(stderr, "Associate a clos id to a CPU\n"); |
| 2561 | fprintf(stderr, | 2587 | fprintf(stderr, |
| @@ -2637,6 +2663,8 @@ static void set_turbo_mode(int arg) · 75 unmodified lines | |||
| 2637 | int i, disable = arg; | 2663 | int i, disable = arg; |
| 2638 | struct isst_id id; | 2664 | struct isst_id id; |
| 2639 | 2665 | ||
| 2666 | check_privilege(); | ||
| 2667 | |||
| 2640 | if (cmd_help) { | 2668 | if (cmd_help) { |
| 2641 | if (disable) | 2669 | if (disable) |
| 2642 | fprintf(stderr, "Set turbo mode disable\n"); | 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 | if (set) { | 2712 | if (set) { |
| 2713 | check_privilege(); | ||
| 2685 | ret = isst_set_trl(id, fact_trl); | 2714 | ret = isst_set_trl(id, fact_trl); |
| 2686 | isst_display_result(id, outf, "turbo-mode", "set-trl", ret); | 2715 | isst_display_result(id, outf, "turbo-mode", "set-trl", ret); |
| 2687 | return; | 2716 | return; |
| @@ -3204,8 +3233,16 @@ static void cmdline(int argc, char **argv) · 516 unmodified lines | |||
| 3204 | }; | 3233 | }; |
| 3205 | 3234 | ||
| 3206 | if (geteuid() != 0) { | 3235 | if (geteuid() != 0) { |
| 3207 | fprintf(stderr, "Must run as root\n"); | 3236 | int fd; |
| 3208 | exit(0); | 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 | ret = update_cpu_model(); | 3248 | ret = update_cpu_model(); |