mirror of
https://github.com/torvalds/linux.git
synced 2026-03-07 23:04:33 +01:00
platform-drivers-x86 for v7.0-1
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
The following is an automated shortlog grouped by driver:
amd/pmf:
- Added a module parameter to disable the Smart PC function
- Introduce new interface to export NPU metrics
- Prevent TEE errors after hibernate
- Use ring buffer to store custom BIOS input values
amd:
- Use scope-based cleanup for wbrf_record()
asus-wmi:
- add keyboard brightness event handler
- Add support for multiple kbd led handlers
- remove unused keyboard backlight quirk
crypto:
- ccp - Add an S4 restore flow
- ccp - Declare PSP dead if PSP_CMD_TEE_RING_INIT fails
- ccp - Factor out ring destroy handling to a helper
- ccp - Send PSP_CMD_TEE_RING_DESTROY when PSP_CMD_TEE_RING_INIT fails
HID: asus:
- add support for the asus-wmi brightness handler
- early return for ROG devices
- fortify keyboard handshake
- initialize additional endpoints only for certain devices
- listen to the asus-wmi brightness device instead of creating one
- move vendor initialization to probe
- simplify RGB init sequence
- use same report_id in response
hp-wmi:
- Add EC offsets to read Victus S thermal profile
- add manual fan control for Victus S models
- fix platform profile values for Omen 16-wf1xxx
- implement fan keep-alive
- order include headers
ideadpad-laptop:
- Clean up style warnings and checks
intel/pmc:
- Change LPM mode fields to u8
- Enable substate residencies for multiple PMCs
- Move LPM mode attributes to PMC
- Remove double empty line
intel/pmt:
- Replace sprintf() with sysfs_emit()
intel/uncore-freq:
- Replace sprintf() with scnprintf()
- Replace sprintf() with sysfs_emit()
intel-wmi-sbl-fw-update:
- Use new buffer-based WMI API
intel/wmi: thunderbolt:
- Use new buffer-based WMI API
ISST:
- Add missing write block check
- Check for admin capability for write commands
- Optimize suspend/resume callbacks
- Store and restore all domains data
lenovo-wmi-capdata:
- Add support for Capability Data 00
- Add support for Fan Test Data
lenovo-wmi-{capdata,other}:
- Fix HWMON channel visibility
- Support multiple Capability Data
lenovo-wmi-capdata:
- Wire up Fan Test Data
lenovo-wmi-helpers:
- Convert returned buffer into u32
lenovo-wmi-other:
- Add HWMON for fan reporting/tuning
mlx-platform:
- Add support DGX flavor of next-generation 800GB/s ethernet switch.
- Add support for new Nvidia DGX system based on class VMOD0010
Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata:
- Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
surface: aggregator_registry:
- Add Surface Pro 11 (QCOM)
surface:
- Replace deprecated strcpy() in surface_button_add()
thinkpad_acpi:
- Add support to detect hardware damage detection capability.
- Add sysfs to display details of damaged device.
tools/power/x86/intel-speed-select:
- Allow non root users
- Fix file descriptor leak in isolate_cpus()
- Use pkg-config for libnl-3.0 detection
- v1.25 release
uniwill:
- Implement cTGP setting
uniwill-laptop:
- Introduce device descriptor system
wmi:
- Add helper functions for WMI string conversions
- Add kunit test for the marshalling code
- Add kunit test for the string conversion code
wmi-bmof:
- Use new buffer-based WMI API
wmi:
- Introduce marshalling support
wmi: string-kunit:
- Add missing oversized string test case
wmi:
- Update driver development guide
xiaomi-wmi:
- Use new buffer-based WMI API
yogabook:
- Clean up code style
Merges:
- Merge branch 'fixes' of into for-next
- Merge branch 'intel-sst' of https://github.com/spandruvada/linux-kernel into for-next
- Merge branch 'platform-drivers-x86-asus-kbd' into for-next
-----BEGIN PGP SIGNATURE-----
iHUEABYIAB0WIQSCSUwRdwTNL2MhaBlZrE9hU+XOMQUCaY9soAAKCRBZrE9hU+XO
MT5EAP9aK1wHlVGDfuC2k07X4gk8ZX5Ks9anXJlBcZFrpC9okwD5Aeqj3XLK338x
g5k/x+r87GwXjcBLnFi2TnNA2c8SWQY=
=eGAm
-----END PGP SIGNATURE-----
Merge tag 'platform-drivers-x86-v7.0-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86
Pull x86 platform driver updates from Ilpo Järvinen:
"Highlights:
- amd/pmf:
- Avoid overwriting BIOS input values when events occur rapidly
- Fix PMF driver issues related to S4 (in part on crypto/ccp side)
- Add NPU metrics API (for accel side consumers)
- Allow disabling Smart PC function through a module parameter
- asus-wmi & HID/asus:
- Unification of backlight control (replaces quirks)
- Support multiple interfaces for controlling keyboard/RGB brightness
- Simplify init sequence
- hp-wmi:
- Add manual fan control for Victus S models
- Add fan mode keep-alive
- Fix platform profile values for Omen 16-wf1xxx
- Add EC offset to get the thermal profile
- intel/pmc: Show substate residencies also for non-primary PMCs
- intel/ISST:
- Store and restore data for all domains
- Write interface improvements
- lenovo-wmi:
- Support multiple Capability Data
- Add HWMON reporting and tuning support
- mellanox/mlx-platform: Add HI173 & HI174 support
- surface/aggregator_registry: Add Surface Pro 11 (QCOM)
- thinkpad_acpi: Add support for HW damage detection capability
- uniwill: Implement cTGP setting
- wmi:
- Introduce marshalling support
- Convert a few drivers to use the new buffer-based WMI API
- tools/power/x86/intel-speed-select: Allow read operations for non-root
- Miscellaneous cleanups / refactoring / improvements"
* tag 'platform-drivers-x86-v7.0-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (68 commits)
platform/x86: lenovo-wmi-{capdata,other}: Fix HWMON channel visibility
platform/x86: hp-wmi: Add EC offsets to read Victus S thermal profile
platform: mellanox: mlx-platform: Add support DGX flavor of next-generation 800GB/s ethernet switch.
platform: mellanox: mlx-platform: Add support for new Nvidia DGX system based on class VMOD0010
HID: asus: add support for the asus-wmi brightness handler
platform/x86: asus-wmi: add keyboard brightness event handler
platform/x86: asus-wmi: remove unused keyboard backlight quirk
HID: asus: listen to the asus-wmi brightness device instead of creating one
platform/x86: asus-wmi: Add support for multiple kbd led handlers
HID: asus: early return for ROG devices
HID: asus: move vendor initialization to probe
HID: asus: fortify keyboard handshake
HID: asus: use same report_id in response
HID: asus: initialize additional endpoints only for certain devices
HID: asus: simplify RGB init sequence
platform/wmi: string-kunit: Add missing oversized string test case
platform/x86/amd/pmf: Added a module parameter to disable the Smart PC function
platform/x86/uniwill: Implement cTGP setting
platform/x86: uniwill-laptop: Introduce device descriptor system
platform/x86/amd: Use scope-based cleanup for wbrf_record()
...
This commit is contained in:
commit
f50822fd86
61 changed files with 5394 additions and 957 deletions
|
|
@ -54,6 +54,7 @@ detailed description):
|
|||
- Setting keyboard language
|
||||
- WWAN Antenna type
|
||||
- Auxmac
|
||||
- Hardware damage detection capability
|
||||
|
||||
A compatibility table by model and feature is maintained on the web
|
||||
site, http://ibm-acpi.sf.net/. I appreciate any success or failure
|
||||
|
|
@ -1576,6 +1577,42 @@ percentage level, above which charging will stop.
|
|||
The exact semantics of the attributes may be found in
|
||||
Documentation/ABI/testing/sysfs-class-power.
|
||||
|
||||
Hardware damage detection capability
|
||||
------------------------------------
|
||||
|
||||
sysfs attributes: hwdd_status, hwdd_detail
|
||||
|
||||
Thinkpads are adding the ability to detect and report hardware damage.
|
||||
Add new sysfs interface to identify the damaged device status.
|
||||
Initial support is available for the USB-C replaceable connector.
|
||||
|
||||
The command to check device damaged status is::
|
||||
|
||||
cat /sys/devices/platform/thinkpad_acpi/hwdd_status
|
||||
|
||||
This value displays status of device damaged.
|
||||
|
||||
- 0 = Not Damaged
|
||||
- 1 = Damaged
|
||||
|
||||
The command to check location of damaged device is::
|
||||
|
||||
cat /sys/devices/platform/thinkpad_acpi/hwdd_detail
|
||||
|
||||
This value displays location of damaged device having 1 line per damaged "item".
|
||||
For example:
|
||||
|
||||
if no damage is detected:
|
||||
|
||||
- No damage detected
|
||||
|
||||
if damage detected:
|
||||
|
||||
- TYPE-C: Base, Right side, Center port
|
||||
|
||||
The property is read-only. If feature is not supported then sysfs
|
||||
attribute is not created.
|
||||
|
||||
Multiple Commands, Module Parameters
|
||||
------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -16,5 +16,8 @@ which will be bound to compatible WMI devices by the driver core.
|
|||
.. kernel-doc:: include/linux/wmi.h
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/platform/wmi/string.c
|
||||
:export:
|
||||
|
||||
.. kernel-doc:: drivers/platform/wmi/core.c
|
||||
:export:
|
||||
|
|
|
|||
|
|
@ -104,3 +104,71 @@ holding the notification ID of the event. This method should be evaluated every
|
|||
time an ACPI notification is received, since some ACPI implementations use a
|
||||
queue to store WMI event data items. This queue will overflow after a couple
|
||||
of WMI events are received without retrieving the associated WMI event data.
|
||||
|
||||
Conversion rules for ACPI data types
|
||||
------------------------------------
|
||||
|
||||
Consumers of the ACPI-WMI interface use binary buffers to exchange data with the WMI driver core,
|
||||
with the internal structure of the buffer being only know to the consumers. The WMI driver core is
|
||||
thus responsible for converting the data inside the buffer into an appropriate ACPI data type for
|
||||
consumption by the ACPI firmware. Additionally, any data returned by the various ACPI methods needs
|
||||
to be converted back into a binary buffer.
|
||||
|
||||
The layout of said buffers is defined by the MOF description of the WMI method or data block in
|
||||
question [1]_:
|
||||
|
||||
=============== ======================================================================= =========
|
||||
Data Type Layout Alignment
|
||||
=============== ======================================================================= =========
|
||||
``string`` Starts with an unsigned 16-bit little endian integer specifying 2 bytes
|
||||
the length of the string data in bytes, followed by the string data
|
||||
encoded as UTF-16LE with **optional** NULL termination and padding.
|
||||
Keep in mind that some firmware implementations might depend on the
|
||||
terminating NULL character to be present. Also the padding should
|
||||
always be performed with NULL characters.
|
||||
``boolean`` Single byte where 0 means ``false`` and nonzero means ``true``. 1 byte
|
||||
``sint8`` Signed 8-bit integer. 1 byte
|
||||
``uint8`` Unsigned 8-bit integer. 1 byte
|
||||
``sint16`` Signed 16-bit little endian integer. 2 bytes
|
||||
``uint16`` Unsigned 16-bit little endian integer. 2 bytes
|
||||
``sint32`` Signed 32-bit little endian integer. 4 bytes
|
||||
``uint32`` Unsigned 32-bit little endian integer. 4 bytes
|
||||
``sint64`` Signed 64-bit little endian integer. 8 bytes
|
||||
``uint64`` Unsigned 64-bit little endian integer. 8 bytes
|
||||
``datetime`` A fixed-length 25-character UTF-16LE string with the format 2 bytes
|
||||
*yyyymmddhhmmss.mmmmmmsutc* where *yyyy* is the 4-digit year, *mm* is
|
||||
the 2-digit month, *dd* is the 2-digit day, *hh* is the 2-digit hour
|
||||
based on a 24-hour clock, *mm* is the 2-digit minute, *ss* is the
|
||||
2-digit second, *mmmmmm* is the 6-digit microsecond, *s* is a plus or
|
||||
minus character depending on whether *utc* is a positive or negative
|
||||
offset from UTC (or a colon if the date is an interval). Unpopulated
|
||||
fields should be filled with asterisks.
|
||||
=============== ======================================================================= =========
|
||||
|
||||
Arrays should be aligned based on the alignment of their base type, while objects should be
|
||||
aligned based on the largest alignment of an element inside them.
|
||||
|
||||
All buffers returned by the WMI driver core are 8-byte aligned. When converting ACPI data types
|
||||
into such buffers the following conversion rules apply:
|
||||
|
||||
=============== ============================================================
|
||||
ACPI Data Type Converted into
|
||||
=============== ============================================================
|
||||
Buffer Copied as-is.
|
||||
Integer Converted into a ``uint32``.
|
||||
String Converted into a ``string`` with a terminating NULL character
|
||||
to match the behavior the of the Windows driver.
|
||||
Package Each element inside the package is converted with alignment
|
||||
of the resulting data types being respected. Nested packages
|
||||
are not allowed.
|
||||
=============== ============================================================
|
||||
|
||||
The Windows driver does attempt to handle nested packages, but this results in internal data
|
||||
structures (``_ACPI_METHOD_ARGUMENT_V1``) erroneously being copied into the resulting buffer.
|
||||
ACPI firmware implementations should thus not return nested packages from ACPI methods
|
||||
associated with the ACPI-WMI interface.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items
|
||||
|
|
|
|||
|
|
@ -31,13 +31,32 @@ under the following path:
|
|||
|
||||
/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
|
||||
|
||||
Additionally, this driver also exports attributes to HWMON.
|
||||
|
||||
LENOVO_CAPABILITY_DATA_00
|
||||
-------------------------
|
||||
|
||||
WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E``
|
||||
|
||||
The LENOVO_CAPABILITY_DATA_00 interface provides various information that
|
||||
does not rely on the gamezone thermal mode.
|
||||
|
||||
The following HWMON attributes are implemented:
|
||||
- fanX_div: internal RPM divisor
|
||||
- fanX_input: current RPM
|
||||
- fanX_target: target RPM (tunable, 0=auto)
|
||||
|
||||
Due to the internal RPM divisor, the current/target RPMs are rounded down to
|
||||
its nearest multiple. The divisor itself is not necessary to be a power of two.
|
||||
|
||||
LENOVO_CAPABILITY_DATA_01
|
||||
-------------------------
|
||||
|
||||
WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154``
|
||||
|
||||
The LENOVO_CAPABILITY_DATA_01 interface provides information on various
|
||||
power limits of integrated CPU and GPU components.
|
||||
The LENOVO_CAPABILITY_DATA_01 interface provides various information that
|
||||
relies on the gamezone thermal mode, including power limits of integrated
|
||||
CPU and GPU components.
|
||||
|
||||
Each attribute has the following properties:
|
||||
- current_value
|
||||
|
|
@ -48,11 +67,22 @@ Each attribute has the following properties:
|
|||
- scalar_increment
|
||||
- type
|
||||
|
||||
The following attributes are implemented:
|
||||
The following firmware-attributes are implemented:
|
||||
- ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
|
||||
- ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
|
||||
- ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
|
||||
|
||||
LENOVO_FAN_TEST_DATA
|
||||
-------------------------
|
||||
|
||||
WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21``
|
||||
|
||||
The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of
|
||||
cooling fans.
|
||||
|
||||
The following HWMON attributes are implemented:
|
||||
- fanX_max: maximum RPM
|
||||
- fanX_min: minimum RPM
|
||||
|
||||
WMI interface description
|
||||
=========================
|
||||
|
|
@ -106,3 +136,13 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
|
|||
[WmiDataId(3), read, Description("Data Size.")] uint32 DataSize;
|
||||
[WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[];
|
||||
};
|
||||
|
||||
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of Fan Test Data"), guid("{B642801B-3D21-45DE-90AE-6E86F164FB21}")]
|
||||
class LENOVO_FAN_TEST_DATA {
|
||||
[key, read] string InstanceName;
|
||||
[read] boolean Active;
|
||||
[WmiDataId(1), read, Description("Mode.")] uint32 NumOfFans;
|
||||
[WmiDataId(2), read, Description("Fan ID."), WmiSizeIs("NumOfFans")] uint32 FanId[];
|
||||
[WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMaxSpeed[];
|
||||
[WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ to matching WMI devices using a struct wmi_device_id table:
|
|||
.probe = foo_probe,
|
||||
.remove = foo_remove, /* optional, devres is preferred */
|
||||
.shutdown = foo_shutdown, /* optional, called during shutdown */
|
||||
.notify = foo_notify, /* optional, for event handling */
|
||||
.notify_new = foo_notify, /* optional, for event handling */
|
||||
.no_notify_data = true, /* optional, enables events containing no additional data */
|
||||
.no_singleton = true, /* required for new WMI drivers */
|
||||
};
|
||||
|
|
@ -90,9 +90,9 @@ the WMI device and put it in a well-known state for the WMI driver to pick up la
|
|||
or kexec. Most WMI drivers need no special shutdown handling and can thus omit this callback.
|
||||
|
||||
Please note that new WMI drivers are required to be able to be instantiated multiple times,
|
||||
and are forbidden from using any deprecated GUID-based WMI functions. This means that the
|
||||
WMI driver should be prepared for the scenario that multiple matching WMI devices are present
|
||||
on a given machine.
|
||||
and are forbidden from using any deprecated GUID-based or ACPI-based WMI functions. This means
|
||||
that the WMI driver should be prepared for the scenario that multiple matching WMI devices are
|
||||
present on a given machine.
|
||||
|
||||
Because of this, WMI drivers should use the state container design pattern as described in
|
||||
Documentation/driver-api/driver-model/design-patterns.rst.
|
||||
|
|
@ -104,38 +104,37 @@ Documentation/driver-api/driver-model/design-patterns.rst.
|
|||
WMI method drivers
|
||||
------------------
|
||||
|
||||
WMI drivers can call WMI device methods using wmidev_evaluate_method(), the
|
||||
structure of the ACPI buffer passed to this function is device-specific and usually
|
||||
needs some tinkering to get right. Looking at the ACPI tables containing the WMI
|
||||
device usually helps here. The method id and instance number passed to this function
|
||||
are also device-specific, looking at the decoded Binary MOF is usually enough to
|
||||
find the right values.
|
||||
WMI drivers can call WMI device methods using wmidev_invoke_method(). For each WMI method
|
||||
invocation the WMI driver needs to provide the instance number and the method ID, as well as
|
||||
a buffer with the method arguments and optionally a buffer for the results.
|
||||
|
||||
The maximum instance number can be retrieved during runtime using wmidev_instance_count().
|
||||
The layout of said buffers is device-specific and described by the Binary MOF data associated
|
||||
with a given WMI device. Said Binary MOF data also describes the method ID of a given WMI method
|
||||
with the ``WmiMethodId`` qualifier. WMI devices exposing WMI methods usually expose only a single
|
||||
instance (instance number 0), but in theory can expose multiple instances as well. In such a case
|
||||
the number of instances can be retrieved using wmidev_instance_count().
|
||||
|
||||
Take a look at drivers/platform/x86/inspur_platform_profile.c for an example WMI method driver.
|
||||
Take a look at drivers/platform/x86/intel/wmi/thunderbolt.c for an example WMI method driver.
|
||||
|
||||
WMI data block drivers
|
||||
----------------------
|
||||
|
||||
WMI drivers can query WMI device data blocks using wmidev_block_query(), the
|
||||
structure of the returned ACPI object is again device-specific. Some WMI devices
|
||||
also allow for setting data blocks using wmidev_block_set().
|
||||
WMI drivers can query WMI data blocks using wmidev_query_block(), the layout of the returned
|
||||
buffer is again device-specific and described by the Binary MOF data. Some WMI data blocks are
|
||||
also writeable and can be set using wmidev_set_block(). The number of data block instances can
|
||||
again be retrieved using wmidev_instance_count().
|
||||
|
||||
The maximum instance number can also be retrieved using wmidev_instance_count().
|
||||
|
||||
Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example
|
||||
WMI data block driver.
|
||||
Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example WMI data block driver.
|
||||
|
||||
WMI event drivers
|
||||
-----------------
|
||||
|
||||
WMI drivers can receive WMI events via the notify() callback inside the struct wmi_driver.
|
||||
WMI drivers can receive WMI events via the notify_new() callback inside the struct wmi_driver.
|
||||
The WMI subsystem will then take care of setting up the WMI event accordingly. Please note that
|
||||
the structure of the ACPI object passed to this callback is device-specific, and freeing the
|
||||
ACPI object is being done by the WMI subsystem, not the driver.
|
||||
the layout of the buffer passed to this callback is device-specific, and freeing of the buffer
|
||||
is done by the WMI subsystem itself, not the driver.
|
||||
|
||||
The WMI driver core will take care that the notify() callback will only be called after
|
||||
The WMI driver core will take care that the notify_new() callback will only be called after
|
||||
the probe() callback has been called, and that no events are being received by the driver
|
||||
right before and after calling its remove() or shutdown() callback.
|
||||
|
||||
|
|
@ -147,6 +146,36 @@ the ``no_notify_data`` flag inside struct wmi_driver should be set to ``true``.
|
|||
|
||||
Take a look at drivers/platform/x86/xiaomi-wmi.c for an example WMI event driver.
|
||||
|
||||
Exchanging data with the WMI driver core
|
||||
----------------------------------------
|
||||
|
||||
WMI drivers can exchange data with the WMI driver core using struct wmi_buffer. The internal
|
||||
structure of those buffers is device-specific and only known by the WMI driver. Because of this
|
||||
the WMI driver itself is responsible for parsing and validating the data received from its
|
||||
WMI device.
|
||||
|
||||
The structure of said buffers is described by the MOF data associated with the WMI device in
|
||||
question. When such a buffer contains multiple data items it usually makes sense to define a
|
||||
C structure and use it during parsing. Since the WMI driver core guarantees that all buffers
|
||||
received from a WMI device are aligned on an 8-byte boundary, WMI drivers can simply perform
|
||||
a cast between the WMI buffer data and this C structure.
|
||||
|
||||
This however should only be done after the size of the buffer was verified to be large enough
|
||||
to hold the whole C structure. WMI drivers should reject undersized buffers as they are usually
|
||||
sent by the WMI device to signal an internal error. Oversized buffers however should be accepted
|
||||
to emulate the behavior of the Windows WMI implementation.
|
||||
|
||||
When defining a C structure for parsing WMI buffers the alignment of the data items should be
|
||||
respected. This is especially important for 64-bit integers as those have different alignments
|
||||
on 64-bit (8-byte alignment) and 32-bit (4-byte alignment) architectures. It is thus a good idea
|
||||
to manually specify the alignment of such data items or mark the whole structure as packed when
|
||||
appropriate. Integer data items in general are little-endian integers and should be marked as
|
||||
such using ``__le64`` and friends. When parsing WMI string data items the struct wmi_string should
|
||||
be used as WMI strings have a different layout than C strings.
|
||||
|
||||
See Documentation/wmi/acpi-interface.rst for more information regarding the binary format
|
||||
of WMI data items.
|
||||
|
||||
Handling multiple WMI devices at once
|
||||
-------------------------------------
|
||||
|
||||
|
|
@ -171,6 +200,7 @@ Things to avoid
|
|||
When developing WMI drivers, there are a couple of things which should be avoided:
|
||||
|
||||
- usage of the deprecated GUID-based WMI interface which uses GUIDs instead of WMI device structs
|
||||
- usage of the deprecated ACPI-based WMI interface which uses ACPI objects instead of plain buffers
|
||||
- bypassing of the WMI subsystem when talking to WMI devices
|
||||
- WMI drivers which cannot be instantiated multiple times.
|
||||
|
||||
|
|
|
|||
|
|
@ -351,6 +351,17 @@ struct psp_device *psp_get_master_device(void)
|
|||
return sp ? sp->psp_data : NULL;
|
||||
}
|
||||
|
||||
int psp_restore(struct sp_device *sp)
|
||||
{
|
||||
struct psp_device *psp = sp->psp_data;
|
||||
int ret = 0;
|
||||
|
||||
if (psp->tee_data)
|
||||
ret = tee_restore(psp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void psp_pci_init(void)
|
||||
{
|
||||
psp_master = psp_get_master_device();
|
||||
|
|
|
|||
|
|
@ -230,6 +230,18 @@ int sp_resume(struct sp_device *sp)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int sp_restore(struct sp_device *sp)
|
||||
{
|
||||
if (sp->psp_data) {
|
||||
int ret = psp_restore(sp);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return sp_resume(sp);
|
||||
}
|
||||
|
||||
struct sp_device *sp_get_psp_master_device(void)
|
||||
{
|
||||
struct sp_device *i, *ret = NULL;
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ void sp_destroy(struct sp_device *sp);
|
|||
|
||||
int sp_suspend(struct sp_device *sp);
|
||||
int sp_resume(struct sp_device *sp);
|
||||
int sp_restore(struct sp_device *sp);
|
||||
int sp_request_ccp_irq(struct sp_device *sp, irq_handler_t handler,
|
||||
const char *name, void *data);
|
||||
void sp_free_ccp_irq(struct sp_device *sp, void *data);
|
||||
|
|
@ -174,6 +175,7 @@ int psp_dev_init(struct sp_device *sp);
|
|||
void psp_pci_init(void);
|
||||
void psp_dev_destroy(struct sp_device *sp);
|
||||
void psp_pci_exit(void);
|
||||
int psp_restore(struct sp_device *sp);
|
||||
|
||||
#else /* !CONFIG_CRYPTO_DEV_SP_PSP */
|
||||
|
||||
|
|
@ -181,6 +183,7 @@ static inline int psp_dev_init(struct sp_device *sp) { return 0; }
|
|||
static inline void psp_pci_init(void) { }
|
||||
static inline void psp_dev_destroy(struct sp_device *sp) { }
|
||||
static inline void psp_pci_exit(void) { }
|
||||
static inline int psp_restore(struct sp_device *sp) { return 0; }
|
||||
|
||||
#endif /* CONFIG_CRYPTO_DEV_SP_PSP */
|
||||
|
||||
|
|
|
|||
|
|
@ -353,6 +353,13 @@ static int __maybe_unused sp_pci_resume(struct device *dev)
|
|||
return sp_resume(sp);
|
||||
}
|
||||
|
||||
static int __maybe_unused sp_pci_restore(struct device *dev)
|
||||
{
|
||||
struct sp_device *sp = dev_get_drvdata(dev);
|
||||
|
||||
return sp_restore(sp);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
|
||||
static const struct sev_vdata sevv1 = {
|
||||
.cmdresp_reg = 0x10580, /* C2PMSG_32 */
|
||||
|
|
@ -563,7 +570,14 @@ static const struct pci_device_id sp_pci_table[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(pci, sp_pci_table);
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(sp_pci_pm_ops, sp_pci_suspend, sp_pci_resume);
|
||||
static const struct dev_pm_ops sp_pci_pm_ops = {
|
||||
.suspend = pm_sleep_ptr(sp_pci_suspend),
|
||||
.resume = pm_sleep_ptr(sp_pci_resume),
|
||||
.freeze = pm_sleep_ptr(sp_pci_suspend),
|
||||
.thaw = pm_sleep_ptr(sp_pci_resume),
|
||||
.poweroff = pm_sleep_ptr(sp_pci_suspend),
|
||||
.restore_early = pm_sleep_ptr(sp_pci_restore),
|
||||
};
|
||||
|
||||
static struct pci_driver sp_pci_driver = {
|
||||
.name = "ccp",
|
||||
|
|
|
|||
|
|
@ -86,10 +86,34 @@ static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd)
|
|||
kfree(cmd);
|
||||
}
|
||||
|
||||
static bool tee_send_destroy_cmd(struct psp_tee_device *tee)
|
||||
{
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL,
|
||||
TEE_DEFAULT_CMD_TIMEOUT, ®);
|
||||
if (ret) {
|
||||
dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n");
|
||||
psp_dead = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
|
||||
dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n",
|
||||
FIELD_GET(PSP_CMDRESP_STS, reg));
|
||||
psp_dead = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int tee_init_ring(struct psp_tee_device *tee)
|
||||
{
|
||||
int ring_size = MAX_RING_BUFFER_ENTRIES * sizeof(struct tee_ring_cmd);
|
||||
struct tee_init_ring_cmd *cmd;
|
||||
bool retry = false;
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
|
|
@ -112,6 +136,7 @@ static int tee_init_ring(struct psp_tee_device *tee)
|
|||
/* Send command buffer details to Trusted OS by writing to
|
||||
* CPU-PSP message registers
|
||||
*/
|
||||
retry_init:
|
||||
ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_INIT, cmd,
|
||||
TEE_DEFAULT_CMD_TIMEOUT, ®);
|
||||
if (ret) {
|
||||
|
|
@ -122,9 +147,22 @@ static int tee_init_ring(struct psp_tee_device *tee)
|
|||
}
|
||||
|
||||
if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
|
||||
/*
|
||||
* During the hibernate resume sequence driver may have gotten loaded
|
||||
* but the ring not properly destroyed. If the ring doesn't work, try
|
||||
* to destroy and re-init once.
|
||||
*/
|
||||
if (!retry && FIELD_GET(PSP_CMDRESP_STS, reg) == PSP_TEE_STS_RING_BUSY) {
|
||||
dev_info(tee->dev, "tee: ring init command failed with busy status, retrying\n");
|
||||
if (tee_send_destroy_cmd(tee)) {
|
||||
retry = true;
|
||||
goto retry_init;
|
||||
}
|
||||
}
|
||||
dev_err(tee->dev, "tee: ring init command failed (%#010lx)\n",
|
||||
FIELD_GET(PSP_CMDRESP_STS, reg));
|
||||
tee_free_ring(tee);
|
||||
psp_dead = true;
|
||||
ret = -EIO;
|
||||
}
|
||||
|
||||
|
|
@ -136,24 +174,13 @@ free_buf:
|
|||
|
||||
static void tee_destroy_ring(struct psp_tee_device *tee)
|
||||
{
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
if (!tee->rb_mgr.ring_start)
|
||||
return;
|
||||
|
||||
if (psp_dead)
|
||||
goto free_ring;
|
||||
|
||||
ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL,
|
||||
TEE_DEFAULT_CMD_TIMEOUT, ®);
|
||||
if (ret) {
|
||||
dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n");
|
||||
psp_dead = true;
|
||||
} else if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
|
||||
dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n",
|
||||
FIELD_GET(PSP_CMDRESP_STS, reg));
|
||||
}
|
||||
tee_send_destroy_cmd(tee);
|
||||
|
||||
free_ring:
|
||||
tee_free_ring(tee);
|
||||
|
|
@ -365,3 +392,8 @@ int psp_check_tee_status(void)
|
|||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(psp_check_tee_status);
|
||||
|
||||
int tee_restore(struct psp_device *psp)
|
||||
{
|
||||
return tee_init_ring(psp->tee_data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,5 +111,6 @@ struct tee_ring_cmd {
|
|||
|
||||
int tee_dev_init(struct psp_device *psp);
|
||||
void tee_dev_destroy(struct psp_device *psp);
|
||||
int tee_restore(struct psp_device *psp);
|
||||
|
||||
#endif /* __TEE_DEV_H__ */
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/x86/asus-wmi.h>
|
||||
#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
|
||||
#include <linux/input/mt.h>
|
||||
#include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
|
||||
#include <linux/power_supply.h>
|
||||
|
|
@ -50,7 +49,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
|
|||
#define FEATURE_REPORT_ID 0x0d
|
||||
#define INPUT_REPORT_ID 0x5d
|
||||
#define FEATURE_KBD_REPORT_ID 0x5a
|
||||
#define FEATURE_KBD_REPORT_SIZE 16
|
||||
#define FEATURE_KBD_REPORT_SIZE 64
|
||||
#define FEATURE_KBD_LED_REPORT_ID1 0x5d
|
||||
#define FEATURE_KBD_LED_REPORT_ID2 0x5e
|
||||
|
||||
|
|
@ -99,9 +98,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
|
|||
#define QUIRK_T90CHI BIT(9)
|
||||
#define QUIRK_MEDION_E1239T BIT(10)
|
||||
#define QUIRK_ROG_NKEY_KEYBOARD BIT(11)
|
||||
#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
|
||||
#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
|
||||
#define QUIRK_ROG_ALLY_XPAD BIT(13)
|
||||
#define QUIRK_HID_FN_LOCK BIT(14)
|
||||
#define QUIRK_ROG_NKEY_ID1ID2_INIT BIT(15)
|
||||
|
||||
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
|
||||
QUIRK_NO_INIT_REPORTS | \
|
||||
|
|
@ -113,7 +113,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
|
|||
#define TRKID_SGN ((TRKID_MAX + 1) >> 1)
|
||||
|
||||
struct asus_kbd_leds {
|
||||
struct led_classdev cdev;
|
||||
struct asus_hid_listener listener;
|
||||
struct hid_device *hdev;
|
||||
struct work_struct work;
|
||||
unsigned int brightness;
|
||||
|
|
@ -138,7 +138,6 @@ struct asus_drvdata {
|
|||
struct input_dev *tp_kbd_input;
|
||||
struct asus_kbd_leds *kbd_backlight;
|
||||
const struct asus_touchpad_info *tp;
|
||||
bool enable_backlight;
|
||||
struct power_supply *battery;
|
||||
struct power_supply_desc battery_desc;
|
||||
int battery_capacity;
|
||||
|
|
@ -363,10 +362,21 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
|
|||
usage->hid & HID_USAGE);
|
||||
}
|
||||
|
||||
if (drvdata->quirks & QUIRK_HID_FN_LOCK &&
|
||||
usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) {
|
||||
drvdata->fn_lock = !drvdata->fn_lock;
|
||||
schedule_work(&drvdata->fn_lock_sync_work);
|
||||
if (usage->type == EV_KEY && value) {
|
||||
switch (usage->code) {
|
||||
case KEY_KBDILLUMUP:
|
||||
return !asus_hid_event(ASUS_EV_BRTUP);
|
||||
case KEY_KBDILLUMDOWN:
|
||||
return !asus_hid_event(ASUS_EV_BRTDOWN);
|
||||
case KEY_KBDILLUMTOGGLE:
|
||||
return !asus_hid_event(ASUS_EV_BRTTOGGLE);
|
||||
case KEY_FN_ESC:
|
||||
if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
|
||||
drvdata->fn_lock = !drvdata->fn_lock;
|
||||
schedule_work(&drvdata->fn_lock_sync_work);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -476,15 +486,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
|
|||
|
||||
static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
|
||||
{
|
||||
/*
|
||||
* The handshake is first sent as a set_report, then retrieved
|
||||
* from a get_report. They should be equal.
|
||||
*/
|
||||
const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
|
||||
0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
|
||||
int ret;
|
||||
|
||||
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
|
||||
if (ret < 0)
|
||||
hid_err(hdev, "Asus failed to send init command: %d\n", ret);
|
||||
if (ret < 0) {
|
||||
hid_err(hdev, "Asus handshake %02x failed to send: %d\n",
|
||||
report_id, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
u8 *readbuf __free(kfree) = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
|
||||
if (!readbuf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hid_hw_raw_request(hdev, report_id, readbuf,
|
||||
FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
|
||||
HID_REQ_GET_REPORT);
|
||||
if (ret < 0) {
|
||||
hid_warn(hdev, "Asus handshake %02x failed to receive ack: %d\n",
|
||||
report_id, ret);
|
||||
} else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
|
||||
hid_warn(hdev, "Asus handshake %02x returned invalid response: %*ph\n",
|
||||
report_id, FEATURE_KBD_REPORT_SIZE, readbuf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Do not return error if handshake is wrong until this is
|
||||
* verified to work for all devices.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_kbd_get_functions(struct hid_device *hdev,
|
||||
|
|
@ -505,7 +541,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
|
|||
if (!readbuf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
|
||||
ret = hid_hw_raw_request(hdev, report_id, readbuf,
|
||||
FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
|
||||
HID_REQ_GET_REPORT);
|
||||
if (ret < 0) {
|
||||
|
|
@ -565,11 +601,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
|
|||
spin_unlock_irqrestore(&led->lock, flags);
|
||||
}
|
||||
|
||||
static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
|
||||
int brightness)
|
||||
{
|
||||
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
|
||||
cdev);
|
||||
struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
|
||||
listener);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&led->lock, flags);
|
||||
|
|
@ -579,20 +615,6 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
|
|||
asus_schedule_work(led);
|
||||
}
|
||||
|
||||
static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
|
||||
cdev);
|
||||
enum led_brightness brightness;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&led->lock, flags);
|
||||
brightness = led->brightness;
|
||||
spin_unlock_irqrestore(&led->lock, flags);
|
||||
|
||||
return brightness;
|
||||
}
|
||||
|
||||
static void asus_kbd_backlight_work(struct work_struct *work)
|
||||
{
|
||||
struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
|
||||
|
|
@ -609,34 +631,6 @@ static void asus_kbd_backlight_work(struct work_struct *work)
|
|||
hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
|
||||
}
|
||||
|
||||
/* WMI-based keyboard backlight LED control (via asus-wmi driver) takes
|
||||
* precedence. We only activate HID-based backlight control when the
|
||||
* WMI control is not available.
|
||||
*/
|
||||
static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
|
||||
{
|
||||
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
u32 value;
|
||||
int ret;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_ASUS_WMI))
|
||||
return false;
|
||||
|
||||
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD &&
|
||||
dmi_check_system(asus_use_hid_led_dmi_ids)) {
|
||||
hid_info(hdev, "using HID for asus::kbd_backlight\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS,
|
||||
ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value);
|
||||
hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value);
|
||||
if (ret)
|
||||
return false;
|
||||
|
||||
return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT);
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't care about any other part of the string except the version section.
|
||||
* Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01
|
||||
|
|
@ -736,48 +730,35 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
|
|||
unsigned char kbd_func;
|
||||
int ret;
|
||||
|
||||
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
|
||||
/* Initialize keyboard */
|
||||
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
|
||||
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Get keyboard functions */
|
||||
ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Check for backlight support */
|
||||
if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
|
||||
return -ENODEV;
|
||||
|
||||
if (drvdata->quirks & QUIRK_ROG_NKEY_ID1ID2_INIT) {
|
||||
asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
|
||||
asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
|
||||
}
|
||||
|
||||
if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
|
||||
ret = asus_kbd_disable_oobe(hdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* The LED endpoint is initialised in two HID */
|
||||
ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
|
||||
ret = asus_kbd_disable_oobe(hdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
|
||||
intf = to_usb_interface(hdev->dev.parent);
|
||||
udev = interface_to_usbdev(intf);
|
||||
validate_mcu_fw_version(hdev,
|
||||
le16_to_cpu(udev->descriptor.idProduct));
|
||||
}
|
||||
|
||||
} else {
|
||||
/* Initialize keyboard */
|
||||
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Get keyboard functions */
|
||||
ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Check for backlight support */
|
||||
if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
|
||||
return -ENODEV;
|
||||
if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
|
||||
intf = to_usb_interface(hdev->dev.parent);
|
||||
udev = interface_to_usbdev(intf);
|
||||
validate_mcu_fw_version(hdev,
|
||||
le16_to_cpu(udev->descriptor.idProduct));
|
||||
}
|
||||
|
||||
drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
|
||||
|
|
@ -789,14 +770,11 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
|
|||
drvdata->kbd_backlight->removed = false;
|
||||
drvdata->kbd_backlight->brightness = 0;
|
||||
drvdata->kbd_backlight->hdev = hdev;
|
||||
drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight";
|
||||
drvdata->kbd_backlight->cdev.max_brightness = 3;
|
||||
drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
|
||||
drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
|
||||
drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
|
||||
INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
|
||||
spin_lock_init(&drvdata->kbd_backlight->lock);
|
||||
|
||||
ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
|
||||
ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
|
||||
if (ret < 0) {
|
||||
/* No need to have this still around */
|
||||
devm_kfree(&hdev->dev, drvdata->kbd_backlight);
|
||||
|
|
@ -1021,11 +999,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
|
|||
|
||||
drvdata->input = input;
|
||||
|
||||
if (drvdata->enable_backlight &&
|
||||
!asus_kbd_wmi_led_control_present(hdev) &&
|
||||
asus_kbd_register_leds(hdev))
|
||||
hid_warn(hdev, "Failed to initialize backlight.\n");
|
||||
|
||||
if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
|
||||
drvdata->fn_lock = true;
|
||||
INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock);
|
||||
|
|
@ -1104,15 +1077,6 @@ static int asus_input_mapping(struct hid_device *hdev,
|
|||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check and enable backlight only on devices with UsagePage ==
|
||||
* 0xff31 to avoid initializing the keyboard firmware multiple
|
||||
* times on devices with multiple HID descriptors but same
|
||||
* PID/VID.
|
||||
*/
|
||||
if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
|
||||
drvdata->enable_backlight = true;
|
||||
|
||||
set_bit(EV_REP, hi->input->evbit);
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -1205,7 +1169,7 @@ static int __maybe_unused asus_resume(struct hid_device *hdev) {
|
|||
|
||||
if (drvdata->kbd_backlight) {
|
||||
const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4,
|
||||
drvdata->kbd_backlight->cdev.brightness };
|
||||
drvdata->kbd_backlight->brightness };
|
||||
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
|
||||
if (ret < 0) {
|
||||
hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
|
||||
|
|
@ -1229,8 +1193,11 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
|
|||
|
||||
static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct hid_report_enum *rep_enum;
|
||||
struct asus_drvdata *drvdata;
|
||||
struct hid_report *rep;
|
||||
bool is_vendor = false;
|
||||
int ret;
|
||||
|
||||
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
|
||||
if (drvdata == NULL) {
|
||||
|
|
@ -1314,12 +1281,30 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Check for vendor for RGB init and handle generic devices properly. */
|
||||
rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
|
||||
list_for_each_entry(rep, &rep_enum->report_list, list) {
|
||||
if ((rep->application & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR)
|
||||
is_vendor = true;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Asus hw start failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
|
||||
asus_kbd_register_leds(hdev))
|
||||
hid_warn(hdev, "Failed to initialize backlight.\n");
|
||||
|
||||
/*
|
||||
* For ROG keyboards, skip rename for consistency and ->input check as
|
||||
* some devices do not have inputs.
|
||||
*/
|
||||
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Check that input registration succeeded. Checking that
|
||||
* HID_CLAIMED_INPUT is set prevents a UAF when all input devices
|
||||
|
|
@ -1356,6 +1341,8 @@ static void asus_remove(struct hid_device *hdev)
|
|||
unsigned long flags;
|
||||
|
||||
if (drvdata->kbd_backlight) {
|
||||
asus_hid_unregister_listener(&drvdata->kbd_backlight->listener);
|
||||
|
||||
spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
|
||||
drvdata->kbd_backlight->removed = true;
|
||||
spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
|
||||
|
|
@ -1490,10 +1477,10 @@ static const struct hid_device_id asus_devices[] = {
|
|||
QUIRK_USE_KBD_BACKLIGHT },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK },
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK | QUIRK_ROG_NKEY_ID1ID2_INIT },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
* Copyright (C) 2016-2018 Vadim Pasternak <vadimp@mellanox.com>
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/i2c.h>
|
||||
|
|
@ -727,6 +729,16 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = {
|
|||
},
|
||||
};
|
||||
|
||||
/* Platform hotplug dgx data */
|
||||
static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pdb_items_data[] = {
|
||||
{
|
||||
.label = "pdb1",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
|
||||
.mask = BIT(0),
|
||||
.hpdev.nr = MLXPLAT_CPLD_NR_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_items_data[] = {
|
||||
{
|
||||
.label = "pwr1",
|
||||
|
|
@ -776,6 +788,15 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_ng800_items_data[] =
|
|||
},
|
||||
};
|
||||
|
||||
static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pwr_items_data[] = {
|
||||
{
|
||||
.label = "pwr1",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
|
||||
.mask = BIT(0),
|
||||
.hpdev.nr = MLXPLAT_CPLD_NR_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_items_data[] = {
|
||||
{
|
||||
.label = "fan1",
|
||||
|
|
@ -1399,6 +1420,45 @@ static struct mlxreg_core_item mlxplat_mlxcpld_ext_items[] = {
|
|||
}
|
||||
};
|
||||
|
||||
static struct mlxreg_core_item mlxplat_mlxcpld_ext_dgx_items[] = {
|
||||
{
|
||||
.data = mlxplat_mlxcpld_dgx_pdb_items_data,
|
||||
.aggr_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF,
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_PSU_MASK,
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pdb_items_data),
|
||||
.inversed = 1,
|
||||
.health = false,
|
||||
},
|
||||
{
|
||||
.data = mlxplat_mlxcpld_dgx_pwr_items_data,
|
||||
.aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF,
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_PWR_MASK,
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pwr_items_data),
|
||||
.inversed = 0,
|
||||
.health = false,
|
||||
},
|
||||
{
|
||||
.data = mlxplat_mlxcpld_default_ng_fan_items_data,
|
||||
.aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF,
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_FAN_NG_MASK,
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_fan_items_data),
|
||||
.inversed = 1,
|
||||
.health = false,
|
||||
},
|
||||
{
|
||||
.data = mlxplat_mlxcpld_default_asic_items_data,
|
||||
.aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF,
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_ASIC_MASK,
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_default_asic_items_data),
|
||||
.inversed = 0,
|
||||
.health = true,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mlxreg_core_item mlxplat_mlxcpld_ng800_items[] = {
|
||||
{
|
||||
.data = mlxplat_mlxcpld_default_ng_psu_items_data,
|
||||
|
|
@ -1450,6 +1510,16 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ext_data = {
|
|||
.mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2,
|
||||
};
|
||||
|
||||
static
|
||||
struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_dgx_ext_data = {
|
||||
.items = mlxplat_mlxcpld_ext_dgx_items,
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_ext_dgx_items),
|
||||
.cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX,
|
||||
.cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET,
|
||||
.mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2,
|
||||
};
|
||||
|
||||
static
|
||||
struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_data = {
|
||||
.items = mlxplat_mlxcpld_ng800_items,
|
||||
|
|
@ -4625,6 +4695,359 @@ static struct mlxreg_core_platform_data mlxplat_default_ng_regs_io_data = {
|
|||
.counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_regs_io_data),
|
||||
};
|
||||
|
||||
/* Platform register access for next generation systems families data */
|
||||
static struct mlxreg_core_data mlxplat_mlxcpld_dgx_ng_regs_io_data[] = {
|
||||
{
|
||||
.label = "cpld1_version",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "cpld2_version",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "cpld3_version",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "cpld4_version",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "cpld1_pn",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET,
|
||||
.bit = GENMASK(15, 0),
|
||||
.mode = 0444,
|
||||
.regnum = 2,
|
||||
},
|
||||
{
|
||||
.label = "cpld2_pn",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET,
|
||||
.bit = GENMASK(15, 0),
|
||||
.mode = 0444,
|
||||
.regnum = 2,
|
||||
},
|
||||
{
|
||||
.label = "cpld3_pn",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET,
|
||||
.bit = GENMASK(15, 0),
|
||||
.mode = 0444,
|
||||
.regnum = 2,
|
||||
},
|
||||
{
|
||||
.label = "cpld4_pn",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET,
|
||||
.bit = GENMASK(15, 0),
|
||||
.mode = 0444,
|
||||
.regnum = 2,
|
||||
},
|
||||
{
|
||||
.label = "cpld1_version_min",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "cpld2_version_min",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "cpld3_version_min",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "cpld4_version_min",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "asic_reset",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(3),
|
||||
.mode = 0200,
|
||||
},
|
||||
{
|
||||
.label = "reset_long_pb",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_short_pb",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(1),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_aux_pwr_or_ref",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(2),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_swb_dc_dc_pwr_fail",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(3),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_from_asic",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(5),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_swb_wd",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(6),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_asic_thermal",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(7),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_sw_reset",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_comex_pwr_fail",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(3),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_platform",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(4),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_soc",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(5),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_comex_wd",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(6),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_system",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(1),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_sw_pwr_off",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(2),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_comex_thermal",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(3),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_reload_bios",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(5),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_pdb_pwr_fail",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(6),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "pdb_reset_stby",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(0),
|
||||
.mode = 0200,
|
||||
},
|
||||
{
|
||||
.label = "pwr_cycle",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(2),
|
||||
.mode = 0200,
|
||||
},
|
||||
{
|
||||
.label = "pwr_down",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(3),
|
||||
.mode = 0200,
|
||||
},
|
||||
{
|
||||
.label = "deep_pwr_cycle",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(5),
|
||||
.mode = 0200,
|
||||
},
|
||||
{
|
||||
.label = "latch_reset",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(6),
|
||||
.mode = 0200,
|
||||
},
|
||||
{
|
||||
.label = "jtag_cap",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_FU_CAP_MASK,
|
||||
.bit = 1,
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "jtag_enable",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(4),
|
||||
.mode = 0644,
|
||||
},
|
||||
{
|
||||
.label = "dbg1",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0644,
|
||||
},
|
||||
{
|
||||
.label = "dbg2",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0644,
|
||||
},
|
||||
{
|
||||
.label = "dbg3",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0644,
|
||||
},
|
||||
{
|
||||
.label = "dbg4",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0644,
|
||||
},
|
||||
{
|
||||
.label = "asic_health",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_ASIC_MASK,
|
||||
.bit = 1,
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "fan_dir",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "bios_safe_mode",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(4),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "bios_active_image",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(5),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "bios_auth_fail",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(6),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "bios_upgrade_fail",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(7),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "voltreg_update_status",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_VOLTREG_UPD_MASK,
|
||||
.bit = 5,
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "pwr_converter_prog_en",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(0),
|
||||
.mode = 0644,
|
||||
.secured = 1,
|
||||
},
|
||||
{
|
||||
.label = "vpd_wp",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(3),
|
||||
.mode = 0644,
|
||||
},
|
||||
{
|
||||
.label = "pcie_asic_reset_dis",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(4),
|
||||
.mode = 0644,
|
||||
},
|
||||
{
|
||||
.label = "shutdown_unlock",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(5),
|
||||
.mode = 0644,
|
||||
},
|
||||
{
|
||||
.label = "config1",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "config2",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "config3",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "ufm_version",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mlxreg_core_platform_data mlxplat_dgx_ng_regs_io_data = {
|
||||
.data = mlxplat_mlxcpld_dgx_ng_regs_io_data,
|
||||
.counter = ARRAY_SIZE(mlxplat_mlxcpld_dgx_ng_regs_io_data),
|
||||
};
|
||||
|
||||
/* Platform register access for modular systems families data */
|
||||
static struct mlxreg_core_data mlxplat_mlxcpld_modular_regs_io_data[] = {
|
||||
{
|
||||
|
|
@ -7239,6 +7662,32 @@ static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi)
|
|||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_ng400_dgx_matched(const struct dmi_system_id *dmi)
|
||||
{
|
||||
int i;
|
||||
|
||||
mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM;
|
||||
mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data);
|
||||
mlxplat_mux_data = mlxplat_default_mux_data;
|
||||
for (i = 0; i < mlxplat_mux_num; i++) {
|
||||
mlxplat_mux_data[i].values = mlxplat_msn21xx_channels;
|
||||
mlxplat_mux_data[i].n_values =
|
||||
ARRAY_SIZE(mlxplat_msn21xx_channels);
|
||||
}
|
||||
mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data;
|
||||
mlxplat_hotplug->deferred_nr =
|
||||
mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
|
||||
mlxplat_led = &mlxplat_default_ng_led_data;
|
||||
mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data;
|
||||
mlxplat_fan = &mlxplat_default_fan_data;
|
||||
for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++)
|
||||
mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i];
|
||||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
|
||||
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi)
|
||||
{
|
||||
int i;
|
||||
|
|
@ -7323,6 +7772,27 @@ static int __init mlxplat_dmi_ng800_matched(const struct dmi_system_id *dmi)
|
|||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_ng800_dgx_matched(const struct dmi_system_id *dmi)
|
||||
{
|
||||
int i;
|
||||
|
||||
mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM;
|
||||
mlxplat_mux_num = ARRAY_SIZE(mlxplat_ng800_mux_data);
|
||||
mlxplat_mux_data = mlxplat_ng800_mux_data;
|
||||
mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data;
|
||||
mlxplat_hotplug->deferred_nr =
|
||||
mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
|
||||
mlxplat_led = &mlxplat_default_ng_led_data;
|
||||
mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data;
|
||||
mlxplat_fan = &mlxplat_default_fan_data;
|
||||
for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++)
|
||||
mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i];
|
||||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
|
||||
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi)
|
||||
{
|
||||
int i;
|
||||
|
|
@ -7458,6 +7928,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = {
|
|||
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI142"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_ng400_dgx_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0010"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI173"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_ng400_matched,
|
||||
.matches = {
|
||||
|
|
@ -7470,6 +7947,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0011"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_ng800_dgx_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0013"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI174"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_ng800_matched,
|
||||
.matches = {
|
||||
|
|
|
|||
|
|
@ -406,6 +406,22 @@ static const struct software_node *ssam_node_group_sp9_5g[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
/* Devices for Surface Pro 11 (ARM/QCOM) */
|
||||
static const struct software_node *ssam_node_group_sp11[] = {
|
||||
&ssam_node_root,
|
||||
&ssam_node_hub_kip,
|
||||
&ssam_node_bat_ac,
|
||||
&ssam_node_bat_main,
|
||||
&ssam_node_tmp_sensors,
|
||||
&ssam_node_hid_kip_keyboard,
|
||||
&ssam_node_hid_kip_penstash,
|
||||
&ssam_node_hid_kip_touchpad,
|
||||
&ssam_node_hid_kip_fwupd,
|
||||
&ssam_node_hid_sam_sensors,
|
||||
&ssam_node_kip_tablet_switch,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
|
||||
|
||||
static const struct acpi_device_id ssam_platform_hub_acpi_match[] = {
|
||||
|
|
@ -482,6 +498,8 @@ MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_acpi_match);
|
|||
static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
|
||||
/* Surface Pro 9 5G (ARM/QCOM) */
|
||||
{ .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g },
|
||||
/* Surface Pro 11 (ARM/QCOM) */
|
||||
{ .compatible = "microsoft,denali", (void *)ssam_node_group_sp11 },
|
||||
/* Surface Laptop 7 */
|
||||
{ .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
|
||||
{ .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/acpi.h>
|
||||
|
|
@ -189,7 +190,6 @@ static int surface_button_add(struct acpi_device *device)
|
|||
struct surface_button *button;
|
||||
struct input_dev *input;
|
||||
const char *hid = acpi_device_hid(device);
|
||||
char *name;
|
||||
int error;
|
||||
|
||||
if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME,
|
||||
|
|
@ -210,11 +210,10 @@ static int surface_button_add(struct acpi_device *device)
|
|||
goto err_free_button;
|
||||
}
|
||||
|
||||
name = acpi_device_name(device);
|
||||
strcpy(name, SURFACE_BUTTON_DEVICE_NAME);
|
||||
strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME);
|
||||
snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid);
|
||||
|
||||
input->name = name;
|
||||
input->name = acpi_device_name(device);
|
||||
input->phys = button->phys;
|
||||
input->id.bustype = BUS_HOST;
|
||||
input->dev.parent = &device->dev;
|
||||
|
|
@ -228,8 +227,8 @@ static int surface_button_add(struct acpi_device *device)
|
|||
goto err_free_input;
|
||||
|
||||
device_init_wakeup(&device->dev, true);
|
||||
dev_info(&device->dev,
|
||||
"%s [%s]\n", name, acpi_device_bid(device));
|
||||
dev_info(&device->dev, "%s [%s]\n", acpi_device_name(device),
|
||||
acpi_device_bid(device));
|
||||
return 0;
|
||||
|
||||
err_free_input:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
menuconfig ACPI_WMI
|
||||
tristate "ACPI-WMI support"
|
||||
depends on ACPI && X86
|
||||
select NLS
|
||||
help
|
||||
This option enables support for the ACPI-WMI driver core.
|
||||
|
||||
|
|
@ -31,4 +32,6 @@ config ACPI_WMI_LEGACY_DEVICE_NAMES
|
|||
userspace applications but will cause the registration of WMI devices with
|
||||
the same GUID to fail in some corner cases.
|
||||
|
||||
source "drivers/platform/wmi/tests/Kconfig"
|
||||
|
||||
endif # ACPI_WMI
|
||||
|
|
|
|||
|
|
@ -4,5 +4,8 @@
|
|||
# ACPI WMI core
|
||||
#
|
||||
|
||||
wmi-y := core.o
|
||||
wmi-y := core.o marshalling.o string.o
|
||||
obj-$(CONFIG_ACPI_WMI) += wmi.o
|
||||
|
||||
# Unit tests
|
||||
obj-y += tests/
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include <linux/idr.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rwsem.h>
|
||||
|
|
@ -33,6 +34,8 @@
|
|||
#include <linux/wmi.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
MODULE_AUTHOR("Carlos Corbacho");
|
||||
MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -302,7 +305,7 @@ acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method
|
|||
EXPORT_SYMBOL_GPL(wmi_evaluate_method);
|
||||
|
||||
/**
|
||||
* wmidev_evaluate_method - Evaluate a WMI method
|
||||
* wmidev_evaluate_method - Evaluate a WMI method (deprecated)
|
||||
* @wdev: A wmi bus device from a driver
|
||||
* @instance: Instance index
|
||||
* @method_id: Method ID to call
|
||||
|
|
@ -360,6 +363,70 @@ acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 met
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(wmidev_evaluate_method);
|
||||
|
||||
/**
|
||||
* wmidev_invoke_method - Invoke a WMI method
|
||||
* @wdev: A wmi bus device from a driver
|
||||
* @instance: Instance index
|
||||
* @method_id: Method ID to call
|
||||
* @in: Mandatory WMI buffer containing input for the method call
|
||||
* @out: Optional WMI buffer to return the method results
|
||||
*
|
||||
* Invoke a WMI method, the caller must free the resulting data inside @out.
|
||||
* Said data is guaranteed to be aligned on a 8-byte boundary.
|
||||
*
|
||||
* Return: 0 on success or negative error code on failure.
|
||||
*/
|
||||
int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id,
|
||||
const struct wmi_buffer *in, struct wmi_buffer *out)
|
||||
{
|
||||
struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
|
||||
struct acpi_buffer aout = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
struct acpi_buffer ain;
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
if (wblock->gblock.flags & ACPI_WMI_STRING) {
|
||||
ret = wmi_marshal_string(in, &ain);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
if (in->length > U32_MAX)
|
||||
return -E2BIG;
|
||||
|
||||
ain.length = in->length;
|
||||
ain.pointer = in->data;
|
||||
}
|
||||
|
||||
if (out)
|
||||
status = wmidev_evaluate_method(wdev, instance, method_id, &ain, &aout);
|
||||
else
|
||||
status = wmidev_evaluate_method(wdev, instance, method_id, &ain, NULL);
|
||||
|
||||
if (wblock->gblock.flags & ACPI_WMI_STRING)
|
||||
kfree(ain.pointer);
|
||||
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
if (!out)
|
||||
return 0;
|
||||
|
||||
obj = aout.pointer;
|
||||
if (!obj) {
|
||||
out->length = 0;
|
||||
out->data = ZERO_SIZE_PTR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = wmi_unmarshal_acpi_object(obj, out);
|
||||
kfree(obj);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wmidev_invoke_method);
|
||||
|
||||
static acpi_status __query_block(struct wmi_block *wblock, u8 instance,
|
||||
struct acpi_buffer *out)
|
||||
{
|
||||
|
|
@ -432,7 +499,7 @@ acpi_status wmi_query_block(const char *guid_string, u8 instance,
|
|||
EXPORT_SYMBOL_GPL(wmi_query_block);
|
||||
|
||||
/**
|
||||
* wmidev_block_query - Return contents of a WMI block
|
||||
* wmidev_block_query - Return contents of a WMI block (deprectated)
|
||||
* @wdev: A wmi bus device from a driver
|
||||
* @instance: Instance index
|
||||
*
|
||||
|
|
@ -452,6 +519,33 @@ union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(wmidev_block_query);
|
||||
|
||||
/**
|
||||
* wmidev_query_block - Return contents of a WMI data block
|
||||
* @wdev: A wmi bus device from a driver
|
||||
* @instance: Instance index
|
||||
* @out: WMI buffer to fill
|
||||
*
|
||||
* Query a WMI data block, the caller must free the resulting data inside @out.
|
||||
* Said data is guaranteed to be aligned on a 8-byte boundary.
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out)
|
||||
{
|
||||
union acpi_object *obj;
|
||||
int ret;
|
||||
|
||||
obj = wmidev_block_query(wdev, instance);
|
||||
if (!obj)
|
||||
return -EIO;
|
||||
|
||||
ret = wmi_unmarshal_acpi_object(obj, out);
|
||||
kfree(obj);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wmidev_query_block);
|
||||
|
||||
/**
|
||||
* wmi_set_block - Write to a WMI block (deprecated)
|
||||
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
|
||||
|
|
@ -486,7 +580,7 @@ acpi_status wmi_set_block(const char *guid_string, u8 instance, const struct acp
|
|||
EXPORT_SYMBOL_GPL(wmi_set_block);
|
||||
|
||||
/**
|
||||
* wmidev_block_set - Write to a WMI block
|
||||
* wmidev_block_set - Write to a WMI block (deprecated)
|
||||
* @wdev: A wmi bus device from a driver
|
||||
* @instance: Instance index
|
||||
* @in: Buffer containing new values for the data block
|
||||
|
|
@ -535,6 +629,46 @@ acpi_status wmidev_block_set(struct wmi_device *wdev, u8 instance, const struct
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(wmidev_block_set);
|
||||
|
||||
/**
|
||||
* wmidev_set_block - Write to a WMI data block
|
||||
* @wdev: A wmi bus device from a driver
|
||||
* @instance: Instance index
|
||||
* @in: WMI buffer containing new values for the data block
|
||||
*
|
||||
* Write the content of @in into a WMI data block.
|
||||
*
|
||||
* Return: 0 on success or negative error code on failure.
|
||||
*/
|
||||
int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in)
|
||||
{
|
||||
struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
|
||||
struct acpi_buffer buffer;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
if (wblock->gblock.flags & ACPI_WMI_STRING) {
|
||||
ret = wmi_marshal_string(in, &buffer);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
if (in->length > U32_MAX)
|
||||
return -E2BIG;
|
||||
|
||||
buffer.length = in->length;
|
||||
buffer.pointer = in->data;
|
||||
}
|
||||
|
||||
status = wmidev_block_set(wdev, instance, &buffer);
|
||||
if (wblock->gblock.flags & ACPI_WMI_STRING)
|
||||
kfree(buffer.pointer);
|
||||
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wmidev_set_block);
|
||||
|
||||
/**
|
||||
* wmi_install_notify_handler - Register handler for WMI events (deprecated)
|
||||
* @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
|
||||
|
|
@ -862,7 +996,7 @@ static int wmi_dev_probe(struct device *dev)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (wdriver->notify) {
|
||||
if (wdriver->notify || wdriver->notify_new) {
|
||||
if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
|
@ -1221,6 +1355,8 @@ static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj
|
|||
static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj)
|
||||
{
|
||||
struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver);
|
||||
struct wmi_buffer buffer;
|
||||
int ret;
|
||||
|
||||
if (!obj && !driver->no_notify_data) {
|
||||
dev_warn(&wblock->dev.dev, "Event contains no event data\n");
|
||||
|
|
@ -1229,6 +1365,22 @@ static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj)
|
|||
|
||||
if (driver->notify)
|
||||
driver->notify(&wblock->dev, obj);
|
||||
|
||||
if (driver->notify_new) {
|
||||
if (!obj) {
|
||||
driver->notify_new(&wblock->dev, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = wmi_unmarshal_acpi_object(obj, &buffer);
|
||||
if (ret < 0) {
|
||||
dev_warn(&wblock->dev.dev, "Failed to unmarshal event data: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
driver->notify_new(&wblock->dev, &buffer);
|
||||
kfree(buffer.data);
|
||||
}
|
||||
}
|
||||
|
||||
static int wmi_notify_device(struct device *dev, void *data)
|
||||
|
|
|
|||
17
drivers/platform/wmi/internal.h
Normal file
17
drivers/platform/wmi/internal.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Internal interfaces used by the WMI core.
|
||||
*
|
||||
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
|
||||
*/
|
||||
|
||||
#ifndef _WMI_INTERNAL_H_
|
||||
#define _WMI_INTERNAL_H_
|
||||
|
||||
union acpi_object;
|
||||
struct wmi_buffer;
|
||||
|
||||
int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer);
|
||||
int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out);
|
||||
|
||||
#endif /* _WMI_INTERNAL_H_ */
|
||||
247
drivers/platform/wmi/marshalling.c
Normal file
247
drivers/platform/wmi/marshalling.c
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* ACPI-WMI buffer marshalling.
|
||||
*
|
||||
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/align.h>
|
||||
#include <linux/math.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/unaligned.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include <kunit/visibility.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
static int wmi_adjust_buffer_length(size_t *length, const union acpi_object *obj)
|
||||
{
|
||||
size_t alignment, size;
|
||||
|
||||
switch (obj->type) {
|
||||
case ACPI_TYPE_INTEGER:
|
||||
/*
|
||||
* Integers are threated as 32 bit even if the ACPI DSDT
|
||||
* declares 64 bit integer width.
|
||||
*/
|
||||
alignment = 4;
|
||||
size = sizeof(u32);
|
||||
break;
|
||||
case ACPI_TYPE_STRING:
|
||||
/*
|
||||
* Strings begin with a single little-endian 16-bit field containing
|
||||
* the string length in bytes and are encoded as UTF-16LE with a terminating
|
||||
* nul character.
|
||||
*/
|
||||
if (obj->string.length + 1 > U16_MAX / 2)
|
||||
return -EOVERFLOW;
|
||||
|
||||
alignment = 2;
|
||||
size = struct_size_t(struct wmi_string, chars, obj->string.length + 1);
|
||||
break;
|
||||
case ACPI_TYPE_BUFFER:
|
||||
/*
|
||||
* Buffers are copied as-is.
|
||||
*/
|
||||
alignment = 1;
|
||||
size = obj->buffer.length;
|
||||
break;
|
||||
default:
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
*length = size_add(ALIGN(*length, alignment), size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wmi_obj_get_buffer_length(const union acpi_object *obj, size_t *length)
|
||||
{
|
||||
size_t total = 0;
|
||||
int ret;
|
||||
|
||||
if (obj->type == ACPI_TYPE_PACKAGE) {
|
||||
for (int i = 0; i < obj->package.count; i++) {
|
||||
ret = wmi_adjust_buffer_length(&total, &obj->package.elements[i]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
ret = wmi_adjust_buffer_length(&total, obj);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
*length = total;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wmi_obj_transform_simple(const union acpi_object *obj, u8 *buffer, size_t *consumed)
|
||||
{
|
||||
struct wmi_string *string;
|
||||
size_t length;
|
||||
__le32 value;
|
||||
u8 *aligned;
|
||||
|
||||
switch (obj->type) {
|
||||
case ACPI_TYPE_INTEGER:
|
||||
aligned = PTR_ALIGN(buffer, 4);
|
||||
length = sizeof(value);
|
||||
|
||||
value = cpu_to_le32(obj->integer.value);
|
||||
memcpy(aligned, &value, length);
|
||||
break;
|
||||
case ACPI_TYPE_STRING:
|
||||
aligned = PTR_ALIGN(buffer, 2);
|
||||
string = (struct wmi_string *)aligned;
|
||||
length = struct_size(string, chars, obj->string.length + 1);
|
||||
|
||||
/* We do not have to worry about unaligned accesses here as the WMI
|
||||
* string will already be aligned on a two-byte boundary.
|
||||
*/
|
||||
string->length = cpu_to_le16((obj->string.length + 1) * 2);
|
||||
for (int i = 0; i < obj->string.length; i++)
|
||||
string->chars[i] = cpu_to_le16(obj->string.pointer[i]);
|
||||
|
||||
/*
|
||||
* The Windows WMI-ACPI driver always emits a terminating nul character,
|
||||
* so we emulate this behavior here as well.
|
||||
*/
|
||||
string->chars[obj->string.length] = '\0';
|
||||
break;
|
||||
case ACPI_TYPE_BUFFER:
|
||||
aligned = buffer;
|
||||
length = obj->buffer.length;
|
||||
|
||||
memcpy(aligned, obj->buffer.pointer, length);
|
||||
break;
|
||||
default:
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
*consumed = (aligned - buffer) + length;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wmi_obj_transform(const union acpi_object *obj, u8 *buffer)
|
||||
{
|
||||
size_t consumed;
|
||||
int ret;
|
||||
|
||||
if (obj->type == ACPI_TYPE_PACKAGE) {
|
||||
for (int i = 0; i < obj->package.count; i++) {
|
||||
ret = wmi_obj_transform_simple(&obj->package.elements[i], buffer,
|
||||
&consumed);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
buffer += consumed;
|
||||
}
|
||||
} else {
|
||||
ret = wmi_obj_transform_simple(obj, buffer, &consumed);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer)
|
||||
{
|
||||
size_t length, alloc_length;
|
||||
u8 *data;
|
||||
int ret;
|
||||
|
||||
ret = wmi_obj_get_buffer_length(obj, &length);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ARCH_KMALLOC_MINALIGN < 8) {
|
||||
/*
|
||||
* kmalloc() guarantees that the alignment of the resulting memory allocation is at
|
||||
* least the largest power-of-two divisor of the allocation size. The WMI buffer
|
||||
* data needs to be aligned on a 8 byte boundary to properly support 64-bit WMI
|
||||
* integers, so we have to round the allocation size to the next multiple of 8.
|
||||
*/
|
||||
alloc_length = round_up(length, 8);
|
||||
} else {
|
||||
alloc_length = length;
|
||||
}
|
||||
|
||||
data = kzalloc(alloc_length, GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = wmi_obj_transform(obj, data);
|
||||
if (ret < 0) {
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
buffer->length = length;
|
||||
buffer->data = data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(wmi_unmarshal_acpi_object);
|
||||
|
||||
int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out)
|
||||
{
|
||||
const struct wmi_string *string;
|
||||
u16 length, value;
|
||||
size_t chars;
|
||||
char *str;
|
||||
|
||||
if (buffer->length < sizeof(*string))
|
||||
return -ENODATA;
|
||||
|
||||
string = buffer->data;
|
||||
length = get_unaligned_le16(&string->length);
|
||||
if (buffer->length < sizeof(*string) + length)
|
||||
return -ENODATA;
|
||||
|
||||
/* Each character needs to be 16 bits long */
|
||||
if (length % 2)
|
||||
return -EINVAL;
|
||||
|
||||
chars = length / 2;
|
||||
str = kmalloc(chars + 1, GFP_KERNEL);
|
||||
if (!str)
|
||||
return -ENOMEM;
|
||||
|
||||
for (int i = 0; i < chars; i++) {
|
||||
value = get_unaligned_le16(&string->chars[i]);
|
||||
|
||||
/* ACPI only accepts ASCII strings */
|
||||
if (value > 0x7F) {
|
||||
kfree(str);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
str[i] = value & 0xFF;
|
||||
|
||||
/*
|
||||
* ACPI strings should only contain a single nul character at the end.
|
||||
* Because of this we must not copy any padding from the WMI string.
|
||||
*/
|
||||
if (!value) {
|
||||
/* ACPICA wants the length of the string without the nul character */
|
||||
out->length = i;
|
||||
out->pointer = str;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
str[chars] = '\0';
|
||||
|
||||
out->length = chars;
|
||||
out->pointer = str;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(wmi_marshal_string);
|
||||
92
drivers/platform/wmi/string.c
Normal file
92
drivers/platform/wmi/string.c
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* WMI string utility functions.
|
||||
*
|
||||
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
|
||||
*/
|
||||
|
||||
#include <linux/build_bug.h>
|
||||
#include <linux/compiler_types.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/nls.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
static_assert(sizeof(__le16) == sizeof(wchar_t));
|
||||
|
||||
/**
|
||||
* wmi_string_to_utf8s - Convert a WMI string into a UTF8 string.
|
||||
* @str: WMI string representation
|
||||
* @dst: Buffer to fill with UTF8 characters
|
||||
* @length: Length of the destination buffer
|
||||
*
|
||||
* Convert as WMI string into a standard UTF8 string. The conversion will stop
|
||||
* once a NUL character is detected or when the buffer is full. Any invalid UTF16
|
||||
* characters will be ignored. The resulting UTF8 string will always be NUL-terminated
|
||||
* when this function returns successfully.
|
||||
*
|
||||
* Return: Length of the resulting UTF8 string or negative errno code on failure.
|
||||
*/
|
||||
ssize_t wmi_string_to_utf8s(const struct wmi_string *str, u8 *dst, size_t length)
|
||||
{
|
||||
/* Contains the maximum number of UTF16 code points to read */
|
||||
int inlen = le16_to_cpu(str->length) / 2;
|
||||
int ret;
|
||||
|
||||
if (length < 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* We must leave room for the NUL character at the end of the destination buffer */
|
||||
ret = utf16s_to_utf8s((__force const wchar_t *)str->chars, inlen, UTF16_LITTLE_ENDIAN, dst,
|
||||
length - 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dst[ret] = '\0';
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wmi_string_to_utf8s);
|
||||
|
||||
/**
|
||||
* wmi_string_from_utf8s - Convert a UTF8 string into a WMI string.
|
||||
* @str: WMI string representation
|
||||
* @max_chars: Maximum number of UTF16 code points to store inside the WMI string
|
||||
* @src: UTF8 string to convert
|
||||
* @src_length: Length of the source string without any trailing NUL-characters
|
||||
*
|
||||
* Convert a UTF8 string into a WMI string. The conversion will stop when the WMI string is
|
||||
* full. The resulting WMI string will always be NUL-terminated and have its length field set
|
||||
* to and appropriate value when this function returns successfully.
|
||||
*
|
||||
* Return: Number of UTF16 code points inside the WMI string or negative errno code on failure.
|
||||
*/
|
||||
ssize_t wmi_string_from_utf8s(struct wmi_string *str, size_t max_chars, const u8 *src,
|
||||
size_t src_length)
|
||||
{
|
||||
size_t str_length;
|
||||
int ret;
|
||||
|
||||
if (max_chars < 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* We must leave room for the NUL character at the end of the WMI string */
|
||||
ret = utf8s_to_utf16s(src, src_length, UTF16_LITTLE_ENDIAN, (__force wchar_t *)str->chars,
|
||||
max_chars - 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
str_length = (ret + 1) * sizeof(u16);
|
||||
if (str_length > U16_MAX)
|
||||
return -EOVERFLOW;
|
||||
|
||||
str->length = cpu_to_le16(str_length);
|
||||
str->chars[ret] = '\0';
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wmi_string_from_utf8s);
|
||||
27
drivers/platform/wmi/tests/Kconfig
Normal file
27
drivers/platform/wmi/tests/Kconfig
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# ACPI WMI KUnit tests
|
||||
#
|
||||
|
||||
config ACPI_WMI_MARSHALLING_KUNIT_TEST
|
||||
tristate "KUnit Test for ACPI-WMI marshalling" if !KUNIT_ALL_TESTS
|
||||
depends on KUNIT
|
||||
default KUNIT_ALL_TESTS
|
||||
help
|
||||
This builds unit tests for the ACPI-WMI marshalling code.
|
||||
|
||||
For more information on KUnit and unit tests in general, please refer
|
||||
to the KUnit documentation in Documentation/dev-tools/kunit/.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config ACPI_WMI_STRING_KUNIT_TEST
|
||||
tristate "KUnit Test for ACPI-WMI string conversion" if !KUNIT_ALL_TESTS
|
||||
depends on KUNIT
|
||||
default KUNIT_ALL_TESTS
|
||||
help
|
||||
This builds unit tests for the ACPI-WMI string conversion code.
|
||||
For more information on KUnit and unit tests in general, please refer
|
||||
to the KUnit documentation in Documentation/dev-tools/kunit/.
|
||||
|
||||
If unsure, say N.
|
||||
11
drivers/platform/wmi/tests/Makefile
Normal file
11
drivers/platform/wmi/tests/Makefile
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# Makefile for linux/drivers/platform/x86/wmi/tests
|
||||
# ACPI WMI KUnit tests
|
||||
#
|
||||
|
||||
wmi_marshalling_kunit-y := marshalling_kunit.o
|
||||
obj-$(CONFIG_ACPI_WMI_MARSHALLING_KUNIT_TEST) += wmi_marshalling_kunit.o
|
||||
|
||||
wmi_string_kunit-y := string_kunit.o
|
||||
obj-$(CONFIG_ACPI_WMI_STRING_KUNIT_TEST) += wmi_string_kunit.o
|
||||
452
drivers/platform/wmi/tests/marshalling_kunit.c
Normal file
452
drivers/platform/wmi/tests/marshalling_kunit.c
Normal file
|
|
@ -0,0 +1,452 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* KUnit test for the ACPI-WMI marshalling code.
|
||||
*
|
||||
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/align.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include <kunit/resource.h>
|
||||
#include <kunit/test.h>
|
||||
|
||||
#include "../internal.h"
|
||||
|
||||
struct wmi_acpi_param {
|
||||
const char *name;
|
||||
const union acpi_object obj;
|
||||
const struct wmi_buffer buffer;
|
||||
};
|
||||
|
||||
struct wmi_string_param {
|
||||
const char *name;
|
||||
const char *string;
|
||||
const struct wmi_buffer buffer;
|
||||
};
|
||||
|
||||
struct wmi_invalid_acpi_param {
|
||||
const char *name;
|
||||
const union acpi_object obj;
|
||||
};
|
||||
|
||||
struct wmi_invalid_string_param {
|
||||
const char *name;
|
||||
const struct wmi_buffer buffer;
|
||||
};
|
||||
|
||||
/* 0xdeadbeef */
|
||||
static u8 expected_single_integer[] = {
|
||||
0xef, 0xbe, 0xad, 0xde,
|
||||
};
|
||||
|
||||
/* "TEST" */
|
||||
static u8 expected_single_string[] = {
|
||||
0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static u8 test_buffer[] = {
|
||||
0xab, 0xcd,
|
||||
};
|
||||
|
||||
static u8 expected_single_buffer[] = {
|
||||
0xab, 0xcd,
|
||||
};
|
||||
|
||||
static union acpi_object simple_package_elements[] = {
|
||||
{
|
||||
.buffer = {
|
||||
.type = ACPI_TYPE_BUFFER,
|
||||
.length = sizeof(test_buffer),
|
||||
.pointer = test_buffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = 0x01020304,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static u8 expected_simple_package[] = {
|
||||
0xab, 0xcd,
|
||||
0x00, 0x00,
|
||||
0x04, 0x03, 0x02, 0x01,
|
||||
};
|
||||
|
||||
static u8 test_small_buffer[] = {
|
||||
0xde,
|
||||
};
|
||||
|
||||
static union acpi_object complex_package_elements[] = {
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = 0xdeadbeef,
|
||||
},
|
||||
},
|
||||
{
|
||||
.buffer = {
|
||||
.type = ACPI_TYPE_BUFFER,
|
||||
.length = sizeof(test_small_buffer),
|
||||
.pointer = test_small_buffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
.string = {
|
||||
.type = ACPI_TYPE_STRING,
|
||||
.length = sizeof("TEST") - 1,
|
||||
.pointer = "TEST",
|
||||
},
|
||||
},
|
||||
{
|
||||
.buffer = {
|
||||
.type = ACPI_TYPE_BUFFER,
|
||||
.length = sizeof(test_small_buffer),
|
||||
.pointer = test_small_buffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = 0x01020304,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
static u8 expected_complex_package[] = {
|
||||
0xef, 0xbe, 0xad, 0xde,
|
||||
0xde,
|
||||
0x00,
|
||||
0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00,
|
||||
0xde,
|
||||
0x00,
|
||||
0x04, 0x03, 0x02, 0x01,
|
||||
};
|
||||
|
||||
static const struct wmi_acpi_param wmi_acpi_params_array[] = {
|
||||
{
|
||||
.name = "single_integer",
|
||||
.obj = {
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = 0xdeadbeef,
|
||||
},
|
||||
},
|
||||
.buffer = {
|
||||
.data = expected_single_integer,
|
||||
.length = sizeof(expected_single_integer),
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "single_string",
|
||||
.obj = {
|
||||
.string = {
|
||||
.type = ACPI_TYPE_STRING,
|
||||
.length = sizeof("TEST") - 1,
|
||||
.pointer = "TEST",
|
||||
},
|
||||
},
|
||||
.buffer = {
|
||||
.data = expected_single_string,
|
||||
.length = sizeof(expected_single_string),
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "single_buffer",
|
||||
.obj = {
|
||||
.buffer = {
|
||||
.type = ACPI_TYPE_BUFFER,
|
||||
.length = sizeof(test_buffer),
|
||||
.pointer = test_buffer,
|
||||
},
|
||||
},
|
||||
.buffer = {
|
||||
.data = expected_single_buffer,
|
||||
.length = sizeof(expected_single_buffer),
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "simple_package",
|
||||
.obj = {
|
||||
.package = {
|
||||
.type = ACPI_TYPE_PACKAGE,
|
||||
.count = ARRAY_SIZE(simple_package_elements),
|
||||
.elements = simple_package_elements,
|
||||
},
|
||||
},
|
||||
.buffer = {
|
||||
.data = expected_simple_package,
|
||||
.length = sizeof(expected_simple_package),
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "complex_package",
|
||||
.obj = {
|
||||
.package = {
|
||||
.type = ACPI_TYPE_PACKAGE,
|
||||
.count = ARRAY_SIZE(complex_package_elements),
|
||||
.elements = complex_package_elements,
|
||||
},
|
||||
},
|
||||
.buffer = {
|
||||
.data = expected_complex_package,
|
||||
.length = sizeof(expected_complex_package),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static void wmi_acpi_param_get_desc(const struct wmi_acpi_param *param, char *desc)
|
||||
{
|
||||
strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object, wmi_acpi_params_array, wmi_acpi_param_get_desc);
|
||||
|
||||
/* "WMI\0" */
|
||||
static u8 padded_wmi_string[] = {
|
||||
0x0a, 0x00,
|
||||
0x57, 0x00,
|
||||
0x4D, 0x00,
|
||||
0x49, 0x00,
|
||||
0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
};
|
||||
|
||||
static const struct wmi_string_param wmi_string_params_array[] = {
|
||||
{
|
||||
.name = "test",
|
||||
.string = "TEST",
|
||||
.buffer = {
|
||||
.length = sizeof(expected_single_string),
|
||||
.data = expected_single_string,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "padded",
|
||||
.string = "WMI",
|
||||
.buffer = {
|
||||
.length = sizeof(padded_wmi_string),
|
||||
.data = padded_wmi_string,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc)
|
||||
{
|
||||
strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(wmi_marshal_string, wmi_string_params_array, wmi_string_param_get_desc);
|
||||
|
||||
static union acpi_object nested_package_elements[] = {
|
||||
{
|
||||
.package = {
|
||||
.type = ACPI_TYPE_PACKAGE,
|
||||
.count = ARRAY_SIZE(simple_package_elements),
|
||||
.elements = simple_package_elements,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
static const struct wmi_invalid_acpi_param wmi_invalid_acpi_params_array[] = {
|
||||
{
|
||||
.name = "nested_package",
|
||||
.obj = {
|
||||
.package = {
|
||||
.type = ACPI_TYPE_PACKAGE,
|
||||
.count = ARRAY_SIZE(nested_package_elements),
|
||||
.elements = nested_package_elements,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "reference",
|
||||
.obj = {
|
||||
.reference = {
|
||||
.type = ACPI_TYPE_LOCAL_REFERENCE,
|
||||
.actual_type = ACPI_TYPE_ANY,
|
||||
.handle = NULL,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "processor",
|
||||
.obj = {
|
||||
.processor = {
|
||||
.type = ACPI_TYPE_PROCESSOR,
|
||||
.proc_id = 0,
|
||||
.pblk_address = 0,
|
||||
.pblk_length = 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "power_resource",
|
||||
.obj = {
|
||||
.power_resource = {
|
||||
.type = ACPI_TYPE_POWER,
|
||||
.system_level = 0,
|
||||
.resource_order = 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static void wmi_invalid_acpi_param_get_desc(const struct wmi_invalid_acpi_param *param, char *desc)
|
||||
{
|
||||
strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object_failure, wmi_invalid_acpi_params_array,
|
||||
wmi_invalid_acpi_param_get_desc);
|
||||
|
||||
static u8 oversized_wmi_string[] = {
|
||||
0x04, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
/*
|
||||
* The error is that 3 bytes can not hold UTF-16 characters
|
||||
* without cutting of the last one.
|
||||
*/
|
||||
static u8 undersized_wmi_string[] = {
|
||||
0x03, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static u8 non_ascii_wmi_string[] = {
|
||||
0x04, 0x00, 0xC4, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const struct wmi_invalid_string_param wmi_invalid_string_params_array[] = {
|
||||
{
|
||||
.name = "empty_buffer",
|
||||
.buffer = {
|
||||
.length = 0,
|
||||
.data = ZERO_SIZE_PTR,
|
||||
},
|
||||
|
||||
},
|
||||
{
|
||||
.name = "oversized",
|
||||
.buffer = {
|
||||
.length = sizeof(oversized_wmi_string),
|
||||
.data = oversized_wmi_string,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "undersized",
|
||||
.buffer = {
|
||||
.length = sizeof(undersized_wmi_string),
|
||||
.data = undersized_wmi_string,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "non_ascii",
|
||||
.buffer = {
|
||||
.length = sizeof(non_ascii_wmi_string),
|
||||
.data = non_ascii_wmi_string,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static void wmi_invalid_string_param_get_desc(const struct wmi_invalid_string_param *param,
|
||||
char *desc)
|
||||
{
|
||||
strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(wmi_marshal_string_failure, wmi_invalid_string_params_array,
|
||||
wmi_invalid_string_param_get_desc);
|
||||
|
||||
KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *);
|
||||
|
||||
static void wmi_unmarshal_acpi_object_test(struct kunit *test)
|
||||
{
|
||||
const struct wmi_acpi_param *param = test->param_value;
|
||||
struct wmi_buffer result;
|
||||
int ret;
|
||||
|
||||
ret = wmi_unmarshal_acpi_object(¶m->obj, &result);
|
||||
if (ret < 0)
|
||||
KUNIT_FAIL_AND_ABORT(test, "Unmarshalling of ACPI object failed\n");
|
||||
|
||||
kunit_add_action(test, kfree_wrapper, result.data);
|
||||
|
||||
KUNIT_EXPECT_TRUE(test, IS_ALIGNED((uintptr_t)result.data, 8));
|
||||
KUNIT_EXPECT_EQ(test, result.length, param->buffer.length);
|
||||
KUNIT_EXPECT_MEMEQ(test, result.data, param->buffer.data, result.length);
|
||||
}
|
||||
|
||||
static void wmi_unmarshal_acpi_object_failure_test(struct kunit *test)
|
||||
{
|
||||
const struct wmi_invalid_acpi_param *param = test->param_value;
|
||||
struct wmi_buffer result;
|
||||
int ret;
|
||||
|
||||
ret = wmi_unmarshal_acpi_object(¶m->obj, &result);
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
kfree(result.data);
|
||||
KUNIT_FAIL(test, "Invalid ACPI object was not rejected\n");
|
||||
}
|
||||
|
||||
static void wmi_marshal_string_test(struct kunit *test)
|
||||
{
|
||||
const struct wmi_string_param *param = test->param_value;
|
||||
struct acpi_buffer result;
|
||||
int ret;
|
||||
|
||||
ret = wmi_marshal_string(¶m->buffer, &result);
|
||||
if (ret < 0)
|
||||
KUNIT_FAIL_AND_ABORT(test, "Marshalling of WMI string failed\n");
|
||||
|
||||
kunit_add_action(test, kfree_wrapper, result.pointer);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, result.length, strlen(param->string));
|
||||
KUNIT_EXPECT_STREQ(test, result.pointer, param->string);
|
||||
}
|
||||
|
||||
static void wmi_marshal_string_failure_test(struct kunit *test)
|
||||
{
|
||||
const struct wmi_invalid_string_param *param = test->param_value;
|
||||
struct acpi_buffer result;
|
||||
int ret;
|
||||
|
||||
ret = wmi_marshal_string(¶m->buffer, &result);
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
kfree(result.pointer);
|
||||
KUNIT_FAIL(test, "Invalid string was not rejected\n");
|
||||
}
|
||||
|
||||
static struct kunit_case wmi_marshalling_test_cases[] = {
|
||||
KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_test,
|
||||
wmi_unmarshal_acpi_object_gen_params),
|
||||
KUNIT_CASE_PARAM(wmi_marshal_string_test,
|
||||
wmi_marshal_string_gen_params),
|
||||
KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_failure_test,
|
||||
wmi_unmarshal_acpi_object_failure_gen_params),
|
||||
KUNIT_CASE_PARAM(wmi_marshal_string_failure_test,
|
||||
wmi_marshal_string_failure_gen_params),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct kunit_suite wmi_marshalling_test_suite = {
|
||||
.name = "wmi_marshalling",
|
||||
.test_cases = wmi_marshalling_test_cases,
|
||||
};
|
||||
|
||||
kunit_test_suite(wmi_marshalling_test_suite);
|
||||
|
||||
MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
|
||||
MODULE_DESCRIPTION("KUnit test for the ACPI-WMI marshalling code");
|
||||
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
|
||||
MODULE_LICENSE("GPL");
|
||||
296
drivers/platform/wmi/tests/string_kunit.c
Normal file
296
drivers/platform/wmi/tests/string_kunit.c
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* KUnit test for the ACPI-WMI string conversion code.
|
||||
*
|
||||
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include <kunit/resource.h>
|
||||
#include <kunit/test.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
struct wmi_string_param {
|
||||
const char *name;
|
||||
const struct wmi_string *wmi_string;
|
||||
/*
|
||||
* Remember that using sizeof() on a struct wmi_string will
|
||||
* always return a size of two bytes due to the flexible
|
||||
* array member!
|
||||
*/
|
||||
size_t wmi_string_length;
|
||||
const u8 *utf8_string;
|
||||
size_t utf8_string_length;
|
||||
};
|
||||
|
||||
#define TEST_WMI_STRING_LENGTH 12
|
||||
|
||||
static const struct wmi_string test_wmi_string = {
|
||||
.length = cpu_to_le16(10),
|
||||
.chars = {
|
||||
cpu_to_le16(u'T'),
|
||||
cpu_to_le16(u'E'),
|
||||
cpu_to_le16(u'S'),
|
||||
cpu_to_le16(u'T'),
|
||||
cpu_to_le16(u'\0'),
|
||||
},
|
||||
};
|
||||
|
||||
static const u8 test_utf8_string[] = "TEST";
|
||||
|
||||
#define SPECIAL_WMI_STRING_LENGTH 14
|
||||
|
||||
static const struct wmi_string special_wmi_string = {
|
||||
.length = cpu_to_le16(12),
|
||||
.chars = {
|
||||
cpu_to_le16(u'Ä'),
|
||||
cpu_to_le16(u'Ö'),
|
||||
cpu_to_le16(u'Ü'),
|
||||
cpu_to_le16(u'ß'),
|
||||
cpu_to_le16(u'€'),
|
||||
cpu_to_le16(u'\0'),
|
||||
},
|
||||
};
|
||||
|
||||
static const u8 special_utf8_string[] = "ÄÖÜ߀";
|
||||
|
||||
#define MULTI_POINT_WMI_STRING_LENGTH 12
|
||||
|
||||
static const struct wmi_string multi_point_wmi_string = {
|
||||
.length = cpu_to_le16(10),
|
||||
.chars = {
|
||||
cpu_to_le16(u'K'),
|
||||
/* 🐧 */
|
||||
cpu_to_le16(0xD83D),
|
||||
cpu_to_le16(0xDC27),
|
||||
cpu_to_le16(u'!'),
|
||||
cpu_to_le16(u'\0'),
|
||||
},
|
||||
};
|
||||
|
||||
static const u8 multi_point_utf8_string[] = "K🐧!";
|
||||
|
||||
#define PADDED_TEST_WMI_STRING_LENGTH 14
|
||||
|
||||
static const struct wmi_string padded_test_wmi_string = {
|
||||
.length = cpu_to_le16(12),
|
||||
.chars = {
|
||||
cpu_to_le16(u'T'),
|
||||
cpu_to_le16(u'E'),
|
||||
cpu_to_le16(u'S'),
|
||||
cpu_to_le16(u'T'),
|
||||
cpu_to_le16(u'\0'),
|
||||
cpu_to_le16(u'\0'),
|
||||
},
|
||||
};
|
||||
|
||||
static const u8 padded_test_utf8_string[] = "TEST\0";
|
||||
|
||||
#define OVERSIZED_TEST_WMI_STRING_LENGTH 14
|
||||
|
||||
static const struct wmi_string oversized_test_wmi_string = {
|
||||
.length = cpu_to_le16(8),
|
||||
.chars = {
|
||||
cpu_to_le16(u'T'),
|
||||
cpu_to_le16(u'E'),
|
||||
cpu_to_le16(u'S'),
|
||||
cpu_to_le16(u'T'),
|
||||
cpu_to_le16(u'!'),
|
||||
cpu_to_le16(u'\0'),
|
||||
},
|
||||
};
|
||||
|
||||
static const u8 oversized_test_utf8_string[] = "TEST!";
|
||||
|
||||
#define INVALID_TEST_WMI_STRING_LENGTH 14
|
||||
|
||||
static const struct wmi_string invalid_test_wmi_string = {
|
||||
.length = cpu_to_le16(12),
|
||||
.chars = {
|
||||
cpu_to_le16(u'T'),
|
||||
/* 🐧, with low surrogate missing */
|
||||
cpu_to_le16(0xD83D),
|
||||
cpu_to_le16(u'E'),
|
||||
cpu_to_le16(u'S'),
|
||||
cpu_to_le16(u'T'),
|
||||
cpu_to_le16(u'\0'),
|
||||
},
|
||||
};
|
||||
|
||||
/* We have to split the string here to end the hex escape sequence */
|
||||
static const u8 invalid_test_utf8_string[] = "T" "\xF0\x9F" "EST";
|
||||
|
||||
static const struct wmi_string_param wmi_string_params_array[] = {
|
||||
{
|
||||
.name = "ascii_string",
|
||||
.wmi_string = &test_wmi_string,
|
||||
.wmi_string_length = TEST_WMI_STRING_LENGTH,
|
||||
.utf8_string = test_utf8_string,
|
||||
.utf8_string_length = sizeof(test_utf8_string),
|
||||
},
|
||||
{
|
||||
.name = "special_string",
|
||||
.wmi_string = &special_wmi_string,
|
||||
.wmi_string_length = SPECIAL_WMI_STRING_LENGTH,
|
||||
.utf8_string = special_utf8_string,
|
||||
.utf8_string_length = sizeof(special_utf8_string),
|
||||
},
|
||||
{
|
||||
.name = "multi_point_string",
|
||||
.wmi_string = &multi_point_wmi_string,
|
||||
.wmi_string_length = MULTI_POINT_WMI_STRING_LENGTH,
|
||||
.utf8_string = multi_point_utf8_string,
|
||||
.utf8_string_length = sizeof(multi_point_utf8_string),
|
||||
},
|
||||
};
|
||||
|
||||
static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc)
|
||||
{
|
||||
strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(wmi_string, wmi_string_params_array, wmi_string_param_get_desc);
|
||||
|
||||
static void wmi_string_to_utf8s_test(struct kunit *test)
|
||||
{
|
||||
const struct wmi_string_param *param = test->param_value;
|
||||
ssize_t ret;
|
||||
u8 *result;
|
||||
|
||||
result = kunit_kzalloc(test, param->utf8_string_length, GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
|
||||
|
||||
ret = wmi_string_to_utf8s(param->wmi_string, result, param->utf8_string_length);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, ret, param->utf8_string_length - 1);
|
||||
KUNIT_EXPECT_MEMEQ(test, result, param->utf8_string, param->utf8_string_length);
|
||||
}
|
||||
|
||||
static void wmi_string_from_utf8s_test(struct kunit *test)
|
||||
{
|
||||
const struct wmi_string_param *param = test->param_value;
|
||||
struct wmi_string *result;
|
||||
size_t max_chars;
|
||||
ssize_t ret;
|
||||
|
||||
max_chars = (param->wmi_string_length - sizeof(*result)) / 2;
|
||||
result = kunit_kzalloc(test, param->wmi_string_length, GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
|
||||
|
||||
ret = wmi_string_from_utf8s(result, max_chars, param->utf8_string,
|
||||
param->utf8_string_length);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, ret, max_chars - 1);
|
||||
KUNIT_EXPECT_MEMEQ(test, result, param->wmi_string, param->wmi_string_length);
|
||||
}
|
||||
|
||||
static void wmi_string_to_utf8s_padded_test(struct kunit *test)
|
||||
{
|
||||
u8 result[sizeof(padded_test_utf8_string)];
|
||||
ssize_t ret;
|
||||
|
||||
ret = wmi_string_to_utf8s(&padded_test_wmi_string, result, sizeof(result));
|
||||
|
||||
KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
|
||||
KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
|
||||
}
|
||||
|
||||
static void wmi_string_from_utf8s_padded_test(struct kunit *test)
|
||||
{
|
||||
struct wmi_string *result;
|
||||
size_t max_chars;
|
||||
ssize_t ret;
|
||||
|
||||
max_chars = (PADDED_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
|
||||
result = kunit_kzalloc(test, PADDED_TEST_WMI_STRING_LENGTH, GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
|
||||
|
||||
ret = wmi_string_from_utf8s(result, max_chars, padded_test_utf8_string,
|
||||
sizeof(padded_test_utf8_string));
|
||||
|
||||
KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
|
||||
KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string));
|
||||
}
|
||||
|
||||
static void wmi_string_to_utf8s_oversized_test(struct kunit *test)
|
||||
{
|
||||
u8 result[sizeof(oversized_test_utf8_string)];
|
||||
ssize_t ret;
|
||||
|
||||
ret = wmi_string_to_utf8s(&oversized_test_wmi_string, result, sizeof(result));
|
||||
|
||||
KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
|
||||
KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
|
||||
}
|
||||
|
||||
static void wmi_string_from_utf8s_oversized_test(struct kunit *test)
|
||||
{
|
||||
struct wmi_string *result;
|
||||
size_t max_chars;
|
||||
ssize_t ret;
|
||||
|
||||
max_chars = (TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
|
||||
result = kunit_kzalloc(test, TEST_WMI_STRING_LENGTH, GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
|
||||
|
||||
ret = wmi_string_from_utf8s(result, max_chars, oversized_test_utf8_string,
|
||||
sizeof(oversized_test_utf8_string));
|
||||
|
||||
KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
|
||||
KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string));
|
||||
}
|
||||
|
||||
static void wmi_string_to_utf8s_invalid_test(struct kunit *test)
|
||||
{
|
||||
u8 result[sizeof(invalid_test_utf8_string)];
|
||||
ssize_t ret;
|
||||
|
||||
ret = wmi_string_to_utf8s(&invalid_test_wmi_string, result, sizeof(result));
|
||||
|
||||
KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
|
||||
KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
|
||||
}
|
||||
|
||||
static void wmi_string_from_utf8s_invalid_test(struct kunit *test)
|
||||
{
|
||||
struct wmi_string *result;
|
||||
size_t max_chars;
|
||||
ssize_t ret;
|
||||
|
||||
max_chars = (INVALID_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
|
||||
result = kunit_kzalloc(test, INVALID_TEST_WMI_STRING_LENGTH, GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
|
||||
|
||||
ret = wmi_string_from_utf8s(result, max_chars, invalid_test_utf8_string,
|
||||
sizeof(invalid_test_utf8_string));
|
||||
|
||||
KUNIT_EXPECT_EQ(test, ret, -EINVAL);
|
||||
}
|
||||
|
||||
static struct kunit_case wmi_string_test_cases[] = {
|
||||
KUNIT_CASE_PARAM(wmi_string_to_utf8s_test, wmi_string_gen_params),
|
||||
KUNIT_CASE_PARAM(wmi_string_from_utf8s_test, wmi_string_gen_params),
|
||||
KUNIT_CASE(wmi_string_to_utf8s_padded_test),
|
||||
KUNIT_CASE(wmi_string_from_utf8s_padded_test),
|
||||
KUNIT_CASE(wmi_string_to_utf8s_oversized_test),
|
||||
KUNIT_CASE(wmi_string_from_utf8s_oversized_test),
|
||||
KUNIT_CASE(wmi_string_to_utf8s_invalid_test),
|
||||
KUNIT_CASE(wmi_string_from_utf8s_invalid_test),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct kunit_suite wmi_string_test_suite = {
|
||||
.name = "wmi_string",
|
||||
.test_cases = wmi_string_test_cases,
|
||||
};
|
||||
|
||||
kunit_test_suite(wmi_string_test_suite);
|
||||
|
||||
MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
|
||||
MODULE_DESCRIPTION("KUnit test for the ACPI-WMI string conversion code");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -9,6 +9,9 @@
|
|||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include "pmf.h"
|
||||
|
||||
#define APMF_CQL_NOTIFICATION 2
|
||||
|
|
@ -331,6 +334,39 @@ int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req
|
|||
req, sizeof(*req));
|
||||
}
|
||||
|
||||
/* Store custom BIOS inputs data in ring buffer */
|
||||
static void amd_pmf_custom_bios_inputs_rb(struct amd_pmf_dev *pmf_dev)
|
||||
{
|
||||
struct pmf_cbi_ring_buffer *rb = &pmf_dev->cbi_buf;
|
||||
int i;
|
||||
|
||||
guard(mutex)(&pmf_dev->cbi_mutex);
|
||||
|
||||
switch (pmf_dev->cpu_id) {
|
||||
case AMD_CPU_ID_PS:
|
||||
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs_v1); i++)
|
||||
rb->data[rb->head].val[i] = pmf_dev->req1.custom_policy[i];
|
||||
rb->data[rb->head].preq = pmf_dev->req1.pending_req;
|
||||
break;
|
||||
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
|
||||
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
|
||||
rb->data[rb->head].val[i] = pmf_dev->req.custom_policy[i];
|
||||
rb->data[rb->head].preq = pmf_dev->req.pending_req;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (CIRC_SPACE(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0) {
|
||||
/* Rare case: ensures the newest BIOS input value is kept */
|
||||
dev_warn(pmf_dev->dev, "Overwriting BIOS input value, data may be lost\n");
|
||||
rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
|
||||
}
|
||||
|
||||
rb->head = (rb->head + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
|
||||
}
|
||||
|
||||
static void amd_pmf_handle_early_preq(struct amd_pmf_dev *pdev)
|
||||
{
|
||||
if (!pdev->cb_flag)
|
||||
|
|
@ -356,6 +392,8 @@ static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data)
|
|||
dev_dbg(pmf_dev->dev, "Pending request (preq): 0x%x\n", pmf_dev->req.pending_req);
|
||||
|
||||
amd_pmf_handle_early_preq(pmf_dev);
|
||||
|
||||
amd_pmf_custom_bios_inputs_rb(pmf_dev);
|
||||
}
|
||||
|
||||
static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data)
|
||||
|
|
@ -374,6 +412,8 @@ static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data)
|
|||
dev_dbg(pmf_dev->dev, "Pending request (preq1): 0x%x\n", pmf_dev->req1.pending_req);
|
||||
|
||||
amd_pmf_handle_early_preq(pmf_dev);
|
||||
|
||||
amd_pmf_custom_bios_inputs_rb(pmf_dev);
|
||||
}
|
||||
|
||||
static void apmf_event_handler(acpi_handle handle, u32 event, void *data)
|
||||
|
|
|
|||
|
|
@ -8,12 +8,16 @@
|
|||
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/string.h>
|
||||
#include <asm/amd/node.h>
|
||||
#include "pmf.h"
|
||||
|
||||
|
|
@ -53,6 +57,12 @@ static bool force_load;
|
|||
module_param(force_load, bool, 0444);
|
||||
MODULE_PARM_DESC(force_load, "Force load this driver on supported older platforms (experimental)");
|
||||
|
||||
static bool smart_pc_support = true;
|
||||
module_param(smart_pc_support, bool, 0444);
|
||||
MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)");
|
||||
|
||||
static struct device *pmf_device;
|
||||
|
||||
static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data)
|
||||
{
|
||||
struct amd_pmf_dev *pmf = container_of(nb, struct amd_pmf_dev, pwr_src_notifier);
|
||||
|
|
@ -314,6 +324,126 @@ int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
|
||||
{
|
||||
switch (pdev->cpu_id) {
|
||||
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
guard(mutex)(&dev->metrics_mutex);
|
||||
|
||||
ret = is_npu_metrics_supported(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = amd_pmf_set_dram_addr(dev, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memset(dev->buf, 0, dev->mtable_size);
|
||||
|
||||
/* Send SMU command to get NPU metrics */
|
||||
ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
|
||||
|
||||
data->npuclk_freq = dev->m_table_v2.npuclk_freq;
|
||||
for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
|
||||
data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
|
||||
data->npu_power = dev->m_table_v2.npu_power;
|
||||
data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
|
||||
data->npu_reads = dev->m_table_v2.npu_reads;
|
||||
data->npu_writes = dev->m_table_v2.npu_writes;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
|
||||
{
|
||||
struct amd_pmf_dev *pdev;
|
||||
|
||||
if (!info)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pmf_device)
|
||||
return -ENODEV;
|
||||
|
||||
pdev = dev_get_drvdata(pmf_device);
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
|
||||
return amd_pmf_get_smu_metrics(pdev, info);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
|
||||
|
||||
static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev)
|
||||
{
|
||||
bool status;
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(amd_pmf_ta_uuid); i++) {
|
||||
ret = amd_pmf_tee_init(pdev, &amd_pmf_ta_uuid[i]);
|
||||
if (ret) {
|
||||
dev_err(pdev->dev, "TEE init failed for UUID[%d] ret: %d\n", i, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = amd_pmf_start_policy_engine(pdev);
|
||||
dev_dbg(pdev->dev, "start policy engine ret: %d (UUID idx: %d)\n", ret, i);
|
||||
status = ret == TA_PMF_TYPE_SUCCESS;
|
||||
if (status)
|
||||
break;
|
||||
amd_pmf_tee_deinit(pdev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_pmf_restore_handler(struct device *dev)
|
||||
{
|
||||
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
if (pdev->buf) {
|
||||
ret = amd_pmf_set_dram_addr(pdev, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (pdev->smart_pc_enabled)
|
||||
amd_pmf_reinit_ta(pdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_pmf_freeze_handler(struct device *dev)
|
||||
{
|
||||
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
|
||||
|
||||
if (!pdev->smart_pc_enabled)
|
||||
return 0;
|
||||
|
||||
cancel_delayed_work_sync(&pdev->pb_work);
|
||||
/* Clear all TEE resources */
|
||||
amd_pmf_tee_deinit(pdev);
|
||||
pdev->session_id = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_pmf_suspend_handler(struct device *dev)
|
||||
{
|
||||
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
|
||||
|
|
@ -347,7 +477,12 @@ static int amd_pmf_resume_handler(struct device *dev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(amd_pmf_pm, amd_pmf_suspend_handler, amd_pmf_resume_handler);
|
||||
static const struct dev_pm_ops amd_pmf_pm = {
|
||||
.suspend = amd_pmf_suspend_handler,
|
||||
.resume = amd_pmf_resume_handler,
|
||||
.freeze = amd_pmf_freeze_handler,
|
||||
.restore = amd_pmf_restore_handler,
|
||||
};
|
||||
|
||||
static void amd_pmf_init_features(struct amd_pmf_dev *dev)
|
||||
{
|
||||
|
|
@ -362,11 +497,15 @@ static void amd_pmf_init_features(struct amd_pmf_dev *dev)
|
|||
dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n");
|
||||
}
|
||||
|
||||
amd_pmf_init_smart_pc(dev);
|
||||
if (dev->smart_pc_enabled) {
|
||||
dev_dbg(dev->dev, "Smart PC Solution Enabled\n");
|
||||
/* If Smart PC is enabled, no need to check for other features */
|
||||
return;
|
||||
if (smart_pc_support) {
|
||||
amd_pmf_init_smart_pc(dev);
|
||||
if (dev->smart_pc_enabled) {
|
||||
dev_dbg(dev->dev, "Smart PC Solution Enabled\n");
|
||||
/* If Smart PC is enabled, no need to check for other features */
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
dev->smart_pc_enabled = false;
|
||||
}
|
||||
|
||||
if (is_apmf_func_supported(dev, APMF_FUNC_AUTO_MODE)) {
|
||||
|
|
@ -477,6 +616,14 @@ static int amd_pmf_probe(struct platform_device *pdev)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
err = devm_mutex_init(dev->dev, &dev->cbi_mutex);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = devm_mutex_init(dev->dev, &dev->metrics_mutex);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
apmf_acpi_init(dev);
|
||||
platform_set_drvdata(pdev, dev);
|
||||
amd_pmf_dbgfs_register(dev);
|
||||
|
|
@ -485,6 +632,8 @@ static int amd_pmf_probe(struct platform_device *pdev)
|
|||
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
|
||||
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD);
|
||||
|
||||
pmf_device = dev->dev;
|
||||
|
||||
dev_info(dev->dev, "registered PMF device successfully\n");
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@
|
|||
#define PMF_H
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/amd-pmf-io.h>
|
||||
#include <linux/circ_buf.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/mutex_types.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_profile.h>
|
||||
|
||||
|
|
@ -120,6 +123,7 @@ struct cookie_header {
|
|||
#define APTS_MAX_STATES 16
|
||||
#define CUSTOM_BIOS_INPUT_BITS GENMASK(16, 7)
|
||||
#define BIOS_INPUTS_MAX 10
|
||||
#define CUSTOM_BIOS_INPUT_RING_ENTRIES 64 /* Must be power of two for CIRC_* macros */
|
||||
|
||||
/* amd_pmf_send_cmd() set/get */
|
||||
#define SET_CMD false
|
||||
|
|
@ -129,6 +133,12 @@ struct cookie_header {
|
|||
|
||||
typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data);
|
||||
|
||||
static const uuid_t amd_pmf_ta_uuid[] __used = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8,
|
||||
0x8a, 0xcc, 0x2b, 0x2b, 0x60, 0xd6),
|
||||
UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d,
|
||||
0xc5, 0x29, 0xb1, 0x3d, 0x85, 0x43),
|
||||
};
|
||||
|
||||
/* APTS PMF BIOS Interface */
|
||||
struct amd_pmf_apts_output {
|
||||
u16 table_version;
|
||||
|
|
@ -365,6 +375,22 @@ struct pmf_bios_inputs_prev {
|
|||
u32 custom_bios_inputs[BIOS_INPUTS_MAX];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pmf_bios_input_entry - Snapshot of custom BIOS input event
|
||||
* @val: Array of custom BIOS input values
|
||||
* @preq: Pending request value associated with this event
|
||||
*/
|
||||
struct pmf_bios_input_entry {
|
||||
u32 val[BIOS_INPUTS_MAX];
|
||||
u32 preq;
|
||||
};
|
||||
|
||||
struct pmf_cbi_ring_buffer {
|
||||
struct pmf_bios_input_entry data[CUSTOM_BIOS_INPUT_RING_ENTRIES];
|
||||
int head;
|
||||
int tail;
|
||||
};
|
||||
|
||||
struct amd_pmf_dev {
|
||||
void __iomem *regbase;
|
||||
void __iomem *smu_virt_addr;
|
||||
|
|
@ -413,6 +439,9 @@ struct amd_pmf_dev {
|
|||
struct apmf_sbios_req_v1 req1;
|
||||
struct pmf_bios_inputs_prev cb_prev; /* To preserve custom BIOS inputs */
|
||||
bool cb_flag; /* To handle first custom BIOS input */
|
||||
struct pmf_cbi_ring_buffer cbi_buf;
|
||||
struct mutex cbi_mutex; /* Protects ring buffer access */
|
||||
struct mutex metrics_mutex;
|
||||
};
|
||||
|
||||
struct apmf_sps_prop_granular_v2 {
|
||||
|
|
@ -895,4 +924,8 @@ void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_tab
|
|||
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
|
||||
int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev);
|
||||
|
||||
int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid);
|
||||
void amd_pmf_tee_deinit(struct amd_pmf_dev *dev);
|
||||
int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev);
|
||||
|
||||
#endif /* PMF_H */
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <acpi/button.h>
|
||||
#include <linux/amd-pmf-io.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/units.h>
|
||||
#include "pmf.h"
|
||||
|
|
@ -132,32 +133,39 @@ static void amd_pmf_set_ta_custom_bios_input(struct ta_pmf_enact_table *in, int
|
|||
}
|
||||
}
|
||||
|
||||
static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, u32 pending_req,
|
||||
static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, struct pmf_bios_input_entry *data,
|
||||
const struct amd_pmf_pb_bitmap *inputs,
|
||||
const u32 *custom_policy, struct ta_pmf_enact_table *in)
|
||||
struct ta_pmf_enact_table *in)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) {
|
||||
if (!(pending_req & inputs[i].bit_mask))
|
||||
if (!(data->preq & inputs[i].bit_mask))
|
||||
continue;
|
||||
amd_pmf_set_ta_custom_bios_input(in, i, custom_policy[i]);
|
||||
pdev->cb_prev.custom_bios_inputs[i] = custom_policy[i];
|
||||
dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, custom_policy[i]);
|
||||
amd_pmf_set_ta_custom_bios_input(in, i, data->val[i]);
|
||||
pdev->cb_prev.custom_bios_inputs[i] = data->val[i];
|
||||
dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, data->val[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
|
||||
struct ta_pmf_enact_table *in)
|
||||
{
|
||||
struct pmf_cbi_ring_buffer *rb = &pdev->cbi_buf;
|
||||
unsigned int i;
|
||||
|
||||
guard(mutex)(&pdev->cbi_mutex);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
|
||||
amd_pmf_set_ta_custom_bios_input(in, i, pdev->cb_prev.custom_bios_inputs[i]);
|
||||
|
||||
if (!(pdev->req.pending_req || pdev->req1.pending_req))
|
||||
if (CIRC_CNT(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0)
|
||||
return;
|
||||
|
||||
/* If no active custom BIOS input pending request, do not consume further work */
|
||||
if (!rb->data[rb->tail].preq)
|
||||
goto out_rbadvance;
|
||||
|
||||
if (!pdev->smart_pc_enabled)
|
||||
return;
|
||||
|
||||
|
|
@ -165,20 +173,17 @@ static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
|
|||
case PMF_IF_V1:
|
||||
if (!is_apmf_bios_input_notifications_supported(pdev))
|
||||
return;
|
||||
amd_pmf_update_bios_inputs(pdev, pdev->req1.pending_req, custom_bios_inputs_v1,
|
||||
pdev->req1.custom_policy, in);
|
||||
amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs_v1, in);
|
||||
break;
|
||||
case PMF_IF_V2:
|
||||
amd_pmf_update_bios_inputs(pdev, pdev->req.pending_req, custom_bios_inputs,
|
||||
pdev->req.custom_policy, in);
|
||||
amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs, in);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Clear pending requests after handling */
|
||||
memset(&pdev->req, 0, sizeof(pdev->req));
|
||||
memset(&pdev->req1, 0, sizeof(pdev->req1));
|
||||
out_rbadvance:
|
||||
rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
|
||||
}
|
||||
|
||||
static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in)
|
||||
|
|
|
|||
|
|
@ -27,12 +27,6 @@ module_param(pb_side_load, bool, 0444);
|
|||
MODULE_PARM_DESC(pb_side_load, "Sideload policy binaries debug policy failures");
|
||||
#endif
|
||||
|
||||
static const uuid_t amd_pmf_ta_uuid[] = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, 0x8a,
|
||||
0xcc, 0x2b, 0x2b, 0x60, 0xd6),
|
||||
UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d, 0xc5,
|
||||
0x29, 0xb1, 0x3d, 0x85, 0x43),
|
||||
};
|
||||
|
||||
static const char *amd_pmf_uevent_as_str(unsigned int state)
|
||||
{
|
||||
switch (state) {
|
||||
|
|
@ -324,7 +318,7 @@ static void amd_pmf_invoke_cmd(struct work_struct *work)
|
|||
schedule_delayed_work(&dev->pb_work, msecs_to_jiffies(pb_actions_ms));
|
||||
}
|
||||
|
||||
static int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
|
||||
int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
|
||||
{
|
||||
struct cookie_header *header;
|
||||
int res;
|
||||
|
|
@ -480,7 +474,7 @@ static int amd_pmf_register_input_device(struct amd_pmf_dev *dev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
|
||||
int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
|
||||
{
|
||||
u32 size;
|
||||
int ret;
|
||||
|
|
@ -528,7 +522,7 @@ out_ctx:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
|
||||
void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
|
||||
{
|
||||
if (!dev->tee_ctx)
|
||||
return;
|
||||
|
|
@ -591,6 +585,8 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
|
|||
status = ret == TA_PMF_TYPE_SUCCESS;
|
||||
if (status) {
|
||||
dev->cb_flag = true;
|
||||
dev->cbi_buf.head = 0;
|
||||
dev->cbi_buf.tail = 0;
|
||||
break;
|
||||
}
|
||||
amd_pmf_tee_deinit(dev);
|
||||
|
|
|
|||
|
|
@ -42,8 +42,6 @@ static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head);
|
|||
static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ranges_in_out *in)
|
||||
{
|
||||
union acpi_object argv4;
|
||||
union acpi_object *tmp;
|
||||
union acpi_object *obj;
|
||||
u32 num_of_ranges = 0;
|
||||
u32 num_of_elements;
|
||||
u32 arg_idx = 0;
|
||||
|
|
@ -74,7 +72,7 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran
|
|||
*/
|
||||
num_of_elements = 2 * num_of_ranges + 2;
|
||||
|
||||
tmp = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL);
|
||||
union acpi_object *tmp __free(kfree) = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL);
|
||||
if (!tmp)
|
||||
return -ENOMEM;
|
||||
|
||||
|
|
@ -101,26 +99,19 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran
|
|||
tmp[arg_idx++].integer.value = in->band_list[i].end;
|
||||
}
|
||||
|
||||
obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
|
||||
WBRF_REVISION, WBRF_RECORD, &argv4);
|
||||
union acpi_object *obj __free(kfree) =
|
||||
acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
|
||||
WBRF_REVISION, WBRF_RECORD, &argv4);
|
||||
|
||||
if (!obj) {
|
||||
kfree(tmp);
|
||||
if (!obj)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (obj->type != ACPI_TYPE_INTEGER) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (obj->type != ACPI_TYPE_INTEGER)
|
||||
return -EINVAL;
|
||||
|
||||
ret = obj->integer.value;
|
||||
if (ret)
|
||||
ret = -EINVAL;
|
||||
|
||||
out:
|
||||
ACPI_FREE(obj);
|
||||
kfree(tmp);
|
||||
return -EINVAL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,13 +31,13 @@
|
|||
#include <linux/pci.h>
|
||||
#include <linux/pci_hotplug.h>
|
||||
#include <linux/platform_data/x86/asus-wmi.h>
|
||||
#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_profile.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/rfkill.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
|
|
@ -256,6 +256,9 @@ struct asus_wmi {
|
|||
int tpd_led_wk;
|
||||
struct led_classdev kbd_led;
|
||||
int kbd_led_wk;
|
||||
bool kbd_led_notify;
|
||||
bool kbd_led_avail;
|
||||
bool kbd_led_registered;
|
||||
struct led_classdev lightbar_led;
|
||||
int lightbar_led_wk;
|
||||
struct led_classdev micmute_led;
|
||||
|
|
@ -264,6 +267,7 @@ struct asus_wmi {
|
|||
struct work_struct tpd_led_work;
|
||||
struct work_struct wlan_led_work;
|
||||
struct work_struct lightbar_led_work;
|
||||
struct work_struct kbd_led_work;
|
||||
|
||||
struct asus_rfkill wlan;
|
||||
struct asus_rfkill bluetooth;
|
||||
|
|
@ -1615,6 +1619,144 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
|
|||
|
||||
/* LEDs ***********************************************************************/
|
||||
|
||||
struct asus_hid_ref {
|
||||
struct list_head listeners;
|
||||
struct asus_wmi *asus;
|
||||
/* Protects concurrent access from hid-asus and asus-wmi to leds */
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
static struct asus_hid_ref asus_ref = {
|
||||
.listeners = LIST_HEAD_INIT(asus_ref.listeners),
|
||||
.asus = NULL,
|
||||
/*
|
||||
* Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other
|
||||
* asus variables are read-only after .asus is set.
|
||||
*
|
||||
* The led cdev device is not protected because it calls backlight_get
|
||||
* during initialization, which would result in a nested lock attempt.
|
||||
*
|
||||
* The led cdev is safe to access without a lock because if
|
||||
* kbd_led_avail is true it is initialized before .asus is set and never
|
||||
* changed until .asus is dropped. If kbd_led_avail is false, the led
|
||||
* cdev is registered by the workqueue, which is single-threaded and
|
||||
* cancelled before asus-wmi would access the led cdev to unregister it.
|
||||
*
|
||||
* A spinlock is used, because the protected variables can be accessed
|
||||
* from an IRQ context from asus-hid.
|
||||
*/
|
||||
.lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
|
||||
};
|
||||
|
||||
/*
|
||||
* Allows registering hid-asus listeners that want to be notified of
|
||||
* keyboard backlight changes.
|
||||
*/
|
||||
int asus_hid_register_listener(struct asus_hid_listener *bdev)
|
||||
{
|
||||
struct asus_wmi *asus;
|
||||
|
||||
guard(spinlock_irqsave)(&asus_ref.lock);
|
||||
list_add_tail(&bdev->list, &asus_ref.listeners);
|
||||
asus = asus_ref.asus;
|
||||
if (asus)
|
||||
queue_work(asus->led_workqueue, &asus->kbd_led_work);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(asus_hid_register_listener);
|
||||
|
||||
/*
|
||||
* Allows unregistering hid-asus listeners that were added with
|
||||
* asus_hid_register_listener().
|
||||
*/
|
||||
void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
|
||||
{
|
||||
guard(spinlock_irqsave)(&asus_ref.lock);
|
||||
list_del(&bdev->list);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
|
||||
|
||||
static void do_kbd_led_set(struct led_classdev *led_cdev, int value);
|
||||
|
||||
static void kbd_led_update_all(struct work_struct *work)
|
||||
{
|
||||
struct asus_wmi *asus;
|
||||
bool registered, notify;
|
||||
int ret, value;
|
||||
|
||||
asus = container_of(work, struct asus_wmi, kbd_led_work);
|
||||
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock) {
|
||||
registered = asus->kbd_led_registered;
|
||||
value = asus->kbd_led_wk;
|
||||
notify = asus->kbd_led_notify;
|
||||
}
|
||||
|
||||
if (!registered) {
|
||||
/*
|
||||
* This workqueue runs under asus-wmi, which means probe has
|
||||
* completed and asus-wmi will keep running until it finishes.
|
||||
* Therefore, we can safely register the LED without holding
|
||||
* a spinlock.
|
||||
*/
|
||||
ret = devm_led_classdev_register(&asus->platform_device->dev,
|
||||
&asus->kbd_led);
|
||||
if (!ret) {
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock)
|
||||
asus->kbd_led_registered = true;
|
||||
} else {
|
||||
pr_warn("Failed to register keyboard backlight LED: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (value >= 0)
|
||||
do_kbd_led_set(&asus->kbd_led, value);
|
||||
if (notify) {
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock)
|
||||
asus->kbd_led_notify = false;
|
||||
led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called from hid-asus to inform asus-wmi of brightness
|
||||
* changes initiated by the keyboard backlight keys.
|
||||
*/
|
||||
int asus_hid_event(enum asus_hid_event event)
|
||||
{
|
||||
struct asus_wmi *asus;
|
||||
int brightness;
|
||||
|
||||
guard(spinlock_irqsave)(&asus_ref.lock);
|
||||
asus = asus_ref.asus;
|
||||
if (!asus || !asus->kbd_led_registered)
|
||||
return -EBUSY;
|
||||
|
||||
brightness = asus->kbd_led_wk;
|
||||
|
||||
switch (event) {
|
||||
case ASUS_EV_BRTUP:
|
||||
brightness += 1;
|
||||
break;
|
||||
case ASUS_EV_BRTDOWN:
|
||||
brightness -= 1;
|
||||
break;
|
||||
case ASUS_EV_BRTTOGGLE:
|
||||
if (brightness >= ASUS_EV_MAX_BRIGHTNESS)
|
||||
brightness = 0;
|
||||
else
|
||||
brightness += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
asus->kbd_led_wk = clamp_val(brightness, 0, ASUS_EV_MAX_BRIGHTNESS);
|
||||
asus->kbd_led_notify = true;
|
||||
queue_work(asus->led_workqueue, &asus->kbd_led_work);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(asus_hid_event);
|
||||
|
||||
/*
|
||||
* These functions actually update the LED's, and are called from a
|
||||
* workqueue. By doing this as separate work rather than when the LED
|
||||
|
|
@ -1661,7 +1803,8 @@ static void kbd_led_update(struct asus_wmi *asus)
|
|||
{
|
||||
int ctrl_param = 0;
|
||||
|
||||
ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock)
|
||||
ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
|
||||
asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
|
||||
}
|
||||
|
||||
|
|
@ -1694,14 +1837,21 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
|
|||
|
||||
static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
|
||||
{
|
||||
struct asus_hid_listener *listener;
|
||||
struct asus_wmi *asus;
|
||||
int max_level;
|
||||
|
||||
asus = container_of(led_cdev, struct asus_wmi, kbd_led);
|
||||
max_level = asus->kbd_led.max_brightness;
|
||||
|
||||
asus->kbd_led_wk = clamp_val(value, 0, max_level);
|
||||
kbd_led_update(asus);
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock)
|
||||
asus->kbd_led_wk = clamp_val(value, 0, ASUS_EV_MAX_BRIGHTNESS);
|
||||
|
||||
if (asus->kbd_led_avail)
|
||||
kbd_led_update(asus);
|
||||
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock) {
|
||||
list_for_each_entry(listener, &asus_ref.listeners, list)
|
||||
listener->brightness_set(listener, asus->kbd_led_wk);
|
||||
}
|
||||
}
|
||||
|
||||
static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
|
||||
|
|
@ -1716,10 +1866,11 @@ static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
|
|||
|
||||
static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
|
||||
{
|
||||
struct led_classdev *led_cdev = &asus->kbd_led;
|
||||
|
||||
do_kbd_led_set(led_cdev, value);
|
||||
led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock) {
|
||||
asus->kbd_led_wk = value;
|
||||
asus->kbd_led_notify = true;
|
||||
}
|
||||
queue_work(asus->led_workqueue, &asus->kbd_led_work);
|
||||
}
|
||||
|
||||
static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
|
||||
|
|
@ -1729,10 +1880,18 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
|
|||
|
||||
asus = container_of(led_cdev, struct asus_wmi, kbd_led);
|
||||
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock) {
|
||||
if (!asus->kbd_led_avail)
|
||||
return asus->kbd_led_wk;
|
||||
}
|
||||
|
||||
retval = kbd_led_read(asus, &value, NULL);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock)
|
||||
asus->kbd_led_wk = value;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
@ -1844,7 +2003,9 @@ static int camera_led_set(struct led_classdev *led_cdev,
|
|||
|
||||
static void asus_wmi_led_exit(struct asus_wmi *asus)
|
||||
{
|
||||
led_classdev_unregister(&asus->kbd_led);
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock)
|
||||
asus_ref.asus = NULL;
|
||||
|
||||
led_classdev_unregister(&asus->tpd_led);
|
||||
led_classdev_unregister(&asus->wlan_led);
|
||||
led_classdev_unregister(&asus->lightbar_led);
|
||||
|
|
@ -1882,22 +2043,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
|
|||
goto error;
|
||||
}
|
||||
|
||||
if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
|
||||
pr_info("using asus-wmi for asus::kbd_backlight\n");
|
||||
asus->kbd_led_wk = led_val;
|
||||
asus->kbd_led.name = "asus::kbd_backlight";
|
||||
asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
|
||||
asus->kbd_led.brightness_set_blocking = kbd_led_set;
|
||||
asus->kbd_led.brightness_get = kbd_led_get;
|
||||
asus->kbd_led.max_brightness = 3;
|
||||
asus->kbd_led.name = "asus::kbd_backlight";
|
||||
asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
|
||||
asus->kbd_led.brightness_set_blocking = kbd_led_set;
|
||||
asus->kbd_led.brightness_get = kbd_led_get;
|
||||
asus->kbd_led.max_brightness = ASUS_EV_MAX_BRIGHTNESS;
|
||||
asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
|
||||
INIT_WORK(&asus->kbd_led_work, kbd_led_update_all);
|
||||
|
||||
if (asus->kbd_led_avail) {
|
||||
asus->kbd_led_wk = led_val;
|
||||
if (num_rgb_groups != 0)
|
||||
asus->kbd_led.groups = kbd_rgb_mode_groups;
|
||||
} else {
|
||||
asus->kbd_led_wk = -1;
|
||||
}
|
||||
|
||||
rv = led_classdev_register(&asus->platform_device->dev,
|
||||
&asus->kbd_led);
|
||||
if (rv)
|
||||
goto error;
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock) {
|
||||
asus_ref.asus = asus;
|
||||
if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners))
|
||||
queue_work(asus->led_workqueue, &asus->kbd_led_work);
|
||||
}
|
||||
|
||||
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
|
||||
|
|
@ -4372,6 +4537,7 @@ static int asus_wmi_get_event_code(union acpi_object *obj)
|
|||
|
||||
static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
|
||||
{
|
||||
enum led_brightness led_value;
|
||||
unsigned int key_value = 1;
|
||||
bool autorelease = 1;
|
||||
|
||||
|
|
@ -4388,19 +4554,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
|
|||
return;
|
||||
}
|
||||
|
||||
scoped_guard(spinlock_irqsave, &asus_ref.lock)
|
||||
led_value = asus->kbd_led_wk;
|
||||
|
||||
if (code == NOTIFY_KBD_BRTUP) {
|
||||
kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
|
||||
kbd_led_set_by_kbd(asus, led_value + 1);
|
||||
return;
|
||||
}
|
||||
if (code == NOTIFY_KBD_BRTDWN) {
|
||||
kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1);
|
||||
kbd_led_set_by_kbd(asus, led_value - 1);
|
||||
return;
|
||||
}
|
||||
if (code == NOTIFY_KBD_BRTTOGGLE) {
|
||||
if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
|
||||
if (led_value >= ASUS_EV_MAX_BRIGHTNESS)
|
||||
kbd_led_set_by_kbd(asus, 0);
|
||||
else
|
||||
kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
|
||||
kbd_led_set_by_kbd(asus, led_value + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,23 +13,28 @@
|
|||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/fixp-arith.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/input/sparse-keymap.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_profile.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/rfkill.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
|
||||
MODULE_DESCRIPTION("HP laptop WMI driver");
|
||||
|
|
@ -41,9 +46,13 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4");
|
|||
#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
|
||||
#define HPWMI_BIOS_GUID "5FB7F034-2C63-45E9-BE91-3D44E2C707E4"
|
||||
|
||||
#define HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET 0x62
|
||||
#define HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET 0x63
|
||||
#define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95
|
||||
enum hp_ec_offsets {
|
||||
HP_EC_OFFSET_UNKNOWN = 0x00,
|
||||
HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET = 0x59,
|
||||
HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET = 0x62,
|
||||
HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET = 0x63,
|
||||
HP_OMEN_EC_THERMAL_PROFILE_OFFSET = 0x95,
|
||||
};
|
||||
|
||||
#define HP_FAN_SPEED_AUTOMATIC 0x00
|
||||
#define HP_POWER_LIMIT_DEFAULT 0x00
|
||||
|
|
@ -53,6 +62,70 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4");
|
|||
|
||||
#define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
|
||||
|
||||
enum hp_thermal_profile_omen_v0 {
|
||||
HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00,
|
||||
HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01,
|
||||
HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile_omen_v1 {
|
||||
HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30,
|
||||
HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31,
|
||||
HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile_omen_flags {
|
||||
HP_OMEN_EC_FLAGS_TURBO = 0x04,
|
||||
HP_OMEN_EC_FLAGS_NOTIMER = 0x02,
|
||||
HP_OMEN_EC_FLAGS_JUSTSET = 0x01,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile_victus {
|
||||
HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00,
|
||||
HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01,
|
||||
HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile_victus_s {
|
||||
HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00,
|
||||
HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile {
|
||||
HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
|
||||
HP_THERMAL_PROFILE_DEFAULT = 0x01,
|
||||
HP_THERMAL_PROFILE_COOL = 0x02,
|
||||
HP_THERMAL_PROFILE_QUIET = 0x03,
|
||||
};
|
||||
|
||||
|
||||
struct thermal_profile_params {
|
||||
u8 performance;
|
||||
u8 balanced;
|
||||
u8 low_power;
|
||||
u8 ec_tp_offset;
|
||||
};
|
||||
|
||||
static const struct thermal_profile_params victus_s_thermal_params = {
|
||||
.performance = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE,
|
||||
.balanced = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT,
|
||||
.low_power = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT,
|
||||
.ec_tp_offset = HP_EC_OFFSET_UNKNOWN,
|
||||
};
|
||||
|
||||
static const struct thermal_profile_params omen_v1_thermal_params = {
|
||||
.performance = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE,
|
||||
.balanced = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
|
||||
.low_power = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
|
||||
.ec_tp_offset = HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET,
|
||||
};
|
||||
|
||||
/*
|
||||
* A generic pointer for the currently-active board's thermal profile
|
||||
* parameters.
|
||||
*/
|
||||
static struct thermal_profile_params *active_thermal_profile_params;
|
||||
|
||||
/* DMI board names of devices that should use the omen specific path for
|
||||
* thermal profiles.
|
||||
* This was obtained by taking a look in the windows omen command center
|
||||
|
|
@ -99,12 +172,40 @@ static const char * const victus_thermal_profile_boards[] = {
|
|||
};
|
||||
|
||||
/* DMI Board names of Victus 16-r and Victus 16-s laptops */
|
||||
static const char * const victus_s_thermal_profile_boards[] = {
|
||||
"8BBE", "8BD4", "8BD5",
|
||||
"8C78", "8C99", "8C9C",
|
||||
"8D41",
|
||||
static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = {
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") },
|
||||
.driver_data = (void *)&omen_v1_thermal_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static bool is_victus_s_board;
|
||||
|
||||
enum hp_wmi_radio {
|
||||
HPWMI_WIFI = 0x0,
|
||||
HPWMI_BLUETOOTH = 0x1,
|
||||
|
|
@ -190,7 +291,8 @@ enum hp_wmi_gm_commandtype {
|
|||
HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22,
|
||||
HPWMI_SET_POWER_LIMITS_QUERY = 0x29,
|
||||
HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY = 0x2D,
|
||||
HPWMI_FAN_SPEED_SET_QUERY = 0x2E,
|
||||
HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY = 0x2E,
|
||||
HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY = 0x2F,
|
||||
};
|
||||
|
||||
enum hp_wmi_command {
|
||||
|
|
@ -225,42 +327,6 @@ enum hp_wireless2_bits {
|
|||
HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile_omen_v0 {
|
||||
HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00,
|
||||
HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01,
|
||||
HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile_omen_v1 {
|
||||
HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30,
|
||||
HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31,
|
||||
HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile_omen_flags {
|
||||
HP_OMEN_EC_FLAGS_TURBO = 0x04,
|
||||
HP_OMEN_EC_FLAGS_NOTIMER = 0x02,
|
||||
HP_OMEN_EC_FLAGS_JUSTSET = 0x01,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile_victus {
|
||||
HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00,
|
||||
HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01,
|
||||
HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile_victus_s {
|
||||
HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00,
|
||||
HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile {
|
||||
HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
|
||||
HP_THERMAL_PROFILE_DEFAULT = 0x01,
|
||||
HP_THERMAL_PROFILE_COOL = 0x02,
|
||||
HP_THERMAL_PROFILE_QUIET = 0x03,
|
||||
};
|
||||
|
||||
#define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW)
|
||||
#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
|
||||
|
||||
|
|
@ -348,6 +414,58 @@ static const char * const tablet_chassis_types[] = {
|
|||
|
||||
#define DEVICE_MODE_TABLET 0x06
|
||||
|
||||
#define CPU_FAN 0
|
||||
#define GPU_FAN 1
|
||||
|
||||
enum pwm_modes {
|
||||
PWM_MODE_MAX = 0,
|
||||
PWM_MODE_MANUAL = 1,
|
||||
PWM_MODE_AUTO = 2,
|
||||
};
|
||||
|
||||
struct hp_wmi_hwmon_priv {
|
||||
u8 min_rpm;
|
||||
u8 max_rpm;
|
||||
u8 gpu_delta;
|
||||
u8 mode;
|
||||
u8 pwm;
|
||||
struct delayed_work keep_alive_dwork;
|
||||
};
|
||||
|
||||
struct victus_s_fan_table_header {
|
||||
u8 unknown;
|
||||
u8 num_entries;
|
||||
} __packed;
|
||||
|
||||
struct victus_s_fan_table_entry {
|
||||
u8 cpu_rpm;
|
||||
u8 gpu_rpm;
|
||||
u8 unknown;
|
||||
} __packed;
|
||||
|
||||
struct victus_s_fan_table {
|
||||
struct victus_s_fan_table_header header;
|
||||
struct victus_s_fan_table_entry entries[];
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* 90s delay to prevent the firmware from resetting fan mode after fixed
|
||||
* 120s timeout
|
||||
*/
|
||||
#define KEEP_ALIVE_DELAY_SECS 90
|
||||
|
||||
static inline u8 rpm_to_pwm(u8 rpm, struct hp_wmi_hwmon_priv *priv)
|
||||
{
|
||||
return fixp_linear_interpolate(0, 0, priv->max_rpm, U8_MAX,
|
||||
clamp_val(rpm, 0, priv->max_rpm));
|
||||
}
|
||||
|
||||
static inline u8 pwm_to_rpm(u8 pwm, struct hp_wmi_hwmon_priv *priv)
|
||||
{
|
||||
return fixp_linear_interpolate(0, 0, U8_MAX, priv->max_rpm,
|
||||
clamp_val(pwm, 0, U8_MAX));
|
||||
}
|
||||
|
||||
/* map output size to the corresponding WMI method id */
|
||||
static inline int encode_outsize_for_pvsz(int outsize)
|
||||
{
|
||||
|
|
@ -637,18 +755,44 @@ static int hp_wmi_fan_speed_max_set(int enabled)
|
|||
return enabled;
|
||||
}
|
||||
|
||||
static int hp_wmi_fan_speed_reset(void)
|
||||
static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed)
|
||||
{
|
||||
u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC };
|
||||
int ret;
|
||||
u8 fan_speed[2];
|
||||
int gpu_speed, ret;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM,
|
||||
fan_speed[CPU_FAN] = speed;
|
||||
fan_speed[GPU_FAN] = speed;
|
||||
|
||||
/*
|
||||
* GPU fan speed is always a little higher than CPU fan speed, we fetch
|
||||
* this delta value from the fan table during hwmon init.
|
||||
* Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to
|
||||
* automatic mode.
|
||||
*/
|
||||
if (speed != HP_FAN_SPEED_AUTOMATIC) {
|
||||
gpu_speed = speed + priv->gpu_delta;
|
||||
fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX);
|
||||
}
|
||||
|
||||
ret = hp_wmi_get_fan_count_userdefine_trigger();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/* Max fans need to be explicitly disabled */
|
||||
ret = hp_wmi_fan_speed_max_set(0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY, HPWMI_GM,
|
||||
&fan_speed, sizeof(fan_speed), 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hp_wmi_fan_speed_max_reset(void)
|
||||
static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv)
|
||||
{
|
||||
return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC);
|
||||
}
|
||||
|
||||
static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
|
@ -657,21 +801,7 @@ static int hp_wmi_fan_speed_max_reset(void)
|
|||
return ret;
|
||||
|
||||
/* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */
|
||||
ret = hp_wmi_fan_speed_reset();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hp_wmi_fan_speed_max_get(void)
|
||||
{
|
||||
int val = 0, ret;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM,
|
||||
&val, zero_if_sup(val), sizeof(val));
|
||||
|
||||
if (ret)
|
||||
return ret < 0 ? ret : -EINVAL;
|
||||
|
||||
return val;
|
||||
return hp_wmi_fan_speed_reset(priv);
|
||||
}
|
||||
|
||||
static int __init hp_wmi_bios_2008_later(void)
|
||||
|
|
@ -1581,15 +1711,8 @@ static int platform_profile_victus_set_ec(enum platform_profile_option profile)
|
|||
|
||||
static bool is_victus_s_thermal_profile(void)
|
||||
{
|
||||
const char *board_name;
|
||||
|
||||
board_name = dmi_get_system_info(DMI_BOARD_NAME);
|
||||
if (!board_name)
|
||||
return false;
|
||||
|
||||
return match_string(victus_s_thermal_profile_boards,
|
||||
ARRAY_SIZE(victus_s_thermal_profile_boards),
|
||||
board_name) >= 0;
|
||||
/* Initialised in driver init, hence safe to use here */
|
||||
return is_victus_s_board;
|
||||
}
|
||||
|
||||
static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable,
|
||||
|
|
@ -1670,27 +1793,86 @@ static int victus_s_set_cpu_pl1_pl2(u8 pl1, u8 pl2)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int platform_profile_victus_s_get_ec(enum platform_profile_option *profile)
|
||||
{
|
||||
int ret = 0;
|
||||
bool current_ctgp_state, current_ppab_state;
|
||||
u8 current_dstate, current_gpu_slowdown_temp, tp;
|
||||
const struct thermal_profile_params *params;
|
||||
|
||||
params = active_thermal_profile_params;
|
||||
if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
|
||||
*profile = active_platform_profile;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = ec_read(params->ec_tp_offset, &tp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* We cannot use active_thermal_profile_params here, because boards
|
||||
* like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically
|
||||
* it should have tp == 0x30 || tp == 0x31, as corrected by the Omen
|
||||
* Gaming Hub on windows. Hence accept both of these values.
|
||||
*/
|
||||
if (tp == victus_s_thermal_params.performance ||
|
||||
tp == omen_v1_thermal_params.performance) {
|
||||
*profile = PLATFORM_PROFILE_PERFORMANCE;
|
||||
} else if (tp == victus_s_thermal_params.balanced ||
|
||||
tp == omen_v1_thermal_params.balanced) {
|
||||
/*
|
||||
* Since both PLATFORM_PROFILE_LOW_POWER and
|
||||
* PLATFORM_PROFILE_BALANCED share the same thermal profile
|
||||
* parameter value, hence to differentiate between them, we
|
||||
* query the GPU CTGP and PPAB states and compare based off of
|
||||
* that.
|
||||
*/
|
||||
ret = victus_s_gpu_thermal_profile_get(¤t_ctgp_state,
|
||||
¤t_ppab_state,
|
||||
¤t_dstate,
|
||||
¤t_gpu_slowdown_temp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (current_ctgp_state == 0 && current_ppab_state == 0)
|
||||
*profile = PLATFORM_PROFILE_LOW_POWER;
|
||||
else if (current_ctgp_state == 0 && current_ppab_state == 1)
|
||||
*profile = PLATFORM_PROFILE_BALANCED;
|
||||
else
|
||||
return -EINVAL;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int platform_profile_victus_s_set_ec(enum platform_profile_option profile)
|
||||
{
|
||||
struct thermal_profile_params *params;
|
||||
bool gpu_ctgp_enable, gpu_ppab_enable;
|
||||
u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */
|
||||
int err, tp;
|
||||
|
||||
params = active_thermal_profile_params;
|
||||
if (!params)
|
||||
return -ENODEV;
|
||||
|
||||
switch (profile) {
|
||||
case PLATFORM_PROFILE_PERFORMANCE:
|
||||
tp = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE;
|
||||
tp = params->performance;
|
||||
gpu_ctgp_enable = true;
|
||||
gpu_ppab_enable = true;
|
||||
gpu_dstate = 1;
|
||||
break;
|
||||
case PLATFORM_PROFILE_BALANCED:
|
||||
tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
|
||||
tp = params->balanced;
|
||||
gpu_ctgp_enable = false;
|
||||
gpu_ppab_enable = true;
|
||||
gpu_dstate = 1;
|
||||
break;
|
||||
case PLATFORM_PROFILE_LOW_POWER:
|
||||
tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
|
||||
tp = params->low_power;
|
||||
gpu_ctgp_enable = false;
|
||||
gpu_ppab_enable = false;
|
||||
gpu_dstate = 1;
|
||||
|
|
@ -1832,6 +2014,7 @@ static int victus_s_powersource_event(struct notifier_block *nb,
|
|||
void *data)
|
||||
{
|
||||
struct acpi_bus_event *event_entry = data;
|
||||
enum platform_profile_option actual_profile;
|
||||
int err;
|
||||
|
||||
if (strcmp(event_entry->device_class, ACPI_AC_CLASS) != 0)
|
||||
|
|
@ -1839,6 +2022,17 @@ static int victus_s_powersource_event(struct notifier_block *nb,
|
|||
|
||||
pr_debug("Received power source device event\n");
|
||||
|
||||
guard(mutex)(&active_platform_profile_lock);
|
||||
err = platform_profile_victus_s_get_ec(&actual_profile);
|
||||
if (err < 0) {
|
||||
/*
|
||||
* Although we failed to get the current platform profile, we
|
||||
* still want the other event consumers to process it.
|
||||
*/
|
||||
pr_warn("Failed to read current platform profile (%d)\n", err);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Switching to battery power source while Performance mode is active
|
||||
* needs manual triggering of CPU power limits. Same goes when switching
|
||||
|
|
@ -1847,7 +2041,7 @@ static int victus_s_powersource_event(struct notifier_block *nb,
|
|||
* Seen on HP 16-s1034nf (board 8C9C) with F.11 and F.13 BIOS versions.
|
||||
*/
|
||||
|
||||
if (active_platform_profile == PLATFORM_PROFILE_PERFORMANCE) {
|
||||
if (actual_profile == PLATFORM_PROFILE_PERFORMANCE) {
|
||||
pr_debug("Triggering CPU PL1/PL2 actualization\n");
|
||||
err = victus_s_set_cpu_pl1_pl2(HP_POWER_LIMIT_DEFAULT,
|
||||
HP_POWER_LIMIT_DEFAULT);
|
||||
|
|
@ -1958,11 +2152,22 @@ static int thermal_profile_setup(struct platform_device *device)
|
|||
ops = &platform_profile_victus_ops;
|
||||
} else if (is_victus_s_thermal_profile()) {
|
||||
/*
|
||||
* Being unable to retrieve laptop's current thermal profile,
|
||||
* during this setup, we set it to Balanced by default.
|
||||
* For an unknown EC layout board, platform_profile_victus_s_get_ec(),
|
||||
* behaves like a wrapper around active_platform_profile, to avoid using
|
||||
* uninitialized data, we default to PLATFORM_PROFILE_BALANCED.
|
||||
*/
|
||||
active_platform_profile = PLATFORM_PROFILE_BALANCED;
|
||||
if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
|
||||
active_platform_profile = PLATFORM_PROFILE_BALANCED;
|
||||
} else {
|
||||
err = platform_profile_victus_s_get_ec(&active_platform_profile);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* call thermal profile write command to ensure that the
|
||||
* firmware correctly sets the OEM variables
|
||||
*/
|
||||
err = platform_profile_victus_s_set_ec(active_platform_profile);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
|
@ -2031,6 +2236,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
|
|||
static void __exit hp_wmi_bios_remove(struct platform_device *device)
|
||||
{
|
||||
int i;
|
||||
struct hp_wmi_hwmon_priv *priv;
|
||||
|
||||
for (i = 0; i < rfkill2_count; i++) {
|
||||
rfkill_unregister(rfkill2[i].rfkill);
|
||||
|
|
@ -2049,6 +2255,10 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device)
|
|||
rfkill_unregister(wwan_rfkill);
|
||||
rfkill_destroy(wwan_rfkill);
|
||||
}
|
||||
|
||||
priv = platform_get_drvdata(device);
|
||||
if (priv)
|
||||
cancel_delayed_work_sync(&priv->keep_alive_dwork);
|
||||
}
|
||||
|
||||
static int hp_wmi_resume_handler(struct device *device)
|
||||
|
|
@ -2108,12 +2318,56 @@ static struct platform_driver hp_wmi_driver __refdata = {
|
|||
.remove = __exit_p(hp_wmi_bios_remove),
|
||||
};
|
||||
|
||||
static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (priv->mode) {
|
||||
case PWM_MODE_MAX:
|
||||
if (is_victus_s_thermal_profile())
|
||||
hp_wmi_get_fan_count_userdefine_trigger();
|
||||
ret = hp_wmi_fan_speed_max_set(1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
schedule_delayed_work(&priv->keep_alive_dwork,
|
||||
secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
|
||||
return 0;
|
||||
case PWM_MODE_MANUAL:
|
||||
if (!is_victus_s_thermal_profile())
|
||||
return -EOPNOTSUPP;
|
||||
ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
schedule_delayed_work(&priv->keep_alive_dwork,
|
||||
secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
|
||||
return 0;
|
||||
case PWM_MODE_AUTO:
|
||||
if (is_victus_s_thermal_profile()) {
|
||||
hp_wmi_get_fan_count_userdefine_trigger();
|
||||
ret = hp_wmi_fan_speed_max_reset(priv);
|
||||
} else {
|
||||
ret = hp_wmi_fan_speed_max_set(0);
|
||||
}
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
cancel_delayed_work_sync(&priv->keep_alive_dwork);
|
||||
return 0;
|
||||
default:
|
||||
/* shouldn't happen */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static umode_t hp_wmi_hwmon_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile())
|
||||
return 0;
|
||||
return 0644;
|
||||
case hwmon_fan:
|
||||
if (is_victus_s_thermal_profile()) {
|
||||
|
|
@ -2134,8 +2388,10 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data,
|
|||
static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
int ret;
|
||||
struct hp_wmi_hwmon_priv *priv;
|
||||
int rpm, ret;
|
||||
|
||||
priv = dev_get_drvdata(dev);
|
||||
switch (type) {
|
||||
case hwmon_fan:
|
||||
if (is_victus_s_thermal_profile())
|
||||
|
|
@ -2147,16 +2403,21 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
|||
*val = ret;
|
||||
return 0;
|
||||
case hwmon_pwm:
|
||||
switch (hp_wmi_fan_speed_max_get()) {
|
||||
case 0:
|
||||
/* 0 is automatic fan, which is 2 for hwmon */
|
||||
*val = 2;
|
||||
if (attr == hwmon_pwm_input) {
|
||||
if (!is_victus_s_thermal_profile())
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
rpm = hp_wmi_get_fan_speed_victus_s(channel);
|
||||
if (rpm < 0)
|
||||
return rpm;
|
||||
*val = rpm_to_pwm(rpm / 100, priv);
|
||||
return 0;
|
||||
case 1:
|
||||
/* 1 is max fan, which is 0
|
||||
* (no fan speed control) for hwmon
|
||||
*/
|
||||
*val = 0;
|
||||
}
|
||||
switch (priv->mode) {
|
||||
case PWM_MODE_MAX:
|
||||
case PWM_MODE_MANUAL:
|
||||
case PWM_MODE_AUTO:
|
||||
*val = priv->mode;
|
||||
return 0;
|
||||
default:
|
||||
/* shouldn't happen */
|
||||
|
|
@ -2170,23 +2431,46 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
|||
static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct hp_wmi_hwmon_priv *priv;
|
||||
int rpm;
|
||||
|
||||
priv = dev_get_drvdata(dev);
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
if (attr == hwmon_pwm_input) {
|
||||
if (!is_victus_s_thermal_profile())
|
||||
return -EOPNOTSUPP;
|
||||
/* PWM input is invalid when not in manual mode */
|
||||
if (priv->mode != PWM_MODE_MANUAL)
|
||||
return -EINVAL;
|
||||
|
||||
/* ensure PWM input is within valid fan speeds */
|
||||
rpm = pwm_to_rpm(val, priv);
|
||||
rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm);
|
||||
priv->pwm = rpm_to_pwm(rpm, priv);
|
||||
return hp_wmi_apply_fan_settings(priv);
|
||||
}
|
||||
switch (val) {
|
||||
case 0:
|
||||
if (is_victus_s_thermal_profile())
|
||||
hp_wmi_get_fan_count_userdefine_trigger();
|
||||
/* 0 is no fan speed control (max), which is 1 for us */
|
||||
return hp_wmi_fan_speed_max_set(1);
|
||||
case 2:
|
||||
/* 2 is automatic speed control, which is 0 for us */
|
||||
if (is_victus_s_thermal_profile()) {
|
||||
hp_wmi_get_fan_count_userdefine_trigger();
|
||||
return hp_wmi_fan_speed_max_reset();
|
||||
} else
|
||||
return hp_wmi_fan_speed_max_set(0);
|
||||
case PWM_MODE_MAX:
|
||||
priv->mode = PWM_MODE_MAX;
|
||||
return hp_wmi_apply_fan_settings(priv);
|
||||
case PWM_MODE_MANUAL:
|
||||
if (!is_victus_s_thermal_profile())
|
||||
return -EOPNOTSUPP;
|
||||
/*
|
||||
* When switching to manual mode, set fan speed to
|
||||
* current RPM values to ensure a smooth transition.
|
||||
*/
|
||||
rpm = hp_wmi_get_fan_speed_victus_s(channel);
|
||||
if (rpm < 0)
|
||||
return rpm;
|
||||
priv->pwm = rpm_to_pwm(rpm / 100, priv);
|
||||
priv->mode = PWM_MODE_MANUAL;
|
||||
return hp_wmi_apply_fan_settings(priv);
|
||||
case PWM_MODE_AUTO:
|
||||
priv->mode = PWM_MODE_AUTO;
|
||||
return hp_wmi_apply_fan_settings(priv);
|
||||
default:
|
||||
/* we don't support manual fan speed control */
|
||||
return -EINVAL;
|
||||
}
|
||||
default:
|
||||
|
|
@ -2196,7 +2480,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
|||
|
||||
static const struct hwmon_channel_info * const info[] = {
|
||||
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT),
|
||||
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE),
|
||||
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT),
|
||||
NULL
|
||||
};
|
||||
|
||||
|
|
@ -2211,12 +2495,70 @@ static const struct hwmon_chip_info chip_info = {
|
|||
.info = info,
|
||||
};
|
||||
|
||||
static void hp_wmi_hwmon_keep_alive_handler(struct work_struct *work)
|
||||
{
|
||||
struct delayed_work *dwork;
|
||||
struct hp_wmi_hwmon_priv *priv;
|
||||
|
||||
dwork = to_delayed_work(work);
|
||||
priv = container_of(dwork, struct hp_wmi_hwmon_priv, keep_alive_dwork);
|
||||
/*
|
||||
* Re-apply the current hwmon context settings.
|
||||
* NOTE: hp_wmi_apply_fan_settings will handle the re-scheduling.
|
||||
*/
|
||||
hp_wmi_apply_fan_settings(priv);
|
||||
}
|
||||
|
||||
static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
|
||||
{
|
||||
u8 fan_data[128] = { 0 };
|
||||
struct victus_s_fan_table *fan_table;
|
||||
u8 min_rpm, max_rpm, gpu_delta;
|
||||
int ret;
|
||||
|
||||
/* Default behaviour on hwmon init is automatic mode */
|
||||
priv->mode = PWM_MODE_AUTO;
|
||||
|
||||
/* Bypass all non-Victus S devices */
|
||||
if (!is_victus_s_thermal_profile())
|
||||
return 0;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY,
|
||||
HPWMI_GM, &fan_data, 4, sizeof(fan_data));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
fan_table = (struct victus_s_fan_table *)fan_data;
|
||||
if (fan_table->header.num_entries == 0 ||
|
||||
sizeof(struct victus_s_fan_table_header) +
|
||||
sizeof(struct victus_s_fan_table_entry) * fan_table->header.num_entries > sizeof(fan_data))
|
||||
return -EINVAL;
|
||||
|
||||
min_rpm = fan_table->entries[0].cpu_rpm;
|
||||
max_rpm = fan_table->entries[fan_table->header.num_entries - 1].cpu_rpm;
|
||||
gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm;
|
||||
priv->min_rpm = min_rpm;
|
||||
priv->max_rpm = max_rpm;
|
||||
priv->gpu_delta = gpu_delta;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hp_wmi_hwmon_init(void)
|
||||
{
|
||||
struct device *dev = &hp_wmi_platform_dev->dev;
|
||||
struct hp_wmi_hwmon_priv *priv;
|
||||
struct device *hwmon;
|
||||
int ret;
|
||||
|
||||
hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver,
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hp_wmi_setup_fan_settings(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
hwmon = devm_hwmon_device_register_with_info(dev, "hp", priv,
|
||||
&chip_info, NULL);
|
||||
|
||||
if (IS_ERR(hwmon)) {
|
||||
|
|
@ -2224,9 +2566,37 @@ static int hp_wmi_hwmon_init(void)
|
|||
return PTR_ERR(hwmon);
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&priv->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler);
|
||||
platform_set_drvdata(hp_wmi_platform_dev, priv);
|
||||
hp_wmi_apply_fan_settings(priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __init setup_active_thermal_profile_params(void)
|
||||
{
|
||||
const struct dmi_system_id *id;
|
||||
|
||||
/*
|
||||
* Currently only victus_s devices use the
|
||||
* active_thermal_profile_params
|
||||
*/
|
||||
id = dmi_first_match(victus_s_thermal_profile_boards);
|
||||
if (id) {
|
||||
/*
|
||||
* Marking this boolean is required to ensure that
|
||||
* is_victus_s_thermal_profile() behaves like a valid
|
||||
* wrapper.
|
||||
*/
|
||||
is_victus_s_board = true;
|
||||
active_thermal_profile_params = id->driver_data;
|
||||
if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
|
||||
pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n",
|
||||
dmi_get_system_info(DMI_BOARD_NAME));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int __init hp_wmi_init(void)
|
||||
{
|
||||
int event_capable = wmi_has_guid(HPWMI_EVENT_GUID);
|
||||
|
|
@ -2254,6 +2624,11 @@ static int __init hp_wmi_init(void)
|
|||
goto err_destroy_input;
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup active board's thermal profile parameters before
|
||||
* starting platform driver probe.
|
||||
*/
|
||||
setup_active_thermal_profile_params();
|
||||
err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup);
|
||||
if (err)
|
||||
goto err_unregister_device;
|
||||
|
|
|
|||
|
|
@ -776,16 +776,26 @@ static inline u64 adjust_lpm_residency(struct pmc *pmc, u32 offset,
|
|||
static int pmc_core_substate_res_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct pmc_dev *pmcdev = s->private;
|
||||
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
|
||||
const int lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
|
||||
u32 offset = pmc->map->lpm_residency_offset;
|
||||
int mode;
|
||||
unsigned int pmc_idx;
|
||||
|
||||
seq_printf(s, "%-10s %-15s\n", "Substate", "Residency");
|
||||
for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
|
||||
int lpm_adj_x2;
|
||||
struct pmc *pmc;
|
||||
u32 offset;
|
||||
u8 mode;
|
||||
|
||||
pmc_for_each_mode(mode, pmcdev) {
|
||||
seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode],
|
||||
adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2));
|
||||
pmc = pmcdev->pmcs[pmc_idx];
|
||||
if (!pmc)
|
||||
continue;
|
||||
|
||||
lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
|
||||
offset = pmc->map->lpm_residency_offset;
|
||||
|
||||
seq_printf(s, "pmc%u %10s %15s\n", pmc_idx, "Substate", "Residency");
|
||||
pmc_for_each_mode(mode, pmc) {
|
||||
seq_printf(s, "%15s %15llu\n", pmc_lpm_modes[mode],
|
||||
adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -838,10 +848,11 @@ static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index,
|
|||
enum header_type type)
|
||||
{
|
||||
struct pmc_dev *pmcdev = s->private;
|
||||
int mode;
|
||||
struct pmc *pmc = pmcdev->pmcs[pmc_index];
|
||||
u8 mode;
|
||||
|
||||
seq_printf(s, "%40s |", "Element");
|
||||
pmc_for_each_mode(mode, pmcdev)
|
||||
pmc_for_each_mode(mode, pmc)
|
||||
seq_printf(s, " %9s |", pmc_lpm_modes[mode]);
|
||||
|
||||
if (type == HEADER_STATUS) {
|
||||
|
|
@ -880,14 +891,14 @@ static int pmc_core_substate_blk_req_show(struct seq_file *s, void *unused)
|
|||
const struct pmc_bit_map *map;
|
||||
|
||||
for (map = maps[r_idx]; map->name; map++) {
|
||||
int mode;
|
||||
u8 mode;
|
||||
|
||||
if (!map->blk)
|
||||
continue;
|
||||
|
||||
counter = pmc_core_reg_read(pmc, offset);
|
||||
seq_printf(s, "pmc%u: %34s |", pmc_idx, map->name);
|
||||
pmc_for_each_mode(mode, pmcdev) {
|
||||
pmc_for_each_mode(mode, pmc) {
|
||||
bool required = *lpm_req_regs & BIT(mode);
|
||||
|
||||
seq_printf(s, " %9s |", required ? "Required" : " ");
|
||||
|
|
@ -953,14 +964,15 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
|
|||
u32 lpm_status;
|
||||
u32 lpm_status_live;
|
||||
const struct pmc_bit_map *map;
|
||||
int mode, i, len = 32;
|
||||
int i, len = 32;
|
||||
u8 mode;
|
||||
|
||||
/*
|
||||
* Capture the requirements and create a mask so that we only
|
||||
* show an element if it's required for at least one of the
|
||||
* enabled low power modes
|
||||
*/
|
||||
pmc_for_each_mode(mode, pmcdev)
|
||||
pmc_for_each_mode(mode, pmc)
|
||||
req_mask |= lpm_req_regs[mp + (mode * num_maps)];
|
||||
|
||||
/* Get the last latched status for this map */
|
||||
|
|
@ -986,7 +998,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
|
|||
seq_printf(s, "pmc%d: %34s |", pmc_idx, map[i].name);
|
||||
|
||||
/* Loop over the enabled states and display if required */
|
||||
pmc_for_each_mode(mode, pmcdev) {
|
||||
pmc_for_each_mode(mode, pmc) {
|
||||
bool required = lpm_req_regs[mp + (mode * num_maps)] &
|
||||
bit_mask;
|
||||
seq_printf(s, " %9s |", required ? "Required" : " ");
|
||||
|
|
@ -1065,7 +1077,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
|
|||
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
|
||||
bool c10;
|
||||
u32 reg;
|
||||
int mode;
|
||||
u8 mode;
|
||||
|
||||
reg = pmc_core_reg_read(pmc, pmc->map->lpm_sts_latch_en_offset);
|
||||
if (reg & LPM_STS_LATCH_MODE) {
|
||||
|
|
@ -1076,7 +1088,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
|
|||
c10 = true;
|
||||
}
|
||||
|
||||
pmc_for_each_mode(mode, pmcdev) {
|
||||
pmc_for_each_mode(mode, pmc) {
|
||||
if ((BIT(mode) & reg) && !c10)
|
||||
seq_printf(s, " [%s]", pmc_lpm_modes[mode]);
|
||||
else
|
||||
|
|
@ -1097,8 +1109,9 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
|
|||
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
|
||||
bool clear = false, c10 = false;
|
||||
unsigned char buf[8];
|
||||
int m, mode;
|
||||
int mode;
|
||||
u32 reg;
|
||||
u8 m;
|
||||
|
||||
if (count > sizeof(buf) - 1)
|
||||
return -EINVAL;
|
||||
|
|
@ -1115,7 +1128,7 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
|
|||
mode = sysfs_match_string(pmc_lpm_modes, buf);
|
||||
|
||||
/* Check string matches enabled mode */
|
||||
pmc_for_each_mode(m, pmcdev)
|
||||
pmc_for_each_mode(m, pmc)
|
||||
if (mode == m)
|
||||
break;
|
||||
|
||||
|
|
@ -1211,15 +1224,15 @@ static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order)
|
|||
return true;
|
||||
}
|
||||
|
||||
void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
|
||||
static void pmc_core_pmc_get_low_power_modes(struct pmc_dev *pmcdev, struct pmc *pmc)
|
||||
{
|
||||
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
|
||||
u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI;
|
||||
u8 mode_order[LPM_MAX_NUM_MODES];
|
||||
u32 lpm_pri;
|
||||
u32 lpm_en;
|
||||
u8 mode;
|
||||
unsigned int i;
|
||||
int mode, p;
|
||||
int p;
|
||||
|
||||
/* Use LPM Maps to indicate support for substates */
|
||||
if (!pmc->map->lpm_num_maps)
|
||||
|
|
@ -1230,12 +1243,11 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
|
|||
* Lower byte is enough to cover the number of lpm modes for all
|
||||
* platforms and hence mask the upper 3 bytes.
|
||||
*/
|
||||
pmcdev->num_lpm_modes = hweight32(lpm_en & 0xFF);
|
||||
pmc->num_lpm_modes = hweight32(lpm_en & 0xFF);
|
||||
|
||||
/* Read 32 bit LPM_PRI register */
|
||||
lpm_pri = pmc_core_reg_read(pmc, pmc->map->lpm_priority_offset);
|
||||
|
||||
|
||||
/*
|
||||
* If lpm_pri value passes verification, then override the default
|
||||
* modes here. Otherwise stick with the default.
|
||||
|
|
@ -1254,12 +1266,27 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
|
|||
*/
|
||||
i = 0;
|
||||
for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) {
|
||||
int mode = pri_order[p];
|
||||
u8 mode = pri_order[p];
|
||||
|
||||
if (!(BIT(mode) & lpm_en))
|
||||
continue;
|
||||
|
||||
pmcdev->lpm_en_modes[i++] = mode;
|
||||
pmc->lpm_en_modes[i++] = mode;
|
||||
}
|
||||
}
|
||||
|
||||
static void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
|
||||
{
|
||||
unsigned int pmc_idx;
|
||||
|
||||
for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) {
|
||||
struct pmc *pmc;
|
||||
|
||||
pmc = pmcdev->pmcs[pmc_idx];
|
||||
if (!pmc)
|
||||
continue;
|
||||
|
||||
pmc_core_pmc_get_low_power_modes(pmcdev, pmc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1490,8 +1517,8 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel
|
|||
{
|
||||
const u8 *lpm_indices;
|
||||
int num_maps, mode_offset = 0;
|
||||
int ret, mode;
|
||||
int lpm_size;
|
||||
int ret, lpm_size;
|
||||
u8 mode;
|
||||
|
||||
lpm_indices = pmc->map->lpm_reg_index;
|
||||
num_maps = pmc->map->lpm_num_maps;
|
||||
|
|
@ -1504,7 +1531,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel
|
|||
return -ENOMEM;
|
||||
|
||||
mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET;
|
||||
pmc_for_each_mode(mode, pmcdev) {
|
||||
pmc_for_each_mode(mode, pmc) {
|
||||
u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps);
|
||||
int m;
|
||||
|
||||
|
|
|
|||
|
|
@ -423,6 +423,8 @@ struct pmc_info {
|
|||
* specific attributes
|
||||
* @lpm_req_regs: List of substate requirements
|
||||
* @ltr_ign: Holds LTR ignore data while suspended
|
||||
* @num_lpm_modes: Count of enabled modes
|
||||
* @lpm_en_modes: Array of enabled modes from lowest to highest priority
|
||||
*
|
||||
* pmc contains info about one power management controller device.
|
||||
*/
|
||||
|
|
@ -432,6 +434,8 @@ struct pmc {
|
|||
const struct pmc_reg_map *map;
|
||||
u32 *lpm_req_regs;
|
||||
u32 ltr_ign;
|
||||
u8 num_lpm_modes;
|
||||
u8 lpm_en_modes[LPM_MAX_NUM_MODES];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -446,8 +450,6 @@ struct pmc {
|
|||
* @pkgc_res_cnt: Array of PKGC residency counters
|
||||
* @num_of_pkgc: Number of PKGC
|
||||
* @s0ix_counter: S0ix residency (step adjusted)
|
||||
* @num_lpm_modes: Count of enabled modes
|
||||
* @lpm_en_modes: Array of enabled modes from lowest to highest priority
|
||||
* @suspend: Function to perform platform specific suspend
|
||||
* @resume: Function to perform platform specific resume
|
||||
*
|
||||
|
|
@ -462,8 +464,6 @@ struct pmc_dev {
|
|||
struct mutex lock; /* generic mutex lock for PMC Core */
|
||||
|
||||
u64 s0ix_counter;
|
||||
int num_lpm_modes;
|
||||
int lpm_en_modes[LPM_MAX_NUM_MODES];
|
||||
void (*suspend)(struct pmc_dev *pmcdev);
|
||||
int (*resume)(struct pmc_dev *pmcdev);
|
||||
|
||||
|
|
@ -535,7 +535,6 @@ int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore);
|
|||
|
||||
int pmc_core_resume_common(struct pmc_dev *pmcdev);
|
||||
int get_primary_reg_base(struct pmc *pmc);
|
||||
void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev);
|
||||
void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids);
|
||||
void pmc_core_set_device_d3(unsigned int device);
|
||||
|
||||
|
|
@ -563,10 +562,10 @@ int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc,
|
|||
extern const struct file_operations pmc_core_substate_req_regs_fops;
|
||||
extern const struct file_operations pmc_core_substate_blk_req_fops;
|
||||
|
||||
#define pmc_for_each_mode(mode, pmcdev) \
|
||||
#define pmc_for_each_mode(mode, pmc) \
|
||||
for (unsigned int __i = 0, __cond; \
|
||||
__cond = __i < (pmcdev)->num_lpm_modes, \
|
||||
__cond && ((mode) = (pmcdev)->lpm_en_modes[__i]), \
|
||||
__cond = __i < (pmc)->num_lpm_modes, \
|
||||
__cond && ((mode) = (pmc)->lpm_en_modes[__i]), \
|
||||
__cond; \
|
||||
__i++)
|
||||
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ guid_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|||
{
|
||||
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "0x%x\n", entry->guid);
|
||||
return sysfs_emit(buf, "0x%x\n", entry->guid);
|
||||
}
|
||||
static DEVICE_ATTR_RO(guid);
|
||||
|
||||
|
|
@ -149,7 +149,7 @@ static ssize_t size_show(struct device *dev, struct device_attribute *attr,
|
|||
{
|
||||
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%zu\n", entry->size);
|
||||
return sysfs_emit(buf, "%zu\n", entry->size);
|
||||
}
|
||||
static DEVICE_ATTR_RO(size);
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ offset_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|||
{
|
||||
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr));
|
||||
return sysfs_emit(buf, "%lu\n", offset_in_page(entry->base_addr));
|
||||
}
|
||||
static DEVICE_ATTR_RO(offset);
|
||||
|
||||
|
|
|
|||
|
|
@ -612,6 +612,9 @@ static long isst_if_core_power_state(void __user *argp)
|
|||
return -EINVAL;
|
||||
|
||||
if (core_power.get_set) {
|
||||
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
_write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET,
|
||||
SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE)
|
||||
_write_cp_info("cp_prio_type", core_power.priority_type, SST_CP_CONTROL_OFFSET,
|
||||
|
|
@ -656,7 +659,7 @@ static long isst_if_clos_param(void __user *argp)
|
|||
return -EINVAL;
|
||||
|
||||
if (clos_param.get_set) {
|
||||
if (power_domain_info->write_blocked)
|
||||
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
_write_cp_info("clos.min_freq", clos_param.min_freq_mhz,
|
||||
|
|
@ -748,7 +751,8 @@ static long isst_if_clos_assoc(void __user *argp)
|
|||
|
||||
power_domain_info = &sst_inst->power_domain_info[part][punit_id];
|
||||
|
||||
if (assoc_cmds.get_set && power_domain_info->write_blocked)
|
||||
if (assoc_cmds.get_set && (power_domain_info->write_blocked ||
|
||||
!capable(CAP_SYS_ADMIN)))
|
||||
return -EPERM;
|
||||
|
||||
offset = SST_CLOS_ASSOC_0_OFFSET +
|
||||
|
|
@ -925,7 +929,7 @@ static int isst_if_set_perf_level(void __user *argp)
|
|||
if (!power_domain_info)
|
||||
return -EINVAL;
|
||||
|
||||
if (power_domain_info->write_blocked)
|
||||
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!(power_domain_info->pp_header.allowed_level_mask & BIT(perf_level.level)))
|
||||
|
|
@ -985,7 +989,7 @@ static int isst_if_set_perf_feature(void __user *argp)
|
|||
if (!power_domain_info)
|
||||
return -EINVAL;
|
||||
|
||||
if (power_domain_info->write_blocked)
|
||||
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
_write_pp_info("perf_feature", perf_feature.feature, SST_PP_CONTROL_OFFSET,
|
||||
|
|
@ -1717,58 +1721,87 @@ void tpmi_sst_dev_remove(struct auxiliary_device *auxdev)
|
|||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_remove, "INTEL_TPMI_SST");
|
||||
|
||||
#define SST_PP_CAP_CP_ENABLE BIT(0)
|
||||
#define SST_PP_CAP_PP_ENABLE BIT(1)
|
||||
|
||||
void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
|
||||
struct tpmi_per_power_domain_info *power_domain_info;
|
||||
struct tpmi_per_power_domain_info *power_domain_info, *pd_info;
|
||||
struct oobmsm_plat_info *plat_info;
|
||||
void __iomem *cp_base;
|
||||
int num_resources, i;
|
||||
|
||||
plat_info = tpmi_get_platform_data(auxdev);
|
||||
if (!plat_info)
|
||||
return;
|
||||
|
||||
power_domain_info = tpmi_sst->power_domain_info[plat_info->partition];
|
||||
num_resources = tpmi_sst->number_of_power_domains[plat_info->partition];
|
||||
|
||||
cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
|
||||
power_domain_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET);
|
||||
for (i = 0; i < num_resources; i++) {
|
||||
pd_info = &power_domain_info[i];
|
||||
if (!pd_info || !pd_info->sst_base)
|
||||
continue;
|
||||
|
||||
memcpy_fromio(power_domain_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET,
|
||||
sizeof(power_domain_info->saved_clos_configs));
|
||||
if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE))
|
||||
goto process_pp_suspend;
|
||||
|
||||
memcpy_fromio(power_domain_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET,
|
||||
sizeof(power_domain_info->saved_clos_assocs));
|
||||
cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset;
|
||||
pd_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET);
|
||||
memcpy_fromio(pd_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET,
|
||||
sizeof(pd_info->saved_clos_configs));
|
||||
memcpy_fromio(pd_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET,
|
||||
sizeof(pd_info->saved_clos_assocs));
|
||||
|
||||
power_domain_info->saved_pp_control = readq(power_domain_info->sst_base +
|
||||
power_domain_info->sst_header.pp_offset +
|
||||
SST_PP_CONTROL_OFFSET);
|
||||
process_pp_suspend:
|
||||
if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE))
|
||||
continue;
|
||||
|
||||
pd_info->saved_pp_control = readq(pd_info->sst_base +
|
||||
pd_info->sst_header.pp_offset +
|
||||
SST_PP_CONTROL_OFFSET);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_suspend, "INTEL_TPMI_SST");
|
||||
|
||||
void tpmi_sst_dev_resume(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
|
||||
struct tpmi_per_power_domain_info *power_domain_info;
|
||||
struct tpmi_per_power_domain_info *power_domain_info, *pd_info;
|
||||
struct oobmsm_plat_info *plat_info;
|
||||
void __iomem *cp_base;
|
||||
int num_resources, i;
|
||||
|
||||
plat_info = tpmi_get_platform_data(auxdev);
|
||||
if (!plat_info)
|
||||
return;
|
||||
|
||||
power_domain_info = tpmi_sst->power_domain_info[plat_info->partition];
|
||||
num_resources = tpmi_sst->number_of_power_domains[plat_info->partition];
|
||||
|
||||
cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
|
||||
writeq(power_domain_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET);
|
||||
for (i = 0; i < num_resources; i++) {
|
||||
pd_info = &power_domain_info[i];
|
||||
if (!pd_info || !pd_info->sst_base)
|
||||
continue;
|
||||
|
||||
memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, power_domain_info->saved_clos_configs,
|
||||
sizeof(power_domain_info->saved_clos_configs));
|
||||
if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE))
|
||||
goto process_pp_resume;
|
||||
|
||||
memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, power_domain_info->saved_clos_assocs,
|
||||
sizeof(power_domain_info->saved_clos_assocs));
|
||||
cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset;
|
||||
writeq(pd_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET);
|
||||
memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, pd_info->saved_clos_configs,
|
||||
sizeof(pd_info->saved_clos_configs));
|
||||
memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, pd_info->saved_clos_assocs,
|
||||
sizeof(pd_info->saved_clos_assocs));
|
||||
|
||||
writeq(power_domain_info->saved_pp_control, power_domain_info->sst_base +
|
||||
power_domain_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
|
||||
process_pp_resume:
|
||||
if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE))
|
||||
continue;
|
||||
|
||||
writeq(pd_info->saved_pp_control, power_domain_info->sst_base +
|
||||
pd_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_resume, "INTEL_TPMI_SST");
|
||||
|
||||
|
|
|
|||
|
|
@ -26,21 +26,21 @@ static ssize_t show_domain_id(struct kobject *kobj, struct kobj_attribute *attr,
|
|||
{
|
||||
struct uncore_data *data = container_of(attr, struct uncore_data, domain_id_kobj_attr);
|
||||
|
||||
return sprintf(buf, "%u\n", data->domain_id);
|
||||
return sysfs_emit(buf, "%u\n", data->domain_id);
|
||||
}
|
||||
|
||||
static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_kobj_attr);
|
||||
|
||||
return sprintf(buf, "%u\n", data->cluster_id);
|
||||
return sysfs_emit(buf, "%u\n", data->cluster_id);
|
||||
}
|
||||
|
||||
static ssize_t show_package_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
struct uncore_data *data = container_of(attr, struct uncore_data, package_id_kobj_attr);
|
||||
|
||||
return sprintf(buf, "%u\n", data->package_id);
|
||||
return sysfs_emit(buf, "%u\n", data->package_id);
|
||||
}
|
||||
|
||||
#define MAX_UNCORE_AGENT_TYPES 4
|
||||
|
|
@ -77,7 +77,7 @@ static ssize_t show_attr(struct uncore_data *data, char *buf, enum uncore_index
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%u\n", value);
|
||||
return sysfs_emit(buf, "%u\n", value);
|
||||
}
|
||||
|
||||
static ssize_t store_attr(struct uncore_data *data, const char *buf, ssize_t count,
|
||||
|
|
@ -269,9 +269,10 @@ int uncore_freq_add_entry(struct uncore_data *data, int cpu)
|
|||
goto uncore_unlock;
|
||||
|
||||
data->instance_id = ret;
|
||||
sprintf(data->name, "uncore%02d", ret);
|
||||
scnprintf(data->name, sizeof(data->name), "uncore%02d", ret);
|
||||
} else {
|
||||
sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id);
|
||||
scnprintf(data->name, sizeof(data->name), "package_%02d_die_%02d",
|
||||
data->package_id, data->die_id);
|
||||
}
|
||||
|
||||
uncore_read(data, &data->initial_min_freq_khz, UNCORE_INDEX_MIN_FREQ);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
* https://slimbootloader.github.io/security/firmware-update.html
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
|
@ -25,41 +24,35 @@
|
|||
|
||||
static int get_fwu_request(struct device *dev, u32 *out)
|
||||
{
|
||||
union acpi_object *obj;
|
||||
struct wmi_buffer buffer;
|
||||
__le32 *result;
|
||||
int ret;
|
||||
|
||||
obj = wmidev_block_query(to_wmi_device(dev), 0);
|
||||
if (!obj)
|
||||
return -ENODEV;
|
||||
ret = wmidev_query_block(to_wmi_device(dev), 0, &buffer);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (obj->type != ACPI_TYPE_INTEGER) {
|
||||
dev_warn(dev, "wmidev_block_query returned invalid value\n");
|
||||
kfree(obj);
|
||||
return -EINVAL;
|
||||
if (buffer.length < sizeof(*result)) {
|
||||
kfree(buffer.data);
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
*out = obj->integer.value;
|
||||
kfree(obj);
|
||||
result = buffer.data;
|
||||
*out = le32_to_cpu(*result);
|
||||
kfree(result);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_fwu_request(struct device *dev, u32 in)
|
||||
{
|
||||
struct acpi_buffer input;
|
||||
acpi_status status;
|
||||
u32 value;
|
||||
__le32 value = cpu_to_le32(in);
|
||||
struct wmi_buffer buffer = {
|
||||
.length = sizeof(value),
|
||||
.data = &value,
|
||||
};
|
||||
|
||||
value = in;
|
||||
input.length = sizeof(u32);
|
||||
input.pointer = &value;
|
||||
|
||||
status = wmidev_block_set(to_wmi_device(dev), 0, &input);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(dev, "wmidev_block_set failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return wmidev_set_block(to_wmi_device(dev), 0, &buffer);
|
||||
}
|
||||
|
||||
static ssize_t firmware_update_request_show(struct device *dev,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/hex.h>
|
||||
|
|
@ -24,24 +23,21 @@ static ssize_t force_power_store(struct device *dev,
|
|||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct acpi_buffer input;
|
||||
acpi_status status;
|
||||
struct wmi_buffer buffer;
|
||||
int ret;
|
||||
u8 mode;
|
||||
|
||||
input.length = sizeof(u8);
|
||||
input.pointer = &mode;
|
||||
buffer.length = sizeof(mode);
|
||||
buffer.data = &mode;
|
||||
|
||||
mode = hex_to_bin(buf[0]);
|
||||
dev_dbg(dev, "force_power: storing %#x\n", mode);
|
||||
if (mode == 0 || mode == 1) {
|
||||
status = wmidev_evaluate_method(to_wmi_device(dev), 0, 1, &input, NULL);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_dbg(dev, "force_power: failed to evaluate ACPI method\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
} else {
|
||||
dev_dbg(dev, "force_power: unsupported mode\n");
|
||||
if (mode > 1)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = wmidev_invoke_method(to_wmi_device(dev), 0, 1, &buffer, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ config YT2_1380
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called lenovo-yogabook.
|
||||
|
||||
config LENOVO_WMI_DATA01
|
||||
config LENOVO_WMI_CAPDATA
|
||||
tristate
|
||||
depends on ACPI_WMI
|
||||
|
||||
|
|
@ -263,8 +263,9 @@ config LENOVO_WMI_GAMEZONE
|
|||
config LENOVO_WMI_TUNING
|
||||
tristate "Lenovo Other Mode WMI Driver"
|
||||
depends on ACPI_WMI
|
||||
select HWMON
|
||||
select FW_ATTR_CLASS
|
||||
select LENOVO_WMI_DATA01
|
||||
select LENOVO_WMI_CAPDATA
|
||||
select LENOVO_WMI_EVENTS
|
||||
select LENOVO_WMI_HELPERS
|
||||
help
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
|
|||
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
|
||||
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
|
||||
lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o
|
||||
lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) += wmi-capdata01.o
|
||||
lenovo-target-$(CONFIG_LENOVO_WMI_CAPDATA) += wmi-capdata.o
|
||||
lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o
|
||||
lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o
|
||||
lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += wmi-gamezone.o
|
||||
|
|
|
|||
|
|
@ -219,38 +219,32 @@ MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
|
|||
static bool allow_v4_dytc;
|
||||
module_param(allow_v4_dytc, bool, 0444);
|
||||
MODULE_PARM_DESC(allow_v4_dytc,
|
||||
"Enable DYTC version 4 platform-profile support. "
|
||||
"If you need this please report this to: platform-driver-x86@vger.kernel.org");
|
||||
"Enable DYTC version 4 platform-profile support. If you need this please report this to: platform-driver-x86@vger.kernel.org");
|
||||
|
||||
static bool hw_rfkill_switch;
|
||||
module_param(hw_rfkill_switch, bool, 0444);
|
||||
MODULE_PARM_DESC(hw_rfkill_switch,
|
||||
"Enable rfkill support for laptops with a hw on/off wifi switch/slider. "
|
||||
"If you need this please report this to: platform-driver-x86@vger.kernel.org");
|
||||
"Enable rfkill support for laptops with a hw on/off wifi switch/slider. If you need this please report this to: platform-driver-x86@vger.kernel.org");
|
||||
|
||||
static bool set_fn_lock_led;
|
||||
module_param(set_fn_lock_led, bool, 0444);
|
||||
MODULE_PARM_DESC(set_fn_lock_led,
|
||||
"Enable driver based updates of the fn-lock LED on fn-lock changes. "
|
||||
"If you need this please report this to: platform-driver-x86@vger.kernel.org");
|
||||
"Enable driver based updates of the fn-lock LED on fn-lock changes. If you need this please report this to: platform-driver-x86@vger.kernel.org");
|
||||
|
||||
static bool ctrl_ps2_aux_port;
|
||||
module_param(ctrl_ps2_aux_port, bool, 0444);
|
||||
MODULE_PARM_DESC(ctrl_ps2_aux_port,
|
||||
"Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. "
|
||||
"If you need this please report this to: platform-driver-x86@vger.kernel.org");
|
||||
"Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. If you need this please report this to: platform-driver-x86@vger.kernel.org");
|
||||
|
||||
static bool touchpad_ctrl_via_ec;
|
||||
module_param(touchpad_ctrl_via_ec, bool, 0444);
|
||||
MODULE_PARM_DESC(touchpad_ctrl_via_ec,
|
||||
"Enable registering a 'touchpad' sysfs-attribute which can be used to manually "
|
||||
"tell the EC to enable/disable the touchpad. This may not work on all models.");
|
||||
"Enable registering a 'touchpad' sysfs-attribute which can be used to manually tell the EC to enable/disable the touchpad. This may not work on all models.");
|
||||
|
||||
static bool ymc_ec_trigger __read_mostly;
|
||||
module_param(ymc_ec_trigger, bool, 0444);
|
||||
MODULE_PARM_DESC(ymc_ec_trigger,
|
||||
"Enable EC triggering work-around to force emitting tablet mode events. "
|
||||
"If you need this please report this to: platform-driver-x86@vger.kernel.org");
|
||||
"Enable EC triggering work-around to force emitting tablet mode events. If you need this please report this to: platform-driver-x86@vger.kernel.org");
|
||||
|
||||
/*
|
||||
* shared data
|
||||
|
|
@ -1446,7 +1440,7 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv)
|
|||
if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value))
|
||||
return;
|
||||
|
||||
for_each_set_bit (bit, &value, 16) {
|
||||
for_each_set_bit(bit, &value, 16) {
|
||||
switch (bit) {
|
||||
case 6: /* Z570 */
|
||||
case 0: /* Z580 */
|
||||
|
|
@ -1706,11 +1700,10 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
|
|||
if (WARN_ON(priv->kbd_bl.initialized))
|
||||
return -EEXIST;
|
||||
|
||||
if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) {
|
||||
if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type))
|
||||
priv->kbd_bl.led.max_brightness = 2;
|
||||
} else {
|
||||
else
|
||||
priv->kbd_bl.led.max_brightness = 1;
|
||||
}
|
||||
|
||||
brightness = ideapad_kbd_bl_brightness_get(priv);
|
||||
if (brightness < 0)
|
||||
|
|
@ -1752,7 +1745,7 @@ static enum led_brightness ideapad_fn_lock_led_cdev_get(struct led_classdev *led
|
|||
}
|
||||
|
||||
static int ideapad_fn_lock_led_cdev_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, fn_lock.led);
|
||||
|
||||
|
|
@ -1928,7 +1921,7 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
|
|||
|
||||
vpc1 = (vpc2 << 8) | vpc1;
|
||||
|
||||
for_each_set_bit (bit, &vpc1, 16) {
|
||||
for_each_set_bit(bit, &vpc1, 16) {
|
||||
switch (bit) {
|
||||
case 13:
|
||||
case 11:
|
||||
|
|
@ -2142,14 +2135,14 @@ static const enum power_supply_property ideapad_power_supply_props[] = {
|
|||
}
|
||||
|
||||
DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v1,
|
||||
(BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
|
||||
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
|
||||
(BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
|
||||
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
|
||||
);
|
||||
|
||||
DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v2,
|
||||
(BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
|
||||
BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
|
||||
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
|
||||
(BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
|
||||
BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
|
||||
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
|
||||
);
|
||||
|
||||
static int ideapad_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dmi.h>
|
||||
|
|
@ -11080,6 +11081,206 @@ static const struct attribute_group auxmac_attr_group = {
|
|||
.attrs = auxmac_attributes,
|
||||
};
|
||||
|
||||
/*************************************************************************
|
||||
* HWDD subdriver, for the Lenovo Hardware Damage Detection feature.
|
||||
*/
|
||||
|
||||
#define HWDD_GET_DMG_USBC 0x80000001
|
||||
#define HWDD_GET_CAP 0
|
||||
#define HWDD_NOT_SUPPORTED BIT(31)
|
||||
#define HWDD_SUPPORT_USBC BIT(0)
|
||||
|
||||
#define PORT_STATUS GENMASK(7, 4)
|
||||
#define LID_STATUS GENMASK(11, 8)
|
||||
#define BASE_STATUS GENMASK(15, 12)
|
||||
#define POS_STATUS GENMASK(3, 2)
|
||||
#define PANEL_STATUS GENMASK(1, 0)
|
||||
|
||||
#define PORT_DETAIL_OFFSET 16
|
||||
|
||||
#define PANEL_TOP 0
|
||||
#define PANEL_BASE 1
|
||||
#define PANEL_LEFT 2
|
||||
#define PANEL_RIGHT 3
|
||||
|
||||
#define POS_LEFT 0
|
||||
#define POS_CENTER 1
|
||||
#define POS_RIGHT 2
|
||||
|
||||
#define NUM_PORTS 4
|
||||
|
||||
static bool hwdd_support_available;
|
||||
static bool ucdd_supported;
|
||||
|
||||
static int hwdd_command(int command, int *output)
|
||||
{
|
||||
acpi_handle hwdd_handle;
|
||||
|
||||
if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "HWDD", &hwdd_handle)))
|
||||
return -ENODEV;
|
||||
|
||||
if (!acpi_evalf(hwdd_handle, output, NULL, "dd", command))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool display_damage(char *buf, int *count, char *type, unsigned int dmg_status)
|
||||
{
|
||||
unsigned char lid_status, base_status, port_status;
|
||||
unsigned char loc_status, pos_status, panel_status;
|
||||
bool damage_detected = false;
|
||||
int i;
|
||||
|
||||
port_status = FIELD_GET(PORT_STATUS, dmg_status);
|
||||
lid_status = FIELD_GET(LID_STATUS, dmg_status);
|
||||
base_status = FIELD_GET(BASE_STATUS, dmg_status);
|
||||
for (i = 0; i < NUM_PORTS; i++) {
|
||||
if (!(dmg_status & BIT(i)) || !(port_status & BIT(i)))
|
||||
continue;
|
||||
|
||||
*count += sysfs_emit_at(buf, *count, "%s: ", type);
|
||||
loc_status = (dmg_status >> (PORT_DETAIL_OFFSET + (4 * i))) & 0xF;
|
||||
pos_status = FIELD_GET(POS_STATUS, loc_status);
|
||||
panel_status = FIELD_GET(PANEL_STATUS, loc_status);
|
||||
|
||||
if (lid_status & BIT(i))
|
||||
*count += sysfs_emit_at(buf, *count, "Lid, ");
|
||||
if (base_status & BIT(i))
|
||||
*count += sysfs_emit_at(buf, *count, "Base, ");
|
||||
|
||||
switch (pos_status) {
|
||||
case PANEL_TOP:
|
||||
*count += sysfs_emit_at(buf, *count, "Top, ");
|
||||
break;
|
||||
case PANEL_BASE:
|
||||
*count += sysfs_emit_at(buf, *count, "Bottom, ");
|
||||
break;
|
||||
case PANEL_LEFT:
|
||||
*count += sysfs_emit_at(buf, *count, "Left, ");
|
||||
break;
|
||||
case PANEL_RIGHT:
|
||||
*count += sysfs_emit_at(buf, *count, "Right, ");
|
||||
break;
|
||||
default:
|
||||
pr_err("Unexpected value %d in switch statement\n", pos_status);
|
||||
}
|
||||
|
||||
switch (panel_status) {
|
||||
case POS_LEFT:
|
||||
*count += sysfs_emit_at(buf, *count, "Left port\n");
|
||||
break;
|
||||
case POS_CENTER:
|
||||
*count += sysfs_emit_at(buf, *count, "Center port\n");
|
||||
break;
|
||||
case POS_RIGHT:
|
||||
*count += sysfs_emit_at(buf, *count, "Right port\n");
|
||||
break;
|
||||
default:
|
||||
*count += sysfs_emit_at(buf, *count, "Undefined\n");
|
||||
break;
|
||||
}
|
||||
damage_detected = true;
|
||||
}
|
||||
return damage_detected;
|
||||
}
|
||||
|
||||
/* sysfs type-c damage detection detail */
|
||||
static ssize_t hwdd_detail_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
unsigned int damage_status;
|
||||
int err, count = 0;
|
||||
|
||||
if (!ucdd_supported)
|
||||
return -ENODEV;
|
||||
|
||||
/* Get USB TYPE-C damage status */
|
||||
err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (!display_damage(buf, &count, "Type-C", damage_status))
|
||||
count += sysfs_emit_at(buf, count, "No damage detected\n");
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* sysfs type-c damage detection capability */
|
||||
static ssize_t hwdd_status_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
unsigned int damage_status, port_status;
|
||||
int err, i;
|
||||
|
||||
if (!ucdd_supported)
|
||||
return -ENODEV;
|
||||
|
||||
/* Get USB TYPE-C damage status */
|
||||
err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
port_status = FIELD_GET(PORT_STATUS, damage_status);
|
||||
for (i = 0; i < NUM_PORTS; i++) {
|
||||
if (!(damage_status & BIT(i)))
|
||||
continue;
|
||||
if (port_status & BIT(i))
|
||||
return sysfs_emit(buf, "1\n");
|
||||
}
|
||||
|
||||
return sysfs_emit(buf, "0\n");
|
||||
}
|
||||
static DEVICE_ATTR_RO(hwdd_status);
|
||||
static DEVICE_ATTR_RO(hwdd_detail);
|
||||
|
||||
static struct attribute *hwdd_attributes[] = {
|
||||
&dev_attr_hwdd_status.attr,
|
||||
&dev_attr_hwdd_detail.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static umode_t hwdd_attr_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int n)
|
||||
{
|
||||
return hwdd_support_available ? attr->mode : 0;
|
||||
}
|
||||
|
||||
static const struct attribute_group hwdd_attr_group = {
|
||||
.is_visible = hwdd_attr_is_visible,
|
||||
.attrs = hwdd_attributes,
|
||||
};
|
||||
|
||||
static int tpacpi_hwdd_init(struct ibm_init_struct *iibm)
|
||||
{
|
||||
int err, output;
|
||||
|
||||
/* Below command checks the HWDD damage capability */
|
||||
err = hwdd_command(HWDD_GET_CAP, &output);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (!(output & HWDD_NOT_SUPPORTED))
|
||||
return -ENODEV;
|
||||
|
||||
hwdd_support_available = true;
|
||||
|
||||
/*
|
||||
* BIT(0) is assigned to check capability of damage detection is
|
||||
* supported for USB Type-C port or not.
|
||||
*/
|
||||
if (output & HWDD_SUPPORT_USBC)
|
||||
ucdd_supported = true;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct ibm_struct hwdd_driver_data = {
|
||||
.name = "hwdd",
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
static struct attribute *tpacpi_driver_attributes[] = {
|
||||
|
|
@ -11139,6 +11340,7 @@ static const struct attribute_group *tpacpi_groups[] = {
|
|||
&kbdlang_attr_group,
|
||||
&dprc_attr_group,
|
||||
&auxmac_attr_group,
|
||||
&hwdd_attr_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
|
@ -11752,6 +11954,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
|
|||
.init = auxmac_init,
|
||||
.data = &auxmac_data,
|
||||
},
|
||||
{
|
||||
.init = tpacpi_hwdd_init,
|
||||
.data = &hwdd_driver_data,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init set_ibm_param(const char *val, const struct kernel_param *kp)
|
||||
|
|
|
|||
829
drivers/platform/x86/lenovo/wmi-capdata.c
Normal file
829
drivers/platform/x86/lenovo/wmi-capdata.c
Normal file
|
|
@ -0,0 +1,829 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Lenovo Capability Data WMI Data Block driver.
|
||||
*
|
||||
* Lenovo Capability Data provides information on tunable attributes used by
|
||||
* the "Other Mode" WMI interface.
|
||||
*
|
||||
* Capability Data 00 includes if the attribute is supported by the hardware,
|
||||
* and the default_value. All attributes are independent of thermal modes.
|
||||
*
|
||||
* Capability Data 01 includes if the attribute is supported by the hardware,
|
||||
* and the default_value, max_value, min_value, and step increment. Each
|
||||
* attribute has multiple pages, one for each of the thermal modes managed by
|
||||
* the Gamezone interface.
|
||||
*
|
||||
* Fan Test Data includes the max/min fan speed RPM for each fan. This is
|
||||
* reference data for self-test. If the fan is in good condition, it is capable
|
||||
* to spin faster than max RPM or slower than min RPM.
|
||||
*
|
||||
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
|
||||
* - Initial implementation (formerly named lenovo-wmi-capdata01)
|
||||
*
|
||||
* Copyright (C) 2025 Rong Zhang <i@rong.moe>
|
||||
* - Unified implementation
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/gfp_types.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/mutex_types.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include "wmi-capdata.h"
|
||||
|
||||
#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E"
|
||||
#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
|
||||
#define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21"
|
||||
|
||||
#define ACPI_AC_CLASS "ac_adapter"
|
||||
#define ACPI_AC_NOTIFY_STATUS 0x80
|
||||
|
||||
#define LWMI_FEATURE_ID_FAN_TEST 0x05
|
||||
|
||||
#define LWMI_ATTR_ID_FAN_TEST \
|
||||
(FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
|
||||
FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST))
|
||||
|
||||
enum lwmi_cd_type {
|
||||
LENOVO_CAPABILITY_DATA_00,
|
||||
LENOVO_CAPABILITY_DATA_01,
|
||||
LENOVO_FAN_TEST_DATA,
|
||||
CD_TYPE_NONE = -1,
|
||||
};
|
||||
|
||||
#define LWMI_CD_TABLE_ITEM(_type) \
|
||||
[_type] = { \
|
||||
.name = #_type, \
|
||||
.type = _type, \
|
||||
}
|
||||
|
||||
static const struct lwmi_cd_info {
|
||||
const char *name;
|
||||
enum lwmi_cd_type type;
|
||||
} lwmi_cd_table[] = {
|
||||
LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00),
|
||||
LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01),
|
||||
LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA),
|
||||
};
|
||||
|
||||
struct lwmi_cd_priv {
|
||||
struct notifier_block acpi_nb; /* ACPI events */
|
||||
struct wmi_device *wdev;
|
||||
struct cd_list *list;
|
||||
|
||||
/*
|
||||
* A capdata device may be a component master of another capdata device.
|
||||
* E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan
|
||||
* |- master |- component
|
||||
* |- sub-master
|
||||
* |- sub-component
|
||||
*/
|
||||
struct lwmi_cd_sub_master_priv {
|
||||
struct device *master_dev;
|
||||
cd_list_cb_t master_cb;
|
||||
struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */
|
||||
bool registered; /* Has the sub-master been registered? */
|
||||
} *sub_master;
|
||||
};
|
||||
|
||||
struct cd_list {
|
||||
struct mutex list_mutex; /* list R/W mutex */
|
||||
enum lwmi_cd_type type;
|
||||
u8 count;
|
||||
|
||||
union {
|
||||
DECLARE_FLEX_ARRAY(struct capdata00, cd00);
|
||||
DECLARE_FLEX_ARRAY(struct capdata01, cd01);
|
||||
DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan);
|
||||
};
|
||||
};
|
||||
|
||||
static struct wmi_driver lwmi_cd_driver;
|
||||
|
||||
/**
|
||||
* lwmi_cd_match() - Match rule for the master driver.
|
||||
* @dev: Pointer to the capability data parent device.
|
||||
* @type: Pointer to capability data type (enum lwmi_cd_type *) to match.
|
||||
*
|
||||
* Return: int.
|
||||
*/
|
||||
static int lwmi_cd_match(struct device *dev, void *type)
|
||||
{
|
||||
struct lwmi_cd_priv *priv;
|
||||
|
||||
if (dev->driver != &lwmi_cd_driver.driver)
|
||||
return false;
|
||||
|
||||
priv = dev_get_drvdata(dev);
|
||||
return priv->list->type == *(enum lwmi_cd_type *)type;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd_match_add_all() - Add all match rule for the master driver.
|
||||
* @master: Pointer to the master device.
|
||||
* @matchptr: Pointer to the returned component_match pointer.
|
||||
*
|
||||
* Adds all component matches to the list stored in @matchptr for the @master
|
||||
* device. @matchptr must be initialized to NULL.
|
||||
*/
|
||||
void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (WARN_ON(*matchptr))
|
||||
return;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) {
|
||||
/* Skip sub-components. */
|
||||
if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA)
|
||||
continue;
|
||||
|
||||
component_match_add(master, matchptr, lwmi_cd_match,
|
||||
(void *)&lwmi_cd_table[i].type);
|
||||
if (IS_ERR(*matchptr))
|
||||
return;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA");
|
||||
|
||||
/**
|
||||
* lwmi_cd_call_master_cb() - Call the master callback for the sub-component.
|
||||
* @priv: Pointer to the capability data private data.
|
||||
*
|
||||
* Call the master callback and pass the sub-component list to it if the
|
||||
* dependency chain (master <-> sub-master <-> sub-component) is complete.
|
||||
*/
|
||||
static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv)
|
||||
{
|
||||
struct cd_list *sub_component_list = priv->sub_master->sub_component_list;
|
||||
|
||||
/*
|
||||
* Call the callback only if the dependency chain is ready:
|
||||
* - Binding between master and sub-master: fills master_dev and master_cb
|
||||
* - Binding between sub-master and sub-component: fills sub_component_list
|
||||
*
|
||||
* If a binding has been unbound before the other binding is bound, the
|
||||
* corresponding members filled by the former are guaranteed to be cleared.
|
||||
*
|
||||
* This function is only called in bind callbacks, and the component
|
||||
* framework guarantees bind/unbind callbacks may never execute
|
||||
* simultaneously, which implies that it's impossible to have a race
|
||||
* condition.
|
||||
*
|
||||
* Hence, this check is sufficient to ensure that the callback is called
|
||||
* at most once and with the correct state, without relying on a specific
|
||||
* sequence of binding establishment.
|
||||
*/
|
||||
if (!sub_component_list ||
|
||||
!priv->sub_master->master_dev ||
|
||||
!priv->sub_master->master_cb)
|
||||
return;
|
||||
|
||||
if (PTR_ERR(sub_component_list) == -ENODEV)
|
||||
sub_component_list = NULL;
|
||||
else if (WARN_ON(IS_ERR(sub_component_list)))
|
||||
return;
|
||||
|
||||
priv->sub_master->master_cb(priv->sub_master->master_dev,
|
||||
sub_component_list);
|
||||
|
||||
/*
|
||||
* Userspace may unbind a device from its driver and bind it again
|
||||
* through sysfs. Let's call this operation "reprobe" to distinguish it
|
||||
* from component "rebind".
|
||||
*
|
||||
* When reprobing capdata00/01 or the master device, the master device
|
||||
* is unbound from us with appropriate cleanup before we bind to it and
|
||||
* call master_cb. Everything is fine in this case.
|
||||
*
|
||||
* When reprobing capdata_fan, the master device has never been unbound
|
||||
* from us (hence no cleanup is done)[1], but we call master_cb the
|
||||
* second time. To solve this issue, we clear master_cb and master_dev
|
||||
* so we won't call master_cb twice while a binding is still complete.
|
||||
*
|
||||
* Note that we can't clear sub_component_list, otherwise reprobing
|
||||
* capdata01 or the master device causes master_cb to be never called
|
||||
* after we rebind to the master device.
|
||||
*
|
||||
* [1]: The master device does not need capdata_fan in run time, so
|
||||
* losing capdata_fan will not break the binding to the master device.
|
||||
*/
|
||||
priv->sub_master->master_cb = NULL;
|
||||
priv->sub_master->master_dev = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd_component_bind() - Bind component to master device.
|
||||
* @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
|
||||
* @om_dev: Pointer to the lenovo-wmi-other driver parent device.
|
||||
* @data: lwmi_cd_binder object pointer used to return the capability data.
|
||||
*
|
||||
* On lenovo-wmi-other's master bind, provide a pointer to the local capdata
|
||||
* list. This is used to call lwmi_cd*_get_data to look up attribute data
|
||||
* from the lenovo-wmi-other driver.
|
||||
*
|
||||
* If cd_dev is a sub-master, try to call the master callback.
|
||||
*
|
||||
* Return: 0
|
||||
*/
|
||||
static int lwmi_cd_component_bind(struct device *cd_dev,
|
||||
struct device *om_dev, void *data)
|
||||
{
|
||||
struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
|
||||
struct lwmi_cd_binder *binder = data;
|
||||
|
||||
switch (priv->list->type) {
|
||||
case LENOVO_CAPABILITY_DATA_00:
|
||||
binder->cd00_list = priv->list;
|
||||
|
||||
priv->sub_master->master_dev = om_dev;
|
||||
priv->sub_master->master_cb = binder->cd_fan_list_cb;
|
||||
lwmi_cd_call_master_cb(priv);
|
||||
|
||||
break;
|
||||
case LENOVO_CAPABILITY_DATA_01:
|
||||
binder->cd01_list = priv->list;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd_component_unbind() - Unbind component to master device.
|
||||
* @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
|
||||
* @om_dev: Pointer to the lenovo-wmi-other driver parent device.
|
||||
* @data: Unused.
|
||||
*
|
||||
* If cd_dev is a sub-master, clear the collected data from the master device to
|
||||
* prevent the binding establishment between the sub-master and the sub-
|
||||
* component (if it's about to happen) from calling the master callback.
|
||||
*/
|
||||
static void lwmi_cd_component_unbind(struct device *cd_dev,
|
||||
struct device *om_dev, void *data)
|
||||
{
|
||||
struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
|
||||
|
||||
switch (priv->list->type) {
|
||||
case LENOVO_CAPABILITY_DATA_00:
|
||||
priv->sub_master->master_dev = NULL;
|
||||
priv->sub_master->master_cb = NULL;
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct component_ops lwmi_cd_component_ops = {
|
||||
.bind = lwmi_cd_component_bind,
|
||||
.unbind = lwmi_cd_component_unbind,
|
||||
};
|
||||
|
||||
/**
|
||||
* lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device
|
||||
* @dev: The sub-master capdata basic device.
|
||||
*
|
||||
* Call component_bind_all to bind the sub-component device to the sub-master
|
||||
* device. On success, collect the pointer to the sub-component list and try
|
||||
* to call the master callback.
|
||||
*
|
||||
* Return: 0 on success, or an error code.
|
||||
*/
|
||||
static int lwmi_cd_sub_master_bind(struct device *dev)
|
||||
{
|
||||
struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
|
||||
struct cd_list *sub_component_list;
|
||||
int ret;
|
||||
|
||||
ret = component_bind_all(dev, &sub_component_list);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
priv->sub_master->sub_component_list = sub_component_list;
|
||||
lwmi_cd_call_master_cb(priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device
|
||||
* @dev: The sub-master capdata basic device
|
||||
*
|
||||
* Clear the collected pointer to the sub-component list to prevent the binding
|
||||
* establishment between the sub-master and the sub-component (if it's about to
|
||||
* happen) from calling the master callback. Then, call component_unbind_all to
|
||||
* unbind the sub-component device from the sub-master device.
|
||||
*/
|
||||
static void lwmi_cd_sub_master_unbind(struct device *dev)
|
||||
{
|
||||
struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
|
||||
|
||||
priv->sub_master->sub_component_list = NULL;
|
||||
|
||||
component_unbind_all(dev, NULL);
|
||||
}
|
||||
|
||||
static const struct component_master_ops lwmi_cd_sub_master_ops = {
|
||||
.bind = lwmi_cd_sub_master_bind,
|
||||
.unbind = lwmi_cd_sub_master_unbind,
|
||||
};
|
||||
|
||||
/**
|
||||
* lwmi_cd_sub_master_add() - Register a sub-master with its sub-component
|
||||
* @priv: Pointer to the sub-master capdata device private data.
|
||||
* @sub_component_type: Type of the sub-component.
|
||||
*
|
||||
* Match the sub-component type and register the current capdata device as a
|
||||
* sub-master. If the given sub-component type is CD_TYPE_NONE, mark the sub-
|
||||
* component as non-existent without registering sub-master.
|
||||
*
|
||||
* Return: 0 on success, or an error code.
|
||||
*/
|
||||
static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv,
|
||||
enum lwmi_cd_type sub_component_type)
|
||||
{
|
||||
struct component_match *master_match = NULL;
|
||||
int ret;
|
||||
|
||||
priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL);
|
||||
if (!priv->sub_master)
|
||||
return -ENOMEM;
|
||||
|
||||
if (sub_component_type == CD_TYPE_NONE) {
|
||||
/* The master callback will be called with NULL on bind. */
|
||||
priv->sub_master->sub_component_list = ERR_PTR(-ENODEV);
|
||||
priv->sub_master->registered = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack
|
||||
* data cannot be used here. Steal one from lwmi_cd_table.
|
||||
*/
|
||||
component_match_add(&priv->wdev->dev, &master_match, lwmi_cd_match,
|
||||
(void *)&lwmi_cd_table[sub_component_type].type);
|
||||
if (IS_ERR(master_match))
|
||||
return PTR_ERR(master_match);
|
||||
|
||||
ret = component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_master_ops,
|
||||
master_match);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
priv->sub_master->registered = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered
|
||||
* @priv: Pointer to the sub-master capdata device private data.
|
||||
*/
|
||||
static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv)
|
||||
{
|
||||
if (!priv->sub_master->registered)
|
||||
return;
|
||||
|
||||
component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops);
|
||||
priv->sub_master->registered = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device.
|
||||
* @sc_dev: Pointer to the sub-component capdata parent device.
|
||||
* @sm_dev: Pointer to the sub-master capdata parent device.
|
||||
* @data: Pointer used to return the capability data list pointer.
|
||||
*
|
||||
* On sub-master's bind, provide a pointer to the local capdata list.
|
||||
* This is used by the sub-master to call the master callback.
|
||||
*
|
||||
* Return: 0
|
||||
*/
|
||||
static int lwmi_cd_sub_component_bind(struct device *sc_dev,
|
||||
struct device *sm_dev, void *data)
|
||||
{
|
||||
struct lwmi_cd_priv *priv = dev_get_drvdata(sc_dev);
|
||||
struct cd_list **listp = data;
|
||||
|
||||
*listp = priv->list;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct component_ops lwmi_cd_sub_component_ops = {
|
||||
.bind = lwmi_cd_sub_component_bind,
|
||||
};
|
||||
|
||||
/*
|
||||
* lwmi_cd*_get_data - Get the data of the specified attribute
|
||||
* @list: The lenovo-wmi-capdata pointer to its cd_list struct.
|
||||
* @attribute_id: The capdata attribute ID to be found.
|
||||
* @output: Pointer to a capdata* struct to return the data.
|
||||
*
|
||||
* Retrieves the capability data struct pointer for the given
|
||||
* attribute.
|
||||
*
|
||||
* Return: 0 on success, or -EINVAL.
|
||||
*/
|
||||
#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \
|
||||
int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output) \
|
||||
{ \
|
||||
u8 idx; \
|
||||
\
|
||||
if (WARN_ON(list->type != _cd_type)) \
|
||||
return -EINVAL; \
|
||||
\
|
||||
guard(mutex)(&list->list_mutex); \
|
||||
for (idx = 0; idx < list->count; idx++) { \
|
||||
if (list->_cdxx[idx].id != attribute_id) \
|
||||
continue; \
|
||||
memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx])); \
|
||||
return 0; \
|
||||
} \
|
||||
return -EINVAL; \
|
||||
}
|
||||
|
||||
DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00);
|
||||
EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA");
|
||||
|
||||
DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
|
||||
EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA");
|
||||
|
||||
DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan);
|
||||
EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA");
|
||||
|
||||
/**
|
||||
* lwmi_cd_cache() - Cache all WMI data block information
|
||||
* @priv: lenovo-wmi-capdata driver data.
|
||||
*
|
||||
* Loop through each WMI data block and cache the data.
|
||||
*
|
||||
* Return: 0 on success, or an error.
|
||||
*/
|
||||
static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
|
||||
{
|
||||
size_t size;
|
||||
int idx;
|
||||
void *p;
|
||||
|
||||
switch (priv->list->type) {
|
||||
case LENOVO_CAPABILITY_DATA_00:
|
||||
p = &priv->list->cd00[0];
|
||||
size = sizeof(priv->list->cd00[0]);
|
||||
break;
|
||||
case LENOVO_CAPABILITY_DATA_01:
|
||||
p = &priv->list->cd01[0];
|
||||
size = sizeof(priv->list->cd01[0]);
|
||||
break;
|
||||
case LENOVO_FAN_TEST_DATA:
|
||||
/* Done by lwmi_cd_alloc() => lwmi_cd_fan_list_alloc_cache(). */
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
guard(mutex)(&priv->list->list_mutex);
|
||||
for (idx = 0; idx < priv->list->count; idx++, p += size) {
|
||||
union acpi_object *ret_obj __free(kfree) = NULL;
|
||||
|
||||
ret_obj = wmidev_block_query(priv->wdev, idx);
|
||||
if (!ret_obj)
|
||||
return -ENODEV;
|
||||
|
||||
if (ret_obj->type != ACPI_TYPE_BUFFER ||
|
||||
ret_obj->buffer.length < size)
|
||||
continue;
|
||||
|
||||
memcpy(p, ret_obj->buffer.pointer, size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list
|
||||
* @priv: lenovo-wmi-capdata driver data.
|
||||
* @listptr: Pointer to returned cd_list pointer.
|
||||
*
|
||||
* Return: count of fans found, or an error.
|
||||
*/
|
||||
static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr)
|
||||
{
|
||||
struct cd_list *list;
|
||||
size_t size;
|
||||
u32 count;
|
||||
int idx;
|
||||
|
||||
/* Emit unaligned access to u8 buffer with __packed. */
|
||||
struct cd_fan_block {
|
||||
u32 nr;
|
||||
u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */
|
||||
} __packed * block;
|
||||
|
||||
union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0);
|
||||
if (!ret_obj)
|
||||
return -ENODEV;
|
||||
|
||||
if (ret_obj->type == ACPI_TYPE_BUFFER) {
|
||||
block = (struct cd_fan_block *)ret_obj->buffer.pointer;
|
||||
size = ret_obj->buffer.length;
|
||||
|
||||
count = size >= sizeof(*block) ? block->nr : 0;
|
||||
if (size < struct_size(block, data, count * 3)) {
|
||||
dev_warn(&priv->wdev->dev,
|
||||
"incomplete fan test data block: %zu < %zu, ignoring\n",
|
||||
size, struct_size(block, data, count * 3));
|
||||
count = 0;
|
||||
} else if (count > U8_MAX) {
|
||||
dev_warn(&priv->wdev->dev,
|
||||
"too many fans reported: %u > %u, truncating\n",
|
||||
count, U8_MAX);
|
||||
count = U8_MAX;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* This is usually caused by a dummy ACPI method. Do not return an error
|
||||
* as failing to probe this device will result in sub-master device being
|
||||
* unbound. This behavior aligns with lwmi_cd_cache().
|
||||
*/
|
||||
count = 0;
|
||||
}
|
||||
|
||||
list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL);
|
||||
if (!list)
|
||||
return -ENOMEM;
|
||||
|
||||
for (idx = 0; idx < count; idx++) {
|
||||
/* Do not calculate array index using count, as it may be truncated. */
|
||||
list->cd_fan[idx] = (struct capdata_fan) {
|
||||
.id = block->data[idx],
|
||||
.max_rpm = block->data[idx + block->nr],
|
||||
.min_rpm = block->data[idx + (2 * block->nr)],
|
||||
};
|
||||
}
|
||||
|
||||
*listptr = list;
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
|
||||
* @priv: lenovo-wmi-capdata driver data.
|
||||
* @type: The type of capability data.
|
||||
*
|
||||
* Allocate a cd_list struct large enough to contain data from all WMI data
|
||||
* blocks provided by the interface.
|
||||
*
|
||||
* Return: 0 on success, or an error.
|
||||
*/
|
||||
static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
|
||||
{
|
||||
struct cd_list *list;
|
||||
size_t list_size;
|
||||
int count, ret;
|
||||
|
||||
count = wmidev_instance_count(priv->wdev);
|
||||
|
||||
switch (type) {
|
||||
case LENOVO_CAPABILITY_DATA_00:
|
||||
list_size = struct_size(list, cd00, count);
|
||||
break;
|
||||
case LENOVO_CAPABILITY_DATA_01:
|
||||
list_size = struct_size(list, cd01, count);
|
||||
break;
|
||||
case LENOVO_FAN_TEST_DATA:
|
||||
count = lwmi_cd_fan_list_alloc_cache(priv, &list);
|
||||
if (count < 0)
|
||||
return count;
|
||||
|
||||
goto got_list;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
|
||||
if (!list)
|
||||
return -ENOMEM;
|
||||
|
||||
got_list:
|
||||
ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
list->type = type;
|
||||
list->count = count;
|
||||
priv->list = list;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd_setup() - Cache all WMI data block information
|
||||
* @priv: lenovo-wmi-capdata driver data.
|
||||
* @type: The type of capability data.
|
||||
*
|
||||
* Allocate a cd_list struct large enough to contain data from all WMI data
|
||||
* blocks provided by the interface. Then loop through each data block and
|
||||
* cache the data.
|
||||
*
|
||||
* Return: 0 on success, or an error code.
|
||||
*/
|
||||
static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = lwmi_cd_alloc(priv, type);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return lwmi_cd_cache(priv);
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd01_notifier_call() - Call method for cd01 notifier.
|
||||
* block call chain.
|
||||
* @nb: The notifier_block registered to lenovo-wmi-events driver.
|
||||
* @action: Unused.
|
||||
* @data: The ACPI event.
|
||||
*
|
||||
* For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
|
||||
* of a change.
|
||||
*
|
||||
* Return: notifier_block status.
|
||||
*/
|
||||
static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
|
||||
void *data)
|
||||
{
|
||||
struct acpi_bus_event *event = data;
|
||||
struct lwmi_cd_priv *priv;
|
||||
int ret;
|
||||
|
||||
if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
|
||||
return NOTIFY_DONE;
|
||||
|
||||
priv = container_of(nb, struct lwmi_cd_priv, acpi_nb);
|
||||
|
||||
switch (event->type) {
|
||||
case ACPI_AC_NOTIFY_STATUS:
|
||||
ret = lwmi_cd_cache(priv);
|
||||
if (ret)
|
||||
return NOTIFY_BAD;
|
||||
|
||||
return NOTIFY_OK;
|
||||
default:
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
|
||||
* @data: The ACPI event notifier_block to unregister.
|
||||
*/
|
||||
static void lwmi_cd01_unregister(void *data)
|
||||
{
|
||||
struct notifier_block *acpi_nb = data;
|
||||
|
||||
unregister_acpi_notifier(acpi_nb);
|
||||
}
|
||||
|
||||
static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
const struct lwmi_cd_info *info = context;
|
||||
struct lwmi_cd_priv *priv;
|
||||
int ret;
|
||||
|
||||
if (!info)
|
||||
return -EINVAL;
|
||||
|
||||
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->wdev = wdev;
|
||||
dev_set_drvdata(&wdev->dev, priv);
|
||||
|
||||
ret = lwmi_cd_setup(priv, info->type);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
switch (info->type) {
|
||||
case LENOVO_CAPABILITY_DATA_00: {
|
||||
enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA;
|
||||
struct capdata00 capdata00;
|
||||
|
||||
ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00);
|
||||
if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) {
|
||||
dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n");
|
||||
sub_component_type = CD_TYPE_NONE;
|
||||
}
|
||||
|
||||
/* Sub-master (capdata00) <-> sub-component (capdata_fan) */
|
||||
ret = lwmi_cd_sub_master_add(priv, sub_component_type);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* Master (lenovo-wmi-other) <-> sub-master (capdata00) */
|
||||
ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
|
||||
if (ret)
|
||||
lwmi_cd_sub_master_del(priv);
|
||||
|
||||
goto out;
|
||||
}
|
||||
case LENOVO_CAPABILITY_DATA_01:
|
||||
priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
|
||||
|
||||
ret = register_acpi_notifier(&priv->acpi_nb);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister,
|
||||
&priv->acpi_nb);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
|
||||
goto out;
|
||||
case LENOVO_FAN_TEST_DATA:
|
||||
ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops);
|
||||
goto out;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
out:
|
||||
if (ret) {
|
||||
dev_err(&wdev->dev, "failed to register %s: %d\n",
|
||||
info->name, ret);
|
||||
} else {
|
||||
dev_dbg(&wdev->dev, "registered %s with %u items\n",
|
||||
info->name, priv->list->count);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void lwmi_cd_remove(struct wmi_device *wdev)
|
||||
{
|
||||
struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev);
|
||||
|
||||
switch (priv->list->type) {
|
||||
case LENOVO_CAPABILITY_DATA_00:
|
||||
lwmi_cd_sub_master_del(priv);
|
||||
fallthrough;
|
||||
case LENOVO_CAPABILITY_DATA_01:
|
||||
component_del(&wdev->dev, &lwmi_cd_component_ops);
|
||||
break;
|
||||
case LENOVO_FAN_TEST_DATA:
|
||||
component_del(&wdev->dev, &lwmi_cd_sub_component_ops);
|
||||
break;
|
||||
default:
|
||||
WARN_ON(1);
|
||||
}
|
||||
}
|
||||
|
||||
#define LWMI_CD_WDEV_ID(_type) \
|
||||
.guid_string = _type##_GUID, \
|
||||
.context = &lwmi_cd_table[_type],
|
||||
|
||||
static const struct wmi_device_id lwmi_cd_id_table[] = {
|
||||
{ LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) },
|
||||
{ LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) },
|
||||
{ LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct wmi_driver lwmi_cd_driver = {
|
||||
.driver = {
|
||||
.name = "lenovo_wmi_capdata",
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
},
|
||||
.id_table = lwmi_cd_id_table,
|
||||
.probe = lwmi_cd_probe,
|
||||
.remove = lwmi_cd_remove,
|
||||
.no_singleton = true,
|
||||
};
|
||||
|
||||
module_wmi_driver(lwmi_cd_driver);
|
||||
|
||||
MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
|
||||
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
|
||||
MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
|
||||
MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
65
drivers/platform/x86/lenovo/wmi-capdata.h
Normal file
65
drivers/platform/x86/lenovo/wmi-capdata.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
|
||||
|
||||
#ifndef _LENOVO_WMI_CAPDATA_H_
|
||||
#define _LENOVO_WMI_CAPDATA_H_
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define LWMI_SUPP_VALID BIT(0)
|
||||
#define LWMI_SUPP_GET BIT(1)
|
||||
#define LWMI_SUPP_SET BIT(2)
|
||||
|
||||
#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
|
||||
#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
|
||||
#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
|
||||
#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
|
||||
|
||||
#define LWMI_DEVICE_ID_FAN 0x04
|
||||
|
||||
struct component_match;
|
||||
struct device;
|
||||
struct cd_list;
|
||||
|
||||
struct capdata00 {
|
||||
u32 id;
|
||||
u32 supported;
|
||||
u32 default_value;
|
||||
};
|
||||
|
||||
struct capdata01 {
|
||||
u32 id;
|
||||
u32 supported;
|
||||
u32 default_value;
|
||||
u32 step;
|
||||
u32 min_value;
|
||||
u32 max_value;
|
||||
};
|
||||
|
||||
struct capdata_fan {
|
||||
u32 id;
|
||||
u32 min_rpm;
|
||||
u32 max_rpm;
|
||||
};
|
||||
|
||||
typedef void (*cd_list_cb_t)(struct device *master_dev, struct cd_list *cd_list);
|
||||
|
||||
struct lwmi_cd_binder {
|
||||
struct cd_list *cd00_list;
|
||||
struct cd_list *cd01_list;
|
||||
/*
|
||||
* May be called during or after the bind callback.
|
||||
* Will be called with NULL if capdata_fan does not exist.
|
||||
* The pointer is only valid in the callback; never keep it for later use!
|
||||
*/
|
||||
cd_list_cb_t cd_fan_list_cb;
|
||||
};
|
||||
|
||||
void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
|
||||
int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capdata00 *output);
|
||||
int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output);
|
||||
int lwmi_cd_fan_get_data(struct cd_list *list, u32 attribute_id, struct capdata_fan *output);
|
||||
|
||||
#endif /* !_LENOVO_WMI_CAPDATA_H_ */
|
||||
|
|
@ -1,302 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Lenovo Capability Data 01 WMI Data Block driver.
|
||||
*
|
||||
* Lenovo Capability Data 01 provides information on tunable attributes used by
|
||||
* the "Other Mode" WMI interface. The data includes if the attribute is
|
||||
* supported by the hardware, the default_value, max_value, min_value, and step
|
||||
* increment. Each attribute has multiple pages, one for each of the thermal
|
||||
* modes managed by the Gamezone interface.
|
||||
*
|
||||
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/gfp_types.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/mutex_types.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include "wmi-capdata01.h"
|
||||
|
||||
#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
|
||||
|
||||
#define ACPI_AC_CLASS "ac_adapter"
|
||||
#define ACPI_AC_NOTIFY_STATUS 0x80
|
||||
|
||||
struct lwmi_cd01_priv {
|
||||
struct notifier_block acpi_nb; /* ACPI events */
|
||||
struct wmi_device *wdev;
|
||||
struct cd01_list *list;
|
||||
};
|
||||
|
||||
struct cd01_list {
|
||||
struct mutex list_mutex; /* list R/W mutex */
|
||||
u8 count;
|
||||
struct capdata01 data[];
|
||||
};
|
||||
|
||||
/**
|
||||
* lwmi_cd01_component_bind() - Bind component to master device.
|
||||
* @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
|
||||
* @om_dev: Pointer to the lenovo-wmi-other driver parent device.
|
||||
* @data: capdata01_list object pointer used to return the capability data.
|
||||
*
|
||||
* On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
|
||||
* list. This is used to call lwmi_cd01_get_data to look up attribute data
|
||||
* from the lenovo-wmi-other driver.
|
||||
*
|
||||
* Return: 0
|
||||
*/
|
||||
static int lwmi_cd01_component_bind(struct device *cd01_dev,
|
||||
struct device *om_dev, void *data)
|
||||
{
|
||||
struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
|
||||
struct cd01_list **cd01_list = data;
|
||||
|
||||
*cd01_list = priv->list;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct component_ops lwmi_cd01_component_ops = {
|
||||
.bind = lwmi_cd01_component_bind,
|
||||
};
|
||||
|
||||
/**
|
||||
* lwmi_cd01_get_data - Get the data of the specified attribute
|
||||
* @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct.
|
||||
* @attribute_id: The capdata attribute ID to be found.
|
||||
* @output: Pointer to a capdata01 struct to return the data.
|
||||
*
|
||||
* Retrieves the capability data 01 struct pointer for the given
|
||||
* attribute for its specified thermal mode.
|
||||
*
|
||||
* Return: 0 on success, or -EINVAL.
|
||||
*/
|
||||
int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output)
|
||||
{
|
||||
u8 idx;
|
||||
|
||||
guard(mutex)(&list->list_mutex);
|
||||
for (idx = 0; idx < list->count; idx++) {
|
||||
if (list->data[idx].id != attribute_id)
|
||||
continue;
|
||||
memcpy(output, &list->data[idx], sizeof(list->data[idx]));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
|
||||
|
||||
/**
|
||||
* lwmi_cd01_cache() - Cache all WMI data block information
|
||||
* @priv: lenovo-wmi-capdata01 driver data.
|
||||
*
|
||||
* Loop through each WMI data block and cache the data.
|
||||
*
|
||||
* Return: 0 on success, or an error.
|
||||
*/
|
||||
static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
|
||||
{
|
||||
int idx;
|
||||
|
||||
guard(mutex)(&priv->list->list_mutex);
|
||||
for (idx = 0; idx < priv->list->count; idx++) {
|
||||
union acpi_object *ret_obj __free(kfree) = NULL;
|
||||
|
||||
ret_obj = wmidev_block_query(priv->wdev, idx);
|
||||
if (!ret_obj)
|
||||
return -ENODEV;
|
||||
|
||||
if (ret_obj->type != ACPI_TYPE_BUFFER ||
|
||||
ret_obj->buffer.length < sizeof(priv->list->data[idx]))
|
||||
continue;
|
||||
|
||||
memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
|
||||
ret_obj->buffer.length);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
|
||||
* @priv: lenovo-wmi-capdata01 driver data.
|
||||
*
|
||||
* Allocate a cd01_list struct large enough to contain data from all WMI data
|
||||
* blocks provided by the interface.
|
||||
*
|
||||
* Return: 0 on success, or an error.
|
||||
*/
|
||||
static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
|
||||
{
|
||||
struct cd01_list *list;
|
||||
size_t list_size;
|
||||
int count, ret;
|
||||
|
||||
count = wmidev_instance_count(priv->wdev);
|
||||
list_size = struct_size(list, data, count);
|
||||
|
||||
list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
|
||||
if (!list)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
list->count = count;
|
||||
priv->list = list;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd01_setup() - Cache all WMI data block information
|
||||
* @priv: lenovo-wmi-capdata01 driver data.
|
||||
*
|
||||
* Allocate a cd01_list struct large enough to contain data from all WMI data
|
||||
* blocks provided by the interface. Then loop through each data block and
|
||||
* cache the data.
|
||||
*
|
||||
* Return: 0 on success, or an error code.
|
||||
*/
|
||||
static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = lwmi_cd01_alloc(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return lwmi_cd01_cache(priv);
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier.
|
||||
* block call chain.
|
||||
* @nb: The notifier_block registered to lenovo-wmi-events driver.
|
||||
* @action: Unused.
|
||||
* @data: The ACPI event.
|
||||
*
|
||||
* For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
|
||||
* of a change.
|
||||
*
|
||||
* Return: notifier_block status.
|
||||
*/
|
||||
static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
|
||||
void *data)
|
||||
{
|
||||
struct acpi_bus_event *event = data;
|
||||
struct lwmi_cd01_priv *priv;
|
||||
int ret;
|
||||
|
||||
if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
|
||||
return NOTIFY_DONE;
|
||||
|
||||
priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
|
||||
|
||||
switch (event->type) {
|
||||
case ACPI_AC_NOTIFY_STATUS:
|
||||
ret = lwmi_cd01_cache(priv);
|
||||
if (ret)
|
||||
return NOTIFY_BAD;
|
||||
|
||||
return NOTIFY_OK;
|
||||
default:
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
|
||||
* @data: The ACPI event notifier_block to unregister.
|
||||
*/
|
||||
static void lwmi_cd01_unregister(void *data)
|
||||
{
|
||||
struct notifier_block *acpi_nb = data;
|
||||
|
||||
unregister_acpi_notifier(acpi_nb);
|
||||
}
|
||||
|
||||
static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
|
||||
|
||||
{
|
||||
struct lwmi_cd01_priv *priv;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->wdev = wdev;
|
||||
dev_set_drvdata(&wdev->dev, priv);
|
||||
|
||||
ret = lwmi_cd01_setup(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
|
||||
|
||||
ret = register_acpi_notifier(&priv->acpi_nb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return component_add(&wdev->dev, &lwmi_cd01_component_ops);
|
||||
}
|
||||
|
||||
static void lwmi_cd01_remove(struct wmi_device *wdev)
|
||||
{
|
||||
component_del(&wdev->dev, &lwmi_cd01_component_ops);
|
||||
}
|
||||
|
||||
static const struct wmi_device_id lwmi_cd01_id_table[] = {
|
||||
{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct wmi_driver lwmi_cd01_driver = {
|
||||
.driver = {
|
||||
.name = "lenovo_wmi_cd01",
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
},
|
||||
.id_table = lwmi_cd01_id_table,
|
||||
.probe = lwmi_cd01_probe,
|
||||
.remove = lwmi_cd01_remove,
|
||||
.no_singleton = true,
|
||||
};
|
||||
|
||||
/**
|
||||
* lwmi_cd01_match() - Match rule for the master driver.
|
||||
* @dev: Pointer to the capability data 01 parent device.
|
||||
* @data: Unused void pointer for passing match criteria.
|
||||
*
|
||||
* Return: int.
|
||||
*/
|
||||
int lwmi_cd01_match(struct device *dev, void *data)
|
||||
{
|
||||
return dev->driver == &lwmi_cd01_driver.driver;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
|
||||
|
||||
module_wmi_driver(lwmi_cd01_driver);
|
||||
|
||||
MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table);
|
||||
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
|
||||
MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
|
||||
|
||||
#ifndef _LENOVO_WMI_CAPDATA01_H_
|
||||
#define _LENOVO_WMI_CAPDATA01_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct device;
|
||||
struct cd01_list;
|
||||
|
||||
struct capdata01 {
|
||||
u32 id;
|
||||
u32 supported;
|
||||
u32 default_value;
|
||||
u32 step;
|
||||
u32 min_value;
|
||||
u32 max_value;
|
||||
};
|
||||
|
||||
int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output);
|
||||
int lwmi_cd01_match(struct device *dev, void *data);
|
||||
|
||||
#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
#include <linux/errno.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/unaligned.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include "wmi-helpers.h"
|
||||
|
|
@ -59,10 +60,24 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
|
|||
if (!ret_obj)
|
||||
return -ENODATA;
|
||||
|
||||
if (ret_obj->type != ACPI_TYPE_INTEGER)
|
||||
return -ENXIO;
|
||||
switch (ret_obj->type) {
|
||||
/*
|
||||
* The ACPI method may simply return a buffer when a u32
|
||||
* is expected. This is valid on Windows as its WMI-ACPI
|
||||
* driver converts everything to a common buffer.
|
||||
*/
|
||||
case ACPI_TYPE_BUFFER:
|
||||
if (ret_obj->buffer.length < sizeof(u32))
|
||||
return -ENXIO;
|
||||
|
||||
*retval = (u32)ret_obj->integer.value;
|
||||
*retval = get_unaligned_le32(ret_obj->buffer.pointer);
|
||||
return 0;
|
||||
case ACPI_TYPE_INTEGER:
|
||||
*retval = (u32)ret_obj->integer.value;
|
||||
return 0;
|
||||
default:
|
||||
return -ENXIO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,16 @@
|
|||
* These attributes typically don't fit anywhere else in the sysfs and are set
|
||||
* in Windows using one of Lenovo's multiple user applications.
|
||||
*
|
||||
* Additionally, this driver also exports tunable fan speed RPM to HWMON.
|
||||
* Min/max RPM are also provided for reference.
|
||||
*
|
||||
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
|
||||
* - fw_attributes
|
||||
* - binding to Capability Data 01
|
||||
*
|
||||
* Copyright (C) 2025 Rong Zhang <i@rong.moe>
|
||||
* - HWMON
|
||||
* - binding to Capability Data 00 and Fan
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
|
|
@ -25,16 +34,18 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/gfp_types.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/platform_profile.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include "wmi-capdata01.h"
|
||||
#include "wmi-capdata.h"
|
||||
#include "wmi-events.h"
|
||||
#include "wmi-gamezone.h"
|
||||
#include "wmi-helpers.h"
|
||||
|
|
@ -49,17 +60,26 @@
|
|||
#define LWMI_FEATURE_ID_CPU_SPL 0x02
|
||||
#define LWMI_FEATURE_ID_CPU_FPPT 0x03
|
||||
|
||||
#define LWMI_FEATURE_ID_FAN_RPM 0x03
|
||||
|
||||
#define LWMI_TYPE_ID_NONE 0x00
|
||||
|
||||
#define LWMI_FEATURE_VALUE_GET 17
|
||||
#define LWMI_FEATURE_VALUE_SET 18
|
||||
|
||||
#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
|
||||
#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
|
||||
#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
|
||||
#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
|
||||
#define LWMI_FAN_ID_BASE 1
|
||||
#define LWMI_FAN_NR 4
|
||||
#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE)
|
||||
|
||||
#define LWMI_ATTR_ID_FAN_RPM(x) \
|
||||
(FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
|
||||
FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \
|
||||
FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x)))
|
||||
|
||||
#define LWMI_FAN_DIV 100
|
||||
|
||||
#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
|
||||
#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
|
||||
|
||||
static BLOCKING_NOTIFIER_HEAD(om_chain_head);
|
||||
static DEFINE_IDA(lwmi_om_ida);
|
||||
|
|
@ -72,16 +92,459 @@ enum attribute_property {
|
|||
SUPPORTED,
|
||||
};
|
||||
|
||||
struct lwmi_fan_info {
|
||||
u32 supported;
|
||||
u32 last_target;
|
||||
long min_rpm;
|
||||
long max_rpm;
|
||||
};
|
||||
|
||||
struct lwmi_om_priv {
|
||||
struct component_master_ops *ops;
|
||||
struct cd01_list *cd01_list; /* only valid after capdata01 bind */
|
||||
|
||||
/* only valid after capdata bind */
|
||||
struct cd_list *cd00_list;
|
||||
struct cd_list *cd01_list;
|
||||
|
||||
struct device *hwmon_dev;
|
||||
struct device *fw_attr_dev;
|
||||
struct kset *fw_attr_kset;
|
||||
struct notifier_block nb;
|
||||
struct wmi_device *wdev;
|
||||
int ida_id;
|
||||
|
||||
struct lwmi_fan_info fan_info[LWMI_FAN_NR];
|
||||
|
||||
struct {
|
||||
bool capdata00_collected : 1;
|
||||
bool capdata_fan_collected : 1;
|
||||
} fan_flags;
|
||||
};
|
||||
|
||||
/*
|
||||
* Visibility of fan channels:
|
||||
*
|
||||
* +-------------------+---------+------------------+-----------------------+------------+
|
||||
* | | default | +expose_all_fans | +relax_fan_constraint | +both |
|
||||
* +-------------------+---------+------------------+-----------------------+------------+
|
||||
* | canonical | RW | RW | RW+relaxed | RW+relaxed |
|
||||
* +-------------------+---------+------------------+-----------------------+------------+
|
||||
* | -capdata_fan[idx] | N | RO | N | RW+relaxed |
|
||||
* +-------------------+---------+------------------+-----------------------+------------+
|
||||
*
|
||||
* Note:
|
||||
* 1. LWMI_ATTR_ID_FAN_RPM[idx].supported is always checked before exposing a channel.
|
||||
* 2. -capdata_fan implies -capdata_fan[idx].
|
||||
*/
|
||||
static bool expose_all_fans;
|
||||
module_param(expose_all_fans, bool, 0444);
|
||||
MODULE_PARM_DESC(expose_all_fans,
|
||||
"This option skips some capability checks and solely relies on per-channel ones "
|
||||
"to expose fan attributes. Use with caution.");
|
||||
|
||||
static bool relax_fan_constraint;
|
||||
module_param(relax_fan_constraint, bool, 0444);
|
||||
MODULE_PARM_DESC(relax_fan_constraint,
|
||||
"Do not enforce fan RPM constraint (div/min/max) "
|
||||
"and enables fan tuning when such data is missing. "
|
||||
"Enabling this may results in HWMON attributes being out-of-sync, "
|
||||
"and setting a too low RPM stops the fan. Use with caution.");
|
||||
|
||||
/* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */
|
||||
|
||||
/**
|
||||
* lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan
|
||||
* @priv: Driver private data structure
|
||||
* @channel: Fan channel index (0-based)
|
||||
* @val: Pointer to value (input for set, output for get)
|
||||
* @set: True to set value, false to get value
|
||||
*
|
||||
* Communicates with WMI interface to either retrieve current fan RPM
|
||||
* or set target fan RPM.
|
||||
*
|
||||
* Return: 0 on success, or an error code.
|
||||
*/
|
||||
static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val, bool set)
|
||||
{
|
||||
struct wmi_method_args_32 args;
|
||||
u32 method_id, retval;
|
||||
int err;
|
||||
|
||||
method_id = set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET;
|
||||
args.arg0 = LWMI_ATTR_ID_FAN_RPM(channel);
|
||||
args.arg1 = set ? *val : 0;
|
||||
|
||||
err = lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id,
|
||||
(unsigned char *)&args, sizeof(args), &retval);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (!set) {
|
||||
*val = retval;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* It seems that 0 means "no error" and 1 means "done". Apparently
|
||||
* different firmware teams have different thoughts on indicating
|
||||
* success, so we accepts both.
|
||||
*/
|
||||
return (retval == 0 || retval == 1) ? 0 : -EIO;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_om_hwmon_is_visible() - Determine visibility of HWMON attributes
|
||||
* @drvdata: Driver private data
|
||||
* @type: Sensor type
|
||||
* @attr: Attribute identifier
|
||||
* @channel: Channel index
|
||||
*
|
||||
* Determines whether an HWMON attribute should be visible in sysfs
|
||||
* based on hardware capabilities and current configuration.
|
||||
*
|
||||
* Return: permission mode, or 0 if invisible.
|
||||
*/
|
||||
static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
struct lwmi_om_priv *priv = (struct lwmi_om_priv *)drvdata;
|
||||
bool visible = false;
|
||||
|
||||
if (type == hwmon_fan) {
|
||||
if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID))
|
||||
return 0;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_fan_target:
|
||||
if (!(priv->fan_info[channel].supported & LWMI_SUPP_SET))
|
||||
return 0;
|
||||
|
||||
if (relax_fan_constraint ||
|
||||
(priv->fan_info[channel].min_rpm >= 0 &&
|
||||
priv->fan_info[channel].max_rpm >= 0))
|
||||
return 0644;
|
||||
|
||||
/*
|
||||
* Reaching here implies expose_all_fans is set.
|
||||
* See lwmi_om_hwmon_add().
|
||||
*/
|
||||
dev_warn_once(&priv->wdev->dev,
|
||||
"fan tuning disabled due to missing RPM constraint\n");
|
||||
return 0;
|
||||
case hwmon_fan_div:
|
||||
case hwmon_fan_input:
|
||||
visible = priv->fan_info[channel].supported & LWMI_SUPP_GET;
|
||||
break;
|
||||
case hwmon_fan_min:
|
||||
visible = priv->fan_info[channel].min_rpm >= 0;
|
||||
break;
|
||||
case hwmon_fan_max:
|
||||
visible = priv->fan_info[channel].max_rpm >= 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return visible ? 0444 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_om_hwmon_read() - Read HWMON sensor data
|
||||
* @dev: Device pointer
|
||||
* @type: Sensor type
|
||||
* @attr: Attribute identifier
|
||||
* @channel: Channel index
|
||||
* @val: Pointer to store value
|
||||
*
|
||||
* Reads current sensor values from hardware through WMI interface.
|
||||
*
|
||||
* Return: 0 on success, or an error code.
|
||||
*/
|
||||
static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
|
||||
u32 retval = 0;
|
||||
int err;
|
||||
|
||||
if (type == hwmon_fan) {
|
||||
switch (attr) {
|
||||
/*
|
||||
* The EC has an internal RPM divisor (i.e., the raw register value is
|
||||
* RPM / fanY_div). For fanY_input, the WMI method reads the register
|
||||
* value and returns raw * fanY_div. For fanY_target, the WMI method
|
||||
* divides the written value by fanY_div before writing it to the EC.
|
||||
*
|
||||
* As a result, reading fanY_input always returns a multiple of fanY_div,
|
||||
* while writing to fanY_target loses the remainder.
|
||||
*/
|
||||
case hwmon_fan_div:
|
||||
*val = LWMI_FAN_DIV;
|
||||
return 0;
|
||||
case hwmon_fan_input:
|
||||
err = lwmi_om_fan_get_set(priv, channel, &retval, false);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
*val = retval;
|
||||
return 0;
|
||||
case hwmon_fan_target:
|
||||
*val = priv->fan_info[channel].last_target;
|
||||
return 0;
|
||||
case hwmon_fan_min:
|
||||
*val = priv->fan_info[channel].min_rpm;
|
||||
return 0;
|
||||
case hwmon_fan_max:
|
||||
*val = priv->fan_info[channel].max_rpm;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_om_hwmon_write() - Write HWMON sensor data
|
||||
* @dev: Device pointer
|
||||
* @type: Sensor type
|
||||
* @attr: Attribute identifier
|
||||
* @channel: Channel index
|
||||
* @val: Value to write
|
||||
*
|
||||
* Writes configuration values to hardware through WMI interface.
|
||||
*
|
||||
* Return: 0 on success, or an error code.
|
||||
*/
|
||||
static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
|
||||
u32 raw, min_rpm, max_rpm;
|
||||
int err;
|
||||
|
||||
if (type == hwmon_fan) {
|
||||
switch (attr) {
|
||||
case hwmon_fan_target:
|
||||
if (relax_fan_constraint) {
|
||||
min_rpm = 1;
|
||||
max_rpm = U16_MAX;
|
||||
} else {
|
||||
min_rpm = priv->fan_info[channel].min_rpm;
|
||||
max_rpm = priv->fan_info[channel].max_rpm;
|
||||
}
|
||||
|
||||
/* 0 means "auto". */
|
||||
if (val != 0 && (val < min_rpm || val > max_rpm))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The effective fanY_target is always a multiple of fanY_div
|
||||
* due to the EC's internal RPM divisor (see lwmi_om_hwmon_read).
|
||||
*
|
||||
* Round down the written value to the nearest multiple of fanY_div
|
||||
* to prevent mismatch between the effective value and last_target.
|
||||
*
|
||||
* For relax_fan_constraint, skip this conversion as setting a
|
||||
* sub-fanY_div value is necessary to completely stop the fan on
|
||||
* some devices.
|
||||
*/
|
||||
if (!relax_fan_constraint)
|
||||
raw = val / LWMI_FAN_DIV * LWMI_FAN_DIV;
|
||||
|
||||
err = lwmi_om_fan_get_set(priv, channel, &raw, true);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
priv->fan_info[channel].last_target = raw;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = {
|
||||
/* Must match LWMI_FAN_NR. */
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
|
||||
HWMON_F_MIN | HWMON_F_MAX,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
|
||||
HWMON_F_MIN | HWMON_F_MAX,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
|
||||
HWMON_F_MIN | HWMON_F_MAX,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
|
||||
HWMON_F_MIN | HWMON_F_MAX),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops lwmi_om_hwmon_ops = {
|
||||
.is_visible = lwmi_om_hwmon_is_visible,
|
||||
.read = lwmi_om_hwmon_read,
|
||||
.write = lwmi_om_hwmon_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = {
|
||||
.ops = &lwmi_om_hwmon_ops,
|
||||
.info = lwmi_om_hwmon_info,
|
||||
};
|
||||
|
||||
/**
|
||||
* lwmi_om_hwmon_add() - Register HWMON device if all info is collected
|
||||
* @priv: Driver private data
|
||||
*/
|
||||
static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
|
||||
{
|
||||
int i, valid;
|
||||
|
||||
if (WARN_ON(priv->hwmon_dev))
|
||||
return;
|
||||
|
||||
if (!priv->fan_flags.capdata00_collected || !priv->fan_flags.capdata_fan_collected) {
|
||||
dev_dbg(&priv->wdev->dev, "HWMON registration pending (00: %d, fan: %d)\n",
|
||||
priv->fan_flags.capdata00_collected,
|
||||
priv->fan_flags.capdata_fan_collected);
|
||||
return;
|
||||
}
|
||||
|
||||
if (expose_all_fans)
|
||||
dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n");
|
||||
|
||||
if (relax_fan_constraint)
|
||||
dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution\n");
|
||||
|
||||
valid = 0;
|
||||
for (i = 0; i < LWMI_FAN_NR; i++) {
|
||||
if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID))
|
||||
continue;
|
||||
|
||||
valid++;
|
||||
|
||||
if (!expose_all_fans &&
|
||||
(priv->fan_info[i].min_rpm < 0 || priv->fan_info[i].max_rpm < 0)) {
|
||||
dev_dbg(&priv->wdev->dev, "missing RPM constraint for fan%d, hiding\n",
|
||||
LWMI_FAN_ID(i));
|
||||
priv->fan_info[i].supported = 0;
|
||||
valid--;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid == 0) {
|
||||
dev_warn(&priv->wdev->dev,
|
||||
"fan reporting/tuning is unsupported on this device\n");
|
||||
return;
|
||||
}
|
||||
|
||||
priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev,
|
||||
LWMI_OM_HWMON_NAME, priv,
|
||||
&lwmi_om_hwmon_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(priv->hwmon_dev)) {
|
||||
dev_warn(&priv->wdev->dev, "failed to register HWMON device: %ld\n",
|
||||
PTR_ERR(priv->hwmon_dev));
|
||||
priv->hwmon_dev = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
dev_dbg(&priv->wdev->dev, "registered HWMON device\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_om_hwmon_remove() - Unregister HWMON device
|
||||
* @priv: Driver private data
|
||||
*
|
||||
* Unregisters the HWMON device if applicable.
|
||||
*/
|
||||
static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv)
|
||||
{
|
||||
if (!priv->hwmon_dev)
|
||||
return;
|
||||
|
||||
hwmon_device_unregister(priv->hwmon_dev);
|
||||
priv->hwmon_dev = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_om_fan_info_init() - Initialzie fan info
|
||||
* @priv: Driver private data
|
||||
*
|
||||
* lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan() may be
|
||||
* called in an arbitrary order. Hence, initializion must be done before.
|
||||
*/
|
||||
static void lwmi_om_fan_info_init(struct lwmi_om_priv *priv)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < LWMI_FAN_NR; i++) {
|
||||
priv->fan_info[i] = (struct lwmi_fan_info) {
|
||||
.supported = 0,
|
||||
/*
|
||||
* Assume 0 on probe as the EC resets all fans to auto mode on (re)boot.
|
||||
*
|
||||
* Note that S0ix (s2idle) preserves the RPM target, so we don't need
|
||||
* suspend/resume callbacks. This behavior has not been tested on S3-
|
||||
* capable devices, but I doubt if such devices even have this interface.
|
||||
*/
|
||||
.last_target = 0,
|
||||
.min_rpm = -ENODATA,
|
||||
.max_rpm = -ENODATA,
|
||||
};
|
||||
}
|
||||
|
||||
priv->fan_flags.capdata00_collected = false;
|
||||
priv->fan_flags.capdata_fan_collected = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_om_fan_info_collect_cd00() - Collect fan info from capdata 00
|
||||
* @priv: Driver private data
|
||||
*/
|
||||
static void lwmi_om_fan_info_collect_cd00(struct lwmi_om_priv *priv)
|
||||
{
|
||||
struct capdata00 capdata00;
|
||||
int i, err;
|
||||
|
||||
dev_dbg(&priv->wdev->dev, "Collecting fan info from capdata00\n");
|
||||
|
||||
for (i = 0; i < LWMI_FAN_NR; i++) {
|
||||
err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &capdata00);
|
||||
priv->fan_info[i].supported = err ? 0 : capdata00.supported;
|
||||
}
|
||||
|
||||
priv->fan_flags.capdata00_collected = true;
|
||||
lwmi_om_hwmon_add(priv);
|
||||
}
|
||||
|
||||
/**
|
||||
* lwmi_om_fan_info_collect_cd_fan() - Collect fan info from capdata fan
|
||||
* @dev: Pointer to the lenovo-wmi-other device
|
||||
* @cd_fan_list: Pointer to the capdata fan list
|
||||
*/
|
||||
static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *cd_fan_list)
|
||||
{
|
||||
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
|
||||
struct capdata_fan capdata_fan;
|
||||
int i, err;
|
||||
|
||||
dev_dbg(dev, "Collecting fan info from capdata_fan\n");
|
||||
|
||||
if (!cd_fan_list)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < LWMI_FAN_NR; i++) {
|
||||
err = lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan);
|
||||
if (err)
|
||||
continue;
|
||||
|
||||
priv->fan_info[i].min_rpm = capdata_fan.min_rpm;
|
||||
priv->fan_info[i].max_rpm = capdata_fan.max_rpm;
|
||||
}
|
||||
|
||||
out:
|
||||
priv->fan_flags.capdata_fan_collected = true;
|
||||
lwmi_om_hwmon_add(priv);
|
||||
}
|
||||
|
||||
/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
|
||||
|
||||
struct tunable_attr_01 {
|
||||
struct capdata01 *capdata;
|
||||
struct device *dev;
|
||||
|
|
@ -561,32 +1024,45 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
|
|||
device_unregister(priv->fw_attr_dev);
|
||||
}
|
||||
|
||||
/* ======== Self (master: lenovo-wmi-other) ======== */
|
||||
|
||||
/**
|
||||
* lwmi_om_master_bind() - Bind all components of the other mode driver
|
||||
* @dev: The lenovo-wmi-other driver basic device.
|
||||
*
|
||||
* Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the
|
||||
* lenovo-wmi-other master driver. On success, assign the capability data 01
|
||||
* list pointer to the driver data struct for later access. This pointer
|
||||
* is only valid while the capdata01 interface exists. Finally, register all
|
||||
* firmware attribute groups.
|
||||
* Call component_bind_all to bind the lenovo-wmi-capdata devices to the
|
||||
* lenovo-wmi-other master driver, with a callback to collect fan info from
|
||||
* capdata_fan. On success, assign the capability data list pointers to the
|
||||
* driver data struct for later access. These pointers are only valid while the
|
||||
* capdata interfaces exist. Finally, collect fan info from capdata00 and
|
||||
* register all firmware attribute groups. Note that the HWMON device is
|
||||
* registered only if all fan info is collected. Hence, it is not registered
|
||||
* here. See lwmi_om_fan_info_collect_cd00() and
|
||||
* lwmi_om_fan_info_collect_cd_fan().
|
||||
*
|
||||
* Return: 0 on success, or an error code.
|
||||
*/
|
||||
static int lwmi_om_master_bind(struct device *dev)
|
||||
{
|
||||
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
|
||||
struct cd01_list *tmp_list;
|
||||
struct lwmi_cd_binder binder = {
|
||||
.cd_fan_list_cb = lwmi_om_fan_info_collect_cd_fan,
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = component_bind_all(dev, &tmp_list);
|
||||
lwmi_om_fan_info_init(priv);
|
||||
|
||||
ret = component_bind_all(dev, &binder);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
priv->cd01_list = tmp_list;
|
||||
if (!priv->cd01_list)
|
||||
priv->cd00_list = binder.cd00_list;
|
||||
priv->cd01_list = binder.cd01_list;
|
||||
if (!priv->cd00_list || !priv->cd01_list)
|
||||
return -ENODEV;
|
||||
|
||||
lwmi_om_fan_info_collect_cd00(priv);
|
||||
|
||||
return lwmi_om_fw_attr_add(priv);
|
||||
}
|
||||
|
||||
|
|
@ -594,15 +1070,18 @@ static int lwmi_om_master_bind(struct device *dev)
|
|||
* lwmi_om_master_unbind() - Unbind all components of the other mode driver
|
||||
* @dev: The lenovo-wmi-other driver basic device
|
||||
*
|
||||
* Unregister all capability data attribute groups. Then call
|
||||
* component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the
|
||||
* lenovo-wmi-other master driver. Finally, free the IDA for this device.
|
||||
* Unregister all firmware attribute groups and the HWMON device. Then call
|
||||
* component_unbind_all to unbind lenovo-wmi-capdata devices from the
|
||||
* lenovo-wmi-other master driver.
|
||||
*/
|
||||
static void lwmi_om_master_unbind(struct device *dev)
|
||||
{
|
||||
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
|
||||
|
||||
lwmi_om_fw_attr_remove(priv);
|
||||
|
||||
lwmi_om_hwmon_remove(priv);
|
||||
|
||||
component_unbind_all(dev, NULL);
|
||||
}
|
||||
|
||||
|
|
@ -620,10 +1099,13 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
|
|||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Sentinel for on-demand ida_free(). */
|
||||
priv->ida_id = -EIDRM;
|
||||
|
||||
priv->wdev = wdev;
|
||||
dev_set_drvdata(&wdev->dev, priv);
|
||||
|
||||
component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
|
||||
lwmi_cd_match_add_all(&wdev->dev, &master_match);
|
||||
if (IS_ERR(master_match))
|
||||
return PTR_ERR(master_match);
|
||||
|
||||
|
|
@ -636,7 +1118,10 @@ static void lwmi_other_remove(struct wmi_device *wdev)
|
|||
struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
|
||||
|
||||
component_master_del(&wdev->dev, &lwmi_om_master_ops);
|
||||
ida_free(&lwmi_om_ida, priv->ida_id);
|
||||
|
||||
/* No IDA to free if the driver is never bound to its components. */
|
||||
if (priv->ida_id >= 0)
|
||||
ida_free(&lwmi_om_ida, priv->ida_id);
|
||||
}
|
||||
|
||||
static const struct wmi_device_id lwmi_other_id_table[] = {
|
||||
|
|
@ -657,9 +1142,10 @@ static struct wmi_driver lwmi_other_driver = {
|
|||
|
||||
module_wmi_driver(lwmi_other_driver);
|
||||
|
||||
MODULE_IMPORT_NS("LENOVO_WMI_CD01");
|
||||
MODULE_IMPORT_NS("LENOVO_WMI_CAPDATA");
|
||||
MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
|
||||
MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
|
||||
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
|
||||
MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
|
||||
MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ struct yogabook_data {
|
|||
struct work_struct work;
|
||||
struct led_classdev kbd_bl_led;
|
||||
unsigned long flags;
|
||||
uint8_t brightness;
|
||||
u8 brightness;
|
||||
};
|
||||
|
||||
static void yogabook_work(struct work_struct *work)
|
||||
|
|
@ -338,16 +338,18 @@ static int yogabook_wmi_probe(struct wmi_device *wdev, const void *context)
|
|||
int r;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (data == NULL)
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->kbd_adev = acpi_dev_get_first_match_dev("GDIX1001", NULL, -1);
|
||||
if (!data->kbd_adev)
|
||||
return dev_err_probe(dev, -ENODEV, "Cannot find the touchpad device in ACPI tables\n");
|
||||
return dev_err_probe(dev, -ENODEV,
|
||||
"Cannot find the touchpad device in ACPI tables\n");
|
||||
|
||||
data->dig_adev = acpi_dev_get_first_match_dev("WCOM0019", NULL, -1);
|
||||
if (!data->dig_adev) {
|
||||
r = dev_err_probe(dev, -ENODEV, "Cannot find the digitizer device in ACPI tables\n");
|
||||
r = dev_err_probe(dev, -ENODEV,
|
||||
"Cannot find the digitizer device in ACPI tables\n");
|
||||
goto error_put_devs;
|
||||
}
|
||||
|
||||
|
|
@ -453,7 +455,7 @@ static int yogabook_pdev_probe(struct platform_device *pdev)
|
|||
int r;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (data == NULL)
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->kbd_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "i2c-goodix_ts");
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@
|
|||
|
||||
#define EC_ADDR_GPU_TEMP 0x044F
|
||||
|
||||
#define EC_ADDR_SYSTEM_ID 0x0456
|
||||
#define HAS_GPU BIT(7)
|
||||
|
||||
#define EC_ADDR_MAIN_FAN_RPM_1 0x0464
|
||||
|
||||
#define EC_ADDR_MAIN_FAN_RPM_2 0x0465
|
||||
|
|
@ -122,11 +125,11 @@
|
|||
#define CTGP_DB_DB_ENABLE BIT(1)
|
||||
#define CTGP_DB_CTGP_ENABLE BIT(2)
|
||||
|
||||
#define EC_ADDR_CTGP_OFFSET 0x0744
|
||||
#define EC_ADDR_CTGP_DB_CTGP_OFFSET 0x0744
|
||||
|
||||
#define EC_ADDR_TPP_OFFSET 0x0745
|
||||
#define EC_ADDR_CTGP_DB_TPP_OFFSET 0x0745
|
||||
|
||||
#define EC_ADDR_MAX_TGP 0x0746
|
||||
#define EC_ADDR_CTGP_DB_DB_OFFSET 0x0746
|
||||
|
||||
#define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748
|
||||
#define LIGHTBAR_APP_EXISTS BIT(0)
|
||||
|
|
@ -317,11 +320,13 @@
|
|||
#define UNIWILL_FEATURE_LIGHTBAR BIT(3)
|
||||
#define UNIWILL_FEATURE_BATTERY BIT(4)
|
||||
#define UNIWILL_FEATURE_HWMON BIT(5)
|
||||
#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(6)
|
||||
|
||||
struct uniwill_data {
|
||||
struct device *dev;
|
||||
acpi_handle handle;
|
||||
struct regmap *regmap;
|
||||
unsigned int features;
|
||||
struct acpi_battery_hook hook;
|
||||
unsigned int last_charge_ctrl;
|
||||
struct mutex battery_lock; /* Protects the list of currently registered batteries */
|
||||
|
|
@ -341,12 +346,21 @@ struct uniwill_battery_entry {
|
|||
struct power_supply *battery;
|
||||
};
|
||||
|
||||
struct uniwill_device_descriptor {
|
||||
unsigned int features;
|
||||
/* Executed during driver probing */
|
||||
int (*probe)(struct uniwill_data *data);
|
||||
};
|
||||
|
||||
static bool force;
|
||||
module_param_unsafe(force, bool, 0);
|
||||
MODULE_PARM_DESC(force, "Force loading without checking for supported devices\n");
|
||||
|
||||
/* Feature bitmask since the associated registers are not reliable */
|
||||
static unsigned int supported_features;
|
||||
/*
|
||||
* Contains device specific data like the feature bitmap since
|
||||
* the associated registers are not always reliable.
|
||||
*/
|
||||
static struct uniwill_device_descriptor device_descriptor __ro_after_init;
|
||||
|
||||
static const char * const uniwill_temp_labels[] = {
|
||||
"CPU",
|
||||
|
|
@ -411,6 +425,12 @@ static const struct key_entry uniwill_keymap[] = {
|
|||
{ KE_END }
|
||||
};
|
||||
|
||||
static inline bool uniwill_device_supports(struct uniwill_data *data,
|
||||
unsigned int features)
|
||||
{
|
||||
return (data->features & features) == features;
|
||||
}
|
||||
|
||||
static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val)
|
||||
{
|
||||
union acpi_object params[2] = {
|
||||
|
|
@ -498,6 +518,10 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg)
|
|||
case EC_ADDR_LIGHTBAR_BAT_RED:
|
||||
case EC_ADDR_LIGHTBAR_BAT_GREEN:
|
||||
case EC_ADDR_LIGHTBAR_BAT_BLUE:
|
||||
case EC_ADDR_CTGP_DB_CTRL:
|
||||
case EC_ADDR_CTGP_DB_CTGP_OFFSET:
|
||||
case EC_ADDR_CTGP_DB_TPP_OFFSET:
|
||||
case EC_ADDR_CTGP_DB_DB_OFFSET:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
@ -531,6 +555,11 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg)
|
|||
case EC_ADDR_LIGHTBAR_BAT_RED:
|
||||
case EC_ADDR_LIGHTBAR_BAT_GREEN:
|
||||
case EC_ADDR_LIGHTBAR_BAT_BLUE:
|
||||
case EC_ADDR_SYSTEM_ID:
|
||||
case EC_ADDR_CTGP_DB_CTRL:
|
||||
case EC_ADDR_CTGP_DB_CTGP_OFFSET:
|
||||
case EC_ADDR_CTGP_DB_TPP_OFFSET:
|
||||
case EC_ADDR_CTGP_DB_DB_OFFSET:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
@ -786,6 +815,70 @@ static ssize_t breathing_in_suspend_show(struct device *dev, struct device_attri
|
|||
|
||||
static DEVICE_ATTR_RW(breathing_in_suspend);
|
||||
|
||||
static ssize_t ctgp_offset_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct uniwill_data *data = dev_get_drvdata(dev);
|
||||
unsigned int value;
|
||||
int ret;
|
||||
|
||||
ret = kstrtouint(buf, 0, &value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (value > U8_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t ctgp_offset_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct uniwill_data *data = dev_get_drvdata(dev);
|
||||
unsigned int value;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, &value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%u\n", value);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(ctgp_offset);
|
||||
|
||||
static int uniwill_nvidia_ctgp_init(struct uniwill_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
|
||||
return 0;
|
||||
|
||||
ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_TPP_OFFSET, 255);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_DB_OFFSET, 25);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
|
||||
CTGP_DB_GENERAL_ENABLE | CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct attribute *uniwill_attrs[] = {
|
||||
/* Keyboard-related */
|
||||
&dev_attr_fn_lock_toggle_enable.attr,
|
||||
|
|
@ -794,29 +887,39 @@ static struct attribute *uniwill_attrs[] = {
|
|||
/* Lightbar-related */
|
||||
&dev_attr_rainbow_animation.attr,
|
||||
&dev_attr_breathing_in_suspend.attr,
|
||||
/* Power-management-related */
|
||||
&dev_attr_ctgp_offset.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct uniwill_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (attr == &dev_attr_fn_lock_toggle_enable.attr) {
|
||||
if (supported_features & UNIWILL_FEATURE_FN_LOCK_TOGGLE)
|
||||
if (uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK_TOGGLE))
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
if (attr == &dev_attr_super_key_toggle_enable.attr) {
|
||||
if (supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE)
|
||||
if (uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
if (attr == &dev_attr_touchpad_toggle_enable.attr) {
|
||||
if (supported_features & UNIWILL_FEATURE_TOUCHPAD_TOGGLE)
|
||||
if (uniwill_device_supports(data, UNIWILL_FEATURE_TOUCHPAD_TOGGLE))
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
if (attr == &dev_attr_rainbow_animation.attr ||
|
||||
attr == &dev_attr_breathing_in_suspend.attr) {
|
||||
if (supported_features & UNIWILL_FEATURE_LIGHTBAR)
|
||||
if (uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR))
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
if (attr == &dev_attr_ctgp_offset.attr) {
|
||||
if (uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
|
|
@ -944,7 +1047,7 @@ static int uniwill_hwmon_init(struct uniwill_data *data)
|
|||
{
|
||||
struct device *hdev;
|
||||
|
||||
if (!(supported_features & UNIWILL_FEATURE_HWMON))
|
||||
if (!uniwill_device_supports(data, UNIWILL_FEATURE_HWMON))
|
||||
return 0;
|
||||
|
||||
hdev = devm_hwmon_device_register_with_info(data->dev, "uniwill", data,
|
||||
|
|
@ -1019,7 +1122,7 @@ static int uniwill_led_init(struct uniwill_data *data)
|
|||
unsigned int value;
|
||||
int ret;
|
||||
|
||||
if (!(supported_features & UNIWILL_FEATURE_LIGHTBAR))
|
||||
if (!uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR))
|
||||
return 0;
|
||||
|
||||
ret = devm_mutex_init(data->dev, &data->led_lock);
|
||||
|
|
@ -1232,7 +1335,7 @@ static int uniwill_battery_init(struct uniwill_data *data)
|
|||
{
|
||||
int ret;
|
||||
|
||||
if (!(supported_features & UNIWILL_FEATURE_BATTERY))
|
||||
if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
|
||||
return 0;
|
||||
|
||||
ret = devm_mutex_init(data->dev, &data->battery_lock);
|
||||
|
|
@ -1361,6 +1464,19 @@ static int uniwill_probe(struct platform_device *pdev)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->features = device_descriptor.features;
|
||||
|
||||
/*
|
||||
* Some devices might need to perform some device-specific initialization steps
|
||||
* before the supported features are initialized. Because of this we have to call
|
||||
* this callback just after the EC itself was initialized.
|
||||
*/
|
||||
if (device_descriptor.probe) {
|
||||
ret = device_descriptor.probe(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = uniwill_battery_init(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
|
@ -1373,6 +1489,10 @@ static int uniwill_probe(struct platform_device *pdev)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = uniwill_nvidia_ctgp_init(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return uniwill_input_init(data);
|
||||
}
|
||||
|
||||
|
|
@ -1385,7 +1505,7 @@ static void uniwill_shutdown(struct platform_device *pdev)
|
|||
|
||||
static int uniwill_suspend_keyboard(struct uniwill_data *data)
|
||||
{
|
||||
if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
|
||||
if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
|
|
@ -1397,7 +1517,7 @@ static int uniwill_suspend_keyboard(struct uniwill_data *data)
|
|||
|
||||
static int uniwill_suspend_battery(struct uniwill_data *data)
|
||||
{
|
||||
if (!(supported_features & UNIWILL_FEATURE_BATTERY))
|
||||
if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
|
|
@ -1408,6 +1528,15 @@ static int uniwill_suspend_battery(struct uniwill_data *data)
|
|||
return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl);
|
||||
}
|
||||
|
||||
static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data)
|
||||
{
|
||||
if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
|
||||
return 0;
|
||||
|
||||
return regmap_clear_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
|
||||
CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
|
||||
}
|
||||
|
||||
static int uniwill_suspend(struct device *dev)
|
||||
{
|
||||
struct uniwill_data *data = dev_get_drvdata(dev);
|
||||
|
|
@ -1421,6 +1550,10 @@ static int uniwill_suspend(struct device *dev)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = uniwill_suspend_nvidia_ctgp(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
regcache_cache_only(data->regmap, true);
|
||||
regcache_mark_dirty(data->regmap);
|
||||
|
||||
|
|
@ -1432,7 +1565,7 @@ static int uniwill_resume_keyboard(struct uniwill_data *data)
|
|||
unsigned int value;
|
||||
int ret;
|
||||
|
||||
if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
|
||||
if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
|
||||
return 0;
|
||||
|
||||
ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value);
|
||||
|
|
@ -1448,13 +1581,22 @@ static int uniwill_resume_keyboard(struct uniwill_data *data)
|
|||
|
||||
static int uniwill_resume_battery(struct uniwill_data *data)
|
||||
{
|
||||
if (!(supported_features & UNIWILL_FEATURE_BATTERY))
|
||||
if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
|
||||
return 0;
|
||||
|
||||
return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK,
|
||||
data->last_charge_ctrl);
|
||||
}
|
||||
|
||||
static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data)
|
||||
{
|
||||
if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
|
||||
return 0;
|
||||
|
||||
return regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
|
||||
CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
|
||||
}
|
||||
|
||||
static int uniwill_resume(struct device *dev)
|
||||
{
|
||||
struct uniwill_data *data = dev_get_drvdata(dev);
|
||||
|
|
@ -1470,7 +1612,11 @@ static int uniwill_resume(struct device *dev)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return uniwill_resume_battery(data);
|
||||
ret = uniwill_resume_battery(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return uniwill_resume_nvidia_ctgp(data);
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(uniwill_pm_ops, uniwill_suspend, uniwill_resume);
|
||||
|
|
@ -1496,6 +1642,48 @@ static struct platform_driver uniwill_driver = {
|
|||
.shutdown = uniwill_shutdown,
|
||||
};
|
||||
|
||||
static struct uniwill_device_descriptor lapac71h_descriptor __initdata = {
|
||||
.features = UNIWILL_FEATURE_FN_LOCK_TOGGLE |
|
||||
UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
|
||||
UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
|
||||
UNIWILL_FEATURE_BATTERY |
|
||||
UNIWILL_FEATURE_HWMON,
|
||||
};
|
||||
|
||||
static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = {
|
||||
.features = UNIWILL_FEATURE_FN_LOCK_TOGGLE |
|
||||
UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
|
||||
UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
|
||||
UNIWILL_FEATURE_LIGHTBAR |
|
||||
UNIWILL_FEATURE_BATTERY |
|
||||
UNIWILL_FEATURE_HWMON,
|
||||
};
|
||||
|
||||
static int phxarx1_phxaqf1_probe(struct uniwill_data *data)
|
||||
{
|
||||
unsigned int value;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(data->regmap, EC_ADDR_SYSTEM_ID, &value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (value & HAS_GPU)
|
||||
data->features |= UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL;
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
static struct uniwill_device_descriptor phxarx1_phxaqf1_descriptor __initdata = {
|
||||
.probe = phxarx1_phxaqf1_probe,
|
||||
};
|
||||
|
||||
static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = {
|
||||
.features = UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
|
||||
};
|
||||
|
||||
static struct uniwill_device_descriptor empty_descriptor __initdata = {};
|
||||
|
||||
static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
||||
{
|
||||
.ident = "XMG FUSION 15",
|
||||
|
|
@ -1503,6 +1691,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "XMG FUSION 15",
|
||||
|
|
@ -1510,6 +1699,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "Intel NUC x15",
|
||||
|
|
@ -1517,11 +1707,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPAC71H"),
|
||||
},
|
||||
.driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE |
|
||||
UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
|
||||
UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
|
||||
UNIWILL_FEATURE_BATTERY |
|
||||
UNIWILL_FEATURE_HWMON),
|
||||
.driver_data = &lapac71h_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "Intel NUC x15",
|
||||
|
|
@ -1529,12 +1715,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"),
|
||||
},
|
||||
.driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE |
|
||||
UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
|
||||
UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
|
||||
UNIWILL_FEATURE_LIGHTBAR |
|
||||
UNIWILL_FEATURE_BATTERY |
|
||||
UNIWILL_FEATURE_HWMON),
|
||||
.driver_data = &lapkc71f_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel",
|
||||
|
|
@ -1542,6 +1723,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel",
|
||||
|
|
@ -1549,6 +1731,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel",
|
||||
|
|
@ -1556,6 +1739,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"),
|
||||
},
|
||||
.driver_data = &phxarx1_phxaqf1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 16 Gen7 Intel/Commodore Omnia-Book Pro Gen 7",
|
||||
|
|
@ -1563,6 +1747,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 14/16 Gen8 Intel/Commodore Omnia-Book Pro Gen 8",
|
||||
|
|
@ -1570,6 +1755,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 14 Gen8 Intel/Commodore Omnia-Book Pro Gen 8",
|
||||
|
|
@ -1577,6 +1763,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel",
|
||||
|
|
@ -1584,6 +1771,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD",
|
||||
|
|
@ -1591,6 +1779,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9",
|
||||
|
|
@ -1598,6 +1787,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
|
||||
|
|
@ -1605,6 +1795,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
|
||||
|
|
@ -1612,6 +1803,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel",
|
||||
|
|
@ -1619,6 +1811,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Max 15 Gen10 AMD",
|
||||
|
|
@ -1626,6 +1819,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
|
||||
|
|
@ -1633,6 +1827,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
|
||||
|
|
@ -1640,6 +1835,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Max 15 Gen10 Intel",
|
||||
|
|
@ -1647,6 +1843,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO InfinityBook Max 16 Gen10 Intel",
|
||||
|
|
@ -1654,6 +1851,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Polaris 15 Gen1 AMD",
|
||||
|
|
@ -1661,6 +1859,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Polaris 15 Gen1 AMD",
|
||||
|
|
@ -1668,6 +1867,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Polaris 17 Gen1 AMD",
|
||||
|
|
@ -1675,6 +1875,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Polaris 17 Gen1 AMD",
|
||||
|
|
@ -1682,6 +1883,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Polaris 15 Gen1 Intel",
|
||||
|
|
@ -1689,6 +1891,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Polaris 15 Gen1 Intel",
|
||||
|
|
@ -1696,6 +1899,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Polaris 17 Gen1 Intel",
|
||||
|
|
@ -1703,6 +1907,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Polaris 17 Gen1 Intel",
|
||||
|
|
@ -1710,6 +1915,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Trinity 15 Intel Gen1",
|
||||
|
|
@ -1717,6 +1923,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Trinity 17 Intel Gen1",
|
||||
|
|
@ -1724,6 +1931,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Polaris 15/17 Gen2 AMD",
|
||||
|
|
@ -1731,6 +1939,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Polaris 15/17 Gen2 Intel",
|
||||
|
|
@ -1738,6 +1947,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD",
|
||||
|
|
@ -1745,6 +1955,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel",
|
||||
|
|
@ -1752,6 +1963,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD",
|
||||
|
|
@ -1759,6 +1971,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris 15 Gen4 Intel",
|
||||
|
|
@ -1766,6 +1979,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Polaris 15/17 Gen5 AMD",
|
||||
|
|
@ -1773,6 +1987,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris 16 Gen5 AMD",
|
||||
|
|
@ -1780,6 +1995,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris 16/17 Gen5 Intel/Commodore ORION Gen 5",
|
||||
|
|
@ -1787,6 +2003,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris Slim 15 Gen6 AMD",
|
||||
|
|
@ -1794,6 +2011,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6",
|
||||
|
|
@ -1801,6 +2019,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
|
||||
|
|
@ -1808,6 +2027,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
|
||||
|
|
@ -1815,6 +2035,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6",
|
||||
|
|
@ -1822,6 +2043,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris 16 Gen7 AMD",
|
||||
|
|
@ -1829,6 +2051,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
|
||||
|
|
@ -1836,6 +2059,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
|
||||
|
|
@ -1843,6 +2067,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"),
|
||||
},
|
||||
.driver_data = &tux_featureset_1_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Book BA15 Gen10 AMD",
|
||||
|
|
@ -1850,6 +2075,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Pulse 14 Gen1 AMD",
|
||||
|
|
@ -1857,6 +2083,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Pulse 15 Gen1 AMD",
|
||||
|
|
@ -1864,6 +2091,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{
|
||||
.ident = "TUXEDO Pulse 15 Gen2 AMD",
|
||||
|
|
@ -1871,6 +2099,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"),
|
||||
},
|
||||
.driver_data = &empty_descriptor,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
|
@ -1878,6 +2107,7 @@ MODULE_DEVICE_TABLE(dmi, uniwill_dmi_table);
|
|||
|
||||
static int __init uniwill_init(void)
|
||||
{
|
||||
const struct uniwill_device_descriptor *descriptor;
|
||||
const struct dmi_system_id *id;
|
||||
int ret;
|
||||
|
||||
|
|
@ -1887,10 +2117,22 @@ static int __init uniwill_init(void)
|
|||
return -ENODEV;
|
||||
|
||||
/* Assume that the device supports all features */
|
||||
supported_features = UINT_MAX;
|
||||
device_descriptor.features = UINT_MAX;
|
||||
pr_warn("Loading on a potentially unsupported device\n");
|
||||
} else {
|
||||
supported_features = (uintptr_t)id->driver_data;
|
||||
/*
|
||||
* Some devices might support additional features depending on
|
||||
* the BIOS version/date, so we call this callback to let them
|
||||
* modify their device descriptor accordingly.
|
||||
*/
|
||||
if (id->callback) {
|
||||
ret = id->callback(id);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
descriptor = id->driver_data;
|
||||
device_descriptor = *descriptor;
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&uniwill_driver);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/kernel.h>
|
||||
|
|
@ -24,9 +23,9 @@ static ssize_t bmof_read(struct file *filp, struct kobject *kobj, const struct b
|
|||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
union acpi_object *obj = dev_get_drvdata(dev);
|
||||
struct wmi_buffer *buffer = dev_get_drvdata(dev);
|
||||
|
||||
return memory_read_from_buffer(buf, count, &off, obj->buffer.pointer, obj->buffer.length);
|
||||
return memory_read_from_buffer(buf, count, &off, buffer->data, buffer->length);
|
||||
}
|
||||
|
||||
static const BIN_ATTR_ADMIN_RO(bmof, 0);
|
||||
|
|
@ -39,9 +38,9 @@ static const struct bin_attribute * const bmof_attrs[] = {
|
|||
static size_t bmof_bin_size(struct kobject *kobj, const struct bin_attribute *attr, int n)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
union acpi_object *obj = dev_get_drvdata(dev);
|
||||
struct wmi_buffer *buffer = dev_get_drvdata(dev);
|
||||
|
||||
return obj->buffer.length;
|
||||
return buffer->length;
|
||||
}
|
||||
|
||||
static const struct attribute_group bmof_group = {
|
||||
|
|
@ -56,30 +55,27 @@ static const struct attribute_group *bmof_groups[] = {
|
|||
|
||||
static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
union acpi_object *obj;
|
||||
struct wmi_buffer *buffer;
|
||||
int ret;
|
||||
|
||||
obj = wmidev_block_query(wdev, 0);
|
||||
if (!obj) {
|
||||
dev_err(&wdev->dev, "failed to read Binary MOF\n");
|
||||
return -EIO;
|
||||
}
|
||||
buffer = devm_kzalloc(&wdev->dev, sizeof(*buffer), GFP_KERNEL);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
if (obj->type != ACPI_TYPE_BUFFER) {
|
||||
dev_err(&wdev->dev, "Binary MOF is not a buffer\n");
|
||||
kfree(obj);
|
||||
return -EIO;
|
||||
}
|
||||
ret = wmidev_query_block(wdev, 0, buffer);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dev_set_drvdata(&wdev->dev, obj);
|
||||
dev_set_drvdata(&wdev->dev, buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wmi_bmof_remove(struct wmi_device *wdev)
|
||||
{
|
||||
union acpi_object *obj = dev_get_drvdata(&wdev->dev);
|
||||
struct wmi_buffer *buffer = dev_get_drvdata(&wdev->dev);
|
||||
|
||||
kfree(obj);
|
||||
kfree(buffer->data);
|
||||
}
|
||||
|
||||
static const struct wmi_device_id wmi_bmof_id_table[] = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* WMI driver for Xiaomi Laptops */
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/module.h>
|
||||
|
|
@ -56,7 +55,7 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
|
|||
return input_register_device(data->input_dev);
|
||||
}
|
||||
|
||||
static void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy)
|
||||
static void xiaomi_wmi_notify(struct wmi_device *wdev, const struct wmi_buffer *dummy)
|
||||
{
|
||||
struct xiaomi_wmi *data = dev_get_drvdata(&wdev->dev);
|
||||
|
||||
|
|
@ -85,7 +84,7 @@ static struct wmi_driver xiaomi_wmi_driver = {
|
|||
},
|
||||
.id_table = xiaomi_wmi_id_table,
|
||||
.probe = xiaomi_wmi_probe,
|
||||
.notify = xiaomi_wmi_notify,
|
||||
.notify_new = xiaomi_wmi_notify,
|
||||
.no_singleton = true,
|
||||
};
|
||||
module_wmi_driver(xiaomi_wmi_driver);
|
||||
|
|
|
|||
|
|
@ -61,5 +61,26 @@ enum laptop_placement {
|
|||
LP_UNDEFINED,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver
|
||||
* @npuclk_freq: NPU clock frequency [MHz]
|
||||
* @npu_busy: NPU busy % [0-100]
|
||||
* @npu_power: NPU power [mW]
|
||||
* @mpnpuclk_freq: MPNPU [MHz]
|
||||
* @npu_reads: NPU read bandwidth [MB/sec]
|
||||
* @npu_writes: NPU write bandwidth [MB/sec]
|
||||
*/
|
||||
struct amd_pmf_npu_metrics {
|
||||
u16 npuclk_freq;
|
||||
u16 npu_busy[8];
|
||||
u16 npu_power;
|
||||
u16 mpnpuclk_freq;
|
||||
u16 npu_reads;
|
||||
u16 npu_writes;
|
||||
};
|
||||
|
||||
int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op);
|
||||
|
||||
/* AMD PMF and NPU interface */
|
||||
int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H
|
||||
#define __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H
|
||||
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */
|
||||
#if IS_REACHABLE(CONFIG_ASUS_WMI) || IS_REACHABLE(CONFIG_HID_ASUS)
|
||||
static const struct dmi_system_id asus_use_hid_led_dmi_ids[] = {
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Zephyrus"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Strix"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Flow"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_PRODUCT_FAMILY, "ProArt P16"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "GA403U"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "GU605M"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "RC71L"),
|
||||
},
|
||||
},
|
||||
{ },
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif /* __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H */
|
||||
|
|
@ -173,12 +173,29 @@ enum asus_ally_mcu_hack {
|
|||
ASUS_WMI_ALLY_MCU_HACK_DISABLED,
|
||||
};
|
||||
|
||||
/* Used to notify hid-asus when asus-wmi changes keyboard backlight */
|
||||
struct asus_hid_listener {
|
||||
struct list_head list;
|
||||
void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
|
||||
};
|
||||
|
||||
enum asus_hid_event {
|
||||
ASUS_EV_BRTUP,
|
||||
ASUS_EV_BRTDOWN,
|
||||
ASUS_EV_BRTTOGGLE,
|
||||
};
|
||||
|
||||
#define ASUS_EV_MAX_BRIGHTNESS 3
|
||||
|
||||
#if IS_REACHABLE(CONFIG_ASUS_WMI)
|
||||
void set_ally_mcu_hack(enum asus_ally_mcu_hack status);
|
||||
void set_ally_mcu_powersave(bool enabled);
|
||||
int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval);
|
||||
int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval);
|
||||
int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
|
||||
int asus_hid_register_listener(struct asus_hid_listener *cdev);
|
||||
void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
|
||||
int asus_hid_event(enum asus_hid_event event);
|
||||
#else
|
||||
static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status)
|
||||
{
|
||||
|
|
@ -199,6 +216,17 @@ static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
|
|||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
static inline int asus_hid_register_listener(struct asus_hid_listener *bdev)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
|
||||
{
|
||||
}
|
||||
static inline int asus_hid_event(enum asus_hid_event event)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
* and should include an appropriate local definition in their source file.
|
||||
*/
|
||||
#define PSP_CMDRESP_STS GENMASK(15, 0)
|
||||
#define PSP_TEE_STS_RING_BUSY 0x0000000d /* Ring already initialized */
|
||||
#define PSP_CMDRESP_CMD GENMASK(23, 16)
|
||||
#define PSP_CMDRESP_RESERVED GENMASK(29, 24)
|
||||
#define PSP_CMDRESP_RECOVERY BIT(30)
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@
|
|||
#ifndef _LINUX_WMI_H
|
||||
#define _LINUX_WMI_H
|
||||
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/**
|
||||
* struct wmi_device - WMI device structure
|
||||
|
|
@ -36,6 +38,42 @@ struct wmi_device {
|
|||
*/
|
||||
#define to_wmi_device(device) container_of_const(device, struct wmi_device, dev)
|
||||
|
||||
/**
|
||||
* struct wmi_buffer - WMI data buffer
|
||||
* @length: Buffer length in bytes
|
||||
* @data: Pointer to the buffer content
|
||||
*
|
||||
* This structure is used to exchange data with the WMI driver core.
|
||||
*/
|
||||
struct wmi_buffer {
|
||||
size_t length;
|
||||
void *data;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct wmi_string - WMI string representation
|
||||
* @length: Size of @chars in bytes
|
||||
* @chars: UTF16-LE characters with optional nul termination and padding
|
||||
*
|
||||
* This structure is used when exchanging string data over the WMI interface.
|
||||
*/
|
||||
struct wmi_string {
|
||||
__le16 length;
|
||||
__le16 chars[];
|
||||
} __packed;
|
||||
|
||||
ssize_t wmi_string_to_utf8s(const struct wmi_string *str, u8 *dst, size_t length);
|
||||
|
||||
ssize_t wmi_string_from_utf8s(struct wmi_string *str, size_t max_chars, const u8 *src,
|
||||
size_t src_length);
|
||||
|
||||
int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id,
|
||||
const struct wmi_buffer *in, struct wmi_buffer *out);
|
||||
|
||||
int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out);
|
||||
|
||||
int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in);
|
||||
|
||||
acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id,
|
||||
const struct acpi_buffer *in, struct acpi_buffer *out);
|
||||
|
||||
|
|
@ -54,9 +92,11 @@ u8 wmidev_instance_count(struct wmi_device *wdev);
|
|||
* @probe: Callback for device binding
|
||||
* @remove: Callback for device unbinding
|
||||
* @shutdown: Callback for device shutdown
|
||||
* @notify: Callback for receiving WMI events
|
||||
* @notify: Callback for receiving WMI events (deprecated)
|
||||
* @notify_new: Callback for receiving WMI events
|
||||
*
|
||||
* This represents WMI drivers which handle WMI devices.
|
||||
* This represents WMI drivers which handle WMI devices. The data inside the buffer
|
||||
* passed to the @notify_new callback is guaranteed to be aligned on a 8-byte boundary.
|
||||
*/
|
||||
struct wmi_driver {
|
||||
struct device_driver driver;
|
||||
|
|
@ -68,6 +108,7 @@ struct wmi_driver {
|
|||
void (*remove)(struct wmi_device *wdev);
|
||||
void (*shutdown)(struct wmi_device *wdev);
|
||||
void (*notify)(struct wmi_device *device, union acpi_object *data);
|
||||
void (*notify_new)(struct wmi_device *device, const struct wmi_buffer *data);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,7 +13,13 @@ endif
|
|||
# Do not use make's built-in rules
|
||||
# (this improves performance and avoids hard-to-debug behaviour);
|
||||
MAKEFLAGS += -r
|
||||
override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include -I$(shell $(CC) -print-sysroot)/usr/include/libnl3
|
||||
|
||||
NL3_CFLAGS = $(shell pkg-config --cflags libnl-3.0 2>/dev/null)
|
||||
ifeq ($(NL3_CFLAGS),)
|
||||
NL3_CFLAGS = -I/usr/include/libnl3
|
||||
endif
|
||||
|
||||
override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include $(NL3_CFLAGS)
|
||||
override LDFLAGS += -lnl-genl-3 -lnl-3
|
||||
|
||||
ALL_TARGETS := intel-speed-select
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ struct process_cmd_struct {
|
|||
int arg;
|
||||
};
|
||||
|
||||
static const char *version_str = "v1.24";
|
||||
static const char *version_str = "v1.25";
|
||||
|
||||
static const int supported_api_ver = 3;
|
||||
static struct isst_if_platform_info isst_platform_info;
|
||||
|
|
@ -80,6 +80,18 @@ struct cpu_topology {
|
|||
short die_id;
|
||||
};
|
||||
|
||||
static int read_only;
|
||||
|
||||
static void check_privilege(void)
|
||||
{
|
||||
if (!read_only)
|
||||
return;
|
||||
|
||||
isst_display_error_info_message(1, "Insufficient privileges", 0, 0);
|
||||
isst_ctdp_display_information_end(outf);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
FILE *get_output_file(void)
|
||||
{
|
||||
return outf;
|
||||
|
|
@ -950,9 +962,11 @@ int isolate_cpus(struct isst_id *id, int mask_size, cpu_set_t *cpu_mask, int lev
|
|||
ret = write(fd, "member", strlen("member"));
|
||||
if (ret == -1) {
|
||||
printf("Can't update to member\n");
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1578,6 +1592,8 @@ free_mask:
|
|||
|
||||
static void set_tdp_level(int arg)
|
||||
{
|
||||
check_privilege();
|
||||
|
||||
if (cmd_help) {
|
||||
fprintf(stderr, "Set Config TDP level\n");
|
||||
fprintf(stderr,
|
||||
|
|
@ -2046,6 +2062,8 @@ static void set_pbf_enable(int arg)
|
|||
{
|
||||
int enable = arg;
|
||||
|
||||
check_privilege();
|
||||
|
||||
if (cmd_help) {
|
||||
if (enable) {
|
||||
fprintf(stderr,
|
||||
|
|
@ -2212,6 +2230,8 @@ static void set_fact_enable(int arg)
|
|||
int i, ret, enable = arg;
|
||||
struct isst_id id;
|
||||
|
||||
check_privilege();
|
||||
|
||||
if (cmd_help) {
|
||||
if (enable) {
|
||||
fprintf(stderr,
|
||||
|
|
@ -2361,6 +2381,8 @@ static void set_clos_enable(int arg)
|
|||
{
|
||||
int enable = arg;
|
||||
|
||||
check_privilege();
|
||||
|
||||
if (cmd_help) {
|
||||
if (enable) {
|
||||
fprintf(stderr,
|
||||
|
|
@ -2491,6 +2513,8 @@ static void set_clos_config_for_cpu(struct isst_id *id, void *arg1, void *arg2,
|
|||
|
||||
static void set_clos_config(int arg)
|
||||
{
|
||||
check_privilege();
|
||||
|
||||
if (cmd_help) {
|
||||
fprintf(stderr,
|
||||
"Set core-power configuration for one of the four clos ids\n");
|
||||
|
|
@ -2556,6 +2580,8 @@ static void set_clos_assoc_for_cpu(struct isst_id *id, void *arg1, void *arg2, v
|
|||
|
||||
static void set_clos_assoc(int arg)
|
||||
{
|
||||
check_privilege();
|
||||
|
||||
if (cmd_help) {
|
||||
fprintf(stderr, "Associate a clos id to a CPU\n");
|
||||
fprintf(stderr,
|
||||
|
|
@ -2637,6 +2663,8 @@ static void set_turbo_mode(int arg)
|
|||
int i, disable = arg;
|
||||
struct isst_id id;
|
||||
|
||||
check_privilege();
|
||||
|
||||
if (cmd_help) {
|
||||
if (disable)
|
||||
fprintf(stderr, "Set turbo mode disable\n");
|
||||
|
|
@ -2682,6 +2710,7 @@ static void get_set_trl(struct isst_id *id, void *arg1, void *arg2, void *arg3,
|
|||
}
|
||||
|
||||
if (set) {
|
||||
check_privilege();
|
||||
ret = isst_set_trl(id, fact_trl);
|
||||
isst_display_result(id, outf, "turbo-mode", "set-trl", ret);
|
||||
return;
|
||||
|
|
@ -3204,8 +3233,16 @@ static void cmdline(int argc, char **argv)
|
|||
};
|
||||
|
||||
if (geteuid() != 0) {
|
||||
fprintf(stderr, "Must run as root\n");
|
||||
exit(0);
|
||||
int fd;
|
||||
|
||||
fd = open(pathname, O_RDWR);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "Must run as root\n");
|
||||
exit(0);
|
||||
}
|
||||
fprintf(stderr, "\nNot running as root, Only read only operations are supported\n");
|
||||
close(fd);
|
||||
read_only = 1;
|
||||
}
|
||||
|
||||
ret = update_cpu_model();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue