platform-drivers-x86 for v6.17-1

Highlights
 
  - alienware: Add more precise labels to fans
 
  - amd/hsmp: Improve misleading probe errors (make the legacy driver
              aware when HSMP is supported through the ACPI driver)
 
  - amd/pmc: Add Lenovo Yoga 6 13ALCL6 to pmc quirk list
 
  - drm/xe: Correct (D)VSEC information to support PMT crashlog feature
 
  - fujitsu: Clamp charge threshold instead of returning an error
 
  - ideapad: Expore change types
 
  - intel/pmt:
 
    - Add PMT Discovery driver
 
    - Add API to retrieve telemetry regions by feature
 
    - Fix crashlog NULL access
 
    - Support Battlemage GPU (BMG) crashlog
 
  - intel/vsec:
 
    - Add Discovery feature
 
    - Add feature dependency support using device links
 
  - lenovo:
 
    - Move lenovo drivers under drivers/platform/x86/lenovo/
 
    - Add WMI drivers for Lenovo Gaming series
 
    - Improve DMI handling
 
  - oxpec:
 
    - Add support for OneXPlayer X1 Mini Pro (Strix Point variant)
 
    - Fix EC registers for G1 AMD
 
  - samsung-laptop: Expose change types
 
  - wmi: Fix WMI device naming issue (same GUID corner cases)
 
  - x86-android-tables: Add ovc-capacity-table to generic battery nodes
 
  - Miscellaneous cleanups / refactoring / improvements
 
 The following is an automated shortlog grouped by driver:
 
 Add Lenovo Capability Data 01 WMI Driver:
  - Add Lenovo Capability Data 01 WMI Driver
 
 Add Lenovo Gamezone WMI Driver:
  - Add Lenovo Gamezone WMI Driver
 
 Add Lenovo Other Mode WMI Driver:
  - Add Lenovo Other Mode WMI Driver
 
 Add lenovo-wmi-* driver Documentation:
  - Add lenovo-wmi-* driver Documentation
 
 Add Lenovo WMI Events Driver:
  - Add Lenovo WMI Events Driver
 
 Add lenovo-wmi-helpers:
  - Add lenovo-wmi-helpers
 
 alienware-wmi-wmax:
  -  Add appropriate labels to fans
 
 amd/hsmp:
  -  Enhance the print messages to prevent confusion
  -  Use IS_ENABLED() instead of IS_REACHABLE()
 
 amd: pmc:
  -  Add Lenovo Yoga 6 13ALC6 to pmc quirk list
 
 arm64: lenovo-yoga-c630:
  -  use the auxiliary device creation helper
 
 dell_rbu:
  -  Remove unused struct
 
 dell-uart-backlight:
  -  Use blacklight power constant
 
 docs:
  -  Add ABI documentation for intel_pmt feature directories
 
 Documentation: ABI:
  -  Update WMI device paths in ABI docs
 
 drm/xe:
  -  Correct BMG VSEC header sizing
  -  Correct the rev value for the DVSEC entries
 
 fujitsu:
  -  clamp charge_control_end_threshold values to 50
  -  use unsigned int for kstrtounit
 
 ideapad:
  -  Expose charge_types
 
 intel/pmt:
  -  Add PMT Discovery driver
  -  add register access helpers
  -  correct types
  -  decouple sysfs and namespace
 
 intel/pmt/discovery:
  -  fix format string warning
  -  Fix size_t specifiers for 32-bit
  -  Get telemetry attributes
 
 intel/pmt:
  -  fix a crashlog NULL pointer access
  -  fix build dependency for kunit test
  -  KUNIT test for PMT Enhanced Discovery API
  -  mutex clean up
  -  refactor base parameter
  -  re-order trigger logic
  -  support BMG crashlog
 
 intel/pmt/telemetry:
  -  Add API to retrieve telemetry regions by feature
 
 intel/pmt:
  -  use a version struct
  -  use guard(mutex)
  -  white space cleanup
 
 intel_telemetry:
  -  Remove unused telemetry_*_events()
  -  Remove unused telemetry_[gs]et_sampling_period()
  -  Remove unused telemetry_raw_read_events()
 
 intel/tpmi:
  -  Get OOBMSM CPU mapping from TPMI
  -  Relocate platform info to intel_vsec.h
 
 intel/vsec:
  -  Add device links to enforce dependencies
  -  Add new Discovery feature
  -  Add private data for per-device data
  -  Create wrapper to walk PCI config space
  -  Set OOBMSM to CPU mapping
  -  Skip absent features during initialization
  -  Skip driverless features
 
 lenovo:
  -  gamezone needs "other mode"
 
 lenovo-yoga-tab2-pro-1380-fastcharger:
  -  Use devm_pinctrl_register_mappings()
 
 MAINTAINERS:
  -  Add link to documentation of Intel PMT ABI
 
 Move Lenovo files into lenovo subdir:
  - Move Lenovo files into lenovo subdir
 
 oxpec:
  -  Add support for OneXPlayer X1 Mini Pro (Strix Point)
  -  Fix turbo register for G1 AMD
 
 samsung-laptop:
  -  Expose charge_types
 
 silicom:
  -  remove unnecessary GPIO line direction check
 
 thinklmi:
  -  improved DMI handling
 
 thinkpad_acpi:
  -  Handle KCOV __init vs inline mismatches
 
 wmi:
  -  Fix WMI device naming issue
 
 x86-android-tablets:
  -  Add generic_lipo_4v2_battery info
  -  Add ovc-capacity-table info
 
 Merges:
  -  Merge branch 'fixes' into 'for-next'
  -  Merge branch 'fixes' into for-next
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYIAB0WIQSCSUwRdwTNL2MhaBlZrE9hU+XOMQUCaIdbygAKCRBZrE9hU+XO
 MbqTAQCqqczU2YXRnq7TIvw/yl40+scIKMXobjX0EEpmgqhlHwEAkWwjQG0ytS2j
 hzES5gog1xT6A4TIjVr0Up5MUj3crwU=
 =+TRi
 -----END PGP SIGNATURE-----

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

Pull x86 platform drivers from Ilpo Järvinen:

 - alienware: Add more precise labels to fans

 - amd/hsmp: Improve misleading probe errors (make the legacy driver
   aware when HSMP is supported through the ACPI driver)

 - amd/pmc: Add Lenovo Yoga 6 13ALCL6 to pmc quirk list

 - drm/xe: Correct (D)VSEC information to support PMT crashlog feature

 - fujitsu: Clamp charge threshold instead of returning an error

 - ideapad: Expore change types

 - intel/pmt:
     - Add PMT Discovery driver
     - Add API to retrieve telemetry regions by feature
     - Fix crashlog NULL access
     - Support Battlemage GPU (BMG) crashlog

 - intel/vsec:
     - Add Discovery feature
     - Add feature dependency support using device links

 - lenovo:
     - Move lenovo drivers under drivers/platform/x86/lenovo/
     - Add WMI drivers for Lenovo Gaming series
     - Improve DMI handling

 - oxpec:
     - Add support for OneXPlayer X1 Mini Pro (Strix Point variant)
     - Fix EC registers for G1 AMD

 - samsung-laptop: Expose change types

 - wmi: Fix WMI device naming issue (same GUID corner cases)

 - x86-android-tables: Add ovc-capacity-table to generic battery nodes

 - Miscellaneous cleanups / refactoring / improvements

* tag 'platform-drivers-x86-v6.17-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (63 commits)
  platform/x86: oxpec: Add support for OneXPlayer X1 Mini Pro (Strix Point)
  platform/x86: oxpec: Fix turbo register for G1 AMD
  platform/x86/intel/pmt: support BMG crashlog
  platform/x86/intel/pmt: use a version struct
  platform/x86/intel/pmt: refactor base parameter
  platform/x86/intel/pmt: add register access helpers
  platform/x86/intel/pmt: decouple sysfs and namespace
  platform/x86/intel/pmt: correct types
  platform/x86/intel/pmt: re-order trigger logic
  platform/x86/intel/pmt: use guard(mutex)
  platform/x86/intel/pmt: mutex clean up
  platform/x86/intel/pmt: white space cleanup
  drm/xe: Correct BMG VSEC header sizing
  drm/xe: Correct the rev value for the DVSEC entries
  platform/x86/intel/pmt: fix a crashlog NULL pointer access
  platform/x86: samsung-laptop: Expose charge_types
  platform/x86/amd: pmc: Add Lenovo Yoga 6 13ALC6 to pmc quirk list
  platform/x86: dell-uart-backlight: Use blacklight power constant
  platform/x86/intel/pmt: fix build dependency for kunit test
  platform/x86: lenovo: gamezone needs "other mode"
  ...
pull/1309/head
Linus Torvalds 2025-07-28 23:21:28 -07:00
commit 9669b2499e
74 changed files with 5192 additions and 1065 deletions

View File

@ -0,0 +1,10 @@
What: /sys/devices/platform/samsung/battery_life_extender
Date: December 1, 2011
KernelVersion: 3.3
Contact: Corentin Chary <corentin.chary@gmail.com>
Description: Max battery charge level can be modified, battery cycle
life can be extended by reducing the max battery charge
level.
- 0 means normal battery mode (100% charge)
- 1 means battery life extender mode (80% charge)

View File

@ -0,0 +1,8 @@
What: /sys/bus/platform/devices/VPC2004:*/conservation_mode
Date: Aug 2017
KernelVersion: 4.14
Contact: platform-driver-x86@vger.kernel.org
Description:
Controls whether the conservation mode is enabled or not.
This feature limits the maximum battery charge percentage to
around 50-60% in order to prolong the lifetime of the battery.

View File

@ -0,0 +1,134 @@
What: /sys/class/intel_pmt/features-<PCI BDF>/
Date: 2025-04-24
KernelVersion: 6.16
Contact: david.e.box@linux.intel.com
Description:
The `features-<PCI BDF>/` directory represents the "features"
capability exposed by Intel PMT (Platform Monitoring Technology)
for the given PCI device.
Each directory corresponds to a PMT feature and contains
attributes describing the available telemetry, monitoring, or
control functionalities.
Directory Structure:
/sys/class/intel_pmt/features-<PCI BDF>/
├── accelerator_telemetry/ # Per-accelerator telemetry data
├── crash_log/ # Contains system crash telemetry logs
├── per_core_environment_telemetry/ # Environmental telemetry per core
├── per_core_performance_telemetry/ # Performance telemetry per core
├── per_rmid_energy_telemetry/ # Energy telemetry for RMIDs
├── per_rmid_perf_telemetry/ # Performance telemetry for RMIDs
├── tpmi_control/ # TPMI-related controls and telemetry
├── tracing/ # PMT tracing features
└── uncore_telemetry/ # Uncore telemetry data
Common Files (Present in all feature directories):
caps
- Read-only
- Lists available capabilities for this feature.
guids
- Read-only
- Lists GUIDs associated with this feature.
Additional Attributes (Conditional Presence):
max_command_size
- Read-only
- Present if the feature supports out-of-band MCTP access.
- Maximum supported MCTP command size for out-of-band PMT access (bytes).
max_stream_size
- Read-only
- Present if the feature supports out-of-band MCTP access.
- Maximum supported MCTP stream size (bytes).
min_watcher_period_ms
- Read-only
- Present if the feature supports the watcher API.
The watcher API provides a writable control interface that allows user
configuration of monitoring behavior, such as setting the sampling or
reporting interval.
- Minimum supported time period for the watcher interface (milliseconds).
num_rmids
- Read-only
- Present if the feature supports RMID (Resource Monitoring ID) telemetry.
RMIDs are identifiers used by hardware to track and report resource usage,
such as memory bandwidth or energy consumption, on a per-logical-entity
basis (e.g., per core, thread, or process group).
- Maximum number of RMIDs tracked simultaneously.
Example:
For a device with PCI BDF `0000:00:03.1`, the directory tree could look like:
/sys/class/intel_pmt/features-0000:00:03.1/
├── accelerator_telemetry/
│ ├── caps
│ ├── guids
│ ├── max_command_size
│ ├── max_stream_size
│ ├── min_watcher_period_ms
├── crash_log/
│ ├── caps
│ ├── guids
│ ├── max_command_size
│ ├── max_stream_size
├── per_core_environment_telemetry/
│ ├── caps
│ ├── guids
│ ├── max_command_size
│ ├── max_stream_size
│ ├── min_watcher_period_ms
├── per_rmid_energy_telemetry/
│ ├── caps
│ ├── guids
│ ├── max_command_size
│ ├── max_stream_size
│ ├── min_watcher_period_ms
│ ├── num_rmids
├── tpmi_control/
│ ├── caps
│ ├── guids
├── tracing/
│ ├── caps
│ ├── guids
├── uncore_telemetry/
│ ├── caps
│ ├── guids
│ ├── max_command_size
│ ├── max_stream_size
│ ├── min_watcher_period_ms
Notes:
- Some attributes are only present if the corresponding feature supports
the capability (e.g., `max_command_size` for MCTP-capable features).
- Features supporting RMIDs include `num_rmids`.
- Features supporting the watcher API include `min_watcher_period_ms`.
- The `caps` file provides additional information about the functionality
of the feature.
Example 'caps' content for the 'tracing' feature:
/sys/class/intel_pmt/features-0000:00:03.1/
├── tracing/
│ ├── caps
telemetry Available: No
watcher Available: Yes
crashlog Available: No
streaming Available: No
threashold Available: No
window Available: No
config Available: Yes
tracing Available: No
inband Available: Yes
oob Available: Yes
secure_chan Available: No
pmt_sp Available: Yes
pmt_sp_policy Available: Yes
mailbox Available: Yes
bios_lock Available: Yes

View File

@ -20,17 +20,6 @@ Description: Some Samsung laptops have different "performance levels"
and it's still unknown if this value even changes
anything, other than making the user feel a bit better.
What: /sys/devices/platform/samsung/battery_life_extender
Date: December 1, 2011
KernelVersion: 3.3
Contact: Corentin Chary <corentin.chary@gmail.com>
Description: Max battery charge level can be modified, battery cycle
life can be extended by reducing the max battery charge
level.
- 0 means normal battery mode (100% charge)
- 1 means battery life extender mode (80% charge)
What: /sys/devices/platform/samsung/usb_charge
Date: December 1, 2011
KernelVersion: 3.3

View File

@ -1,4 +1,4 @@
What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_supported_type
What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919[-X]/dell_privacy_supported_type
Date: Apr 2021
KernelVersion: 5.13
Contact: "<perry.yuan@dell.com>"
@ -29,12 +29,12 @@ Description:
For example to check which privacy devices are supported::
# cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_supported_type
# cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919*/dell_privacy_supported_type
[Microphone Mute] [supported]
[Camera Shutter] [supported]
[ePrivacy Screen] [unsupported]
What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_current_state
What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919[-X]/dell_privacy_current_state
Date: Apr 2021
KernelVersion: 5.13
Contact: "<perry.yuan@dell.com>"
@ -66,6 +66,6 @@ Description:
For example to check all supported current privacy device states::
# cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_current_state
# cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919*/dell_privacy_current_state
[Microphone] [unmuted]
[Camera Shutter] [unmuted]

View File

@ -27,15 +27,6 @@ Description:
* 1 -> Switched On
* 0 -> Switched Off
What: /sys/bus/platform/devices/VPC2004:*/conservation_mode
Date: Aug 2017
KernelVersion: 4.14
Contact: platform-driver-x86@vger.kernel.org
Description:
Controls whether the conservation mode is enabled or not.
This feature limits the maximum battery charge percentage to
around 50-60% in order to prolong the lifetime of the battery.
What: /sys/bus/platform/devices/VPC2004:*/fn_lock
Date: May 2018
KernelVersion: 4.18

View File

@ -1,4 +1,4 @@
What: /sys/bus/wmi/devices/44FADEB1-B204-40F2-8581-394BBDC1B651/firmware_update_request
What: /sys/bus/wmi/devices/44FADEB1-B204-40F2-8581-394BBDC1B651[-X]/firmware_update_request
Date: April 2020
KernelVersion: 5.7
Contact: "Jithu Joseph" <jithu.joseph@intel.com>

View File

@ -1,4 +1,4 @@
What: /sys/devices/platform/<platform>/force_power
What: /sys/bus/wmi/devices/86CCFD48-205E-4A77-9C48-2021CBEDE341[-X]/force_power
Date: September 2017
KernelVersion: 4.15
Contact: "Mario Limonciello" <mario.limonciello@outlook.com>

View File

@ -358,12 +358,7 @@ Forcing power
Many OEMs include a method that can be used to force the power of a
Thunderbolt controller to an "On" state even if nothing is connected.
If supported by your machine this will be exposed by the WMI bus with
a sysfs attribute called "force_power".
For example the intel-wmi-thunderbolt driver exposes this attribute in:
/sys/bus/wmi/devices/86CCFD48-205E-4A77-9C48-2021CBEDE341/force_power
To force the power to on, write 1 to this attribute file.
To disable force power, write 0 to this attribute file.
a sysfs attribute called "force_power", see
Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt for details.
Note: it's currently not possible to query the force power state of a platform.

View File

@ -0,0 +1,203 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
==========================================================
Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
==========================================================
Introduction
============
The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
The primary "Gamezone" GUID provides advanced features such as fan
profiles and overclocking. It is paired with multiple event GUIDs
and data block GUIDs that provide context for the various methods.
Gamezone Data
-------------
WMI GUID ``887B54E3-DDDC-4B2C-8B88-68A26A8835D0``
The Gamezone Data WMI interface provides platform-profile and fan curve
settings for devices that fall under the "Gaming Series" of Lenovo devices.
It uses a notifier chain to inform other Lenovo WMI interface drivers of the
current platform profile when it changes.
The following platform profiles are supported:
- low-power
- balanced
- balanced-performance
- performance
- custom
Balanced-Performance
~~~~~~~~~~~~~~~~~~~~
Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
enabled in their BIOS. For these devices, the performance platform profile
corresponds to the BIOS Extreme Mode, while the balanced-performance
platform profile corresponds to the BIOS Performance mode. For legacy
devices, the performance platform profile will correspond with the BIOS
Performance mode.
For some newer devices the "Extreme Mode" profile is incomplete in the BIOS
and setting it will cause undefined behavior. A BIOS bug quirk table is
provided to ensure these devices cannot set "Extreme Mode" from the driver.
Custom Profile
~~~~~~~~~~~~~~
The custom profile represents a hardware mode on Lenovo devices that enables
user modifications to Package Power Tracking (PPT) and fan curve settings.
When an attribute exposed by the Other Mode WMI interface is to be modified,
the Gamezone driver must first be switched to the "custom" profile manually,
or the setting will have no effect. If another profile is set from the list
of supported profiles, the BIOS will override any user PPT settings when
switching to that profile.
Gamezone Thermal Mode Event
---------------------------
WMI GUID ``D320289E-8FEA-41E0-86F9-911D83151B5F``
The Gamezone Thermal Mode Event interface notifies the system when the platform
profile has changed, either through the hardware event (Fn+Q for laptops or
Legion + Y for Go Series), or through the Gamezone WMI interface. This event is
implemented in the Lenovo WMI Events driver (lenovo-wmi-events).
WMI interface description
=========================
The WMI interface description can be decoded from the embedded binary MOF (bmof)
data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
::
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_GAMEZONE_DATA class"), guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
class LENOVO_GAMEZONE_DATA {
[key, read] string InstanceName;
[read] boolean Active;
[WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")] void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
[WmiMethodId(11), Implemented, Description("Get AslCode Version")] void GetVersion ([out, Description("AslCode version")] UINT32 Data);
[WmiMethodId(12), Implemented, Description("Fan cooling capability")] void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
[WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")] void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
[WmiMethodId(14), Implemented, Description("cpu oc capability")] void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
[WmiMethodId(15), Implemented, Description("bios has overclock capability")] void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
[WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")] void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
[WmiMethodId(18), Implemented, Description("Get CPU temperature")] void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
[WmiMethodId(19), Implemented, Description("Get GPU temperature")] void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
[WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")] void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
[WmiMethodId(21), Implemented, Description("EC support disable windows key capability")] void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
[WmiMethodId(22), Implemented, Description("Set windows key disable/enable")] void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
[WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")] void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
[WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")] void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
[WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")] void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
[WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")] void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
[WmiMethodId(30), Implemented, Description("Get Keyboard feature list")] void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
[WmiMethodId(31), Implemented, Description("Get Memory OC Information")] void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
[WmiMethodId(32), Implemented, Description("Water Cooling feature capability")] void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
[WmiMethodId(33), Implemented, Description("Set Water Cooling status")] void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
[WmiMethodId(34), Implemented, Description("Get Water Cooling status")] void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
[WmiMethodId(35), Implemented, Description("Lighting feature capability")] void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
[WmiMethodId(36), Implemented, Description("Set keyboard light off or on to max")] void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
[WmiMethodId(37), Implemented, Description("Get keyboard light on/off status")] void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
[WmiMethodId(38), Implemented, Description("Get Macrokey scan code")] void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
[WmiMethodId(39), Implemented, Description("Get Macrokey count")] void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
[WmiMethodId(40), Implemented, Description("Support G-Sync feature")] void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
[WmiMethodId(41), Implemented, Description("Get G-Sync Status")] void GetGSyncStatus ([out, Description("Get G-Sync Status")] UINT32 Data);
[WmiMethodId(42), Implemented, Description("Set G-Sync Status")] void SetGSyncStatus ([in, Description("Set G-Sync Status")] UINT32 Data);
[WmiMethodId(43), Implemented, Description("Support Smart Fan feature")] void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
[WmiMethodId(44), Implemented, Description("Set Smart Fan Mode")] void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
[WmiMethodId(45), Implemented, Description("Get Smart Fan Mode")] void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
[WmiMethodId(46), Implemented, Description("Get Smart Fan Setting Mode")] void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
[WmiMethodId(47), Implemented, Description("Get Power Charge Mode")] void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
[WmiMethodId(48), Implemented, Description("Get Gaming Product Info")] void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
[WmiMethodId(49), Implemented, Description("Over Drive feature capability")] void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
[WmiMethodId(50), Implemented, Description("Get Over Drive status")] void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
[WmiMethodId(51), Implemented, Description("Set Over Drive status")] void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
[WmiMethodId(52), Implemented, Description("Set Light Control Owner")] void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
[WmiMethodId(53), Implemented, Description("Set DDS Control Owner")] void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
[WmiMethodId(54), Implemented, Description("Get the flag of restore OC value")] void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
[WmiMethodId(55), Implemented, Description("Get Real Thremal Mode")] void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
[WmiMethodId(56), Implemented, Description("Get the OC switch status in BIOS")] void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
[WmiMethodId(59), Implemented, Description("Get hardware info support version")] void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
[WmiMethodId(60), Implemented, Description("Get Cpu core 0 max frequency")] void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
[WmiMethodId(62), Implemented, Description("Check the Adapter type fit for OC")] void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
[WmiMethodId(63), Implemented, Description("Is support IGPU mode")] void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
[WmiMethodId(64), Implemented, Description("Get IGPU Mode Status")] void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
[WmiMethodId(65), Implemented, Description("Set IGPU Mode")] void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
[WmiMethodId(66), Implemented, Description("Notify DGPU Status")] void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
[WmiMethodId(67), Implemented, Description("Is changed Y log")] void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
[WmiMethodId(68), Implemented, Description("Get DGPU Hardwawre ID")] void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
};
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of CPU OC parameter list"), guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
class LENOVO_GAMEZONE_CPU_OC_DATA {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description("OC tune id.")] uint32 Tuneid;
[WmiDataId(2), read, Description("Default value.")] uint32 DefaultValue;
[WmiDataId(3), read, Description("OC Value.")] uint32 OCValue;
[WmiDataId(4), read, Description("Min Value.")] uint32 MinValue;
[WmiDataId(5), read, Description("Max Value.")] uint32 MaxValue;
[WmiDataId(6), read, Description("Scale Value.")] uint32 ScaleValue;
[WmiDataId(7), read, Description("OC Order id.")] uint32 OCOrderid;
[WmiDataId(8), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
[WmiDataId(9), read, Description("Delay time in ms.")] uint32 Interval;
};
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of GPU OC parameter list"), guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
class LENOVO_GAMEZONE_GPU_OC_DATA {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description("P-State ID.")] uint32 PStateID;
[WmiDataId(2), read, Description("CLOCK ID.")] uint32 ClockID;
[WmiDataId(3), read, Description("Default value.")] uint32 defaultvalue;
[WmiDataId(4), read, Description("OC Offset freqency.")] uint32 OCOffsetFreq;
[WmiDataId(5), read, Description("OC Min offset value.")] uint32 OCMinOffset;
[WmiDataId(6), read, Description("OC Max offset value.")] uint32 OCMaxOffset;
[WmiDataId(7), read, Description("OC Offset Scale.")] uint32 OCOffsetScale;
[WmiDataId(8), read, Description("OC Order id.")] uint32 OCOrderid;
[WmiDataId(9), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
};
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Fancooling finish event"), guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description("Fancooling clean finish event")] uint32 EventId;
};
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan mode change event"), guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description("Smart Fan Mode change event")] uint32 mode;
[WmiDataId(2), read, Description("version of FN+Q")] uint32 version;
};
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan setting mode change event"), guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description("Smart Fan Setting mode change event")] uint32 mode;
};
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("POWER CHARGE MODE Change EVENT"), guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")] uint32 mode;
};
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Thermal Mode Real Mode change event"), guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description("Thermal Mode Real Mode")] uint32 mode;
};

View File

@ -0,0 +1,108 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
===========================================================
Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
===========================================================
Introduction
============
Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
The primary Other Mode interface provides advanced power tuning features
such as Package Power Tracking (PPT). It is paired with multiple data block
GUIDs that provide context for the various methods.
Other Mode
----------
WMI GUID ``DC2A8805-3A8C-41BA-A6F7-092E0089CD3B``
The Other Mode WMI interface uses the firmware_attributes class to expose
various WMI attributes provided by the interface in the sysfs. This enables
CPU and GPU power limit tuning as well as various other attributes for
devices that fall under the "Gaming Series" of Lenovo devices. Each
attribute exposed by the Other Mode interface has corresponding
capability data blocks which allow the driver to probe details about the
attribute. Each attribute has multiple pages, one for each of the platform
profiles managed by the Gamezone interface. Attributes are exposed in sysfs
under the following path:
::
/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
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.
Each attribute has the following properties:
- current_value
- default_value
- display_name
- max_value
- min_value
- scalar_increment
- type
The following 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
WMI interface description
=========================
The WMI interface description can be decoded from the embedded binary MOF (bmof)
data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
::
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_OTHER_METHOD class"), guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
class LENOVO_OTHER_METHOD {
[key, read] string InstanceName;
[read] boolean Active;
[WmiMethodId(17), Implemented, Description("Get Feature Value ")] void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
[WmiMethodId(18), Implemented, Description("Set Feature Value ")] void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
[WmiMethodId(19), Implemented, Description("Get Data By Command ")] void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
[WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")] void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
};
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 00"), guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
class LENOVO_CAPABILITY_DATA_00 {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
[WmiDataId(2), read, Description("Capability.")] uint32 Capability;
[WmiDataId(3), read, Description("Capability Default Value.")] uint32 DefaultValue;
};
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 01"), guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
class LENOVO_CAPABILITY_DATA_01 {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
[WmiDataId(2), read, Description("Capability.")] uint32 Capability;
[WmiDataId(3), read, Description("Default Value.")] uint32 DefaultValue;
[WmiDataId(4), read, Description("Step.")] uint32 Step;
[WmiDataId(5), read, Description("Minimum Value.")] uint32 MinValue;
[WmiDataId(6), read, Description("Maximum Value.")] uint32 MaxValue;
};
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 02"), guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
class LENOVO_CAPABILITY_DATA_02 {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
[WmiDataId(2), read, Description("Capability.")] uint32 Capability;
[WmiDataId(3), read, Description("Data Size.")] uint32 DataSize;
[WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[];
};

View File

@ -11634,7 +11634,7 @@ M: Ike Panhc <ikepanhc@gmail.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
W: http://launchpad.net/ideapad-laptop
F: drivers/platform/x86/ideapad-laptop.c
F: drivers/platform/x86/lenovo/ideapad-laptop.c
IDEAPAD LAPTOP SLIDEBAR DRIVER
M: Andrey Moiseev <o2g.org.ru@gmail.com>
@ -12407,6 +12407,8 @@ F: include/linux/mfd/intel_soc_pmic*
INTEL PMT DRIVERS
M: David E. Box <david.e.box@linux.intel.com>
S: Supported
F: Documentation/ABI/testing/sysfs-class-intel_pmt
F: Documentation/ABI/testing/sysfs-class-intel_pmt-features
F: drivers/platform/x86/intel/pmt/
INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWORK CONNECTION SUPPORT
@ -13697,11 +13699,20 @@ S: Maintained
W: http://legousb.sourceforge.net/
F: drivers/usb/misc/legousbtower.c
LENOVO drivers
M: Mark Pearson <mpearson-lenovo@squebb.ca>
M: Derek J. Clark <derekjohn.clark@gmail.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
F: Documentation/wmi/devices/lenovo-wmi-other.rst
F: drivers/platform/x86/lenovo/*
LENOVO WMI HOTKEY UTILITIES DRIVER
M: Jackie Dong <xy-jackie@139.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/lenovo-wmi-hotkey-utilities.c
F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c
LETSKETCH HID TABLET DRIVER
M: Hans de Goede <hansg@kernel.org>
@ -24729,14 +24740,14 @@ S: Maintained
W: http://ibm-acpi.sourceforge.net
W: http://thinkwiki.org/wiki/Ibm-acpi
T: git git://repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git
F: drivers/platform/x86/thinkpad_acpi.c
F: drivers/platform/x86/lenovo/thinkpad_acpi.c
THINKPAD LMI DRIVER
M: Mark Pearson <markpearson@lenovo.com>
M: Mark Pearson <mpearson-lenovo@squebb.ca>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/sysfs-class-firmware-attributes
F: drivers/platform/x86/think-lmi.?
F: drivers/platform/x86/lenovo/think-lmi.?
THP7312 ISP DRIVER
M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

View File

@ -59,18 +59,6 @@ struct telemetry_plt_config {
};
struct telemetry_core_ops {
int (*get_sampling_period)(u8 *pss_min_period, u8 *pss_max_period,
u8 *ioss_min_period, u8 *ioss_max_period);
int (*get_eventconfig)(struct telemetry_evtconfig *pss_evtconfig,
struct telemetry_evtconfig *ioss_evtconfig,
int pss_len, int ioss_len);
int (*update_events)(struct telemetry_evtconfig pss_evtconfig,
struct telemetry_evtconfig ioss_evtconfig);
int (*set_sampling_period)(u8 pss_period, u8 ioss_period);
int (*get_trace_verbosity)(enum telemetry_unit telem_unit,
u32 *verbosity);
@ -84,11 +72,6 @@ struct telemetry_core_ops {
int (*read_eventlog)(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog,
int len, int log_all_evts);
int (*add_events)(u8 num_pss_evts, u8 num_ioss_evts,
u32 *pss_evtmap, u32 *ioss_evtmap);
int (*reset_events)(void);
};
int telemetry_set_pltdata(const struct telemetry_core_ops *ops,
@ -101,35 +84,15 @@ struct telemetry_plt_config *telemetry_get_pltdata(void);
int telemetry_get_evtname(enum telemetry_unit telem_unit,
const char **name, int len);
int telemetry_update_events(struct telemetry_evtconfig pss_evtconfig,
struct telemetry_evtconfig ioss_evtconfig);
int telemetry_add_events(u8 num_pss_evts, u8 num_ioss_evts,
u32 *pss_evtmap, u32 *ioss_evtmap);
int telemetry_reset_events(void);
int telemetry_get_eventconfig(struct telemetry_evtconfig *pss_config,
struct telemetry_evtconfig *ioss_config,
int pss_len, int ioss_len);
int telemetry_read_events(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len);
int telemetry_raw_read_events(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len);
int telemetry_read_eventlog(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len);
int telemetry_raw_read_eventlog(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len);
int telemetry_get_sampling_period(u8 *pss_min_period, u8 *pss_max_period,
u8 *ioss_min_period, u8 *ioss_max_period);
int telemetry_set_sampling_period(u8 pss_period, u8 ioss_period);
int telemetry_set_trace_verbosity(enum telemetry_unit telem_unit,
u32 verbosity);

View File

@ -24,6 +24,7 @@
#define BMG_DEVICE_ID 0xE2F8
static struct intel_vsec_header bmg_telemetry = {
.rev = 1,
.length = 0x10,
.id = VSEC_ID_TELEMETRY,
.num_entries = 2,
@ -32,28 +33,19 @@ static struct intel_vsec_header bmg_telemetry = {
.offset = BMG_DISCOVERY_OFFSET,
};
static struct intel_vsec_header bmg_punit_crashlog = {
static struct intel_vsec_header bmg_crashlog = {
.rev = 1,
.length = 0x10,
.id = VSEC_ID_CRASHLOG,
.num_entries = 1,
.entry_size = 4,
.num_entries = 2,
.entry_size = 6,
.tbir = 0,
.offset = BMG_DISCOVERY_OFFSET + 0x60,
};
static struct intel_vsec_header bmg_oobmsm_crashlog = {
.length = 0x10,
.id = VSEC_ID_CRASHLOG,
.num_entries = 1,
.entry_size = 4,
.tbir = 0,
.offset = BMG_DISCOVERY_OFFSET + 0x78,
};
static struct intel_vsec_header *bmg_capabilities[] = {
&bmg_telemetry,
&bmg_punit_crashlog,
&bmg_oobmsm_crashlog,
&bmg_crashlog,
NULL
};

View File

@ -191,50 +191,16 @@ void yoga_c630_ec_unregister_notify(struct yoga_c630_ec *ec, struct notifier_blo
}
EXPORT_SYMBOL_GPL(yoga_c630_ec_unregister_notify);
static void yoga_c630_aux_release(struct device *dev)
{
struct auxiliary_device *adev = to_auxiliary_dev(dev);
kfree(adev);
}
static void yoga_c630_aux_remove(void *data)
{
struct auxiliary_device *adev = data;
auxiliary_device_delete(adev);
auxiliary_device_uninit(adev);
}
static int yoga_c630_aux_init(struct device *parent, const char *name,
struct yoga_c630_ec *ec)
{
struct auxiliary_device *adev;
int ret;
adev = kzalloc(sizeof(*adev), GFP_KERNEL);
adev = devm_auxiliary_device_create(parent, name, ec);
if (!adev)
return -ENOMEM;
return -ENODEV;
adev->name = name;
adev->id = 0;
adev->dev.parent = parent;
adev->dev.release = yoga_c630_aux_release;
adev->dev.platform_data = ec;
ret = auxiliary_device_init(adev);
if (ret) {
kfree(adev);
return ret;
}
ret = auxiliary_device_add(adev);
if (ret) {
auxiliary_device_uninit(adev);
return ret;
}
return devm_add_action_or_reset(parent, yoga_c630_aux_remove, adev);
return 0;
}
static int yoga_c630_ec_probe(struct i2c_client *client)

View File

@ -37,6 +37,15 @@ config ACPI_WMI
It is safe to enable this driver even if your DSDT doesn't define
any ACPI-WMI devices.
config ACPI_WMI_LEGACY_DEVICE_NAMES
bool "Use legacy WMI device naming scheme"
depends on ACPI_WMI
help
Say Y here to force the WMI driver core to use the old WMI device naming
scheme when creating WMI devices. Doing so might be necessary for some
userspace applications but will cause the registration of WMI devices with
the same GUID to fail in some corner cases.
config WMI_BMOF
tristate "WMI embedded Binary MOF driver"
depends on ACPI_WMI
@ -120,32 +129,6 @@ config GIGABYTE_WMI
To compile this driver as a module, choose M here: the module will
be called gigabyte-wmi.
config YOGABOOK
tristate "Lenovo Yoga Book tablet key driver"
depends on ACPI_WMI
depends on INPUT
depends on I2C
select LEDS_CLASS
select NEW_LEDS
help
Say Y here if you want to support the 'Pen' key and keyboard backlight
control on the Lenovo Yoga Book tablets.
To compile this driver as a module, choose M here: the module will
be called lenovo-yogabook.
config YT2_1380
tristate "Lenovo Yoga Tablet 2 1380 fast charge driver"
depends on SERIAL_DEV_BUS
depends on EXTCON
depends on ACPI
help
Say Y here to enable support for the custom fast charging protocol
found on the Lenovo Yoga Tablet 2 1380F / 1380L models.
To compile this driver as a module, choose M here: the module will
be called lenovo-yogabook.
config ACERHDF
tristate "Acer Aspire One temperature and fan driver"
depends on ACPI_EC && THERMAL
@ -459,43 +442,6 @@ config IBM_RTL
state = 0 (BIOS SMIs on)
state = 1 (BIOS SMIs off)
config IDEAPAD_LAPTOP
tristate "Lenovo IdeaPad Laptop Extras"
depends on ACPI
depends on RFKILL && INPUT
depends on SERIO_I8042
depends on BACKLIGHT_CLASS_DEVICE
depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on ACPI_WMI || ACPI_WMI = n
select ACPI_PLATFORM_PROFILE
select INPUT_SPARSEKMAP
select NEW_LEDS
select LEDS_CLASS
help
This is a driver for Lenovo IdeaPad netbooks contains drivers for
rfkill switch, hotkey, fan control and backlight control.
config LENOVO_WMI_HOTKEY_UTILITIES
tristate "Lenovo Hotkey Utility WMI extras driver"
depends on ACPI_WMI
select NEW_LEDS
select LEDS_CLASS
imply IDEAPAD_LAPTOP
help
This driver provides WMI support for Lenovo customized hotkeys function,
such as LED control for audio/mic mute event for Ideapad, YOGA, XiaoXin,
Gaming, ThinkBook and so on.
config LENOVO_YMC
tristate "Lenovo Yoga Tablet Mode Control"
depends on ACPI_WMI
depends on INPUT
depends on IDEAPAD_LAPTOP
select INPUT_SPARSEKMAP
help
This driver maps the Tablet Mode Control switch to SW_TABLET_MODE input
events for Lenovo Yoga notebooks.
config SENSORS_HDAPS
tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
depends on INPUT
@ -514,160 +460,8 @@ config SENSORS_HDAPS
Say Y here if you have an applicable laptop and want to experience
the awesome power of hdaps.
config THINKPAD_ACPI
tristate "ThinkPad ACPI Laptop Extras"
depends on ACPI_EC
depends on ACPI_BATTERY
depends on INPUT
depends on RFKILL || RFKILL = n
depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on BACKLIGHT_CLASS_DEVICE
depends on I2C
depends on DRM
select ACPI_PLATFORM_PROFILE
select DRM_PRIVACY_SCREEN
select HWMON
select NVRAM
select NEW_LEDS
select LEDS_CLASS
select INPUT_SPARSEKMAP
help
This is a driver for the IBM and Lenovo ThinkPad laptops. It adds
support for Fn-Fx key combinations, Bluetooth control, video
output switching, ThinkLight control, UltraBay eject and more.
For more information about this driver see
<file:Documentation/admin-guide/laptops/thinkpad-acpi.rst> and
<http://ibm-acpi.sf.net/> .
This driver was formerly known as ibm-acpi.
Extra functionality will be available if the rfkill (CONFIG_RFKILL)
and/or ALSA (CONFIG_SND) subsystems are available in the kernel.
Note that if you want ThinkPad-ACPI to be built-in instead of
modular, ALSA and rfkill will also have to be built-in.
If you have an IBM or Lenovo ThinkPad laptop, say Y or M here.
config THINKPAD_ACPI_ALSA_SUPPORT
bool "Console audio control ALSA interface"
depends on THINKPAD_ACPI
depends on SND
depends on SND = y || THINKPAD_ACPI = SND
default y
help
Enables monitoring of the built-in console audio output control
(headphone and speakers), which is operated by the mute and (in
some ThinkPad models) volume hotkeys.
If this option is enabled, ThinkPad-ACPI will export an ALSA card
with a single read-only mixer control, which should be used for
on-screen-display feedback purposes by the Desktop Environment.
Optionally, the driver will also allow software control (the
ALSA mixer will be made read-write). Please refer to the driver
documentation for details.
All IBM models have both volume and mute control. Newer Lenovo
models only have mute control (the volume hotkeys are just normal
keys and volume control is done through the main HDA mixer).
config THINKPAD_ACPI_DEBUGFACILITIES
bool "Maintainer debug facilities"
depends on THINKPAD_ACPI
help
Enables extra stuff in the thinkpad-acpi which is completely useless
for normal use. Read the driver source to find out what it does.
Say N here, unless you were told by a kernel maintainer to do
otherwise.
config THINKPAD_ACPI_DEBUG
bool "Verbose debug mode"
depends on THINKPAD_ACPI
help
Enables extra debugging information, at the expense of a slightly
increase in driver size.
If you are not sure, say N here.
config THINKPAD_ACPI_UNSAFE_LEDS
bool "Allow control of important LEDs (unsafe)"
depends on THINKPAD_ACPI
help
Overriding LED state on ThinkPads can mask important
firmware alerts (like critical battery condition), or misled
the user into damaging the hardware (undocking or ejecting
the bay while buses are still active), etc.
LED control on the ThinkPad is write-only (with very few
exceptions on very ancient models), which makes it
impossible to know beforehand if important information will
be lost when one changes LED state.
Users that know what they are doing can enable this option
and the driver will allow control of every LED, including
the ones on the dock stations.
Never enable this option on a distribution kernel.
Say N here, unless you are building a kernel for your own
use, and need to control the important firmware LEDs.
config THINKPAD_ACPI_VIDEO
bool "Video output control support"
depends on THINKPAD_ACPI
default y
help
Allows the thinkpad_acpi driver to provide an interface to control
the various video output ports.
This feature often won't work well, depending on ThinkPad model,
display state, video output devices in use, whether there is a X
server running, phase of the moon, and the current mood of
Schroedinger's cat. If you can use X.org's RandR to control
your ThinkPad's video output ports instead of this feature,
don't think twice: do it and say N here to save memory and avoid
bad interactions with X.org.
NOTE: access to this feature is limited to processes with the
CAP_SYS_ADMIN capability, to avoid local DoS issues in platforms
where it interacts badly with X.org.
If you are not sure, say Y here but do try to check if you could
be using X.org RandR instead.
config THINKPAD_ACPI_HOTKEY_POLL
bool "Support NVRAM polling for hot keys"
depends on THINKPAD_ACPI
default y
help
Some thinkpad models benefit from NVRAM polling to detect a few of
the hot key press events. If you know your ThinkPad model does not
need to do NVRAM polling to support any of the hot keys you use,
unselecting this option will save about 1kB of memory.
ThinkPads T40 and newer, R52 and newer, and X31 and newer are
unlikely to need NVRAM polling in their latest BIOS versions.
NVRAM polling can detect at most the following keys: ThinkPad/Access
IBM, Zoom, Switch Display (fn+F7), ThinkLight, Volume up/down/mute,
Brightness up/down, Display Expand (fn+F8), Hibernate (fn+F12).
If you are not sure, say Y here. The driver enables polling only if
it is strictly necessary to do so.
config THINKPAD_LMI
tristate "Lenovo WMI-based systems management driver"
depends on ACPI_WMI
select FW_ATTR_CLASS
help
This driver allows changing BIOS settings on Lenovo machines whose
BIOS support the WMI interface.
To compile this driver as a module, choose M here: the module will
be called think-lmi.
source "drivers/platform/x86/intel/Kconfig"
source "drivers/platform/x86/lenovo/Kconfig"
config ACPI_QUICKSTART
tristate "ACPI Quickstart button driver"
@ -825,6 +619,7 @@ config SAMSUNG_LAPTOP
tristate "Samsung Laptop driver"
depends on RFKILL || RFKILL = n
depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on ACPI_BATTERY
depends on BACKLIGHT_CLASS_DEVICE
select LEDS_CLASS
select NEW_LEDS
@ -1078,18 +873,6 @@ config INSPUR_PLATFORM_PROFILE
To compile this driver as a module, choose M here: the module
will be called inspur-platform-profile.
config LENOVO_WMI_CAMERA
tristate "Lenovo WMI Camera Button driver"
depends on ACPI_WMI
depends on INPUT
help
This driver provides support for Lenovo camera button. The Camera
button is a GPIO device. This driver receives ACPI notifications when
the camera button is switched on/off.
To compile this driver as a module, choose M here: the module
will be called lenovo-wmi-camera.
config DASHARO_ACPI
tristate "Dasharo ACPI Platform Driver"
depends on ACPI

View File

@ -60,17 +60,12 @@ obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o
obj-$(CONFIG_FW_ATTR_CLASS) += firmware_attributes_class.o
# IBM Thinkpad and Lenovo
# IBM Thinkpad (before 2005)
obj-$(CONFIG_IBM_RTL) += ibm_rtl.o
obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
obj-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += lenovo-wmi-hotkey-utilities.o
obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
# Lenovo
obj-y += lenovo/
# Intel
obj-y += intel/

View File

@ -587,8 +587,10 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
if (!hsmp_pdev->is_probed) {
hsmp_pdev->num_sockets = amd_num_nodes();
if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES)
if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES) {
dev_err(&pdev->dev, "Wrong number of sockets\n");
return -ENODEV;
}
hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
sizeof(*hsmp_pdev->sock),
@ -605,9 +607,12 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
if (!hsmp_pdev->is_probed) {
ret = hsmp_misc_register(&pdev->dev);
if (ret)
if (ret) {
dev_err(&pdev->dev, "Failed to register misc device\n");
return ret;
}
hsmp_pdev->is_probed = true;
dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n");
}
return 0;

View File

@ -13,6 +13,7 @@
#include <linux/compiler_types.h>
#include <linux/device.h>
#include <linux/hwmon.h>
#include <linux/kconfig.h>
#include <linux/miscdevice.h>
#include <linux/pci.h>
#include <linux/semaphore.h>
@ -64,7 +65,7 @@ int hsmp_misc_register(struct device *dev);
int hsmp_get_tbl_dram_base(u16 sock_ind);
ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size);
struct hsmp_plat_device *get_hsmp_pdev(void);
#if IS_REACHABLE(CONFIG_HWMON)
#if IS_ENABLED(CONFIG_HWMON)
int hsmp_create_sensor(struct device *dev, u16 sock_ind);
#else
static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; }

View File

@ -14,6 +14,8 @@
#include <linux/acpi.h>
#include <linux/build_bug.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/kconfig.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
@ -215,7 +217,14 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev)
return ret;
}
return hsmp_misc_register(&pdev->dev);
ret = hsmp_misc_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Failed to register misc device\n");
return ret;
}
dev_dbg(&pdev->dev, "AMD HSMP is probed successfully\n");
return 0;
}
static void hsmp_pltdrv_remove(struct platform_device *pdev)
@ -287,15 +296,20 @@ static int __init hsmp_plt_init(void)
{
int ret = -ENODEV;
if (acpi_dev_present(ACPI_HSMP_DEVICE_HID, NULL, -1)) {
if (IS_ENABLED(CONFIG_AMD_HSMP_ACPI))
pr_debug("HSMP is supported through ACPI on this platform, please use hsmp_acpi.ko\n");
else
pr_info("HSMP is supported through ACPI on this platform, please enable AMD_HSMP_ACPI config\n");
return -ENODEV;
}
if (!legacy_hsmp_support()) {
pr_info("HSMP is not supported on Family:%x model:%x\n",
pr_info("HSMP interface is either disabled or not supported on family:%x model:%x\n",
boot_cpu_data.x86, boot_cpu_data.x86_model);
return ret;
}
if (acpi_dev_present(ACPI_HSMP_DEVICE_HID, NULL, -1))
return -ENODEV;
hsmp_pdev = get_hsmp_pdev();
if (!hsmp_pdev)
return -ENOMEM;
@ -305,8 +319,10 @@ static int __init hsmp_plt_init(void)
* if we have N SMN/DF interfaces that ideally means N sockets
*/
hsmp_pdev->num_sockets = amd_num_nodes();
if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES)
if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES) {
pr_err("Wrong number of sockets\n");
return ret;
}
ret = platform_driver_register(&amd_hsmp_driver);
if (ret)

View File

@ -190,6 +190,15 @@ static const struct dmi_system_id fwbug_list[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "82XQ"),
}
},
/* https://gitlab.freedesktop.org/drm/amd/-/issues/4434 */
{
.ident = "Lenovo Yoga 6 13ALC6",
.driver_data = &quirk_s2idle_bug,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "82ND"),
}
},
/* https://gitlab.freedesktop.org/drm/amd/-/issues/2684 */
{
.ident = "HP Laptop 15s-eq2xxx",

View File

@ -290,9 +290,29 @@ enum AWCC_SPECIAL_THERMAL_CODES {
enum AWCC_TEMP_SENSOR_TYPES {
AWCC_TEMP_SENSOR_CPU = 0x01,
AWCC_TEMP_SENSOR_FRONT = 0x03,
AWCC_TEMP_SENSOR_GPU = 0x06,
};
enum AWCC_FAN_TYPES {
AWCC_FAN_CPU_1 = 0x32,
AWCC_FAN_GPU_1 = 0x33,
AWCC_FAN_PCI = 0x34,
AWCC_FAN_MID = 0x35,
AWCC_FAN_TOP_1 = 0x36,
AWCC_FAN_SIDE = 0x37,
AWCC_FAN_U2_1 = 0x38,
AWCC_FAN_U2_2 = 0x39,
AWCC_FAN_FRONT_1 = 0x3A,
AWCC_FAN_CPU_2 = 0x3B,
AWCC_FAN_GPU_2 = 0x3C,
AWCC_FAN_TOP_2 = 0x3D,
AWCC_FAN_TOP_3 = 0x3E,
AWCC_FAN_FRONT_2 = 0x3F,
AWCC_FAN_BOTTOM_1 = 0x40,
AWCC_FAN_BOTTOM_2 = 0x41,
};
enum awcc_thermal_profile {
AWCC_PROFILE_USTT_BALANCED,
AWCC_PROFILE_USTT_BALANCED_PERFORMANCE,
@ -331,7 +351,6 @@ struct wmax_u32_args {
struct awcc_fan_data {
unsigned long auto_channels_temp;
const char *label;
u32 min_rpm;
u32 max_rpm;
u8 suspend_cache;
@ -913,6 +932,9 @@ static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types ty
case AWCC_TEMP_SENSOR_CPU:
*str = "CPU";
break;
case AWCC_TEMP_SENSOR_FRONT:
*str = "Front";
break;
case AWCC_TEMP_SENSOR_GPU:
*str = "GPU";
break;
@ -923,7 +945,46 @@ static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types ty
break;
case hwmon_fan:
*str = priv->fan_data[channel]->label;
switch (priv->fan_data[channel]->id) {
case AWCC_FAN_CPU_1:
case AWCC_FAN_CPU_2:
*str = "CPU Fan";
break;
case AWCC_FAN_GPU_1:
case AWCC_FAN_GPU_2:
*str = "GPU Fan";
break;
case AWCC_FAN_PCI:
*str = "PCI Fan";
break;
case AWCC_FAN_MID:
*str = "Mid Fan";
break;
case AWCC_FAN_TOP_1:
case AWCC_FAN_TOP_2:
case AWCC_FAN_TOP_3:
*str = "Top Fan";
break;
case AWCC_FAN_SIDE:
*str = "Side Fan";
break;
case AWCC_FAN_U2_1:
case AWCC_FAN_U2_2:
*str = "U.2 Fan";
break;
case AWCC_FAN_FRONT_1:
case AWCC_FAN_FRONT_2:
*str = "Front Fan";
break;
case AWCC_FAN_BOTTOM_1:
case AWCC_FAN_BOTTOM_2:
*str = "Bottom Fan";
break;
default:
*str = "Unknown Fan";
break;
}
break;
default:
return -EOPNOTSUPP;
@ -1068,40 +1129,6 @@ static int awcc_hwmon_temps_init(struct wmi_device *wdev)
return 0;
}
static char *awcc_get_fan_label(unsigned long *fan_temps)
{
unsigned int temp_count = bitmap_weight(fan_temps, AWCC_ID_BITMAP_SIZE);
char *label;
u8 temp_id;
switch (temp_count) {
case 0:
label = "Independent Fan";
break;
case 1:
temp_id = find_first_bit(fan_temps, AWCC_ID_BITMAP_SIZE);
switch (temp_id) {
case AWCC_TEMP_SENSOR_CPU:
label = "Processor Fan";
break;
case AWCC_TEMP_SENSOR_GPU:
label = "Video Fan";
break;
default:
label = "Unknown Fan";
break;
}
break;
default:
label = "Shared Fan";
break;
}
return label;
}
static int awcc_hwmon_fans_init(struct wmi_device *wdev)
{
struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
@ -1155,7 +1182,6 @@ static int awcc_hwmon_fans_init(struct wmi_device *wdev)
fan_data->id = id;
fan_data->min_rpm = min_rpm;
fan_data->max_rpm = max_rpm;
fan_data->label = awcc_get_fan_label(fan_temps);
bitmap_gather(gather, fan_temps, priv->temp_sensors, AWCC_ID_BITMAP_SIZE);
bitmap_copy(&fan_data->auto_channels_temp, gather, BITS_PER_LONG);
priv->fan_data[i] = fan_data;

View File

@ -305,7 +305,7 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA);
/* Initialize bl_power to a known value */
ret = dell_uart_set_bl_power(dell_bl, FB_BLANK_UNBLANK);
ret = dell_uart_set_bl_power(dell_bl, BACKLIGHT_POWER_ON);
if (ret)
return ret;

View File

@ -77,14 +77,14 @@ struct packet_data {
int ordernum;
};
static struct packet_data packet_data_head;
static struct list_head packet_data_list;
static struct platform_device *rbu_device;
static int context;
static void init_packet_head(void)
{
INIT_LIST_HEAD(&packet_data_head.list);
INIT_LIST_HEAD(&packet_data_list);
rbu_data.packet_read_count = 0;
rbu_data.num_packets = 0;
rbu_data.packetsize = 0;
@ -183,7 +183,7 @@ static int create_packet(void *data, size_t length) __must_hold(&rbu_data.lock)
/* initialize the newly created packet headers */
INIT_LIST_HEAD(&newpacket->list);
list_add_tail(&newpacket->list, &packet_data_head.list);
list_add_tail(&newpacket->list, &packet_data_list);
memcpy(newpacket->data, data, length);
@ -292,7 +292,7 @@ static int packet_read_list(char *data, size_t * pread_length)
remaining_bytes = *pread_length;
bytes_read = rbu_data.packet_read_count;
list_for_each_entry(newpacket, &packet_data_head.list, list) {
list_for_each_entry(newpacket, &packet_data_list, list) {
bytes_copied = do_packet_read(pdest, newpacket,
remaining_bytes, bytes_read, &temp_count);
remaining_bytes -= bytes_copied;
@ -315,7 +315,7 @@ static void packet_empty_list(void)
{
struct packet_data *newpacket, *tmp;
list_for_each_entry_safe(newpacket, tmp, &packet_data_head.list, list) {
list_for_each_entry_safe(newpacket, tmp, &packet_data_list, list) {
list_del(&newpacket->list);
/*

View File

@ -180,15 +180,19 @@ static ssize_t charge_control_end_threshold_store(struct device *dev,
const char *buf, size_t count)
{
int cc_end_value, s006_cc_return;
int value, ret;
unsigned int value;
int ret;
ret = kstrtouint(buf, 10, &value);
if (ret)
return ret;
if (value < 50 || value > 100)
if (value > 100)
return -EINVAL;
if (value < 50)
value = 50;
cc_end_value = value * 0x100 + 0x20;
s006_cc_return = call_fext_func(fext, FUNC_S006_METHOD,
CHARGE_CONTROL_RW, cc_end_value, 0x0);

View File

@ -14,6 +14,7 @@
#include <linux/err.h>
#include <linux/gfp_types.h>
#include <linux/intel_tpmi.h>
#include <linux/intel_vsec.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kstrtox.h>
@ -256,7 +257,7 @@ DEFINE_SHOW_STORE_ATTRIBUTE(plr_status);
static int intel_plr_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
{
struct intel_tpmi_plat_info *plat_info;
struct oobmsm_plat_info *plat_info;
struct dentry *dentry;
int i, num_resources;
struct resource *res;

View File

@ -18,6 +18,7 @@ config INTEL_PMT_CLASS
config INTEL_PMT_TELEMETRY
tristate "Intel Platform Monitoring Technology (PMT) Telemetry driver"
depends on INTEL_VSEC
select INTEL_PMT_DISCOVERY
select INTEL_PMT_CLASS
help
The Intel Platform Monitory Technology (PMT) Telemetry driver provides
@ -38,3 +39,30 @@ config INTEL_PMT_CRASHLOG
To compile this driver as a module, choose M here: the module
will be called intel_pmt_crashlog.
config INTEL_PMT_DISCOVERY
tristate "Intel Platform Monitoring Technology (PMT) Discovery driver"
depends on INTEL_VSEC
select INTEL_PMT_CLASS
help
The Intel Platform Monitoring Technology (PMT) discovery driver provides
access to details about the various PMT features and feature specific
attributes.
To compile this driver as a module, choose M here: the module
will be called pmt_discovery.
config INTEL_PMT_KUNIT_TEST
tristate "KUnit tests for Intel PMT driver"
depends on INTEL_PMT_DISCOVERY
depends on INTEL_PMT_TELEMETRY || !INTEL_PMT_TELEMETRY
depends on KUNIT
help
Enable this option to compile and run a suite of KUnit tests for the Intel
Platform Monitoring Technology (PMT) driver. These tests are designed to
validate the driver's functionality, error handling, and overall stability,
helping developers catch regressions and ensure code quality during changes.
This option is intended for development and testing environments. It is
recommended to disable it in production builds. To compile this driver as a
module, choose M here: the module will be called pmt-discovery-kunit.

View File

@ -10,3 +10,7 @@ obj-$(CONFIG_INTEL_PMT_TELEMETRY) += pmt_telemetry.o
pmt_telemetry-y := telemetry.o
obj-$(CONFIG_INTEL_PMT_CRASHLOG) += pmt_crashlog.o
pmt_crashlog-y := crashlog.o
obj-$(CONFIG_INTEL_PMT_DISCOVERY) += pmt_discovery.o
pmt_discovery-y := discovery.o features.o
obj-$(CONFIG_INTEL_PMT_KUNIT_TEST) += pmt-discovery-kunit.o
pmt-discovery-kunit-y := discovery-kunit.o

View File

@ -9,11 +9,13 @@
*/
#include <linux/kernel.h>
#include <linux/log2.h>
#include <linux/intel_vsec.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/sysfs.h>
#include "class.h"
@ -97,7 +99,7 @@ intel_pmt_read(struct file *filp, struct kobject *kobj,
if (count > entry->size - off)
count = entry->size - off;
count = pmt_telem_read_mmio(entry->ep->pcidev, entry->cb, entry->header.guid, buf,
count = pmt_telem_read_mmio(entry->pcidev, entry->cb, entry->header.guid, buf,
entry->base, off, count);
return count;
@ -166,12 +168,41 @@ static struct attribute *intel_pmt_attrs[] = {
&dev_attr_offset.attr,
NULL
};
ATTRIBUTE_GROUPS(intel_pmt);
static struct class intel_pmt_class = {
static umode_t intel_pmt_attr_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct auxiliary_device *auxdev = to_auxiliary_dev(dev->parent);
struct intel_vsec_device *ivdev = auxdev_to_ivdev(auxdev);
/*
* Place the discovery features folder in /sys/class/intel_pmt, but
* exclude the common attributes as they are not applicable.
*/
if (ivdev->cap_id == ilog2(VSEC_CAP_DISCOVERY))
return 0;
return attr->mode;
}
static bool intel_pmt_group_visible(struct kobject *kobj)
{
return true;
}
DEFINE_SYSFS_GROUP_VISIBLE(intel_pmt);
static const struct attribute_group intel_pmt_group = {
.attrs = intel_pmt_attrs,
.is_visible = SYSFS_GROUP_VISIBLE(intel_pmt),
};
__ATTRIBUTE_GROUPS(intel_pmt);
struct class intel_pmt_class = {
.name = "intel_pmt",
.dev_groups = intel_pmt_groups,
};
EXPORT_SYMBOL_GPL(intel_pmt_class);
static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
struct intel_vsec_device *ivdev,
@ -252,6 +283,7 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
return -EINVAL;
}
entry->pcidev = pci_dev;
entry->guid = header->guid;
entry->size = header->size;
entry->cb = ivdev->priv_data;
@ -284,8 +316,8 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
entry->kobj = &dev->kobj;
if (ns->attr_grp) {
ret = sysfs_create_group(entry->kobj, ns->attr_grp);
if (entry->attr_grp) {
ret = sysfs_create_group(entry->kobj, entry->attr_grp);
if (ret)
goto fail_sysfs_create_group;
}
@ -326,8 +358,8 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
fail_add_endpoint:
sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);
fail_ioremap:
if (ns->attr_grp)
sysfs_remove_group(entry->kobj, ns->attr_grp);
if (entry->attr_grp)
sysfs_remove_group(entry->kobj, entry->attr_grp);
fail_sysfs_create_group:
device_unregister(dev);
fail_dev_create:
@ -369,8 +401,8 @@ void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
if (entry->size)
sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);
if (ns->attr_grp)
sysfs_remove_group(entry->kobj, ns->attr_grp);
if (entry->attr_grp)
sysfs_remove_group(entry->kobj, entry->attr_grp);
device_unregister(dev);
xa_erase(ns->xa, entry->devid);

View File

@ -20,6 +20,7 @@
#define GET_ADDRESS(v) ((v) & GENMASK(31, 3))
struct pci_dev;
extern struct class intel_pmt_class;
struct telem_endpoint {
struct pci_dev *pcidev;
@ -39,22 +40,25 @@ struct intel_pmt_header {
struct intel_pmt_entry {
struct telem_endpoint *ep;
struct pci_dev *pcidev;
struct intel_pmt_header header;
struct bin_attribute pmt_bin_attr;
const struct attribute_group *attr_grp;
struct kobject *kobj;
void __iomem *disc_table;
void __iomem *base;
struct pmt_callbacks *cb;
unsigned long base_addr;
size_t size;
u64 feature_flags;
u32 guid;
u32 num_rmids; /* Number of Resource Monitoring IDs */
int devid;
};
struct intel_pmt_namespace {
const char *name;
struct xarray *xa;
const struct attribute_group *attr_grp;
int (*pmt_header_decode)(struct intel_pmt_entry *entry,
struct device *dev);
int (*pmt_add_endpoint)(struct intel_vsec_device *ivdev,
@ -69,4 +73,10 @@ int intel_pmt_dev_create(struct intel_pmt_entry *entry,
struct intel_vsec_device *dev, int idx);
void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
struct intel_pmt_namespace *ns);
#if IS_ENABLED(CONFIG_INTEL_PMT_DISCOVERY)
void intel_pmt_get_features(struct intel_pmt_entry *entry);
#else
static inline void intel_pmt_get_features(struct intel_pmt_entry *entry) {}
#endif
#endif

View File

@ -9,9 +9,11 @@
*/
#include <linux/auxiliary_bus.h>
#include <linux/cleanup.h>
#include <linux/intel_vsec.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
@ -22,21 +24,6 @@
/* Crashlog discovery header types */
#define CRASH_TYPE_OOBMSM 1
/* Control Flags */
#define CRASHLOG_FLAG_DISABLE BIT(28)
/*
* Bits 29 and 30 control the state of bit 31.
*
* Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured.
* Bit 30 will immediately trigger a crashlog to be generated, setting bit 31.
* Bit 31 is the read-only status with a 1 indicating log is complete.
*/
#define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(29)
#define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(30)
#define CRASHLOG_FLAG_TRIGGER_COMPLETE BIT(31)
#define CRASHLOG_FLAG_TRIGGER_MASK GENMASK(31, 28)
/* Crashlog Discovery Header */
#define CONTROL_OFFSET 0x0
#define GUID_OFFSET 0x4
@ -48,10 +35,84 @@
/* size is in bytes */
#define GET_SIZE(v) ((v) * sizeof(u32))
/*
* Type 1 Version 0
* status and control registers are combined.
*
* Bits 29 and 30 control the state of bit 31.
* Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured.
* Bit 30 will immediately trigger a crashlog to be generated, setting bit 31.
* Bit 31 is the read-only status with a 1 indicating log is complete.
*/
#define TYPE1_VER0_STATUS_OFFSET 0x00
#define TYPE1_VER0_CONTROL_OFFSET 0x00
#define TYPE1_VER0_DISABLE BIT(28)
#define TYPE1_VER0_CLEAR BIT(29)
#define TYPE1_VER0_EXECUTE BIT(30)
#define TYPE1_VER0_COMPLETE BIT(31)
#define TYPE1_VER0_TRIGGER_MASK GENMASK(31, 28)
/*
* Type 1 Version 2
* status and control are different registers
*/
#define TYPE1_VER2_STATUS_OFFSET 0x00
#define TYPE1_VER2_CONTROL_OFFSET 0x14
/* status register */
#define TYPE1_VER2_CLEAR_SUPPORT BIT(20)
#define TYPE1_VER2_REARMED BIT(25)
#define TYPE1_VER2_ERROR BIT(26)
#define TYPE1_VER2_CONSUMED BIT(27)
#define TYPE1_VER2_DISABLED BIT(28)
#define TYPE1_VER2_CLEARED BIT(29)
#define TYPE1_VER2_IN_PROGRESS BIT(30)
#define TYPE1_VER2_COMPLETE BIT(31)
/* control register */
#define TYPE1_VER2_CONSUME BIT(25)
#define TYPE1_VER2_REARM BIT(28)
#define TYPE1_VER2_EXECUTE BIT(29)
#define TYPE1_VER2_CLEAR BIT(30)
#define TYPE1_VER2_DISABLE BIT(31)
#define TYPE1_VER2_TRIGGER_MASK \
(TYPE1_VER2_EXECUTE | TYPE1_VER2_CLEAR | TYPE1_VER2_DISABLE)
/* After offset, order alphabetically, not bit ordered */
struct crashlog_status {
u32 offset;
u32 clear_supported;
u32 cleared;
u32 complete;
u32 consumed;
u32 disabled;
u32 error;
u32 in_progress;
u32 rearmed;
};
struct crashlog_control {
u32 offset;
u32 trigger_mask;
u32 clear;
u32 consume;
u32 disable;
u32 manual;
u32 rearm;
};
struct crashlog_info {
const struct crashlog_status status;
const struct crashlog_control control;
const struct attribute_group *attr_grp;
};
struct crashlog_entry {
/* entry must be first member of struct */
struct intel_pmt_entry entry;
struct mutex control_mutex;
const struct crashlog_info *info;
};
struct pmt_crashlog_priv {
@ -62,180 +123,397 @@ struct pmt_crashlog_priv {
/*
* I/O
*/
static bool pmt_crashlog_complete(struct intel_pmt_entry *entry)
{
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
/* Read, modify, write the control register, setting or clearing @bit based on @set */
static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set)
{
const struct crashlog_control *control = &crashlog->info->control;
struct intel_pmt_entry *entry = &crashlog->entry;
u32 reg = readl(entry->disc_table + control->offset);
reg &= ~control->trigger_mask;
if (set)
reg |= bit;
else
reg &= ~bit;
writel(reg, entry->disc_table + control->offset);
}
/* Read the status register and see if the specified @bit is set */
static bool pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit)
{
const struct crashlog_status *status = &crashlog->info->status;
u32 reg = readl(crashlog->entry.disc_table + status->offset);
return !!(reg & bit);
}
static bool pmt_crashlog_complete(struct crashlog_entry *crashlog)
{
/* return current value of the crashlog complete flag */
return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE);
return pmt_crashlog_rc(crashlog, crashlog->info->status.complete);
}
static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry)
static bool pmt_crashlog_disabled(struct crashlog_entry *crashlog)
{
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
/* return current value of the crashlog disabled flag */
return !!(control & CRASHLOG_FLAG_DISABLE);
return pmt_crashlog_rc(crashlog, crashlog->info->status.disabled);
}
static bool pmt_crashlog_supported(struct intel_pmt_entry *entry)
static bool pmt_crashlog_supported(struct intel_pmt_entry *entry, u32 *crash_type, u32 *version)
{
u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET);
u32 crash_type, version;
crash_type = GET_TYPE(discovery_header);
version = GET_VERSION(discovery_header);
*crash_type = GET_TYPE(discovery_header);
*version = GET_VERSION(discovery_header);
/*
* Currently we only recognize OOBMSM version 0 devices.
* We can ignore all other crashlog devices in the system.
* Currently we only recognize OOBMSM (type 1) and version 0 or 2
* devices.
*
* Ignore all other crashlog devices in the system.
*/
return crash_type == CRASH_TYPE_OOBMSM && version == 0;
if (*crash_type == CRASH_TYPE_OOBMSM && (*version == 0 || *version == 2))
return true;
return false;
}
static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry,
static void pmt_crashlog_set_disable(struct crashlog_entry *crashlog,
bool disable)
{
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
/* clear trigger bits so we are only modifying disable flag */
control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
if (disable)
control |= CRASHLOG_FLAG_DISABLE;
else
control &= ~CRASHLOG_FLAG_DISABLE;
writel(control, entry->disc_table + CONTROL_OFFSET);
pmt_crashlog_rmw(crashlog, crashlog->info->control.disable, disable);
}
static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry)
static void pmt_crashlog_set_clear(struct crashlog_entry *crashlog)
{
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
control |= CRASHLOG_FLAG_TRIGGER_CLEAR;
writel(control, entry->disc_table + CONTROL_OFFSET);
pmt_crashlog_rmw(crashlog, crashlog->info->control.clear, true);
}
static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry)
static void pmt_crashlog_set_execute(struct crashlog_entry *crashlog)
{
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true);
}
control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
control |= CRASHLOG_FLAG_TRIGGER_EXECUTE;
static bool pmt_crashlog_cleared(struct crashlog_entry *crashlog)
{
return pmt_crashlog_rc(crashlog, crashlog->info->status.cleared);
}
writel(control, entry->disc_table + CONTROL_OFFSET);
static bool pmt_crashlog_consumed(struct crashlog_entry *crashlog)
{
return pmt_crashlog_rc(crashlog, crashlog->info->status.consumed);
}
static void pmt_crashlog_set_consumed(struct crashlog_entry *crashlog)
{
pmt_crashlog_rmw(crashlog, crashlog->info->control.consume, true);
}
static bool pmt_crashlog_error(struct crashlog_entry *crashlog)
{
return pmt_crashlog_rc(crashlog, crashlog->info->status.error);
}
static bool pmt_crashlog_rearm(struct crashlog_entry *crashlog)
{
return pmt_crashlog_rc(crashlog, crashlog->info->status.rearmed);
}
static void pmt_crashlog_set_rearm(struct crashlog_entry *crashlog)
{
pmt_crashlog_rmw(crashlog, crashlog->info->control.rearm, true);
}
/*
* sysfs
*/
static ssize_t
clear_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
bool cleared = pmt_crashlog_cleared(crashlog);
return sysfs_emit(buf, "%d\n", cleared);
}
static ssize_t
clear_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct crashlog_entry *crashlog;
bool clear;
int result;
crashlog = dev_get_drvdata(dev);
result = kstrtobool(buf, &clear);
if (result)
return result;
/* set bit only */
if (!clear)
return -EINVAL;
guard(mutex)(&crashlog->control_mutex);
pmt_crashlog_set_clear(crashlog);
return count;
}
static DEVICE_ATTR_RW(clear);
static ssize_t
consumed_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
bool consumed = pmt_crashlog_consumed(crashlog);
return sysfs_emit(buf, "%d\n", consumed);
}
static ssize_t
consumed_store(struct device *dev, struct device_attribute *attr, const char *buf,
size_t count)
{
struct crashlog_entry *crashlog;
bool consumed;
int result;
crashlog = dev_get_drvdata(dev);
result = kstrtobool(buf, &consumed);
if (result)
return result;
/* set bit only */
if (!consumed)
return -EINVAL;
guard(mutex)(&crashlog->control_mutex);
if (pmt_crashlog_disabled(crashlog))
return -EBUSY;
if (!pmt_crashlog_complete(crashlog))
return -EEXIST;
pmt_crashlog_set_consumed(crashlog);
return count;
}
static DEVICE_ATTR_RW(consumed);
static ssize_t
enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
int enabled = !pmt_crashlog_disabled(entry);
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
bool enabled = !pmt_crashlog_disabled(crashlog);
return sprintf(buf, "%d\n", enabled);
}
static ssize_t
enable_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
const char *buf, size_t count)
{
struct crashlog_entry *entry;
struct crashlog_entry *crashlog;
bool enabled;
int result;
entry = dev_get_drvdata(dev);
crashlog = dev_get_drvdata(dev);
result = kstrtobool(buf, &enabled);
if (result)
return result;
mutex_lock(&entry->control_mutex);
pmt_crashlog_set_disable(&entry->entry, !enabled);
mutex_unlock(&entry->control_mutex);
guard(mutex)(&crashlog->control_mutex);
pmt_crashlog_set_disable(crashlog, !enabled);
return count;
}
static DEVICE_ATTR_RW(enable);
static ssize_t
error_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
bool error = pmt_crashlog_error(crashlog);
return sysfs_emit(buf, "%d\n", error);
}
static DEVICE_ATTR_RO(error);
static ssize_t
rearm_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
int rearmed = pmt_crashlog_rearm(crashlog);
return sysfs_emit(buf, "%d\n", rearmed);
}
static ssize_t
rearm_store(struct device *dev, struct device_attribute *attr, const char *buf,
size_t count)
{
struct crashlog_entry *crashlog;
bool rearm;
int result;
crashlog = dev_get_drvdata(dev);
result = kstrtobool(buf, &rearm);
if (result)
return result;
/* set only */
if (!rearm)
return -EINVAL;
guard(mutex)(&crashlog->control_mutex);
pmt_crashlog_set_rearm(crashlog);
return count;
}
static DEVICE_ATTR_RW(rearm);
static ssize_t
trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry;
int trigger;
struct crashlog_entry *crashlog;
bool trigger;
entry = dev_get_drvdata(dev);
trigger = pmt_crashlog_complete(entry);
crashlog = dev_get_drvdata(dev);
trigger = pmt_crashlog_complete(crashlog);
return sprintf(buf, "%d\n", trigger);
}
static ssize_t
trigger_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
const char *buf, size_t count)
{
struct crashlog_entry *entry;
struct crashlog_entry *crashlog;
bool trigger;
int result;
entry = dev_get_drvdata(dev);
crashlog = dev_get_drvdata(dev);
result = kstrtobool(buf, &trigger);
if (result)
return result;
mutex_lock(&entry->control_mutex);
guard(mutex)(&crashlog->control_mutex);
/* if device is currently disabled, return busy */
if (pmt_crashlog_disabled(crashlog))
return -EBUSY;
if (!trigger) {
pmt_crashlog_set_clear(&entry->entry);
} else if (pmt_crashlog_complete(&entry->entry)) {
/* we cannot trigger a new crash if one is still pending */
result = -EEXIST;
goto err;
} else if (pmt_crashlog_disabled(&entry->entry)) {
/* if device is currently disabled, return busy */
result = -EBUSY;
goto err;
} else {
pmt_crashlog_set_execute(&entry->entry);
pmt_crashlog_set_clear(crashlog);
return count;
}
result = count;
err:
mutex_unlock(&entry->control_mutex);
return result;
/* we cannot trigger a new crash if one is still pending */
if (pmt_crashlog_complete(crashlog))
return -EEXIST;
pmt_crashlog_set_execute(crashlog);
return count;
}
static DEVICE_ATTR_RW(trigger);
static struct attribute *pmt_crashlog_attrs[] = {
static struct attribute *pmt_crashlog_type1_ver0_attrs[] = {
&dev_attr_enable.attr,
&dev_attr_trigger.attr,
NULL
};
static const struct attribute_group pmt_crashlog_group = {
.attrs = pmt_crashlog_attrs,
static struct attribute *pmt_crashlog_type1_ver2_attrs[] = {
&dev_attr_clear.attr,
&dev_attr_consumed.attr,
&dev_attr_enable.attr,
&dev_attr_error.attr,
&dev_attr_rearm.attr,
&dev_attr_trigger.attr,
NULL
};
static const struct attribute_group pmt_crashlog_type1_ver0_group = {
.attrs = pmt_crashlog_type1_ver0_attrs,
};
static const struct attribute_group pmt_crashlog_type1_ver2_group = {
.attrs = pmt_crashlog_type1_ver2_attrs,
};
static const struct crashlog_info crashlog_type1_ver0 = {
.status.offset = TYPE1_VER0_STATUS_OFFSET,
.status.cleared = TYPE1_VER0_CLEAR,
.status.complete = TYPE1_VER0_COMPLETE,
.status.disabled = TYPE1_VER0_DISABLE,
.control.offset = TYPE1_VER0_CONTROL_OFFSET,
.control.trigger_mask = TYPE1_VER0_TRIGGER_MASK,
.control.clear = TYPE1_VER0_CLEAR,
.control.disable = TYPE1_VER0_DISABLE,
.control.manual = TYPE1_VER0_EXECUTE,
.attr_grp = &pmt_crashlog_type1_ver0_group,
};
static const struct crashlog_info crashlog_type1_ver2 = {
.status.offset = TYPE1_VER2_STATUS_OFFSET,
.status.clear_supported = TYPE1_VER2_CLEAR_SUPPORT,
.status.cleared = TYPE1_VER2_CLEARED,
.status.complete = TYPE1_VER2_COMPLETE,
.status.consumed = TYPE1_VER2_CONSUMED,
.status.disabled = TYPE1_VER2_DISABLED,
.status.error = TYPE1_VER2_ERROR,
.status.in_progress = TYPE1_VER2_IN_PROGRESS,
.status.rearmed = TYPE1_VER2_REARMED,
.control.offset = TYPE1_VER2_CONTROL_OFFSET,
.control.trigger_mask = TYPE1_VER2_TRIGGER_MASK,
.control.clear = TYPE1_VER2_CLEAR,
.control.consume = TYPE1_VER2_CONSUME,
.control.disable = TYPE1_VER2_DISABLE,
.control.manual = TYPE1_VER2_EXECUTE,
.control.rearm = TYPE1_VER2_REARM,
.attr_grp = &pmt_crashlog_type1_ver2_group,
};
static const struct crashlog_info *select_crashlog_info(u32 type, u32 version)
{
if (version == 0)
return &crashlog_type1_ver0;
return &crashlog_type1_ver2;
}
static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
struct device *dev)
{
void __iomem *disc_table = entry->disc_table;
struct intel_pmt_header *header = &entry->header;
struct crashlog_entry *crashlog;
u32 version;
u32 type;
if (!pmt_crashlog_supported(entry))
if (!pmt_crashlog_supported(entry, &type, &version))
return 1;
/* initialize control mutex */
/* initialize the crashlog struct */
crashlog = container_of(entry, struct crashlog_entry, entry);
mutex_init(&crashlog->control_mutex);
crashlog->info = select_crashlog_info(type, version);
header->access_type = GET_ACCESS(readl(disc_table));
header->guid = readl(disc_table + GUID_OFFSET);
header->base_offset = readl(disc_table + BASE_OFFSET);
@ -243,6 +521,8 @@ static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
/* Size is measured in DWORDS, but accessor returns bytes */
header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET));
entry->attr_grp = crashlog->info->attr_grp;
return 0;
}
@ -250,7 +530,6 @@ static DEFINE_XARRAY_ALLOC(crashlog_array);
static struct intel_pmt_namespace pmt_crashlog_ns = {
.name = "crashlog",
.xa = &crashlog_array,
.attr_grp = &pmt_crashlog_group,
.pmt_header_decode = pmt_crashlog_header_decode,
};
@ -262,8 +541,12 @@ static void pmt_crashlog_remove(struct auxiliary_device *auxdev)
struct pmt_crashlog_priv *priv = auxiliary_get_drvdata(auxdev);
int i;
for (i = 0; i < priv->num_entries; i++)
intel_pmt_dev_destroy(&priv->entry[i].entry, &pmt_crashlog_ns);
for (i = 0; i < priv->num_entries; i++) {
struct crashlog_entry *crashlog = &priv->entry[i];
intel_pmt_dev_destroy(&crashlog->entry, &pmt_crashlog_ns);
mutex_destroy(&crashlog->control_mutex);
}
}
static int pmt_crashlog_probe(struct auxiliary_device *auxdev,

View File

@ -0,0 +1,116 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel Platform Monitory Technology Discovery KUNIT tests
*
* Copyright (c) 2025, Intel Corporation.
* All Rights Reserved.
*/
#include <kunit/test.h>
#include <linux/err.h>
#include <linux/intel_pmt_features.h>
#include <linux/intel_vsec.h>
#include <linux/module.h>
#include <linux/slab.h>
#define PMT_FEATURE_COUNT (FEATURE_MAX + 1)
static void
validate_pmt_regions(struct kunit *test, struct pmt_feature_group *feature_group, int feature_id)
{
int i;
kunit_info(test, "Feature ID %d [%s] has %d regions.\n", feature_id,
pmt_feature_names[feature_id], feature_group->count);
for (i = 0; i < feature_group->count; i++) {
struct telemetry_region *region = &feature_group->regions[i];
kunit_info(test, " - Region %d: cdie_mask=%u, package_id=%u, partition=%u, segment=%u,",
i, region->plat_info.cdie_mask, region->plat_info.package_id,
region->plat_info.partition, region->plat_info.segment);
kunit_info(test, "\t\tbus=%u, device=%u, function=%u, guid=0x%x,",
region->plat_info.bus_number, region->plat_info.device_number,
region->plat_info.function_number, region->guid);
kunit_info(test, "\t\taddr=%p, size=%zu, num_rmids=%u", region->addr, region->size,
region->num_rmids);
KUNIT_ASSERT_GE(test, region->plat_info.cdie_mask, 0);
KUNIT_ASSERT_GE(test, region->plat_info.package_id, 0);
KUNIT_ASSERT_GE(test, region->plat_info.partition, 0);
KUNIT_ASSERT_GE(test, region->plat_info.segment, 0);
KUNIT_ASSERT_GE(test, region->plat_info.bus_number, 0);
KUNIT_ASSERT_GE(test, region->plat_info.device_number, 0);
KUNIT_ASSERT_GE(test, region->plat_info.function_number, 0);
KUNIT_ASSERT_NE(test, region->guid, 0);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, (__force const void *)region->addr);
}
}
static void linebreak(struct kunit *test)
{
kunit_info(test, "*****************************************************************************\n");
}
static void test_intel_pmt_get_regions_by_feature(struct kunit *test)
{
struct pmt_feature_group *feature_group;
int num_available = 0;
int feature_id;
/* Iterate through all possible feature IDs */
for (feature_id = 1; feature_id < PMT_FEATURE_COUNT; feature_id++, linebreak(test)) {
const char *name;
if (!pmt_feature_id_is_valid(feature_id))
continue;
name = pmt_feature_names[feature_id];
feature_group = intel_pmt_get_regions_by_feature(feature_id);
if (IS_ERR(feature_group)) {
if (PTR_ERR(feature_group) == -ENOENT)
kunit_warn(test, "intel_pmt_get_regions_by_feature() reporting feature %d [%s] is not present.\n",
feature_id, name);
else
kunit_warn(test, "intel_pmt_get_regions_by_feature() returned error %ld while attempt to lookup %d [%s].\n",
PTR_ERR(feature_group), feature_id, name);
continue;
}
if (!feature_group) {
kunit_warn(test, "Feature ID %d: %s is not available.\n", feature_id, name);
continue;
}
num_available++;
validate_pmt_regions(test, feature_group, feature_id);
intel_pmt_put_feature_group(feature_group);
}
if (num_available == 0)
kunit_warn(test, "No PMT region groups were available for any feature ID (0-10).\n");
}
static struct kunit_case intel_pmt_discovery_test_cases[] = {
KUNIT_CASE(test_intel_pmt_get_regions_by_feature),
{}
};
static struct kunit_suite intel_pmt_discovery_test_suite = {
.name = "pmt_discovery_test",
.test_cases = intel_pmt_discovery_test_cases,
};
kunit_test_suite(intel_pmt_discovery_test_suite);
MODULE_IMPORT_NS("INTEL_PMT_DISCOVERY");
MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
MODULE_DESCRIPTION("Intel PMT Discovery KUNIT test driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,635 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel Platform Monitory Technology Discovery driver
*
* Copyright (c) 2025, Intel Corporation.
* All Rights Reserved.
*/
#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/kdev_t.h>
#include <linux/kobject.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/string_choices.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/intel_pmt_features.h>
#include <linux/intel_vsec.h>
#include "class.h"
#define MAX_FEATURE_VERSION 0
#define DT_TBIR GENMASK(2, 0)
#define FEAT_ATTR_SIZE(x) ((x) * sizeof(u32))
#define PMT_GUID_SIZE(x) ((x) * sizeof(u32))
#define PMT_ACCESS_TYPE_RSVD 0xF
#define SKIP_FEATURE 1
struct feature_discovery_table {
u32 access_type:4;
u32 version:8;
u32 size:16;
u32 reserved:4;
u32 id;
u32 offset;
u32 reserved2;
};
/* Common feature table header */
struct feature_header {
u32 attr_size:8;
u32 num_guids:8;
u32 reserved:16;
};
/* Feature attribute fields */
struct caps {
u32 caps;
};
struct command {
u32 max_stream_size:16;
u32 max_command_size:16;
};
struct watcher {
u32 reserved:21;
u32 period:11;
struct command command;
};
struct rmid {
u32 num_rmids:16; /* Number of Resource Monitoring IDs */
u32 reserved:16;
struct watcher watcher;
};
struct feature_table {
struct feature_header header;
struct caps caps;
union {
struct command command;
struct watcher watcher;
struct rmid rmid;
};
u32 *guids;
};
/* For backreference in struct feature */
struct pmt_features_priv;
struct feature {
struct feature_table table;
struct kobject kobj;
struct pmt_features_priv *priv;
struct list_head list;
const struct attribute_group *attr_group;
enum pmt_feature_id id;
};
struct pmt_features_priv {
struct device *parent;
struct device *dev;
int count;
u32 mask;
struct feature feature[];
};
static LIST_HEAD(pmt_feature_list);
static DEFINE_MUTEX(feature_list_lock);
#define to_pmt_feature(x) container_of(x, struct feature, kobj)
static void pmt_feature_release(struct kobject *kobj)
{
}
static ssize_t caps_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct feature *feature = to_pmt_feature(kobj);
struct pmt_cap **pmt_caps;
u32 caps = feature->table.caps.caps;
ssize_t ret = 0;
switch (feature->id) {
case FEATURE_PER_CORE_PERF_TELEM:
pmt_caps = pmt_caps_pcpt;
break;
case FEATURE_PER_CORE_ENV_TELEM:
pmt_caps = pmt_caps_pcet;
break;
case FEATURE_PER_RMID_PERF_TELEM:
pmt_caps = pmt_caps_rmid_perf;
break;
case FEATURE_ACCEL_TELEM:
pmt_caps = pmt_caps_accel;
break;
case FEATURE_UNCORE_TELEM:
pmt_caps = pmt_caps_uncore;
break;
case FEATURE_CRASH_LOG:
pmt_caps = pmt_caps_crashlog;
break;
case FEATURE_PETE_LOG:
pmt_caps = pmt_caps_pete;
break;
case FEATURE_TPMI_CTRL:
pmt_caps = pmt_caps_tpmi;
break;
case FEATURE_TRACING:
pmt_caps = pmt_caps_tracing;
break;
case FEATURE_PER_RMID_ENERGY_TELEM:
pmt_caps = pmt_caps_rmid_energy;
break;
default:
return -EINVAL;
}
while (*pmt_caps) {
struct pmt_cap *pmt_cap = *pmt_caps;
while (pmt_cap->name) {
ret += sysfs_emit_at(buf, ret, "%-40s Available: %s\n", pmt_cap->name,
str_yes_no(pmt_cap->mask & caps));
pmt_cap++;
}
pmt_caps++;
}
return ret;
}
static struct kobj_attribute caps_attribute = __ATTR_RO(caps);
static struct watcher *get_watcher(struct feature *feature)
{
switch (feature_layout[feature->id]) {
case LAYOUT_RMID:
return &feature->table.rmid.watcher;
case LAYOUT_WATCHER:
return &feature->table.watcher;
default:
return ERR_PTR(-EINVAL);
}
}
static struct command *get_command(struct feature *feature)
{
switch (feature_layout[feature->id]) {
case LAYOUT_RMID:
return &feature->table.rmid.watcher.command;
case LAYOUT_WATCHER:
return &feature->table.watcher.command;
case LAYOUT_COMMAND:
return &feature->table.command;
default:
return ERR_PTR(-EINVAL);
}
}
static ssize_t num_rmids_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct feature *feature = to_pmt_feature(kobj);
return sysfs_emit(buf, "%u\n", feature->table.rmid.num_rmids);
}
static struct kobj_attribute num_rmids_attribute = __ATTR_RO(num_rmids);
static ssize_t min_watcher_period_ms_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct feature *feature = to_pmt_feature(kobj);
struct watcher *watcher = get_watcher(feature);
if (IS_ERR(watcher))
return PTR_ERR(watcher);
return sysfs_emit(buf, "%u\n", watcher->period);
}
static struct kobj_attribute min_watcher_period_ms_attribute =
__ATTR_RO(min_watcher_period_ms);
static ssize_t max_stream_size_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct feature *feature = to_pmt_feature(kobj);
struct command *command = get_command(feature);
if (IS_ERR(command))
return PTR_ERR(command);
return sysfs_emit(buf, "%u\n", command->max_stream_size);
}
static struct kobj_attribute max_stream_size_attribute =
__ATTR_RO(max_stream_size);
static ssize_t max_command_size_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct feature *feature = to_pmt_feature(kobj);
struct command *command = get_command(feature);
if (IS_ERR(command))
return PTR_ERR(command);
return sysfs_emit(buf, "%u\n", command->max_command_size);
}
static struct kobj_attribute max_command_size_attribute =
__ATTR_RO(max_command_size);
static ssize_t guids_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct feature *feature = to_pmt_feature(kobj);
int i, count = 0;
for (i = 0; i < feature->table.header.num_guids; i++)
count += sysfs_emit_at(buf, count, "0x%x\n",
feature->table.guids[i]);
return count;
}
static struct kobj_attribute guids_attribute = __ATTR_RO(guids);
static struct attribute *pmt_feature_rmid_attrs[] = {
&caps_attribute.attr,
&num_rmids_attribute.attr,
&min_watcher_period_ms_attribute.attr,
&max_stream_size_attribute.attr,
&max_command_size_attribute.attr,
&guids_attribute.attr,
NULL
};
ATTRIBUTE_GROUPS(pmt_feature_rmid);
static const struct kobj_type pmt_feature_rmid_ktype = {
.sysfs_ops = &kobj_sysfs_ops,
.release = pmt_feature_release,
.default_groups = pmt_feature_rmid_groups,
};
static struct attribute *pmt_feature_watcher_attrs[] = {
&caps_attribute.attr,
&min_watcher_period_ms_attribute.attr,
&max_stream_size_attribute.attr,
&max_command_size_attribute.attr,
&guids_attribute.attr,
NULL
};
ATTRIBUTE_GROUPS(pmt_feature_watcher);
static const struct kobj_type pmt_feature_watcher_ktype = {
.sysfs_ops = &kobj_sysfs_ops,
.release = pmt_feature_release,
.default_groups = pmt_feature_watcher_groups,
};
static struct attribute *pmt_feature_command_attrs[] = {
&caps_attribute.attr,
&max_stream_size_attribute.attr,
&max_command_size_attribute.attr,
&guids_attribute.attr,
NULL
};
ATTRIBUTE_GROUPS(pmt_feature_command);
static const struct kobj_type pmt_feature_command_ktype = {
.sysfs_ops = &kobj_sysfs_ops,
.release = pmt_feature_release,
.default_groups = pmt_feature_command_groups,
};
static struct attribute *pmt_feature_guids_attrs[] = {
&caps_attribute.attr,
&guids_attribute.attr,
NULL
};
ATTRIBUTE_GROUPS(pmt_feature_guids);
static const struct kobj_type pmt_feature_guids_ktype = {
.sysfs_ops = &kobj_sysfs_ops,
.release = pmt_feature_release,
.default_groups = pmt_feature_guids_groups,
};
static int
pmt_feature_get_disc_table(struct pmt_features_priv *priv,
struct resource *disc_res,
struct feature_discovery_table *disc_tbl)
{
void __iomem *disc_base;
disc_base = devm_ioremap_resource(priv->dev, disc_res);
if (IS_ERR(disc_base))
return PTR_ERR(disc_base);
memcpy_fromio(disc_tbl, disc_base, sizeof(*disc_tbl));
devm_iounmap(priv->dev, disc_base);
if (priv->mask & BIT(disc_tbl->id))
return dev_err_probe(priv->dev, -EINVAL, "Duplicate feature: %s\n",
pmt_feature_names[disc_tbl->id]);
/*
* Some devices may expose non-functioning entries that are
* reserved for future use. They have zero size. Do not fail
* probe for these. Just ignore them.
*/
if (disc_tbl->size == 0 || disc_tbl->access_type == PMT_ACCESS_TYPE_RSVD)
return SKIP_FEATURE;
if (disc_tbl->version > MAX_FEATURE_VERSION)
return SKIP_FEATURE;
if (!pmt_feature_id_is_valid(disc_tbl->id))
return SKIP_FEATURE;
priv->mask |= BIT(disc_tbl->id);
return 0;
}
static int
pmt_feature_get_feature_table(struct pmt_features_priv *priv,
struct feature *feature,
struct feature_discovery_table *disc_tbl,
struct resource *disc_res)
{
struct feature_table *feat_tbl = &feature->table;
struct feature_header *header;
struct resource res = {};
resource_size_t res_size;
void __iomem *feat_base, *feat_offset;
void *tbl_offset;
size_t size;
u32 *guids;
u8 tbir;
tbir = FIELD_GET(DT_TBIR, disc_tbl->offset);
switch (disc_tbl->access_type) {
case ACCESS_LOCAL:
if (tbir)
return dev_err_probe(priv->dev, -EINVAL,
"Unsupported BAR index %u for access type %u\n",
tbir, disc_tbl->access_type);
/*
* For access_type LOCAL, the base address is as follows:
* base address = end of discovery region + base offset + 1
*/
res = DEFINE_RES_MEM(disc_res->end + disc_tbl->offset + 1,
disc_tbl->size * sizeof(u32));
break;
default:
return dev_err_probe(priv->dev, -EINVAL, "Unrecognized access_type %u\n",
disc_tbl->access_type);
}
feature->id = disc_tbl->id;
/* Get the feature table */
feat_base = devm_ioremap_resource(priv->dev, &res);
if (IS_ERR(feat_base))
return PTR_ERR(feat_base);
feat_offset = feat_base;
tbl_offset = feat_tbl;
/* Get the header */
header = &feat_tbl->header;
memcpy_fromio(header, feat_offset, sizeof(*header));
/* Validate fields fit within mapped resource */
size = sizeof(*header) + FEAT_ATTR_SIZE(header->attr_size) +
PMT_GUID_SIZE(header->num_guids);
res_size = resource_size(&res);
if (WARN(size > res_size, "Bad table size %zu > %pa", size, &res_size))
return -EINVAL;
/* Get the feature attributes, including capability fields */
tbl_offset += sizeof(*header);
feat_offset += sizeof(*header);
memcpy_fromio(tbl_offset, feat_offset, FEAT_ATTR_SIZE(header->attr_size));
/* Finally, get the guids */
guids = devm_kmalloc(priv->dev, PMT_GUID_SIZE(header->num_guids), GFP_KERNEL);
if (!guids)
return -ENOMEM;
feat_offset += FEAT_ATTR_SIZE(header->attr_size);
memcpy_fromio(guids, feat_offset, PMT_GUID_SIZE(header->num_guids));
feat_tbl->guids = guids;
devm_iounmap(priv->dev, feat_base);
return 0;
}
static void pmt_features_add_feat(struct feature *feature)
{
guard(mutex)(&feature_list_lock);
list_add(&feature->list, &pmt_feature_list);
}
static void pmt_features_remove_feat(struct feature *feature)
{
guard(mutex)(&feature_list_lock);
list_del(&feature->list);
}
/* Get the discovery table and use it to get the feature table */
static int pmt_features_discovery(struct pmt_features_priv *priv,
struct feature *feature,
struct intel_vsec_device *ivdev,
int idx)
{
struct feature_discovery_table disc_tbl = {}; /* Avoid false warning */
struct resource *disc_res = &ivdev->resource[idx];
const struct kobj_type *ktype;
int ret;
ret = pmt_feature_get_disc_table(priv, disc_res, &disc_tbl);
if (ret)
return ret;
ret = pmt_feature_get_feature_table(priv, feature, &disc_tbl, disc_res);
if (ret)
return ret;
switch (feature_layout[feature->id]) {
case LAYOUT_RMID:
ktype = &pmt_feature_rmid_ktype;
feature->attr_group = &pmt_feature_rmid_group;
break;
case LAYOUT_WATCHER:
ktype = &pmt_feature_watcher_ktype;
feature->attr_group = &pmt_feature_watcher_group;
break;
case LAYOUT_COMMAND:
ktype = &pmt_feature_command_ktype;
feature->attr_group = &pmt_feature_command_group;
break;
case LAYOUT_CAPS_ONLY:
ktype = &pmt_feature_guids_ktype;
feature->attr_group = &pmt_feature_guids_group;
break;
default:
return -EINVAL;
}
ret = kobject_init_and_add(&feature->kobj, ktype, &priv->dev->kobj,
"%s", pmt_feature_names[feature->id]);
if (ret)
return ret;
kobject_uevent(&feature->kobj, KOBJ_ADD);
pmt_features_add_feat(feature);
return 0;
}
static void pmt_features_remove(struct auxiliary_device *auxdev)
{
struct pmt_features_priv *priv = auxiliary_get_drvdata(auxdev);
int i;
for (i = 0; i < priv->count; i++) {
struct feature *feature = &priv->feature[i];
pmt_features_remove_feat(feature);
sysfs_remove_group(&feature->kobj, feature->attr_group);
kobject_put(&feature->kobj);
}
device_unregister(priv->dev);
}
static int pmt_features_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
{
struct intel_vsec_device *ivdev = auxdev_to_ivdev(auxdev);
struct pmt_features_priv *priv;
size_t size;
int ret, i;
size = struct_size(priv, feature, ivdev->num_resources);
priv = devm_kzalloc(&auxdev->dev, size, GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->parent = &ivdev->pcidev->dev;
auxiliary_set_drvdata(auxdev, priv);
priv->dev = device_create(&intel_pmt_class, &auxdev->dev, MKDEV(0, 0), priv,
"%s-%s", "features", dev_name(priv->parent));
if (IS_ERR(priv->dev))
return dev_err_probe(priv->dev, PTR_ERR(priv->dev),
"Could not create %s-%s device node\n",
"features", dev_name(priv->dev));
/* Initialize each feature */
for (i = 0; i < ivdev->num_resources; i++) {
struct feature *feature = &priv->feature[priv->count];
ret = pmt_features_discovery(priv, feature, ivdev, i);
if (ret == SKIP_FEATURE)
continue;
if (ret != 0)
goto abort_probe;
feature->priv = priv;
priv->count++;
}
return 0;
abort_probe:
/*
* Only fully initialized features are tracked in priv->count, which is
* incremented only after a feature is completely set up (i.e., after
* discovery and sysfs registration). If feature initialization fails,
* the failing feature's state is local and does not require rollback.
*
* Therefore, on error, we can safely call the driver's remove() routine
* pmt_features_remove() to clean up only those features that were
* fully initialized and counted. All other resources are device-managed
* and will be cleaned up automatically during device_unregister().
*/
pmt_features_remove(auxdev);
return ret;
}
static void pmt_get_features(struct intel_pmt_entry *entry, struct feature *f)
{
int num_guids = f->table.header.num_guids;
int i;
for (i = 0; i < num_guids; i++) {
if (f->table.guids[i] != entry->guid)
continue;
entry->feature_flags |= BIT(f->id);
if (feature_layout[f->id] == LAYOUT_RMID)
entry->num_rmids = f->table.rmid.num_rmids;
else
entry->num_rmids = 0; /* entry is kzalloc but set anyway */
}
}
void intel_pmt_get_features(struct intel_pmt_entry *entry)
{
struct feature *feature;
mutex_lock(&feature_list_lock);
list_for_each_entry(feature, &pmt_feature_list, list) {
if (feature->priv->parent != &entry->ep->pcidev->dev)
continue;
pmt_get_features(entry, feature);
}
mutex_unlock(&feature_list_lock);
}
EXPORT_SYMBOL_NS_GPL(intel_pmt_get_features, "INTEL_PMT");
static const struct auxiliary_device_id pmt_features_id_table[] = {
{ .name = "intel_vsec.discovery" },
{}
};
MODULE_DEVICE_TABLE(auxiliary, pmt_features_id_table);
static struct auxiliary_driver pmt_features_aux_driver = {
.id_table = pmt_features_id_table,
.remove = pmt_features_remove,
.probe = pmt_features_probe,
};
module_auxiliary_driver(pmt_features_aux_driver);
MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
MODULE_DESCRIPTION("Intel PMT Discovery driver");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("INTEL_PMT");

View File

@ -0,0 +1,205 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2025, Intel Corporation.
* All Rights Reserved.
*
* Author: "David E. Box" <david.e.box@linux.intel.com>
*/
#include <linux/export.h>
#include <linux/types.h>
#include <linux/intel_pmt_features.h>
const char * const pmt_feature_names[] = {
[FEATURE_PER_CORE_PERF_TELEM] = "per_core_performance_telemetry",
[FEATURE_PER_CORE_ENV_TELEM] = "per_core_environment_telemetry",
[FEATURE_PER_RMID_PERF_TELEM] = "per_rmid_perf_telemetry",
[FEATURE_ACCEL_TELEM] = "accelerator_telemetry",
[FEATURE_UNCORE_TELEM] = "uncore_telemetry",
[FEATURE_CRASH_LOG] = "crash_log",
[FEATURE_PETE_LOG] = "pete_log",
[FEATURE_TPMI_CTRL] = "tpmi_control",
[FEATURE_TRACING] = "tracing",
[FEATURE_PER_RMID_ENERGY_TELEM] = "per_rmid_energy_telemetry",
};
EXPORT_SYMBOL_NS_GPL(pmt_feature_names, "INTEL_PMT_DISCOVERY");
enum feature_layout feature_layout[] = {
[FEATURE_PER_CORE_PERF_TELEM] = LAYOUT_WATCHER,
[FEATURE_PER_CORE_ENV_TELEM] = LAYOUT_WATCHER,
[FEATURE_PER_RMID_PERF_TELEM] = LAYOUT_RMID,
[FEATURE_ACCEL_TELEM] = LAYOUT_WATCHER,
[FEATURE_UNCORE_TELEM] = LAYOUT_WATCHER,
[FEATURE_CRASH_LOG] = LAYOUT_COMMAND,
[FEATURE_PETE_LOG] = LAYOUT_COMMAND,
[FEATURE_TPMI_CTRL] = LAYOUT_CAPS_ONLY,
[FEATURE_TRACING] = LAYOUT_CAPS_ONLY,
[FEATURE_PER_RMID_ENERGY_TELEM] = LAYOUT_RMID,
};
struct pmt_cap pmt_cap_common[] = {
{PMT_CAP_TELEM, "telemetry"},
{PMT_CAP_WATCHER, "watcher"},
{PMT_CAP_CRASHLOG, "crashlog"},
{PMT_CAP_STREAMING, "streaming"},
{PMT_CAP_THRESHOLD, "threshold"},
{PMT_CAP_WINDOW, "window"},
{PMT_CAP_CONFIG, "config"},
{PMT_CAP_TRACING, "tracing"},
{PMT_CAP_INBAND, "inband"},
{PMT_CAP_OOB, "oob"},
{PMT_CAP_SECURED_CHAN, "secure_chan"},
{PMT_CAP_PMT_SP, "pmt_sp"},
{PMT_CAP_PMT_SP_POLICY, "pmt_sp_policy"},
{}
};
struct pmt_cap pmt_cap_pcpt[] = {
{PMT_CAP_PCPT_CORE_PERF, "core_performance"},
{PMT_CAP_PCPT_CORE_C0_RES, "core_c0_residency"},
{PMT_CAP_PCPT_CORE_ACTIVITY, "core_activity"},
{PMT_CAP_PCPT_CACHE_PERF, "cache_performance"},
{PMT_CAP_PCPT_QUALITY_TELEM, "quality_telemetry"},
{}
};
struct pmt_cap *pmt_caps_pcpt[] = {
pmt_cap_common,
pmt_cap_pcpt,
NULL
};
struct pmt_cap pmt_cap_pcet[] = {
{PMT_CAP_PCET_WORKPOINT_HIST, "workpoint_histogram"},
{PMT_CAP_PCET_CORE_CURR_TEMP, "core_current_temp"},
{PMT_CAP_PCET_CORE_INST_RES, "core_inst_residency"},
{PMT_CAP_PCET_QUALITY_TELEM, "quality_telemetry"},
{PMT_CAP_PCET_CORE_CDYN_LVL, "core_cdyn_level"},
{PMT_CAP_PCET_CORE_STRESS_LVL, "core_stress_level"},
{PMT_CAP_PCET_CORE_DAS, "core_digital_aging_sensor"},
{PMT_CAP_PCET_FIVR_HEALTH, "fivr_health"},
{PMT_CAP_PCET_ENERGY, "energy"},
{PMT_CAP_PCET_PEM_STATUS, "pem_status"},
{PMT_CAP_PCET_CORE_C_STATE, "core_c_state"},
{}
};
struct pmt_cap *pmt_caps_pcet[] = {
pmt_cap_common,
pmt_cap_pcet,
NULL
};
struct pmt_cap pmt_cap_rmid_perf[] = {
{PMT_CAP_RMID_CORES_PERF, "core_performance"},
{PMT_CAP_RMID_CACHE_PERF, "cache_performance"},
{PMT_CAP_RMID_PERF_QUAL, "performance_quality"},
{}
};
struct pmt_cap *pmt_caps_rmid_perf[] = {
pmt_cap_common,
pmt_cap_rmid_perf,
NULL
};
struct pmt_cap pmt_cap_accel[] = {
{PMT_CAP_ACCEL_CPM_TELEM, "content_processing_module"},
{PMT_CAP_ACCEL_TIP_TELEM, "content_turbo_ip"},
{}
};
struct pmt_cap *pmt_caps_accel[] = {
pmt_cap_common,
pmt_cap_accel,
NULL
};
struct pmt_cap pmt_cap_uncore[] = {
{PMT_CAP_UNCORE_IO_CA_TELEM, "io_ca"},
{PMT_CAP_UNCORE_RMID_TELEM, "rmid"},
{PMT_CAP_UNCORE_D2D_ULA_TELEM, "d2d_ula"},
{PMT_CAP_UNCORE_PKGC_TELEM, "package_c"},
{}
};
struct pmt_cap *pmt_caps_uncore[] = {
pmt_cap_common,
pmt_cap_uncore,
NULL
};
struct pmt_cap pmt_cap_crashlog[] = {
{PMT_CAP_CRASHLOG_MAN_TRIG, "manual_trigger"},
{PMT_CAP_CRASHLOG_CORE, "core"},
{PMT_CAP_CRASHLOG_UNCORE, "uncore"},
{PMT_CAP_CRASHLOG_TOR, "tor"},
{PMT_CAP_CRASHLOG_S3M, "s3m"},
{PMT_CAP_CRASHLOG_PERSISTENCY, "persistency"},
{PMT_CAP_CRASHLOG_CLIP_GPIO, "crashlog_in_progress"},
{PMT_CAP_CRASHLOG_PRE_RESET, "pre_reset_extraction"},
{PMT_CAP_CRASHLOG_POST_RESET, "post_reset_extraction"},
{}
};
struct pmt_cap *pmt_caps_crashlog[] = {
pmt_cap_common,
pmt_cap_crashlog,
NULL
};
struct pmt_cap pmt_cap_pete[] = {
{PMT_CAP_PETE_MAN_TRIG, "manual_trigger"},
{PMT_CAP_PETE_ENCRYPTION, "encryption"},
{PMT_CAP_PETE_PERSISTENCY, "persistency"},
{PMT_CAP_PETE_REQ_TOKENS, "required_tokens"},
{PMT_CAP_PETE_PROD_ENABLED, "production_enabled"},
{PMT_CAP_PETE_DEBUG_ENABLED, "debug_enabled"},
{}
};
struct pmt_cap *pmt_caps_pete[] = {
pmt_cap_common,
pmt_cap_pete,
NULL
};
struct pmt_cap pmt_cap_tpmi[] = {
{PMT_CAP_TPMI_MAILBOX, "mailbox"},
{PMT_CAP_TPMI_LOCK, "bios_lock"},
{}
};
struct pmt_cap *pmt_caps_tpmi[] = {
pmt_cap_common,
pmt_cap_tpmi,
NULL
};
struct pmt_cap pmt_cap_tracing[] = {
{PMT_CAP_TRACE_SRAR, "srar_errors"},
{PMT_CAP_TRACE_CORRECTABLE, "correctable_errors"},
{PMT_CAP_TRACE_MCTP, "mctp"},
{PMT_CAP_TRACE_MRT, "memory_resiliency"},
{}
};
struct pmt_cap *pmt_caps_tracing[] = {
pmt_cap_common,
pmt_cap_tracing,
NULL
};
struct pmt_cap pmt_cap_rmid_energy[] = {
{PMT_CAP_RMID_ENERGY, "energy"},
{PMT_CAP_RMID_ACTIVITY, "activity"},
{PMT_CAP_RMID_ENERGY_QUAL, "energy_quality"},
{}
};
struct pmt_cap *pmt_caps_rmid_energy[] = {
pmt_cap_common,
pmt_cap_rmid_energy,
NULL
};

View File

@ -9,13 +9,21 @@
*/
#include <linux/auxiliary_bus.h>
#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/err.h>
#include <linux/intel_pmt_features.h>
#include <linux/intel_vsec.h>
#include <linux/kernel.h>
#include <linux/kref.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/overflow.h>
#include <linux/xarray.h>
#include "class.h"
@ -206,6 +214,87 @@ unlock:
}
EXPORT_SYMBOL_NS_GPL(pmt_telem_get_endpoint_info, "INTEL_PMT_TELEMETRY");
static int pmt_copy_region(struct telemetry_region *region,
struct intel_pmt_entry *entry)
{
struct oobmsm_plat_info *plat_info;
plat_info = intel_vsec_get_mapping(entry->ep->pcidev);
if (IS_ERR(plat_info))
return PTR_ERR(plat_info);
region->plat_info = *plat_info;
region->guid = entry->guid;
region->addr = entry->ep->base;
region->size = entry->size;
region->num_rmids = entry->num_rmids;
return 0;
}
static void pmt_feature_group_release(struct kref *kref)
{
struct pmt_feature_group *feature_group;
feature_group = container_of(kref, struct pmt_feature_group, kref);
kfree(feature_group);
}
struct pmt_feature_group *intel_pmt_get_regions_by_feature(enum pmt_feature_id id)
{
struct pmt_feature_group *feature_group __free(kfree) = NULL;
struct telemetry_region *region;
struct intel_pmt_entry *entry;
unsigned long idx;
int count = 0;
size_t size;
if (!pmt_feature_id_is_valid(id))
return ERR_PTR(-EINVAL);
guard(mutex)(&ep_lock);
xa_for_each(&telem_array, idx, entry) {
if (entry->feature_flags & BIT(id))
count++;
}
if (!count)
return ERR_PTR(-ENOENT);
size = struct_size(feature_group, regions, count);
feature_group = kzalloc(size, GFP_KERNEL);
if (!feature_group)
return ERR_PTR(-ENOMEM);
feature_group->count = count;
region = feature_group->regions;
xa_for_each(&telem_array, idx, entry) {
int ret;
if (!(entry->feature_flags & BIT(id)))
continue;
ret = pmt_copy_region(region, entry);
if (ret)
return ERR_PTR(ret);
region++;
}
kref_init(&feature_group->kref);
return no_free_ptr(feature_group);
}
EXPORT_SYMBOL(intel_pmt_get_regions_by_feature);
void intel_pmt_put_feature_group(struct pmt_feature_group *feature_group)
{
kref_put(&feature_group->kref, pmt_feature_group_release);
}
EXPORT_SYMBOL(intel_pmt_put_feature_group);
int pmt_telem_read(struct telem_endpoint *ep, u32 id, u64 *data, u32 count)
{
u32 offset, size;
@ -311,6 +400,8 @@ static int pmt_telem_probe(struct auxiliary_device *auxdev, const struct auxilia
continue;
priv->num_entries++;
intel_pmt_get_features(entry);
}
return 0;
@ -348,3 +439,4 @@ MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
MODULE_DESCRIPTION("Intel PMT Telemetry driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("INTEL_PMT");
MODULE_IMPORT_NS("INTEL_VSEC");

View File

@ -22,6 +22,7 @@
#include <linux/auxiliary_bus.h>
#include <linux/delay.h>
#include <linux/intel_tpmi.h>
#include <linux/intel_vsec.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/kernel.h>
@ -1546,7 +1547,7 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev)
{
struct tpmi_per_power_domain_info *pd_info;
bool read_blocked = 0, write_blocked = 0;
struct intel_tpmi_plat_info *plat_info;
struct oobmsm_plat_info *plat_info;
struct device *dev = &auxdev->dev;
struct tpmi_sst_struct *tpmi_sst;
u8 i, num_resources, io_die_cnt;
@ -1698,7 +1699,7 @@ EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_add, "INTEL_TPMI_SST");
void tpmi_sst_dev_remove(struct auxiliary_device *auxdev)
{
struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
struct intel_tpmi_plat_info *plat_info;
struct oobmsm_plat_info *plat_info;
plat_info = tpmi_get_platform_data(auxdev);
if (!plat_info)
@ -1720,7 +1721,7 @@ 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 intel_tpmi_plat_info *plat_info;
struct oobmsm_plat_info *plat_info;
void __iomem *cp_base;
plat_info = tpmi_get_platform_data(auxdev);
@ -1748,7 +1749,7 @@ 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 intel_tpmi_plat_info *plat_info;
struct oobmsm_plat_info *plat_info;
void __iomem *cp_base;
plat_info = tpmi_get_platform_data(auxdev);

View File

@ -21,33 +21,6 @@ struct telemetry_core_config {
static struct telemetry_core_config telm_core_conf;
static int telemetry_def_update_events(struct telemetry_evtconfig pss_evtconfig,
struct telemetry_evtconfig ioss_evtconfig)
{
return 0;
}
static int telemetry_def_set_sampling_period(u8 pss_period, u8 ioss_period)
{
return 0;
}
static int telemetry_def_get_sampling_period(u8 *pss_min_period,
u8 *pss_max_period,
u8 *ioss_min_period,
u8 *ioss_max_period)
{
return 0;
}
static int telemetry_def_get_eventconfig(
struct telemetry_evtconfig *pss_evtconfig,
struct telemetry_evtconfig *ioss_evtconfig,
int pss_len, int ioss_len)
{
return 0;
}
static int telemetry_def_get_trace_verbosity(enum telemetry_unit telem_unit,
u32 *verbosity)
{
@ -75,144 +48,13 @@ static int telemetry_def_read_eventlog(enum telemetry_unit telem_unit,
return 0;
}
static int telemetry_def_add_events(u8 num_pss_evts, u8 num_ioss_evts,
u32 *pss_evtmap, u32 *ioss_evtmap)
{
return 0;
}
static int telemetry_def_reset_events(void)
{
return 0;
}
static const struct telemetry_core_ops telm_defpltops = {
.set_sampling_period = telemetry_def_set_sampling_period,
.get_sampling_period = telemetry_def_get_sampling_period,
.get_trace_verbosity = telemetry_def_get_trace_verbosity,
.set_trace_verbosity = telemetry_def_set_trace_verbosity,
.raw_read_eventlog = telemetry_def_raw_read_eventlog,
.get_eventconfig = telemetry_def_get_eventconfig,
.read_eventlog = telemetry_def_read_eventlog,
.update_events = telemetry_def_update_events,
.reset_events = telemetry_def_reset_events,
.add_events = telemetry_def_add_events,
};
/**
* telemetry_update_events() - Update telemetry Configuration
* @pss_evtconfig: PSS related config. No change if num_evts = 0.
* @ioss_evtconfig: IOSS related config. No change if num_evts = 0.
*
* This API updates the IOSS & PSS Telemetry configuration. Old config
* is overwritten. Call telemetry_reset_events when logging is over
* All sample period values should be in the form of:
* bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
*
* Return: 0 success, < 0 for failure
*/
int telemetry_update_events(struct telemetry_evtconfig pss_evtconfig,
struct telemetry_evtconfig ioss_evtconfig)
{
return telm_core_conf.telem_ops->update_events(pss_evtconfig,
ioss_evtconfig);
}
EXPORT_SYMBOL_GPL(telemetry_update_events);
/**
* telemetry_set_sampling_period() - Sets the IOSS & PSS sampling period
* @pss_period: placeholder for PSS Period to be set.
* Set to 0 if not required to be updated
* @ioss_period: placeholder for IOSS Period to be set
* Set to 0 if not required to be updated
*
* All values should be in the form of:
* bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
*
* Return: 0 success, < 0 for failure
*/
int telemetry_set_sampling_period(u8 pss_period, u8 ioss_period)
{
return telm_core_conf.telem_ops->set_sampling_period(pss_period,
ioss_period);
}
EXPORT_SYMBOL_GPL(telemetry_set_sampling_period);
/**
* telemetry_get_sampling_period() - Get IOSS & PSS min & max sampling period
* @pss_min_period: placeholder for PSS Min Period supported
* @pss_max_period: placeholder for PSS Max Period supported
* @ioss_min_period: placeholder for IOSS Min Period supported
* @ioss_max_period: placeholder for IOSS Max Period supported
*
* All values should be in the form of:
* bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
*
* Return: 0 success, < 0 for failure
*/
int telemetry_get_sampling_period(u8 *pss_min_period, u8 *pss_max_period,
u8 *ioss_min_period, u8 *ioss_max_period)
{
return telm_core_conf.telem_ops->get_sampling_period(pss_min_period,
pss_max_period,
ioss_min_period,
ioss_max_period);
}
EXPORT_SYMBOL_GPL(telemetry_get_sampling_period);
/**
* telemetry_reset_events() - Restore the IOSS & PSS configuration to default
*
* Return: 0 success, < 0 for failure
*/
int telemetry_reset_events(void)
{
return telm_core_conf.telem_ops->reset_events();
}
EXPORT_SYMBOL_GPL(telemetry_reset_events);
/**
* telemetry_get_eventconfig() - Returns the pss and ioss events enabled
* @pss_evtconfig: Pointer to PSS related configuration.
* @ioss_evtconfig: Pointer to IOSS related configuration.
* @pss_len: Number of u32 elements allocated for pss_evtconfig array
* @ioss_len: Number of u32 elements allocated for ioss_evtconfig array
*
* Return: 0 success, < 0 for failure
*/
int telemetry_get_eventconfig(struct telemetry_evtconfig *pss_evtconfig,
struct telemetry_evtconfig *ioss_evtconfig,
int pss_len, int ioss_len)
{
return telm_core_conf.telem_ops->get_eventconfig(pss_evtconfig,
ioss_evtconfig,
pss_len, ioss_len);
}
EXPORT_SYMBOL_GPL(telemetry_get_eventconfig);
/**
* telemetry_add_events() - Add IOSS & PSS configuration to existing settings.
* @num_pss_evts: Number of PSS Events (<29) in pss_evtmap. Can be 0.
* @num_ioss_evts: Number of IOSS Events (<29) in ioss_evtmap. Can be 0.
* @pss_evtmap: Array of PSS Event-IDs to Enable
* @ioss_evtmap: Array of PSS Event-IDs to Enable
*
* Events are appended to Old Configuration. In case of total events > 28, it
* returns error. Call telemetry_reset_events to reset after eventlog done
*
* Return: 0 success, < 0 for failure
*/
int telemetry_add_events(u8 num_pss_evts, u8 num_ioss_evts,
u32 *pss_evtmap, u32 *ioss_evtmap)
{
return telm_core_conf.telem_ops->add_events(num_pss_evts,
num_ioss_evts, pss_evtmap,
ioss_evtmap);
}
EXPORT_SYMBOL_GPL(telemetry_add_events);
/**
* telemetry_read_events() - Fetches samples as specified by evtlog.telem_evt_id
* @telem_unit: Specify whether IOSS or PSS Read
@ -230,25 +72,6 @@ int telemetry_read_events(enum telemetry_unit telem_unit,
}
EXPORT_SYMBOL_GPL(telemetry_read_events);
/**
* telemetry_raw_read_events() - Fetch samples specified by evtlog.telem_evt_id
* @telem_unit: Specify whether IOSS or PSS Read
* @evtlog: Array of telemetry_evtlog structs to fill data
* evtlog.telem_evt_id specifies the ids to read
* @len: Length of array of evtlog
*
* The caller must take care of locking in this case.
*
* Return: number of eventlogs read for success, < 0 for failure
*/
int telemetry_raw_read_events(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len)
{
return telm_core_conf.telem_ops->raw_read_eventlog(telem_unit, evtlog,
len, 0);
}
EXPORT_SYMBOL_GPL(telemetry_raw_read_events);
/**
* telemetry_read_eventlog() - Fetch the Telemetry log from PSS or IOSS
* @telem_unit: Specify whether IOSS or PSS Read

View File

@ -639,231 +639,6 @@ static int telemetry_setup(struct platform_device *pdev)
return 0;
}
static int telemetry_plt_update_events(struct telemetry_evtconfig pss_evtconfig,
struct telemetry_evtconfig ioss_evtconfig)
{
int ret;
if ((pss_evtconfig.num_evts > 0) &&
(TELEM_SAMPLE_PERIOD_INVALID(pss_evtconfig.period))) {
pr_err("PSS Sampling Period Out of Range\n");
return -EINVAL;
}
if ((ioss_evtconfig.num_evts > 0) &&
(TELEM_SAMPLE_PERIOD_INVALID(ioss_evtconfig.period))) {
pr_err("IOSS Sampling Period Out of Range\n");
return -EINVAL;
}
ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
TELEM_UPDATE);
if (ret)
pr_err("TELEMETRY Config Failed\n");
return ret;
}
static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period)
{
u32 telem_ctrl = 0;
int ret = 0;
mutex_lock(&(telm_conf->telem_lock));
if (ioss_period) {
struct intel_scu_ipc_dev *scu = telm_conf->scu;
if (TELEM_SAMPLE_PERIOD_INVALID(ioss_period)) {
pr_err("IOSS Sampling Period Out of Range\n");
ret = -EINVAL;
goto out;
}
/* Get telemetry EVENT CTL */
ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
IOSS_TELEM_EVENT_CTL_READ, NULL, 0,
&telem_ctrl, sizeof(telem_ctrl));
if (ret) {
pr_err("IOSS TELEM_CTRL Read Failed\n");
goto out;
}
/* Disable Telemetry */
TELEM_DISABLE(telem_ctrl);
ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
IOSS_TELEM_EVENT_CTL_WRITE,
&telem_ctrl, sizeof(telem_ctrl),
NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
goto out;
}
/* Enable Periodic Telemetry Events and enable SRAM trace */
TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
TELEM_ENABLE_PERIODIC(telem_ctrl);
telem_ctrl |= ioss_period;
ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
IOSS_TELEM_EVENT_CTL_WRITE,
&telem_ctrl, sizeof(telem_ctrl),
NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n");
goto out;
}
telm_conf->ioss_config.curr_period = ioss_period;
}
if (pss_period) {
if (TELEM_SAMPLE_PERIOD_INVALID(pss_period)) {
pr_err("PSS Sampling Period Out of Range\n");
ret = -EINVAL;
goto out;
}
/* Get telemetry EVENT CTL */
ret = intel_punit_ipc_command(
IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL,
0, 0, NULL, &telem_ctrl);
if (ret) {
pr_err("PSS TELEM_CTRL Read Failed\n");
goto out;
}
/* Disable Telemetry */
TELEM_DISABLE(telem_ctrl);
ret = intel_punit_ipc_command(
IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
0, 0, &telem_ctrl, NULL);
if (ret) {
pr_err("PSS TELEM_CTRL Event Disable Write Failed\n");
goto out;
}
/* Enable Periodic Telemetry Events and enable SRAM trace */
TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
TELEM_ENABLE_PERIODIC(telem_ctrl);
telem_ctrl |= pss_period;
ret = intel_punit_ipc_command(
IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
0, 0, &telem_ctrl, NULL);
if (ret) {
pr_err("PSS TELEM_CTRL Event Enable Write Failed\n");
goto out;
}
telm_conf->pss_config.curr_period = pss_period;
}
out:
mutex_unlock(&(telm_conf->telem_lock));
return ret;
}
static int telemetry_plt_get_sampling_period(u8 *pss_min_period,
u8 *pss_max_period,
u8 *ioss_min_period,
u8 *ioss_max_period)
{
*pss_min_period = telm_conf->pss_config.min_period;
*pss_max_period = telm_conf->pss_config.max_period;
*ioss_min_period = telm_conf->ioss_config.min_period;
*ioss_max_period = telm_conf->ioss_config.max_period;
return 0;
}
static int telemetry_plt_reset_events(void)
{
struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig;
int ret;
pss_evtconfig.evtmap = NULL;
pss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
pss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
ioss_evtconfig.evtmap = NULL;
ioss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
ioss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
TELEM_RESET);
if (ret)
pr_err("TELEMETRY Reset Failed\n");
return ret;
}
static int telemetry_plt_get_eventconfig(struct telemetry_evtconfig *pss_config,
struct telemetry_evtconfig *ioss_config,
int pss_len, int ioss_len)
{
u32 *pss_evtmap, *ioss_evtmap;
u32 index;
pss_evtmap = pss_config->evtmap;
ioss_evtmap = ioss_config->evtmap;
mutex_lock(&(telm_conf->telem_lock));
pss_config->num_evts = telm_conf->pss_config.ssram_evts_used;
ioss_config->num_evts = telm_conf->ioss_config.ssram_evts_used;
pss_config->period = telm_conf->pss_config.curr_period;
ioss_config->period = telm_conf->ioss_config.curr_period;
if ((pss_len < telm_conf->pss_config.ssram_evts_used) ||
(ioss_len < telm_conf->ioss_config.ssram_evts_used)) {
mutex_unlock(&(telm_conf->telem_lock));
return -EINVAL;
}
for (index = 0; index < telm_conf->pss_config.ssram_evts_used;
index++) {
pss_evtmap[index] =
telm_conf->pss_config.telem_evts[index].evt_id;
}
for (index = 0; index < telm_conf->ioss_config.ssram_evts_used;
index++) {
ioss_evtmap[index] =
telm_conf->ioss_config.telem_evts[index].evt_id;
}
mutex_unlock(&(telm_conf->telem_lock));
return 0;
}
static int telemetry_plt_add_events(u8 num_pss_evts, u8 num_ioss_evts,
u32 *pss_evtmap, u32 *ioss_evtmap)
{
struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig;
int ret;
pss_evtconfig.evtmap = pss_evtmap;
pss_evtconfig.num_evts = num_pss_evts;
pss_evtconfig.period = telm_conf->pss_config.curr_period;
ioss_evtconfig.evtmap = ioss_evtmap;
ioss_evtconfig.num_evts = num_ioss_evts;
ioss_evtconfig.period = telm_conf->ioss_config.curr_period;
ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
TELEM_ADD);
if (ret)
pr_err("TELEMETRY ADD Failed\n");
return ret;
}
static int telem_evtlog_read(enum telemetry_unit telem_unit,
struct telem_ssram_region *ssram_region, u8 len)
{
@ -1093,14 +868,8 @@ out:
static const struct telemetry_core_ops telm_pltops = {
.get_trace_verbosity = telemetry_plt_get_trace_verbosity,
.set_trace_verbosity = telemetry_plt_set_trace_verbosity,
.set_sampling_period = telemetry_plt_set_sampling_period,
.get_sampling_period = telemetry_plt_get_sampling_period,
.raw_read_eventlog = telemetry_plt_raw_read_eventlog,
.get_eventconfig = telemetry_plt_get_eventconfig,
.update_events = telemetry_plt_update_events,
.read_eventlog = telemetry_plt_read_eventlog,
.reset_events = telemetry_plt_reset_events,
.add_events = telemetry_plt_add_events,
};
static int telemetry_pltdrv_probe(struct platform_device *pdev)

View File

@ -22,9 +22,10 @@
#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/intel_tpmi.h>
#include <linux/intel_vsec.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/intel_tpmi.h>
#include "../tpmi_power_domains.h"
#include "uncore-frequency-common.h"
@ -448,7 +449,7 @@ static void remove_cluster_entries(struct tpmi_uncore_struct *tpmi_uncore)
}
static void set_cdie_id(int domain_id, struct tpmi_uncore_cluster_info *cluster_info,
struct intel_tpmi_plat_info *plat_info)
struct oobmsm_plat_info *plat_info)
{
cluster_info->cdie_id = domain_id;
@ -465,7 +466,7 @@ static void set_cdie_id(int domain_id, struct tpmi_uncore_cluster_info *cluster_
static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
{
bool read_blocked = 0, write_blocked = 0;
struct intel_tpmi_plat_info *plat_info;
struct oobmsm_plat_info *plat_info;
struct tpmi_uncore_struct *tpmi_uncore;
bool uncore_sysfs_added = false;
int ret, i, pkg = 0;

View File

@ -15,9 +15,12 @@
#include <linux/auxiliary_bus.h>
#include <linux/bits.h>
#include <linux/bitops.h>
#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/idr.h>
#include <linux/log2.h>
#include <linux/intel_vsec.h>
#include <linux/kernel.h>
#include <linux/module.h>
@ -32,6 +35,20 @@ static DEFINE_IDA(intel_vsec_ida);
static DEFINE_IDA(intel_vsec_sdsi_ida);
static DEFINE_XARRAY_ALLOC(auxdev_array);
enum vsec_device_state {
STATE_NOT_FOUND,
STATE_REGISTERED,
STATE_SKIP,
};
struct vsec_priv {
struct intel_vsec_platform_info *info;
struct device *suppliers[VSEC_FEATURE_COUNT];
struct oobmsm_plat_info plat_info;
enum vsec_device_state state[VSEC_FEATURE_COUNT];
unsigned long found_caps;
};
static const char *intel_vsec_name(enum intel_vsec_id id)
{
switch (id) {
@ -50,6 +67,9 @@ static const char *intel_vsec_name(enum intel_vsec_id id)
case VSEC_ID_TPMI:
return "tpmi";
case VSEC_ID_DISCOVERY:
return "discovery";
default:
return NULL;
}
@ -68,6 +88,8 @@ static bool intel_vsec_supported(u16 id, unsigned long caps)
return !!(caps & VSEC_CAP_SDSI);
case VSEC_ID_TPMI:
return !!(caps & VSEC_CAP_TPMI);
case VSEC_ID_DISCOVERY:
return !!(caps & VSEC_CAP_DISCOVERY);
default:
return false;
}
@ -91,6 +113,97 @@ static void intel_vsec_dev_release(struct device *dev)
kfree(intel_vsec_dev);
}
static const struct vsec_feature_dependency *
get_consumer_dependencies(struct vsec_priv *priv, int cap_id)
{
const struct vsec_feature_dependency *deps = priv->info->deps;
int consumer_id = priv->info->num_deps;
if (!deps)
return NULL;
while (consumer_id--)
if (deps[consumer_id].feature == BIT(cap_id))
return &deps[consumer_id];
return NULL;
}
static bool vsec_driver_present(int cap_id)
{
unsigned long bit = BIT(cap_id);
switch (bit) {
case VSEC_CAP_TELEMETRY:
return IS_ENABLED(CONFIG_INTEL_PMT_TELEMETRY);
case VSEC_CAP_WATCHER:
return IS_ENABLED(CONFIG_INTEL_PMT_WATCHER);
case VSEC_CAP_CRASHLOG:
return IS_ENABLED(CONFIG_INTEL_PMT_CRASHLOG);
case VSEC_CAP_SDSI:
return IS_ENABLED(CONFIG_INTEL_SDSI);
case VSEC_CAP_TPMI:
return IS_ENABLED(CONFIG_INTEL_TPMI);
case VSEC_CAP_DISCOVERY:
return IS_ENABLED(CONFIG_INTEL_PMT_DISCOVERY);
default:
return false;
}
}
/*
* Although pci_device_id table is available in the pdev, this prototype is
* necessary because the code using it can be called by an exported API that
* might pass a different pdev.
*/
static const struct pci_device_id intel_vsec_pci_ids[];
static int intel_vsec_link_devices(struct pci_dev *pdev, struct device *dev,
int consumer_id)
{
const struct vsec_feature_dependency *deps;
enum vsec_device_state *state;
struct device **suppliers;
struct vsec_priv *priv;
int supplier_id;
if (!consumer_id)
return 0;
if (!pci_match_id(intel_vsec_pci_ids, pdev))
return 0;
priv = pci_get_drvdata(pdev);
state = priv->state;
suppliers = priv->suppliers;
priv->suppliers[consumer_id] = dev;
deps = get_consumer_dependencies(priv, consumer_id);
if (!deps)
return 0;
for_each_set_bit(supplier_id, &deps->supplier_bitmap, VSEC_FEATURE_COUNT) {
struct device_link *link;
if (state[supplier_id] != STATE_REGISTERED ||
!vsec_driver_present(supplier_id))
continue;
if (!suppliers[supplier_id]) {
dev_err(dev, "Bad supplier list\n");
return -EINVAL;
}
link = device_link_add(dev, suppliers[supplier_id],
DL_FLAG_AUTOPROBE_CONSUMER);
if (!link)
return -EINVAL;
}
return 0;
}
int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent,
struct intel_vsec_device *intel_vsec_dev,
const char *name)
@ -128,19 +241,37 @@ int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent,
return ret;
}
/*
* Assign a name now to ensure that the device link doesn't contain
* a null string for the consumer name. This is a problem when a supplier
* supplies more than one consumer and can lead to a duplicate name error
* when the link is created in sysfs.
*/
ret = dev_set_name(&auxdev->dev, "%s.%s.%d", KBUILD_MODNAME, auxdev->name,
auxdev->id);
if (ret)
goto cleanup_aux;
ret = intel_vsec_link_devices(pdev, &auxdev->dev, intel_vsec_dev->cap_id);
if (ret)
goto cleanup_aux;
ret = auxiliary_device_add(auxdev);
if (ret < 0) {
auxiliary_device_uninit(auxdev);
return ret;
}
if (ret)
goto cleanup_aux;
return devm_add_action_or_reset(parent, intel_vsec_remove_aux,
auxdev);
cleanup_aux:
auxiliary_device_uninit(auxdev);
return ret;
}
EXPORT_SYMBOL_NS_GPL(intel_vsec_add_aux, "INTEL_VSEC");
static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *header,
struct intel_vsec_platform_info *info)
struct intel_vsec_platform_info *info,
unsigned long cap_id)
{
struct intel_vsec_device __free(kfree) *intel_vsec_dev = NULL;
struct resource __free(kfree) *res = NULL;
@ -207,6 +338,7 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he
intel_vsec_dev->quirks = info->quirks;
intel_vsec_dev->base_addr = info->base_addr;
intel_vsec_dev->priv_data = info->priv_data;
intel_vsec_dev->cap_id = cap_id;
if (header->id == VSEC_ID_SDSI)
intel_vsec_dev->ida = &intel_vsec_sdsi_ida;
@ -221,6 +353,109 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he
intel_vsec_name(header->id));
}
static bool suppliers_ready(struct vsec_priv *priv,
const struct vsec_feature_dependency *consumer_deps,
int cap_id)
{
enum vsec_device_state *state = priv->state;
int supplier_id;
if (WARN_ON_ONCE(consumer_deps->feature != BIT(cap_id)))
return false;
/*
* Verify that all required suppliers have been found. Return false
* immediately if any are still missing.
*/
for_each_set_bit(supplier_id, &consumer_deps->supplier_bitmap, VSEC_FEATURE_COUNT) {
if (state[supplier_id] == STATE_SKIP)
continue;
if (state[supplier_id] == STATE_NOT_FOUND)
return false;
}
/*
* All suppliers have been found and the consumer is ready to be
* registered.
*/
return true;
}
static int get_cap_id(u32 header_id, unsigned long *cap_id)
{
switch (header_id) {
case VSEC_ID_TELEMETRY:
*cap_id = ilog2(VSEC_CAP_TELEMETRY);
break;
case VSEC_ID_WATCHER:
*cap_id = ilog2(VSEC_CAP_WATCHER);
break;
case VSEC_ID_CRASHLOG:
*cap_id = ilog2(VSEC_CAP_CRASHLOG);
break;
case VSEC_ID_SDSI:
*cap_id = ilog2(VSEC_CAP_SDSI);
break;
case VSEC_ID_TPMI:
*cap_id = ilog2(VSEC_CAP_TPMI);
break;
case VSEC_ID_DISCOVERY:
*cap_id = ilog2(VSEC_CAP_DISCOVERY);
break;
default:
return -EINVAL;
}
return 0;
}
static int intel_vsec_register_device(struct pci_dev *pdev,
struct intel_vsec_header *header,
struct intel_vsec_platform_info *info)
{
const struct vsec_feature_dependency *consumer_deps;
struct vsec_priv *priv;
unsigned long cap_id;
int ret;
ret = get_cap_id(header->id, &cap_id);
if (ret)
return ret;
/*
* Only track dependencies for devices probed by the VSEC driver.
* For others using the exported APIs, add the device directly.
*/
if (!pci_match_id(intel_vsec_pci_ids, pdev))
return intel_vsec_add_dev(pdev, header, info, cap_id);
priv = pci_get_drvdata(pdev);
if (priv->state[cap_id] == STATE_REGISTERED ||
priv->state[cap_id] == STATE_SKIP)
return -EEXIST;
priv->found_caps |= BIT(cap_id);
if (!vsec_driver_present(cap_id)) {
priv->state[cap_id] = STATE_SKIP;
return -ENODEV;
}
consumer_deps = get_consumer_dependencies(priv, cap_id);
if (!consumer_deps || suppliers_ready(priv, consumer_deps, cap_id)) {
ret = intel_vsec_add_dev(pdev, header, info, cap_id);
if (ret)
priv->state[cap_id] = STATE_SKIP;
else
priv->state[cap_id] = STATE_REGISTERED;
return ret;
}
return -EAGAIN;
}
static bool intel_vsec_walk_header(struct pci_dev *pdev,
struct intel_vsec_platform_info *info)
{
@ -229,7 +464,7 @@ static bool intel_vsec_walk_header(struct pci_dev *pdev,
int ret;
for ( ; *header; header++) {
ret = intel_vsec_add_dev(pdev, *header, info);
ret = intel_vsec_register_device(pdev, *header, info);
if (!ret)
have_devices = true;
}
@ -277,7 +512,7 @@ static bool intel_vsec_walk_dvsec(struct pci_dev *pdev,
pci_read_config_dword(pdev, pos + PCI_DVSEC_HEADER2, &hdr);
header.id = PCI_DVSEC_HEADER2_ID(hdr);
ret = intel_vsec_add_dev(pdev, &header, info);
ret = intel_vsec_register_device(pdev, &header, info);
if (ret)
continue;
@ -322,7 +557,7 @@ static bool intel_vsec_walk_vsec(struct pci_dev *pdev,
header.tbir = INTEL_DVSEC_TABLE_BAR(table);
header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
ret = intel_vsec_add_dev(pdev, &header, info);
ret = intel_vsec_register_device(pdev, &header, info);
if (ret)
continue;
@ -345,11 +580,56 @@ int intel_vsec_register(struct pci_dev *pdev,
}
EXPORT_SYMBOL_NS_GPL(intel_vsec_register, "INTEL_VSEC");
static bool intel_vsec_get_features(struct pci_dev *pdev,
struct intel_vsec_platform_info *info)
{
bool found = false;
/*
* Both DVSEC and VSEC capabilities can exist on the same device,
* so both intel_vsec_walk_dvsec() and intel_vsec_walk_vsec() must be
* called independently. Additionally, intel_vsec_walk_header() is
* needed for devices that do not have VSEC/DVSEC but provide the
* information via device_data.
*/
if (intel_vsec_walk_dvsec(pdev, info))
found = true;
if (intel_vsec_walk_vsec(pdev, info))
found = true;
if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) &&
intel_vsec_walk_header(pdev, info))
found = true;
return found;
}
static void intel_vsec_skip_missing_dependencies(struct pci_dev *pdev)
{
struct vsec_priv *priv = pci_get_drvdata(pdev);
const struct vsec_feature_dependency *deps = priv->info->deps;
int consumer_id = priv->info->num_deps;
while (consumer_id--) {
int supplier_id;
deps = &priv->info->deps[consumer_id];
for_each_set_bit(supplier_id, &deps->supplier_bitmap, VSEC_FEATURE_COUNT) {
if (!(BIT(supplier_id) & priv->found_caps))
priv->state[supplier_id] = STATE_SKIP;
}
}
}
static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct intel_vsec_platform_info *info;
bool have_devices = false;
int ret;
struct vsec_priv *priv;
int num_caps, ret;
int run_once = 0;
bool found_any = false;
ret = pcim_enable_device(pdev);
if (ret)
@ -360,22 +640,62 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id
if (!info)
return -EINVAL;
if (intel_vsec_walk_dvsec(pdev, info))
have_devices = true;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
if (intel_vsec_walk_vsec(pdev, info))
have_devices = true;
priv->info = info;
pci_set_drvdata(pdev, priv);
if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) &&
intel_vsec_walk_header(pdev, info))
have_devices = true;
num_caps = hweight_long(info->caps);
while (num_caps--) {
found_any |= intel_vsec_get_features(pdev, info);
if (!have_devices)
if (priv->found_caps == info->caps)
break;
if (!run_once) {
intel_vsec_skip_missing_dependencies(pdev);
run_once = 1;
}
}
if (!found_any)
return -ENODEV;
return 0;
}
int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info,
struct intel_vsec_device *vsec_dev)
{
struct vsec_priv *priv;
priv = pci_get_drvdata(vsec_dev->pcidev);
if (!priv)
return -EINVAL;
priv->plat_info = *plat_info;
return 0;
}
EXPORT_SYMBOL_NS_GPL(intel_vsec_set_mapping, "INTEL_VSEC");
struct oobmsm_plat_info *intel_vsec_get_mapping(struct pci_dev *pdev)
{
struct vsec_priv *priv;
if (!pci_match_id(intel_vsec_pci_ids, pdev))
return ERR_PTR(-EINVAL);
priv = pci_get_drvdata(pdev);
if (!priv)
return ERR_PTR(-EINVAL);
return &priv->plat_info;
}
EXPORT_SYMBOL_NS_GPL(intel_vsec_get_mapping, "INTEL_VSEC");
/* DG1 info */
static struct intel_vsec_header dg1_header = {
.length = 0x10,
@ -402,14 +722,26 @@ static const struct intel_vsec_platform_info mtl_info = {
.caps = VSEC_CAP_TELEMETRY,
};
static const struct vsec_feature_dependency oobmsm_deps[] = {
{
.feature = VSEC_CAP_TELEMETRY,
.supplier_bitmap = VSEC_CAP_DISCOVERY | VSEC_CAP_TPMI,
},
};
/* OOBMSM info */
static const struct intel_vsec_platform_info oobmsm_info = {
.caps = VSEC_CAP_TELEMETRY | VSEC_CAP_SDSI | VSEC_CAP_TPMI,
.caps = VSEC_CAP_TELEMETRY | VSEC_CAP_SDSI | VSEC_CAP_TPMI |
VSEC_CAP_DISCOVERY,
.deps = oobmsm_deps,
.num_deps = ARRAY_SIZE(oobmsm_deps),
};
/* DMR OOBMSM info */
static const struct intel_vsec_platform_info dmr_oobmsm_info = {
.caps = VSEC_CAP_TELEMETRY | VSEC_CAP_TPMI,
.caps = VSEC_CAP_TELEMETRY | VSEC_CAP_TPMI | VSEC_CAP_DISCOVERY,
.deps = oobmsm_deps,
.num_deps = ARRAY_SIZE(oobmsm_deps),
};
/* TGL info */

View File

@ -116,7 +116,7 @@ struct intel_tpmi_info {
struct intel_vsec_device *vsec_dev;
int feature_count;
u64 pfs_start;
struct intel_tpmi_plat_info plat_info;
struct oobmsm_plat_info plat_info;
void __iomem *tpmi_control_mem;
struct dentry *dbgfs_dir;
};
@ -187,7 +187,7 @@ struct tpmi_feature_state {
/* Used during auxbus device creation */
static DEFINE_IDA(intel_vsec_tpmi_ida);
struct intel_tpmi_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev)
struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev)
{
struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
@ -799,6 +799,10 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
ret = tpmi_process_info(tpmi_info, pfs);
if (ret)
return ret;
ret = intel_vsec_set_mapping(&tpmi_info->plat_info, vsec_dev);
if (ret)
return ret;
}
if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID)

View File

@ -0,0 +1,276 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Lenovo X86 Platform Specific Drivers
#
config IDEAPAD_LAPTOP
tristate "Lenovo IdeaPad Laptop Extras"
depends on ACPI
depends on ACPI_BATTERY
depends on RFKILL && INPUT
depends on SERIO_I8042
depends on BACKLIGHT_CLASS_DEVICE
depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on ACPI_WMI || ACPI_WMI = n
select ACPI_PLATFORM_PROFILE
select INPUT_SPARSEKMAP
select NEW_LEDS
select LEDS_CLASS
help
This is a driver for Lenovo IdeaPad netbooks contains drivers for
rfkill switch, hotkey, fan control and backlight control.
config LENOVO_WMI_HOTKEY_UTILITIES
tristate "Lenovo Hotkey Utility WMI extras driver"
depends on ACPI_WMI
select NEW_LEDS
select LEDS_CLASS
imply IDEAPAD_LAPTOP
help
This driver provides WMI support for Lenovo customized hotkeys function,
such as LED control for audio/mic mute event for Ideapad, YOGA, XiaoXin,
Gaming, ThinkBook and so on.
config LENOVO_WMI_CAMERA
tristate "Lenovo WMI Camera Button driver"
depends on ACPI_WMI
depends on INPUT
help
This driver provides support for Lenovo camera button. The Camera
button is a GPIO device. This driver receives ACPI notifications when
the camera button is switched on/off.
To compile this driver as a module, choose M here: the module
will be called lenovo-wmi-camera.
config LENOVO_YMC
tristate "Lenovo Yoga Tablet Mode Control"
depends on ACPI_WMI
depends on INPUT
depends on IDEAPAD_LAPTOP
select INPUT_SPARSEKMAP
help
This driver maps the Tablet Mode Control switch to SW_TABLET_MODE input
events for Lenovo Yoga notebooks.
config THINKPAD_ACPI
tristate "ThinkPad ACPI Laptop Extras"
depends on ACPI_EC
depends on ACPI_BATTERY
depends on INPUT
depends on RFKILL || RFKILL = n
depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on BACKLIGHT_CLASS_DEVICE
depends on I2C
depends on DRM
select ACPI_PLATFORM_PROFILE
select DRM_PRIVACY_SCREEN
select HWMON
select NVRAM
select NEW_LEDS
select LEDS_CLASS
select INPUT_SPARSEKMAP
help
This is a driver for the IBM and Lenovo ThinkPad laptops. It adds
support for Fn-Fx key combinations, Bluetooth control, video
output switching, ThinkLight control, UltraBay eject and more.
For more information about this driver see
<file:Documentation/admin-guide/laptops/thinkpad-acpi.rst> and
<http://ibm-acpi.sf.net/> .
This driver was formerly known as ibm-acpi.
Extra functionality will be available if the rfkill (CONFIG_RFKILL)
and/or ALSA (CONFIG_SND) subsystems are available in the kernel.
Note that if you want ThinkPad-ACPI to be built-in instead of
modular, ALSA and rfkill will also have to be built-in.
If you have an IBM or Lenovo ThinkPad laptop, say Y or M here.
config THINKPAD_ACPI_ALSA_SUPPORT
bool "Console audio control ALSA interface"
depends on THINKPAD_ACPI
depends on SND
depends on SND = y || THINKPAD_ACPI = SND
default y
help
Enables monitoring of the built-in console audio output control
(headphone and speakers), which is operated by the mute and (in
some ThinkPad models) volume hotkeys.
If this option is enabled, ThinkPad-ACPI will export an ALSA card
with a single read-only mixer control, which should be used for
on-screen-display feedback purposes by the Desktop Environment.
Optionally, the driver will also allow software control (the
ALSA mixer will be made read-write). Please refer to the driver
documentation for details.
All IBM models have both volume and mute control. Newer Lenovo
models only have mute control (the volume hotkeys are just normal
keys and volume control is done through the main HDA mixer).
config THINKPAD_ACPI_DEBUGFACILITIES
bool "Maintainer debug facilities"
depends on THINKPAD_ACPI
help
Enables extra stuff in the thinkpad-acpi which is completely useless
for normal use. Read the driver source to find out what it does.
Say N here, unless you were told by a kernel maintainer to do
otherwise.
config THINKPAD_ACPI_DEBUG
bool "Verbose debug mode"
depends on THINKPAD_ACPI
help
Enables extra debugging information, at the expense of a slightly
increase in driver size.
If you are not sure, say N here.
config THINKPAD_ACPI_UNSAFE_LEDS
bool "Allow control of important LEDs (unsafe)"
depends on THINKPAD_ACPI
help
Overriding LED state on ThinkPads can mask important
firmware alerts (like critical battery condition), or misled
the user into damaging the hardware (undocking or ejecting
the bay while buses are still active), etc.
LED control on the ThinkPad is write-only (with very few
exceptions on very ancient models), which makes it
impossible to know beforehand if important information will
be lost when one changes LED state.
Users that know what they are doing can enable this option
and the driver will allow control of every LED, including
the ones on the dock stations.
Never enable this option on a distribution kernel.
Say N here, unless you are building a kernel for your own
use, and need to control the important firmware LEDs.
config THINKPAD_ACPI_VIDEO
bool "Video output control support"
depends on THINKPAD_ACPI
default y
help
Allows the thinkpad_acpi driver to provide an interface to control
the various video output ports.
This feature often won't work well, depending on ThinkPad model,
display state, video output devices in use, whether there is a X
server running, phase of the moon, and the current mood of
Schroedinger's cat. If you can use X.org's RandR to control
your ThinkPad's video output ports instead of this feature,
don't think twice: do it and say N here to save memory and avoid
bad interactions with X.org.
NOTE: access to this feature is limited to processes with the
CAP_SYS_ADMIN capability, to avoid local DoS issues in platforms
where it interacts badly with X.org.
If you are not sure, say Y here but do try to check if you could
be using X.org RandR instead.
config THINKPAD_ACPI_HOTKEY_POLL
bool "Support NVRAM polling for hot keys"
depends on THINKPAD_ACPI
default y
help
Some thinkpad models benefit from NVRAM polling to detect a few of
the hot key press events. If you know your ThinkPad model does not
need to do NVRAM polling to support any of the hot keys you use,
unselecting this option will save about 1kB of memory.
ThinkPads T40 and newer, R52 and newer, and X31 and newer are
unlikely to need NVRAM polling in their latest BIOS versions.
NVRAM polling can detect at most the following keys: ThinkPad/Access
IBM, Zoom, Switch Display (fn+F7), ThinkLight, Volume up/down/mute,
Brightness up/down, Display Expand (fn+F8), Hibernate (fn+F12).
If you are not sure, say Y here. The driver enables polling only if
it is strictly necessary to do so.
config THINKPAD_LMI
tristate "Lenovo WMI-based systems management driver"
depends on ACPI_WMI
depends on DMI
select FW_ATTR_CLASS
help
This driver allows changing BIOS settings on Lenovo machines whose
BIOS support the WMI interface.
To compile this driver as a module, choose M here: the module will
be called think-lmi.
config YOGABOOK
tristate "Lenovo Yoga Book tablet key driver"
depends on ACPI_WMI
depends on INPUT
depends on I2C
select LEDS_CLASS
select NEW_LEDS
help
Say Y here if you want to support the 'Pen' key and keyboard backlight
control on the Lenovo Yoga Book tablets.
To compile this driver as a module, choose M here: the module will
be called lenovo-yogabook.
config YT2_1380
tristate "Lenovo Yoga Tablet 2 1380 fast charge driver"
depends on SERIAL_DEV_BUS
depends on EXTCON
depends on ACPI
help
Say Y here to enable support for the custom fast charging protocol
found on the Lenovo Yoga Tablet 2 1380F / 1380L models.
To compile this driver as a module, choose M here: the module will
be called lenovo-yogabook.
config LENOVO_WMI_DATA01
tristate
depends on ACPI_WMI
config LENOVO_WMI_EVENTS
tristate
depends on ACPI_WMI
config LENOVO_WMI_HELPERS
tristate
depends on ACPI_WMI
config LENOVO_WMI_GAMEZONE
tristate "Lenovo GameZone WMI Driver"
depends on ACPI_WMI
depends on DMI
select ACPI_PLATFORM_PROFILE
select LENOVO_WMI_EVENTS
select LENOVO_WMI_HELPERS
select LENOVO_WMI_TUNING
help
Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
platform-profile firmware interface to manage power usage.
To compile this driver as a module, choose M here: the module will
be called lenovo-wmi-gamezone.
config LENOVO_WMI_TUNING
tristate "Lenovo Other Mode WMI Driver"
depends on ACPI_WMI
select FW_ATTR_CLASS
select LENOVO_WMI_DATA01
select LENOVO_WMI_EVENTS
select LENOVO_WMI_HELPERS
help
Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
firmware_attributes API to control various tunable settings typically exposed by
Lenovo software in Windows.
To compile this driver as a module, choose M here: the module will
be called lenovo-wmi-other.

View File

@ -0,0 +1,28 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for linux/drivers/platform/x86/lenovo
# Lenovo x86 Platform Specific Drivers
#
obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
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_EVENTS) += wmi-events.o
lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o
lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += wmi-gamezone.o
lenovo-target-$(CONFIG_LENOVO_WMI_TUNING) += wmi-other.o
# Add 'lenovo' prefix to each module listed in lenovo-target-*
define LENOVO_OBJ_TARGET
lenovo-$(1)-y := $(1).o
obj-$(2) += lenovo-$(1).o
endef
$(foreach target, $(basename $(lenovo-target-y)), $(eval $(call LENOVO_OBJ_TARGET,$(target),y)))
$(foreach target, $(basename $(lenovo-target-m)), $(eval $(call LENOVO_OBJ_TARGET,$(target),m)))

View File

@ -28,6 +28,7 @@
#include <linux/module.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/sysfs.h>
@ -35,6 +36,7 @@
#include <linux/wmi.h>
#include "ideapad-laptop.h"
#include <acpi/battery.h>
#include <acpi/video.h>
#include <dt-bindings/leds/common.h>
@ -163,6 +165,7 @@ struct ideapad_private {
struct backlight_device *blightdev;
struct ideapad_dytc_priv *dytc;
struct dentry *debug;
struct acpi_battery_hook battery_hook;
unsigned long cfg;
unsigned long r_touchpad_val;
struct {
@ -604,6 +607,11 @@ static ssize_t camera_power_store(struct device *dev,
static DEVICE_ATTR_RW(camera_power);
static void show_conservation_mode_deprecation_warning(struct device *dev)
{
dev_warn_once(dev, "conservation_mode attribute has been deprecated, see charge_types.\n");
}
static ssize_t conservation_mode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
@ -612,6 +620,8 @@ static ssize_t conservation_mode_show(struct device *dev,
unsigned long result;
int err;
show_conservation_mode_deprecation_warning(dev);
err = eval_gbmd(priv->adev->handle, &result);
if (err)
return err;
@ -627,6 +637,8 @@ static ssize_t conservation_mode_store(struct device *dev,
bool state;
int err;
show_conservation_mode_deprecation_warning(dev);
err = kstrtobool(buf, &state);
if (err)
return err;
@ -1988,10 +2000,90 @@ static const struct dmi_system_id ctrl_ps2_aux_port_list[] = {
{}
};
static void ideapad_check_features(struct ideapad_private *priv)
static int ideapad_psy_ext_set_prop(struct power_supply *psy,
const struct power_supply_ext *ext,
void *ext_data,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct ideapad_private *priv = ext_data;
switch (val->intval) {
case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
return exec_sbmc(priv->adev->handle, SBMC_CONSERVATION_ON);
case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
return exec_sbmc(priv->adev->handle, SBMC_CONSERVATION_OFF);
default:
return -EINVAL;
}
}
static int ideapad_psy_ext_get_prop(struct power_supply *psy,
const struct power_supply_ext *ext,
void *ext_data,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct ideapad_private *priv = ext_data;
unsigned long result;
int err;
err = eval_gbmd(priv->adev->handle, &result);
if (err)
return err;
if (test_bit(GBMD_CONSERVATION_STATE_BIT, &result))
val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
else
val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
return 0;
}
static int ideapad_psy_prop_is_writeable(struct power_supply *psy,
const struct power_supply_ext *ext,
void *data,
enum power_supply_property psp)
{
return true;
}
static const enum power_supply_property ideapad_power_supply_props[] = {
POWER_SUPPLY_PROP_CHARGE_TYPES,
};
static const struct power_supply_ext ideapad_battery_ext = {
.name = "ideapad_laptop",
.properties = ideapad_power_supply_props,
.num_properties = ARRAY_SIZE(ideapad_power_supply_props),
.charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
.get_property = ideapad_psy_ext_get_prop,
.set_property = ideapad_psy_ext_set_prop,
.property_is_writeable = ideapad_psy_prop_is_writeable,
};
static int ideapad_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
{
struct ideapad_private *priv = container_of(hook, struct ideapad_private, battery_hook);
return power_supply_register_extension(battery, &ideapad_battery_ext,
&priv->platform_device->dev, priv);
}
static int ideapad_battery_remove(struct power_supply *battery,
struct acpi_battery_hook *hook)
{
power_supply_unregister_extension(battery, &ideapad_battery_ext);
return 0;
}
static int ideapad_check_features(struct ideapad_private *priv)
{
acpi_handle handle = priv->adev->handle;
unsigned long val;
int err;
priv->features.set_fn_lock_led =
set_fn_lock_led || dmi_check_system(set_fn_lock_led_list);
@ -2006,8 +2098,16 @@ static void ideapad_check_features(struct ideapad_private *priv)
if (!read_ec_data(handle, VPCCMD_R_FAN, &val))
priv->features.fan_mode = true;
if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC"))
if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
priv->features.conservation_mode = true;
priv->battery_hook.add_battery = ideapad_battery_add;
priv->battery_hook.remove_battery = ideapad_battery_remove;
priv->battery_hook.name = "Ideapad Battery Extension";
err = devm_battery_hook_register(&priv->platform_device->dev, &priv->battery_hook);
if (err)
return err;
}
if (acpi_has_method(handle, "DYTC"))
priv->features.dytc = true;
@ -2042,6 +2142,8 @@ static void ideapad_check_features(struct ideapad_private *priv)
}
}
}
return 0;
}
#if IS_ENABLED(CONFIG_ACPI_WMI)
@ -2190,7 +2292,9 @@ static int ideapad_acpi_add(struct platform_device *pdev)
if (err)
return err;
ideapad_check_features(priv);
err = ideapad_check_features(priv);
if (err)
return err;
ideapad_debugfs_init(priv);

View File

@ -20,7 +20,7 @@
#include <linux/types.h>
#include <linux/dmi.h>
#include <linux/wmi.h>
#include "firmware_attributes_class.h"
#include "../firmware_attributes_class.h"
#include "think-lmi.h"
static bool debug_support;
@ -772,6 +772,7 @@ static ssize_t certificate_store(struct kobject *kobj,
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
enum cert_install_mode install_mode = TLMI_CERT_INSTALL;
char *auth_str, *new_cert;
const char *serial;
char *signature;
char *guid;
int ret;
@ -789,9 +790,10 @@ static ssize_t certificate_store(struct kobject *kobj,
return -EACCES;
/* Format: 'serial#, signature' */
auth_str = cert_command(setting,
dmi_get_system_info(DMI_PRODUCT_SERIAL),
setting->signature);
serial = dmi_get_system_info(DMI_PRODUCT_SERIAL);
if (!serial)
return -ENODEV;
auth_str = cert_command(setting, serial, setting->signature);
if (!auth_str)
return -ENOMEM;

View File

@ -81,7 +81,7 @@
#include <sound/core.h>
#include <sound/initval.h>
#include "dual_accel_detect.h"
#include "../dual_accel_detect.h"
/* ThinkPad CMOS commands */
#define TP_CMOS_VOLUME_DOWN 0
@ -559,12 +559,12 @@ static unsigned long __init tpacpi_check_quirks(
return 0;
}
static inline bool __pure __init tpacpi_is_lenovo(void)
static __always_inline bool __pure __init tpacpi_is_lenovo(void)
{
return thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO;
}
static inline bool __pure __init tpacpi_is_ibm(void)
static __always_inline bool __pure __init tpacpi_is_ibm(void)
{
return thinkpad_id.vendor == PCI_VENDOR_ID_IBM;
}

View File

@ -0,0 +1,302 @@
// 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");

View File

@ -0,0 +1,25 @@
/* 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_ */

View File

@ -0,0 +1,196 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Lenovo WMI Events driver. Lenovo WMI interfaces provide various
* hardware triggered events that many drivers need to have propagated.
* This driver provides a uniform entrypoint for these events so that
* any driver that needs to respond to these events can subscribe to a
* notifier chain.
*
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
*/
#include <linux/acpi.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include "wmi-events.h"
#include "wmi-gamezone.h"
#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
#define LWMI_EVENT_DEVICE(guid, type) \
.guid_string = (guid), .context = &(enum lwmi_events_type) \
{ \
type \
}
static BLOCKING_NOTIFIER_HEAD(events_chain_head);
struct lwmi_events_priv {
struct wmi_device *wdev;
enum lwmi_events_type type;
};
/**
* lwmi_events_register_notifier() - Add a notifier to the notifier chain.
* @nb: The notifier_block struct to register
*
* Call blocking_notifier_chain_register to register the notifier block to the
* lenovo-wmi-events driver blocking notifier chain.
*
* Return: 0 on success, %-EEXIST on error.
*/
int lwmi_events_register_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&events_chain_head, nb);
}
EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
/**
* lwmi_events_unregister_notifier() - Remove a notifier from the notifier
* chain.
* @nb: The notifier_block struct to unregister
*
* Call blocking_notifier_chain_unregister to unregister the notifier block
* from the lenovo-wmi-events driver blocking notifier chain.
*
* Return: 0 on success, %-ENOENT on error.
*/
int lwmi_events_unregister_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&events_chain_head, nb);
}
EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS");
/**
* devm_lwmi_events_unregister_notifier() - Remove a notifier from the notifier
* chain.
* @data: Void pointer to the notifier_block struct to unregister.
*
* Call lwmi_events_unregister_notifier to unregister the notifier block from
* the lenovo-wmi-events driver blocking notifier chain.
*
* Return: 0 on success, %-ENOENT on error.
*/
static void devm_lwmi_events_unregister_notifier(void *data)
{
struct notifier_block *nb = data;
lwmi_events_unregister_notifier(nb);
}
/**
* devm_lwmi_events_register_notifier() - Add a notifier to the notifier chain.
* @dev: The parent device of the notifier_block struct.
* @nb: The notifier_block struct to register
*
* Call lwmi_events_register_notifier to register the notifier block to the
* lenovo-wmi-events driver blocking notifier chain. Then add, as a device
* managed action, unregister_notifier to automatically unregister the
* notifier block upon its parent device removal.
*
* Return: 0 on success, or an error code.
*/
int devm_lwmi_events_register_notifier(struct device *dev,
struct notifier_block *nb)
{
int ret;
ret = lwmi_events_register_notifier(nb);
if (ret < 0)
return ret;
return devm_add_action_or_reset(dev, devm_lwmi_events_unregister_notifier, nb);
}
EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
/**
* lwmi_events_notify() - Call functions for the notifier call chain.
* @wdev: The parent WMI device of the driver.
* @obj: ACPI object passed by the registered WMI Event.
*
* Validate WMI event data and notify all registered drivers of the event and
* its output.
*
* Return: 0 on success, or an error code.
*/
static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj)
{
struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev);
int sel_prof;
int ret;
switch (priv->type) {
case LWMI_EVENT_THERMAL_MODE:
if (obj->type != ACPI_TYPE_INTEGER)
return;
sel_prof = obj->integer.value;
switch (sel_prof) {
case LWMI_GZ_THERMAL_MODE_QUIET:
case LWMI_GZ_THERMAL_MODE_BALANCED:
case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
case LWMI_GZ_THERMAL_MODE_EXTREME:
case LWMI_GZ_THERMAL_MODE_CUSTOM:
ret = blocking_notifier_call_chain(&events_chain_head,
LWMI_EVENT_THERMAL_MODE,
&sel_prof);
if (ret == NOTIFY_BAD)
dev_err(&wdev->dev,
"Failed to send notification to call chain for WMI Events\n");
return;
default:
dev_err(&wdev->dev, "Got invalid thermal mode: %x",
sel_prof);
return;
}
break;
default:
return;
}
}
static int lwmi_events_probe(struct wmi_device *wdev, const void *context)
{
struct lwmi_events_priv *priv;
if (!context)
return -EINVAL;
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->wdev = wdev;
priv->type = *(enum lwmi_events_type *)context;
dev_set_drvdata(&wdev->dev, priv);
return 0;
}
static const struct wmi_device_id lwmi_events_id_table[] = {
{ LWMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, LWMI_EVENT_THERMAL_MODE) },
{}
};
static struct wmi_driver lwmi_events_driver = {
.driver = {
.name = "lenovo_wmi_events",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.id_table = lwmi_events_id_table,
.probe = lwmi_events_probe,
.notify = lwmi_events_notify,
.no_singleton = true,
};
module_wmi_driver(lwmi_events_driver);
MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
MODULE_DESCRIPTION("Lenovo WMI Events Driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
#ifndef _LENOVO_WMI_EVENTS_H_
#define _LENOVO_WMI_EVENTS_H_
struct device;
struct notifier_block;
enum lwmi_events_type {
LWMI_EVENT_THERMAL_MODE = 1,
};
int lwmi_events_register_notifier(struct notifier_block *nb);
int lwmi_events_unregister_notifier(struct notifier_block *nb);
int devm_lwmi_events_register_notifier(struct device *dev,
struct notifier_block *nb);
#endif /* !_LENOVO_WMI_EVENTS_H_ */

View File

@ -0,0 +1,407 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Lenovo GameZone WMI interface driver.
*
* The GameZone WMI interface provides platform profile and fan curve settings
* for devices that fall under the "Gaming Series" of Lenovo Legion devices.
*
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
*/
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/export.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_profile.h>
#include <linux/spinlock.h>
#include <linux/spinlock_types.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include "wmi-events.h"
#include "wmi-gamezone.h"
#include "wmi-helpers.h"
#include "wmi-other.h"
#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
#define LWMI_GZ_METHOD_ID_SMARTFAN_SUP 43
#define LWMI_GZ_METHOD_ID_SMARTFAN_SET 44
#define LWMI_GZ_METHOD_ID_SMARTFAN_GET 45
static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
struct lwmi_gz_priv {
enum thermal_mode current_mode;
struct notifier_block event_nb;
struct notifier_block mode_nb;
spinlock_t gz_mode_lock; /* current_mode lock */
struct wmi_device *wdev;
int extreme_supported;
struct device *ppdev;
};
struct quirk_entry {
bool extreme_supported;
};
static struct quirk_entry quirk_no_extreme_bug = {
.extreme_supported = false,
};
/**
* lwmi_gz_mode_call() - Call method for lenovo-wmi-other driver notifier.
*
* @nb: The notifier_block registered to lenovo-wmi-other driver.
* @cmd: The event type.
* @data: Thermal mode enum pointer pointer for returning the thermal mode.
*
* For LWMI_GZ_GET_THERMAL_MODE, retrieve the current thermal mode.
*
* Return: Notifier_block status.
*/
static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
void *data)
{
enum thermal_mode **mode = data;
struct lwmi_gz_priv *priv;
priv = container_of(nb, struct lwmi_gz_priv, mode_nb);
switch (cmd) {
case LWMI_GZ_GET_THERMAL_MODE:
scoped_guard(spinlock, &priv->gz_mode_lock) {
**mode = priv->current_mode;
}
return NOTIFY_OK;
default:
return NOTIFY_DONE;
}
}
/**
* lwmi_gz_event_call() - Call method for lenovo-wmi-events driver notifier.
* block call chain.
* @nb: The notifier_block registered to lenovo-wmi-events driver.
* @cmd: The event type.
* @data: The data to be updated by the event.
*
* For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
* of a change.
*
* Return: notifier_block status.
*/
static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
void *data)
{
enum thermal_mode *mode = data;
struct lwmi_gz_priv *priv;
priv = container_of(nb, struct lwmi_gz_priv, event_nb);
switch (cmd) {
case LWMI_EVENT_THERMAL_MODE:
scoped_guard(spinlock, &priv->gz_mode_lock) {
priv->current_mode = *mode;
}
platform_profile_notify(priv->ppdev);
return NOTIFY_STOP;
default:
return NOTIFY_DONE;
}
}
/**
* lwmi_gz_thermal_mode_supported() - Get the version of the WMI
* interface to determine the support level.
* @wdev: The Gamezone WMI device.
* @supported: Pointer to return the support level with.
*
* Return: 0 on success, or an error code.
*/
static int lwmi_gz_thermal_mode_supported(struct wmi_device *wdev,
int *supported)
{
return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_SUP,
NULL, 0, supported);
}
/**
* lwmi_gz_thermal_mode_get() - Get the current thermal mode.
* @wdev: The Gamezone interface WMI device.
* @mode: Pointer to return the thermal mode with.
*
* Return: 0 on success, or an error code.
*/
static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
enum thermal_mode *mode)
{
return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_GET,
NULL, 0, mode);
}
/**
* lwmi_gz_profile_get() - Get the current platform profile.
* @dev: the Gamezone interface parent device.
* @profile: Pointer to provide the current platform profile with.
*
* Call lwmi_gz_thermal_mode_get and convert the thermal mode into a platform
* profile based on the support level of the interface.
*
* Return: 0 on success, or an error code.
*/
static int lwmi_gz_profile_get(struct device *dev,
enum platform_profile_option *profile)
{
struct lwmi_gz_priv *priv = dev_get_drvdata(dev);
enum thermal_mode mode;
int ret;
ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode);
if (ret)
return ret;
switch (mode) {
case LWMI_GZ_THERMAL_MODE_QUIET:
*profile = PLATFORM_PROFILE_LOW_POWER;
break;
case LWMI_GZ_THERMAL_MODE_BALANCED:
*profile = PLATFORM_PROFILE_BALANCED;
break;
case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
if (priv->extreme_supported) {
*profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
break;
}
*profile = PLATFORM_PROFILE_PERFORMANCE;
break;
case LWMI_GZ_THERMAL_MODE_EXTREME:
*profile = PLATFORM_PROFILE_PERFORMANCE;
break;
case LWMI_GZ_THERMAL_MODE_CUSTOM:
*profile = PLATFORM_PROFILE_CUSTOM;
break;
default:
return -EINVAL;
}
guard(spinlock)(&priv->gz_mode_lock);
priv->current_mode = mode;
return 0;
}
/**
* lwmi_gz_profile_set() - Set the current platform profile.
* @dev: The Gamezone interface parent device.
* @profile: Pointer to the desired platform profile.
*
* Convert the given platform profile into a thermal mode based on the support
* level of the interface, then call the WMI method to set the thermal mode.
*
* Return: 0 on success, or an error code.
*/
static int lwmi_gz_profile_set(struct device *dev,
enum platform_profile_option profile)
{
struct lwmi_gz_priv *priv = dev_get_drvdata(dev);
struct wmi_method_args_32 args;
enum thermal_mode mode;
int ret;
switch (profile) {
case PLATFORM_PROFILE_LOW_POWER:
mode = LWMI_GZ_THERMAL_MODE_QUIET;
break;
case PLATFORM_PROFILE_BALANCED:
mode = LWMI_GZ_THERMAL_MODE_BALANCED;
break;
case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE;
break;
case PLATFORM_PROFILE_PERFORMANCE:
if (priv->extreme_supported) {
mode = LWMI_GZ_THERMAL_MODE_EXTREME;
break;
}
mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE;
break;
case PLATFORM_PROFILE_CUSTOM:
mode = LWMI_GZ_THERMAL_MODE_CUSTOM;
break;
default:
return -EOPNOTSUPP;
}
args.arg0 = mode;
ret = lwmi_dev_evaluate_int(priv->wdev, 0x0,
LWMI_GZ_METHOD_ID_SMARTFAN_SET,
(u8 *)&args, sizeof(args), NULL);
if (ret)
return ret;
guard(spinlock)(&priv->gz_mode_lock);
priv->current_mode = mode;
return 0;
}
static const struct dmi_system_id fwbug_list[] = {
{
.ident = "Legion Go 8APU1",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
},
.driver_data = &quirk_no_extreme_bug,
},
{
.ident = "Legion Go S 8APU1",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
},
.driver_data = &quirk_no_extreme_bug,
},
{
.ident = "Legion Go S 8ARP1",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
},
.driver_data = &quirk_no_extreme_bug,
},
{},
};
/**
* lwmi_gz_extreme_supported() - Evaluate if a device supports extreme thermal mode.
* @profile_support_ver: Version of the WMI interface.
*
* Determine if the extreme thermal mode is supported by the hardware.
* Anything version 5 or lower does not. For devices with a version 6 or
* greater do a DMI check, as some devices report a version that supports
* extreme mode but have an incomplete entry in the BIOS. To ensure this
* cannot be set, quirk them to prevent assignment.
*
* Return: bool.
*/
static bool lwmi_gz_extreme_supported(int profile_support_ver)
{
const struct dmi_system_id *dmi_id;
struct quirk_entry *quirks;
if (profile_support_ver < 6)
return false;
dmi_id = dmi_first_match(fwbug_list);
if (!dmi_id)
return true;
quirks = dmi_id->driver_data;
return quirks->extreme_supported;
}
/**
* lwmi_gz_platform_profile_probe - Enable and set up the platform profile
* device.
* @drvdata: Driver data for the interface.
* @choices: Container for enabled platform profiles.
*
* Determine if thermal mode is supported, and if so to what feature level.
* Then enable all supported platform profiles.
*
* Return: 0 on success, or an error code.
*/
static int lwmi_gz_platform_profile_probe(void *drvdata, unsigned long *choices)
{
struct lwmi_gz_priv *priv = drvdata;
int profile_support_ver;
int ret;
ret = lwmi_gz_thermal_mode_supported(priv->wdev, &profile_support_ver);
if (ret)
return ret;
if (profile_support_ver < 1)
return -ENODEV;
set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
set_bit(PLATFORM_PROFILE_BALANCED, choices);
set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
set_bit(PLATFORM_PROFILE_CUSTOM, choices);
priv->extreme_supported = lwmi_gz_extreme_supported(profile_support_ver);
if (priv->extreme_supported)
set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
return 0;
}
static const struct platform_profile_ops lwmi_gz_platform_profile_ops = {
.probe = lwmi_gz_platform_profile_probe,
.profile_get = lwmi_gz_profile_get,
.profile_set = lwmi_gz_profile_set,
};
static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
{
struct lwmi_gz_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);
priv->ppdev = devm_platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
priv, &lwmi_gz_platform_profile_ops);
if (IS_ERR(priv->ppdev))
return -ENODEV;
spin_lock_init(&priv->gz_mode_lock);
ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode);
if (ret)
return ret;
priv->event_nb.notifier_call = lwmi_gz_event_call;
ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
if (ret)
return ret;
priv->mode_nb.notifier_call = lwmi_gz_mode_call;
return devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb);
}
static const struct wmi_device_id lwmi_gz_id_table[] = {
{ LENOVO_GAMEZONE_GUID, NULL },
{}
};
static struct wmi_driver lwmi_gz_driver = {
.driver = {
.name = "lenovo_wmi_gamezone",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.id_table = lwmi_gz_id_table,
.probe = lwmi_gz_probe,
.no_singleton = true,
};
module_wmi_driver(lwmi_gz_driver);
MODULE_IMPORT_NS("LENOVO_WMI_EVENTS");
MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
MODULE_IMPORT_NS("LENOVO_WMI_OTHER");
MODULE_DEVICE_TABLE(wmi, lwmi_gz_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
MODULE_DESCRIPTION("Lenovo GameZone WMI Driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
#ifndef _LENOVO_WMI_GAMEZONE_H_
#define _LENOVO_WMI_GAMEZONE_H_
enum gamezone_events_type {
LWMI_GZ_GET_THERMAL_MODE = 1,
};
enum thermal_mode {
LWMI_GZ_THERMAL_MODE_QUIET = 0x01,
LWMI_GZ_THERMAL_MODE_BALANCED = 0x02,
LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03,
LWMI_GZ_THERMAL_MODE_EXTREME = 0xE0, /* Ver 6+ */
LWMI_GZ_THERMAL_MODE_CUSTOM = 0xFF,
};
#endif /* !_LENOVO_WMI_GAMEZONE_H_ */

View File

@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Lenovo Legion WMI helpers driver.
*
* The Lenovo Legion WMI interface is broken up into multiple GUID interfaces
* that require cross-references between GUID's for some functionality. The
* "Custom Mode" interface is a legacy interface for managing and displaying
* CPU & GPU power and hwmon settings and readings. The "Other Mode" interface
* is a modern interface that replaces or extends the "Custom Mode" interface
* methods. The "Gamezone" interface adds advanced features such as fan
* profiles and overclocking. The "Lighting" interface adds control of various
* status lights related to different hardware components. Each of these
* drivers uses a common procedure to get data from the WMI interface,
* enumerated here.
*
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
*/
#include <linux/acpi.h>
#include <linux/cleanup.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/wmi.h>
#include "wmi-helpers.h"
/**
* lwmi_dev_evaluate_int() - Helper function for calling WMI methods that
* return an integer.
* @wdev: Pointer to the WMI device to be called.
* @instance: Instance of the called method.
* @method_id: WMI Method ID for the method to be called.
* @buf: Buffer of all arguments for the given method_id.
* @size: Length of the buffer.
* @retval: Pointer for the return value to be assigned.
*
* Calls wmidev_evaluate_method for Lenovo WMI devices that return an ACPI
* integer. Validates the return value type and assigns the value to the
* retval pointer.
*
* Return: 0 on success, or an error code.
*/
int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
unsigned char *buf, size_t size, u32 *retval)
{
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *ret_obj __free(kfree) = NULL;
struct acpi_buffer input = { size, buf };
acpi_status status;
status = wmidev_evaluate_method(wdev, instance, method_id, &input,
&output);
if (ACPI_FAILURE(status))
return -EIO;
if (retval) {
ret_obj = output.pointer;
if (!ret_obj)
return -ENODATA;
if (ret_obj->type != ACPI_TYPE_INTEGER)
return -ENXIO;
*retval = (u32)ret_obj->integer.value;
}
return 0;
};
EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_int, "LENOVO_WMI_HELPERS");
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
#ifndef _LENOVO_WMI_HELPERS_H_
#define _LENOVO_WMI_HELPERS_H_
#include <linux/types.h>
struct wmi_device;
struct wmi_method_args_32 {
u32 arg0;
u32 arg1;
};
int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
unsigned char *buf, size_t size, u32 *retval);
#endif /* !_LENOVO_WMI_HELPERS_H_ */

View File

@ -0,0 +1,665 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Lenovo Other Mode WMI interface driver.
*
* This driver uses the fw_attributes class to expose the various WMI functions
* provided by the "Other Mode" WMI interface. This enables CPU and GPU power
* limit as well as various other attributes for devices that fall under the
* "Gaming Series" of Lenovo laptop devices. Each attribute exposed by the
* "Other Mode" interface has a corresponding Capability Data struct that
* allows the driver to probe details about the attribute such as if it is
* supported by the hardware, the default_value, max_value, min_value, and step
* increment.
*
* These attributes typically don't fit anywhere else in the sysfs and are set
* in Windows using one of Lenovo's multiple user applications.
*
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
*/
#include <linux/acpi.h>
#include <linux/bitfield.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/idr.h>
#include <linux/kdev_t.h>
#include <linux/kobject.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-events.h"
#include "wmi-gamezone.h"
#include "wmi-helpers.h"
#include "wmi-other.h"
#include "../firmware_attributes_class.h"
#define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
#define LWMI_DEVICE_ID_CPU 0x01
#define LWMI_FEATURE_ID_CPU_SPPT 0x01
#define LWMI_FEATURE_ID_CPU_SPL 0x02
#define LWMI_FEATURE_ID_CPU_FPPT 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_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
static BLOCKING_NOTIFIER_HEAD(om_chain_head);
static DEFINE_IDA(lwmi_om_ida);
enum attribute_property {
DEFAULT_VAL,
MAX_VAL,
MIN_VAL,
STEP_VAL,
SUPPORTED,
};
struct lwmi_om_priv {
struct component_master_ops *ops;
struct cd01_list *cd01_list; /* only valid after capdata01 bind */
struct device *fw_attr_dev;
struct kset *fw_attr_kset;
struct notifier_block nb;
struct wmi_device *wdev;
int ida_id;
};
struct tunable_attr_01 {
struct capdata01 *capdata;
struct device *dev;
u32 feature_id;
u32 device_id;
u32 type_id;
};
static struct tunable_attr_01 ppt_pl1_spl = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_SPL,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_pl2_sppt = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_SPPT,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_pl3_fppt = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_FPPT,
.type_id = LWMI_TYPE_ID_NONE,
};
struct capdata01_attr_group {
const struct attribute_group *attr_group;
struct tunable_attr_01 *tunable_attr;
};
/**
* lwmi_om_register_notifier() - Add a notifier to the blocking notifier chain
* @nb: The notifier_block struct to register
*
* Call blocking_notifier_chain_register to register the notifier block to the
* lenovo-wmi-other driver notifier chain.
*
* Return: 0 on success, %-EEXIST on error.
*/
int lwmi_om_register_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&om_chain_head, nb);
}
EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
/**
* lwmi_om_unregister_notifier() - Remove a notifier from the blocking notifier
* chain.
* @nb: The notifier_block struct to register
*
* Call blocking_notifier_chain_unregister to unregister the notifier block from the
* lenovo-wmi-other driver notifier chain.
*
* Return: 0 on success, %-ENOENT on error.
*/
int lwmi_om_unregister_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&om_chain_head, nb);
}
EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
/**
* devm_lwmi_om_unregister_notifier() - Remove a notifier from the blocking
* notifier chain.
* @data: Void pointer to the notifier_block struct to register.
*
* Call lwmi_om_unregister_notifier to unregister the notifier block from the
* lenovo-wmi-other driver notifier chain.
*
* Return: 0 on success, %-ENOENT on error.
*/
static void devm_lwmi_om_unregister_notifier(void *data)
{
struct notifier_block *nb = data;
lwmi_om_unregister_notifier(nb);
}
/**
* devm_lwmi_om_register_notifier() - Add a notifier to the blocking notifier
* chain.
* @dev: The parent device of the notifier_block struct.
* @nb: The notifier_block struct to register
*
* Call lwmi_om_register_notifier to register the notifier block to the
* lenovo-wmi-other driver notifier chain. Then add devm_lwmi_om_unregister_notifier
* as a device managed action to automatically unregister the notifier block
* upon parent device removal.
*
* Return: 0 on success, or an error code.
*/
int devm_lwmi_om_register_notifier(struct device *dev,
struct notifier_block *nb)
{
int ret;
ret = lwmi_om_register_notifier(nb);
if (ret < 0)
return ret;
return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier,
nb);
}
EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
/**
* lwmi_om_notifier_call() - Call functions for the notifier call chain.
* @mode: Pointer to a thermal mode enum to retrieve the data from.
*
* Call blocking_notifier_call_chain to retrieve the thermal mode from the
* lenovo-wmi-gamezone driver.
*
* Return: 0 on success, or an error code.
*/
static int lwmi_om_notifier_call(enum thermal_mode *mode)
{
int ret;
ret = blocking_notifier_call_chain(&om_chain_head,
LWMI_GZ_GET_THERMAL_MODE, &mode);
if ((ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK)
return -EINVAL;
return 0;
}
/* Attribute Methods */
/**
* int_type_show() - Emit the data type for an integer attribute
* @kobj: Pointer to the driver object.
* @kattr: Pointer to the attribute calling this function.
* @buf: The buffer to write to.
*
* Return: Number of characters written to buf.
*/
static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
char *buf)
{
return sysfs_emit(buf, "integer\n");
}
/**
* attr_capdata01_show() - Get the value of the specified attribute property
*
* @kobj: Pointer to the driver object.
* @kattr: Pointer to the attribute calling this function.
* @buf: The buffer to write to.
* @tunable_attr: The attribute to be read.
* @prop: The property of this attribute to be read.
*
* Retrieves the given property from the capability data 01 struct for the
* specified attribute's "custom" thermal mode. This function is intended
* to be generic so it can be called from any integer attributes "_show"
* function.
*
* If the WMI is success the sysfs attribute is notified.
*
* Return: Either number of characters written to buf, or an error code.
*/
static ssize_t attr_capdata01_show(struct kobject *kobj,
struct kobj_attribute *kattr, char *buf,
struct tunable_attr_01 *tunable_attr,
enum attribute_property prop)
{
struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
struct capdata01 capdata;
u32 attribute_id;
int value, ret;
attribute_id =
FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
FIELD_PREP(LWMI_ATTR_MODE_ID_MASK,
LWMI_GZ_THERMAL_MODE_CUSTOM) |
FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
if (ret)
return ret;
switch (prop) {
case DEFAULT_VAL:
value = capdata.default_value;
break;
case MAX_VAL:
value = capdata.max_value;
break;
case MIN_VAL:
value = capdata.min_value;
break;
case STEP_VAL:
value = capdata.step;
break;
default:
return -EINVAL;
}
return sysfs_emit(buf, "%d\n", value);
}
/**
* attr_current_value_store() - Set the current value of the given attribute
* @kobj: Pointer to the driver object.
* @kattr: Pointer to the attribute calling this function.
* @buf: The buffer to read from, this is parsed to `int` type.
* @count: Required by sysfs attribute macros, pass in from the callee attr.
* @tunable_attr: The attribute to be stored.
*
* Sets the value of the given attribute when operating under the "custom"
* smartfan profile. The current smartfan profile is retrieved from the
* lenovo-wmi-gamezone driver and error is returned if the result is not
* "custom". This function is intended to be generic so it can be called from
* any integer attribute's "_store" function. The integer to be sent to the WMI
* method is range checked and an error code is returned if out of range.
*
* If the value is valid and WMI is success, then the sysfs attribute is
* notified.
*
* Return: Either count, or an error code.
*/
static ssize_t attr_current_value_store(struct kobject *kobj,
struct kobj_attribute *kattr,
const char *buf, size_t count,
struct tunable_attr_01 *tunable_attr)
{
struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
struct wmi_method_args_32 args;
struct capdata01 capdata;
enum thermal_mode mode;
u32 attribute_id;
u32 value;
int ret;
ret = lwmi_om_notifier_call(&mode);
if (ret)
return ret;
if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
return -EBUSY;
attribute_id =
FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
if (ret)
return ret;
ret = kstrtouint(buf, 10, &value);
if (ret)
return ret;
if (value < capdata.min_value || value > capdata.max_value)
return -EINVAL;
args.arg0 = attribute_id;
args.arg1 = value;
ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
(unsigned char *)&args, sizeof(args), NULL);
if (ret)
return ret;
return count;
};
/**
* attr_current_value_show() - Get the current value of the given attribute
* @kobj: Pointer to the driver object.
* @kattr: Pointer to the attribute calling this function.
* @buf: The buffer to write to.
* @tunable_attr: The attribute to be read.
*
* Retrieves the value of the given attribute for the current smartfan profile.
* The current smartfan profile is retrieved from the lenovo-wmi-gamezone driver.
* This function is intended to be generic so it can be called from any integer
* attribute's "_show" function.
*
* If the WMI is success the sysfs attribute is notified.
*
* Return: Either number of characters written to buf, or an error code.
*/
static ssize_t attr_current_value_show(struct kobject *kobj,
struct kobj_attribute *kattr, char *buf,
struct tunable_attr_01 *tunable_attr)
{
struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
struct wmi_method_args_32 args;
enum thermal_mode mode;
u32 attribute_id;
int retval;
int ret;
ret = lwmi_om_notifier_call(&mode);
if (ret)
return ret;
attribute_id =
FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
args.arg0 = attribute_id;
ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
(unsigned char *)&args, sizeof(args),
&retval);
if (ret)
return ret;
return sysfs_emit(buf, "%d\n", retval);
}
/* Lenovo WMI Other Mode Attribute macros */
#define __LWMI_ATTR_RO(_func, _name) \
{ \
.attr = { .name = __stringify(_name), .mode = 0444 }, \
.show = _func##_##_name##_show, \
}
#define __LWMI_ATTR_RO_AS(_name, _show) \
{ \
.attr = { .name = __stringify(_name), .mode = 0444 }, \
.show = _show, \
}
#define __LWMI_ATTR_RW(_func, _name) \
__ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
/* Shows a formatted static variable */
#define __LWMI_ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \
static ssize_t _attrname##_##_prop##_show( \
struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
{ \
return sysfs_emit(buf, _fmt, _val); \
} \
static struct kobj_attribute attr_##_attrname##_##_prop = \
__LWMI_ATTR_RO(_attrname, _prop)
/* Attribute current value read/write */
#define __LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname) \
static ssize_t _attrname##_current_value_store( \
struct kobject *kobj, struct kobj_attribute *kattr, \
const char *buf, size_t count) \
{ \
return attr_current_value_store(kobj, kattr, buf, count, \
&_attrname); \
} \
static ssize_t _attrname##_current_value_show( \
struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
{ \
return attr_current_value_show(kobj, kattr, buf, &_attrname); \
} \
static struct kobj_attribute attr_##_attrname##_current_value = \
__LWMI_ATTR_RW(_attrname, current_value)
/* Attribute property read only */
#define __LWMI_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) \
static ssize_t _attrname##_##_prop##_show( \
struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
{ \
return attr_capdata01_show(kobj, kattr, buf, &_attrname, \
_prop_type); \
} \
static struct kobj_attribute attr_##_attrname##_##_prop = \
__LWMI_ATTR_RO(_attrname, _prop)
#define LWMI_ATTR_GROUP_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
__LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname); \
__LWMI_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
__LWMI_ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
__LWMI_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
__LWMI_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
__LWMI_TUNABLE_RO_CAP01(scalar_increment, _attrname, STEP_VAL); \
static struct kobj_attribute attr_##_attrname##_type = \
__LWMI_ATTR_RO_AS(type, int_type_show); \
static struct attribute *_attrname##_attrs[] = { \
&attr_##_attrname##_current_value.attr, \
&attr_##_attrname##_default_value.attr, \
&attr_##_attrname##_display_name.attr, \
&attr_##_attrname##_max_value.attr, \
&attr_##_attrname##_min_value.attr, \
&attr_##_attrname##_scalar_increment.attr, \
&attr_##_attrname##_type.attr, \
NULL, \
}; \
static const struct attribute_group _attrname##_attr_group = { \
.name = _fsname, .attrs = _attrname##_attrs \
}
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
"Set the CPU sustained power limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
"Set the CPU slow package power tracking limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
"Set the CPU fast package power tracking limit");
static struct capdata01_attr_group cd01_attr_groups[] = {
{ &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
{ &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
{ &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
{},
};
/**
* lwmi_om_fw_attr_add() - Register all firmware_attributes_class members
* @priv: The Other Mode driver data.
*
* Return: Either 0, or an error code.
*/
static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
{
unsigned int i;
int err;
priv->ida_id = ida_alloc(&lwmi_om_ida, GFP_KERNEL);
if (priv->ida_id < 0)
return priv->ida_id;
priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
MKDEV(0, 0), NULL, "%s-%u",
LWMI_OM_FW_ATTR_BASE_PATH,
priv->ida_id);
if (IS_ERR(priv->fw_attr_dev)) {
err = PTR_ERR(priv->fw_attr_dev);
goto err_free_ida;
}
priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
&priv->fw_attr_dev->kobj);
if (!priv->fw_attr_kset) {
err = -ENOMEM;
goto err_destroy_classdev;
}
for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) {
err = sysfs_create_group(&priv->fw_attr_kset->kobj,
cd01_attr_groups[i].attr_group);
if (err)
goto err_remove_groups;
cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
}
return 0;
err_remove_groups:
while (i--)
sysfs_remove_group(&priv->fw_attr_kset->kobj,
cd01_attr_groups[i].attr_group);
kset_unregister(priv->fw_attr_kset);
err_destroy_classdev:
device_unregister(priv->fw_attr_dev);
err_free_ida:
ida_free(&lwmi_om_ida, priv->ida_id);
return err;
}
/**
* lwmi_om_fw_attr_remove() - Unregister all capability data attribute groups
* @priv: the lenovo-wmi-other driver data.
*/
static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
{
for (unsigned int i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++)
sysfs_remove_group(&priv->fw_attr_kset->kobj,
cd01_attr_groups[i].attr_group);
kset_unregister(priv->fw_attr_kset);
device_unregister(priv->fw_attr_dev);
}
/**
* 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.
*
* 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;
int ret;
ret = component_bind_all(dev, &tmp_list);
if (ret)
return ret;
priv->cd01_list = tmp_list;
if (!priv->cd01_list)
return -ENODEV;
return lwmi_om_fw_attr_add(priv);
}
/**
* 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.
*/
static void lwmi_om_master_unbind(struct device *dev)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
lwmi_om_fw_attr_remove(priv);
component_unbind_all(dev, NULL);
}
static const struct component_master_ops lwmi_om_master_ops = {
.bind = lwmi_om_master_bind,
.unbind = lwmi_om_master_unbind,
};
static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
{
struct component_match *master_match = NULL;
struct lwmi_om_priv *priv;
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
if (IS_ERR(master_match))
return PTR_ERR(master_match);
return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
master_match);
}
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);
}
static const struct wmi_device_id lwmi_other_id_table[] = {
{ LENOVO_OTHER_MODE_GUID, NULL },
{}
};
static struct wmi_driver lwmi_other_driver = {
.driver = {
.name = "lenovo_wmi_other",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.id_table = lwmi_other_id_table,
.probe = lwmi_other_probe,
.remove = lwmi_other_remove,
.no_singleton = true,
};
module_wmi_driver(lwmi_other_driver);
MODULE_IMPORT_NS("LENOVO_WMI_CD01");
MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
#ifndef _LENOVO_WMI_OTHER_H_
#define _LENOVO_WMI_OTHER_H_
struct device;
struct notifier_block;
int lwmi_om_register_notifier(struct notifier_block *nb);
int lwmi_om_unregister_notifier(struct notifier_block *nb);
int devm_lwmi_om_register_notifier(struct device *dev,
struct notifier_block *nb);
#endif /* !_LENOVO_WMI_OTHER_H_ */

View File

@ -21,7 +21,7 @@
#include <linux/time.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include "serdev_helpers.h"
#include "../serdev_helpers.h"
#define YT2_1380_FC_PDEV_NAME "lenovo-yoga-tab2-pro-1380-fastcharger"
#define YT2_1380_FC_SERDEV_CTRL "serial0"
@ -240,30 +240,25 @@ static int yt2_1380_fc_pdev_probe(struct platform_device *pdev)
int ret;
/* Register pinctrl mappings for setting the UART3 pins mode */
ret = pinctrl_register_mappings(yt2_1380_fc_pinctrl_map,
ARRAY_SIZE(yt2_1380_fc_pinctrl_map));
ret = devm_pinctrl_register_mappings(&pdev->dev, yt2_1380_fc_pinctrl_map,
ARRAY_SIZE(yt2_1380_fc_pinctrl_map));
if (ret)
return ret;
/* And create the serdev to talk to the charger over the UART3 pins */
ctrl_dev = get_serdev_controller("PNP0501", "1", 0, YT2_1380_FC_SERDEV_CTRL);
if (IS_ERR(ctrl_dev)) {
ret = PTR_ERR(ctrl_dev);
goto out_pinctrl_unregister_mappings;
}
if (IS_ERR(ctrl_dev))
return PTR_ERR(ctrl_dev);
serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev));
put_device(ctrl_dev);
if (!serdev) {
ret = -ENOMEM;
goto out_pinctrl_unregister_mappings;
}
if (!serdev)
return -ENOMEM;
ret = serdev_device_add(serdev);
if (ret) {
dev_err_probe(&pdev->dev, ret, "adding serdev\n");
serdev_device_put(serdev);
goto out_pinctrl_unregister_mappings;
return dev_err_probe(&pdev->dev, ret, "adding serdev\n");
}
/*
@ -273,20 +268,15 @@ static int yt2_1380_fc_pdev_probe(struct platform_device *pdev)
ret = device_driver_attach(&yt2_1380_fc_serdev_driver.driver, &serdev->dev);
if (ret) {
/* device_driver_attach() maps EPROBE_DEFER to EAGAIN, map it back */
ret = (ret == -EAGAIN) ? -EPROBE_DEFER : ret;
dev_err_probe(&pdev->dev, ret, "attaching serdev driver\n");
goto out_serdev_device_remove;
serdev_device_remove(serdev);
return dev_err_probe(&pdev->dev,
(ret == -EAGAIN) ? -EPROBE_DEFER : ret,
"attaching serdev driver\n");
}
/* So that yt2_1380_fc_pdev_remove() can remove the serdev */
platform_set_drvdata(pdev, serdev);
return 0;
out_serdev_device_remove:
serdev_device_remove(serdev);
out_pinctrl_unregister_mappings:
pinctrl_unregister_mappings(yt2_1380_fc_pinctrl_map);
return ret;
}
static void yt2_1380_fc_pdev_remove(struct platform_device *pdev)
@ -294,7 +284,6 @@ static void yt2_1380_fc_pdev_remove(struct platform_device *pdev)
struct serdev_device *serdev = platform_get_drvdata(pdev);
serdev_device_remove(serdev);
pinctrl_unregister_mappings(yt2_1380_fc_pinctrl_map);
}
static struct platform_driver yt2_1380_fc_pdev_driver = {

View File

@ -58,7 +58,8 @@ enum oxp_board {
oxp_mini_amd_a07,
oxp_mini_amd_pro,
oxp_x1,
oxp_g1,
oxp_g1_i,
oxp_g1_a,
};
static enum oxp_board board;
@ -247,14 +248,14 @@ static const struct dmi_system_id dmi_table[] = {
DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER G1 A"),
},
.driver_data = (void *)oxp_g1,
.driver_data = (void *)oxp_g1_a,
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER G1 i"),
},
.driver_data = (void *)oxp_g1,
.driver_data = (void *)oxp_g1_i,
},
{
.matches = {
@ -291,6 +292,13 @@ static const struct dmi_system_id dmi_table[] = {
},
.driver_data = (void *)oxp_x1,
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1Mini Pro"),
},
.driver_data = (void *)oxp_x1,
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
@ -352,7 +360,8 @@ static umode_t tt_toggle_is_visible(struct kobject *kobj,
case oxp_mini_amd_a07:
case oxp_mini_amd_pro:
case oxp_x1:
case oxp_g1:
case oxp_g1_i:
case oxp_g1_a:
return attr->mode;
default:
break;
@ -381,12 +390,13 @@ static ssize_t tt_toggle_store(struct device *dev,
case aok_zoe_a1:
case oxp_fly:
case oxp_mini_amd_pro:
case oxp_g1_a:
reg = OXP_TURBO_SWITCH_REG;
mask = OXP_TURBO_TAKE_VAL;
break;
case oxp_2:
case oxp_x1:
case oxp_g1:
case oxp_g1_i:
reg = OXP_2_TURBO_SWITCH_REG;
mask = OXP_TURBO_TAKE_VAL;
break;
@ -426,12 +436,13 @@ static ssize_t tt_toggle_show(struct device *dev,
case aok_zoe_a1:
case oxp_fly:
case oxp_mini_amd_pro:
case oxp_g1_a:
reg = OXP_TURBO_SWITCH_REG;
mask = OXP_TURBO_TAKE_VAL;
break;
case oxp_2:
case oxp_x1:
case oxp_g1:
case oxp_g1_i:
reg = OXP_2_TURBO_SWITCH_REG;
mask = OXP_TURBO_TAKE_VAL;
break;
@ -520,7 +531,8 @@ static bool oxp_psy_ext_supported(void)
{
switch (board) {
case oxp_x1:
case oxp_g1:
case oxp_g1_i:
case oxp_g1_a:
case oxp_fly:
return true;
default:
@ -659,7 +671,8 @@ static int oxp_pwm_enable(void)
case oxp_mini_amd_a07:
case oxp_mini_amd_pro:
case oxp_x1:
case oxp_g1:
case oxp_g1_i:
case oxp_g1_a:
return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL);
default:
return -EINVAL;
@ -686,7 +699,8 @@ static int oxp_pwm_disable(void)
case oxp_mini_amd_a07:
case oxp_mini_amd_pro:
case oxp_x1:
case oxp_g1:
case oxp_g1_i:
case oxp_g1_a:
return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);
default:
return -EINVAL;
@ -713,7 +727,8 @@ static int oxp_pwm_read(long *val)
case oxp_mini_amd_a07:
case oxp_mini_amd_pro:
case oxp_x1:
case oxp_g1:
case oxp_g1_i:
case oxp_g1_a:
return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
default:
return -EOPNOTSUPP;
@ -742,7 +757,7 @@ static int oxp_pwm_fan_speed(long *val)
return read_from_ec(ORANGEPI_SENSOR_FAN_REG, 2, val);
case oxp_2:
case oxp_x1:
case oxp_g1:
case oxp_g1_i:
return read_from_ec(OXP_2_SENSOR_FAN_REG, 2, val);
case aok_zoe_a1:
case aya_neo_2:
@ -757,6 +772,7 @@ static int oxp_pwm_fan_speed(long *val)
case oxp_mini_amd:
case oxp_mini_amd_a07:
case oxp_mini_amd_pro:
case oxp_g1_a:
return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
default:
return -EOPNOTSUPP;
@ -776,7 +792,7 @@ static int oxp_pwm_input_write(long val)
return write_to_ec(ORANGEPI_SENSOR_PWM_REG, val);
case oxp_2:
case oxp_x1:
case oxp_g1:
case oxp_g1_i:
/* scale to range [0-184] */
val = (val * 184) / 255;
return write_to_ec(OXP_SENSOR_PWM_REG, val);
@ -796,6 +812,7 @@ static int oxp_pwm_input_write(long val)
case aok_zoe_a1:
case oxp_fly:
case oxp_mini_amd_pro:
case oxp_g1_a:
return write_to_ec(OXP_SENSOR_PWM_REG, val);
default:
return -EOPNOTSUPP;
@ -816,7 +833,7 @@ static int oxp_pwm_input_read(long *val)
break;
case oxp_2:
case oxp_x1:
case oxp_g1:
case oxp_g1_i:
ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
if (ret)
return ret;
@ -842,6 +859,7 @@ static int oxp_pwm_input_read(long *val)
case aok_zoe_a1:
case oxp_fly:
case oxp_mini_amd_pro:
case oxp_g1_a:
default:
ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
if (ret)

View File

@ -16,6 +16,7 @@
#include <linux/leds.h>
#include <linux/dmi.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/rfkill.h>
#include <linux/acpi.h>
#include <linux/seq_file.h>
@ -23,6 +24,7 @@
#include <linux/ctype.h>
#include <linux/efi.h>
#include <linux/suspend.h>
#include <acpi/battery.h>
#include <acpi/video.h>
/*
@ -348,6 +350,8 @@ struct samsung_laptop {
struct notifier_block pm_nb;
struct acpi_battery_hook battery_hook;
bool handle_backlight;
bool has_stepping_quirk;
@ -697,6 +701,11 @@ static ssize_t set_performance_level(struct device *dev,
static DEVICE_ATTR(performance_level, 0644,
get_performance_level, set_performance_level);
static void show_battery_life_extender_deprecation_warning(struct device *dev)
{
dev_warn_once(dev, "battery_life_extender attribute has been deprecated, see charge_types.\n");
}
static int read_battery_life_extender(struct samsung_laptop *samsung)
{
const struct sabi_commands *commands = &samsung->config->commands;
@ -739,6 +748,8 @@ static ssize_t get_battery_life_extender(struct device *dev,
struct samsung_laptop *samsung = dev_get_drvdata(dev);
int ret;
show_battery_life_extender_deprecation_warning(dev);
ret = read_battery_life_extender(samsung);
if (ret < 0)
return ret;
@ -753,6 +764,8 @@ static ssize_t set_battery_life_extender(struct device *dev,
struct samsung_laptop *samsung = dev_get_drvdata(dev);
int ret, value;
show_battery_life_extender_deprecation_warning(dev);
if (!count || kstrtoint(buf, 0, &value) != 0)
return -EINVAL;
@ -766,6 +779,84 @@ static ssize_t set_battery_life_extender(struct device *dev,
static DEVICE_ATTR(battery_life_extender, 0644,
get_battery_life_extender, set_battery_life_extender);
static int samsung_psy_ext_set_prop(struct power_supply *psy,
const struct power_supply_ext *ext,
void *ext_data,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct samsung_laptop *samsung = ext_data;
switch (val->intval) {
case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
return write_battery_life_extender(samsung, 1);
case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
return write_battery_life_extender(samsung, 0);
default:
return -EINVAL;
}
}
static int samsung_psy_ext_get_prop(struct power_supply *psy,
const struct power_supply_ext *ext,
void *ext_data,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct samsung_laptop *samsung = ext_data;
int ret;
ret = read_battery_life_extender(samsung);
if (ret < 0)
return ret;
if (ret == 1)
val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
else
val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
return 0;
}
static int samsung_psy_prop_is_writeable(struct power_supply *psy,
const struct power_supply_ext *ext,
void *data,
enum power_supply_property psp)
{
return true;
}
static const enum power_supply_property samsung_power_supply_props[] = {
POWER_SUPPLY_PROP_CHARGE_TYPES,
};
static const struct power_supply_ext samsung_battery_ext = {
.name = "samsung_laptop",
.properties = samsung_power_supply_props,
.num_properties = ARRAY_SIZE(samsung_power_supply_props),
.charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
.get_property = samsung_psy_ext_get_prop,
.set_property = samsung_psy_ext_set_prop,
.property_is_writeable = samsung_psy_prop_is_writeable,
};
static int samsung_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
{
struct samsung_laptop *samsung = container_of(hook, struct samsung_laptop, battery_hook);
return power_supply_register_extension(battery, &samsung_battery_ext,
&samsung->platform_device->dev, samsung);
}
static int samsung_battery_remove(struct power_supply *battery,
struct acpi_battery_hook *hook)
{
power_supply_unregister_extension(battery, &samsung_battery_ext);
return 0;
}
static int read_usb_charge(struct samsung_laptop *samsung)
{
const struct sabi_commands *commands = &samsung->config->commands;
@ -1043,6 +1134,21 @@ static int __init samsung_lid_handling_init(struct samsung_laptop *samsung)
return retval;
}
static int __init samsung_battery_hook_init(struct samsung_laptop *samsung)
{
int retval = 0;
if (samsung->config->commands.get_battery_life_extender != 0xFFFF) {
samsung->battery_hook.add_battery = samsung_battery_add;
samsung->battery_hook.remove_battery = samsung_battery_remove;
samsung->battery_hook.name = "Samsung Battery Extension";
retval = devm_battery_hook_register(&samsung->platform_device->dev,
&samsung->battery_hook);
}
return retval;
}
static int kbd_backlight_enable(struct samsung_laptop *samsung)
{
const struct sabi_commands *commands = &samsung->config->commands;
@ -1604,6 +1710,10 @@ static int __init samsung_init(void)
if (ret)
goto error_lid_handling;
ret = samsung_battery_hook_init(samsung);
if (ret)
goto error_lid_handling;
samsung_debugfs_init(samsung);
samsung->pm_nb.notifier_call = samsung_pm_notification;

View File

@ -248,13 +248,9 @@ static int silicom_gpio_direction_input(struct gpio_chip *gc,
static int silicom_gpio_set(struct gpio_chip *gc, unsigned int offset,
int value)
{
int direction = silicom_gpio_get_direction(gc, offset);
u8 *channels = gpiochip_get_data(gc);
int channel = channels[offset];
if (direction == GPIO_LINE_DIRECTION_IN)
return -EPERM;
silicom_mec_port_set(channel, !value);
return 0;

View File

@ -20,6 +20,7 @@
#include <linux/bits.h>
#include <linux/build_bug.h>
#include <linux/device.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
@ -74,6 +75,8 @@ struct wmi_guid_count_context {
int count;
};
static DEFINE_IDA(wmi_ida);
/*
* If the GUID data block is marked as expensive, we must enable and
* explicitily disable data collection.
@ -984,6 +987,19 @@ static int guid_count(const guid_t *guid)
return context.count;
}
static int wmi_dev_set_name(struct wmi_block *wblock, int count)
{
if (IS_ENABLED(CONFIG_ACPI_WMI_LEGACY_DEVICE_NAMES)) {
if (count)
return dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid,
count);
else
return dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid);
}
return dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, wblock->dev.dev.id);
}
static int wmi_create_device(struct device *wmi_bus_dev,
struct wmi_block *wblock,
struct acpi_device *device)
@ -992,7 +1008,7 @@ static int wmi_create_device(struct device *wmi_bus_dev,
struct acpi_device_info *info;
acpi_handle method_handle;
acpi_status status;
int count;
int count, ret;
if (wblock->gblock.flags & ACPI_WMI_EVENT) {
wblock->dev.dev.type = &wmi_type_event;
@ -1063,11 +1079,18 @@ static int wmi_create_device(struct device *wmi_bus_dev,
if (count < 0)
return count;
if (count) {
dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, count);
if (count)
set_bit(WMI_GUID_DUPLICATED, &wblock->flags);
} else {
dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid);
ret = ida_alloc(&wmi_ida, GFP_KERNEL);
if (ret < 0)
return ret;
wblock->dev.dev.id = ret;
ret = wmi_dev_set_name(wblock, count);
if (ret < 0) {
ida_free(&wmi_ida, wblock->dev.dev.id);
return ret;
}
device_initialize(&wblock->dev.dev);
@ -1153,6 +1176,7 @@ static int parse_wdg(struct device *wmi_bus_dev, struct platform_device *pdev)
dev_err(wmi_bus_dev, "failed to register %pUL\n",
&wblock->gblock.guid);
ida_free(&wmi_ida, wblock->dev.dev.id);
put_device(&wblock->dev.dev);
}
}
@ -1252,7 +1276,10 @@ static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, void *context
static int wmi_remove_device(struct device *dev, void *data)
{
int id = dev->id;
device_unregister(dev);
ida_free(&wmi_ida, id);
return 0;
}

View File

@ -206,24 +206,9 @@ static const struct software_node asus_tf103c_touchscreen_node = {
.properties = asus_tf103c_touchscreen_props,
};
static const struct property_entry asus_tf103c_battery_props[] = {
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"),
PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000),
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000),
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
{ }
};
static const struct software_node asus_tf103c_battery_node = {
.properties = asus_tf103c_battery_props,
};
static const struct property_entry asus_tf103c_bq24190_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", tusb1211_chg_det_psy, 1),
PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node),
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000),
PROPERTY_ENTRY_BOOL("omit-battery-class"),
PROPERTY_ENTRY_BOOL("disable-reset"),
@ -236,7 +221,7 @@ static const struct software_node asus_tf103c_bq24190_node = {
static const struct property_entry asus_tf103c_ug3105_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", bq24190_psy, 1),
PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node),
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 5000),
{ }
};
@ -321,6 +306,6 @@ const struct x86_dev_info asus_tf103c_info __initconst = {
.gpio_button = &asus_me176c_tf103c_lid,
.gpio_button_count = 1,
.gpiod_lookup_tables = asus_tf103c_gpios,
.bat_swnode = &asus_tf103c_battery_node,
.bat_swnode = &generic_lipo_4v2_battery_node,
.modules = bq24190_modules,
};

View File

@ -39,6 +39,78 @@ const struct software_node fg_bq25890_supply_node = {
.properties = fg_bq25890_supply_props,
};
static const u32 generic_lipo_battery_ovc_cap_celcius[] = { 25 };
static const u32 generic_lipo_4v2_battery_ovc_cap_table0[] = {
4200000, 100,
4150000, 95,
4110000, 90,
4075000, 85,
4020000, 80,
3982500, 75,
3945000, 70,
3907500, 65,
3870000, 60,
3853333, 55,
3836667, 50,
3820000, 45,
3803333, 40,
3786667, 35,
3770000, 30,
3750000, 25,
3730000, 20,
3710000, 15,
3690000, 10,
3610000, 5,
3350000, 0
};
static const u32 generic_lipo_hv_4v35_battery_ovc_cap_table0[] = {
4300000, 100,
4250000, 96,
4200000, 91,
4150000, 86,
4110000, 82,
4075000, 77,
4020000, 73,
3982500, 68,
3945000, 64,
3907500, 59,
3870000, 55,
3853333, 50,
3836667, 45,
3820000, 41,
3803333, 36,
3786667, 32,
3770000, 27,
3750000, 23,
3730000, 18,
3710000, 14,
3690000, 9,
3610000, 5,
3350000, 0
};
/* Standard LiPo (max 4.2V) settings used by most devs with a LiPo battery */
static const struct property_entry generic_lipo_4v2_battery_props[] = {
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"),
PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000),
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000),
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-celsius",
generic_lipo_battery_ovc_cap_celcius),
PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-table-0",
generic_lipo_4v2_battery_ovc_cap_table0),
{ }
};
const struct software_node generic_lipo_4v2_battery_node = {
.properties = generic_lipo_4v2_battery_props,
};
/* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV battery */
static const struct property_entry generic_lipo_hv_4v35_battery_props[] = {
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
@ -48,6 +120,10 @@ static const struct property_entry generic_lipo_hv_4v35_battery_props[] = {
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 1856000),
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4352000),
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-celsius",
generic_lipo_battery_ovc_cap_celcius),
PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-table-0",
generic_lipo_hv_4v35_battery_ovc_cap_table0),
{ }
};

View File

@ -21,6 +21,7 @@ extern const char * const bq25890_psy[];
extern const struct software_node fg_bq24190_supply_node;
extern const struct software_node fg_bq25890_supply_node;
extern const struct software_node generic_lipo_4v2_battery_node;
extern const struct software_node generic_lipo_hv_4v35_battery_node;
extern struct bq24190_platform_data bq24190_pdata;

View File

@ -9,9 +9,10 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/auxiliary_bus.h>
#include <linux/io.h>
#include <linux/intel_tpmi.h>
#include <linux/intel_rapl.h>
#include <linux/intel_tpmi.h>
#include <linux/intel_vsec.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/slab.h>
@ -48,7 +49,7 @@ enum tpmi_rapl_register {
struct tpmi_rapl_package {
struct rapl_if_priv priv;
struct intel_tpmi_plat_info *tpmi_info;
struct oobmsm_plat_info *tpmi_info;
struct rapl_package *rp;
void __iomem *base;
struct list_head node;
@ -253,7 +254,7 @@ static int intel_rapl_tpmi_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id)
{
struct tpmi_rapl_package *trp;
struct intel_tpmi_plat_info *info;
struct oobmsm_plat_info *info;
struct resource *res;
u32 offset;
int ret;

View File

@ -0,0 +1,157 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _FEATURES_H
#define _FEATURES_H
#include <linux/bits.h>
#include <linux/types.h>
/* Common masks */
#define PMT_CAP_TELEM BIT(0)
#define PMT_CAP_WATCHER BIT(1)
#define PMT_CAP_CRASHLOG BIT(2)
#define PMT_CAP_STREAMING BIT(3)
#define PMT_CAP_THRESHOLD BIT(4)
#define PMT_CAP_WINDOW BIT(5)
#define PMT_CAP_CONFIG BIT(6)
#define PMT_CAP_TRACING BIT(7)
#define PMT_CAP_INBAND BIT(8)
#define PMT_CAP_OOB BIT(9)
#define PMT_CAP_SECURED_CHAN BIT(10)
#define PMT_CAP_PMT_SP BIT(11)
#define PMT_CAP_PMT_SP_POLICY GENMASK(17, 12)
/* Per Core Performance Telemetry (PCPT) specific masks */
#define PMT_CAP_PCPT_CORE_PERF BIT(18)
#define PMT_CAP_PCPT_CORE_C0_RES BIT(19)
#define PMT_CAP_PCPT_CORE_ACTIVITY BIT(20)
#define PMT_CAP_PCPT_CACHE_PERF BIT(21)
#define PMT_CAP_PCPT_QUALITY_TELEM BIT(22)
/* Per Core Environmental Telemetry (PCET) specific masks */
#define PMT_CAP_PCET_WORKPOINT_HIST BIT(18)
#define PMT_CAP_PCET_CORE_CURR_TEMP BIT(19)
#define PMT_CAP_PCET_CORE_INST_RES BIT(20)
#define PMT_CAP_PCET_QUALITY_TELEM BIT(21) /* Same as PMT_CAP_PCPT */
#define PMT_CAP_PCET_CORE_CDYN_LVL BIT(22)
#define PMT_CAP_PCET_CORE_STRESS_LVL BIT(23)
#define PMT_CAP_PCET_CORE_DAS BIT(24)
#define PMT_CAP_PCET_FIVR_HEALTH BIT(25)
#define PMT_CAP_PCET_ENERGY BIT(26)
#define PMT_CAP_PCET_PEM_STATUS BIT(27)
#define PMT_CAP_PCET_CORE_C_STATE BIT(28)
/* Per RMID Performance Telemetry specific masks */
#define PMT_CAP_RMID_CORES_PERF BIT(18)
#define PMT_CAP_RMID_CACHE_PERF BIT(19)
#define PMT_CAP_RMID_PERF_QUAL BIT(20)
/* Accelerator Telemetry specific masks */
#define PMT_CAP_ACCEL_CPM_TELEM BIT(18)
#define PMT_CAP_ACCEL_TIP_TELEM BIT(19)
/* Uncore Telemetry specific masks */
#define PMT_CAP_UNCORE_IO_CA_TELEM BIT(18)
#define PMT_CAP_UNCORE_RMID_TELEM BIT(19)
#define PMT_CAP_UNCORE_D2D_ULA_TELEM BIT(20)
#define PMT_CAP_UNCORE_PKGC_TELEM BIT(21)
/* Crash Log specific masks */
#define PMT_CAP_CRASHLOG_MAN_TRIG BIT(11)
#define PMT_CAP_CRASHLOG_CORE BIT(12)
#define PMT_CAP_CRASHLOG_UNCORE BIT(13)
#define PMT_CAP_CRASHLOG_TOR BIT(14)
#define PMT_CAP_CRASHLOG_S3M BIT(15)
#define PMT_CAP_CRASHLOG_PERSISTENCY BIT(16)
#define PMT_CAP_CRASHLOG_CLIP_GPIO BIT(17)
#define PMT_CAP_CRASHLOG_PRE_RESET BIT(18)
#define PMT_CAP_CRASHLOG_POST_RESET BIT(19)
/* PeTe Log specific masks */
#define PMT_CAP_PETE_MAN_TRIG BIT(11)
#define PMT_CAP_PETE_ENCRYPTION BIT(12)
#define PMT_CAP_PETE_PERSISTENCY BIT(13)
#define PMT_CAP_PETE_REQ_TOKENS BIT(14)
#define PMT_CAP_PETE_PROD_ENABLED BIT(15)
#define PMT_CAP_PETE_DEBUG_ENABLED BIT(16)
/* TPMI control specific masks */
#define PMT_CAP_TPMI_MAILBOX BIT(11)
#define PMT_CAP_TPMI_LOCK BIT(12)
/* Tracing specific masks */
#define PMT_CAP_TRACE_SRAR BIT(11)
#define PMT_CAP_TRACE_CORRECTABLE BIT(12)
#define PMT_CAP_TRACE_MCTP BIT(13)
#define PMT_CAP_TRACE_MRT BIT(14)
/* Per RMID Energy Telemetry specific masks */
#define PMT_CAP_RMID_ENERGY BIT(18)
#define PMT_CAP_RMID_ACTIVITY BIT(19)
#define PMT_CAP_RMID_ENERGY_QUAL BIT(20)
enum pmt_feature_id {
FEATURE_INVALID = 0x0,
FEATURE_PER_CORE_PERF_TELEM = 0x1,
FEATURE_PER_CORE_ENV_TELEM = 0x2,
FEATURE_PER_RMID_PERF_TELEM = 0x3,
FEATURE_ACCEL_TELEM = 0x4,
FEATURE_UNCORE_TELEM = 0x5,
FEATURE_CRASH_LOG = 0x6,
FEATURE_PETE_LOG = 0x7,
FEATURE_TPMI_CTRL = 0x8,
FEATURE_RESERVED = 0x9,
FEATURE_TRACING = 0xA,
FEATURE_PER_RMID_ENERGY_TELEM = 0xB,
FEATURE_MAX = 0xB,
};
enum feature_layout {
LAYOUT_RMID,
LAYOUT_WATCHER,
LAYOUT_COMMAND,
LAYOUT_CAPS_ONLY,
};
struct pmt_cap {
u32 mask;
const char *name;
};
extern const char * const pmt_feature_names[];
extern enum feature_layout feature_layout[];
extern struct pmt_cap pmt_cap_common[];
extern struct pmt_cap pmt_cap_pcpt[];
extern struct pmt_cap *pmt_caps_pcpt[];
extern struct pmt_cap pmt_cap_pcet[];
extern struct pmt_cap *pmt_caps_pcet[];
extern struct pmt_cap pmt_cap_rmid_perf[];
extern struct pmt_cap *pmt_caps_rmid_perf[];
extern struct pmt_cap pmt_cap_accel[];
extern struct pmt_cap *pmt_caps_accel[];
extern struct pmt_cap pmt_cap_uncore[];
extern struct pmt_cap *pmt_caps_uncore[];
extern struct pmt_cap pmt_cap_crashlog[];
extern struct pmt_cap *pmt_caps_crashlog[];
extern struct pmt_cap pmt_cap_pete[];
extern struct pmt_cap *pmt_caps_pete[];
extern struct pmt_cap pmt_cap_tpmi[];
extern struct pmt_cap *pmt_caps_tpmi[];
extern struct pmt_cap pmt_cap_s3m[];
extern struct pmt_cap *pmt_caps_s3m[];
extern struct pmt_cap pmt_cap_tracing[];
extern struct pmt_cap *pmt_caps_tracing[];
extern struct pmt_cap pmt_cap_rmid_energy[];
extern struct pmt_cap *pmt_caps_rmid_energy[];
static inline bool pmt_feature_id_is_valid(enum pmt_feature_id id)
{
if (id > FEATURE_MAX)
return false;
if (id == FEATURE_INVALID || id == FEATURE_RESERVED)
return false;
return true;
}
#endif

View File

@ -8,6 +8,8 @@
#include <linux/bitfield.h>
struct oobmsm_plat_info;
#define TPMI_VERSION_INVALID 0xff
#define TPMI_MINOR_VERSION(val) FIELD_GET(GENMASK(4, 0), val)
#define TPMI_MAJOR_VERSION(val) FIELD_GET(GENMASK(7, 5), val)
@ -26,30 +28,7 @@ enum intel_tpmi_id {
TPMI_INFO_ID = 0x81, /* Special ID for PCI BDF and Package ID information */
};
/**
* struct intel_tpmi_plat_info - Platform information for a TPMI device instance
* @cdie_mask: Mask of all compute dies in the partition
* @package_id: CPU Package id
* @partition: Package partition id when multiple VSEC PCI devices per package
* @segment: PCI segment ID
* @bus_number: PCI bus number
* @device_number: PCI device number
* @function_number: PCI function number
*
* Structure to store platform data for a TPMI device instance. This
* struct is used to return data via tpmi_get_platform_data().
*/
struct intel_tpmi_plat_info {
u16 cdie_mask;
u8 package_id;
u8 partition;
u8 segment;
u8 bus_number;
u8 device_number;
u8 function_number;
};
struct intel_tpmi_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev);
struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev);
struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int index);
int tpmi_get_resource_count(struct auxiliary_device *auxdev);
int tpmi_get_feature_status(struct auxiliary_device *auxdev, int feature_id, bool *read_blocked,

View File

@ -4,12 +4,22 @@
#include <linux/auxiliary_bus.h>
#include <linux/bits.h>
#include <linux/err.h>
#include <linux/intel_pmt_features.h>
#define VSEC_CAP_TELEMETRY BIT(0)
#define VSEC_CAP_WATCHER BIT(1)
#define VSEC_CAP_CRASHLOG BIT(2)
#define VSEC_CAP_SDSI BIT(3)
#define VSEC_CAP_TPMI BIT(4)
/*
* VSEC_CAP_UNUSED is reserved. It exists to prevent zero initialized
* intel_vsec devices from being automatically set to a known
* capability with ID 0
*/
#define VSEC_CAP_UNUSED BIT(0)
#define VSEC_CAP_TELEMETRY BIT(1)
#define VSEC_CAP_WATCHER BIT(2)
#define VSEC_CAP_CRASHLOG BIT(3)
#define VSEC_CAP_SDSI BIT(4)
#define VSEC_CAP_TPMI BIT(5)
#define VSEC_CAP_DISCOVERY BIT(6)
#define VSEC_FEATURE_COUNT 7
/* Intel DVSEC offsets */
#define INTEL_DVSEC_ENTRIES 0xA
@ -26,6 +36,7 @@ enum intel_vsec_id {
VSEC_ID_TELEMETRY = 2,
VSEC_ID_WATCHER = 3,
VSEC_ID_CRASHLOG = 4,
VSEC_ID_DISCOVERY = 12,
VSEC_ID_SDSI = 65,
VSEC_ID_TPMI = 66,
};
@ -81,22 +92,31 @@ struct pmt_callbacks {
int (*read_telem)(struct pci_dev *pdev, u32 guid, u64 *data, loff_t off, u32 count);
};
struct vsec_feature_dependency {
unsigned long feature;
unsigned long supplier_bitmap;
};
/**
* struct intel_vsec_platform_info - Platform specific data
* @parent: parent device in the auxbus chain
* @headers: list of headers to define the PMT client devices to create
* @deps: array of feature dependencies
* @priv_data: private data, usable by parent devices, currently a callback
* @caps: bitmask of PMT capabilities for the given headers
* @quirks: bitmask of VSEC device quirks
* @base_addr: allow a base address to be specified (rather than derived)
* @num_deps: Count feature dependencies
*/
struct intel_vsec_platform_info {
struct device *parent;
struct intel_vsec_header **headers;
const struct vsec_feature_dependency *deps;
void *priv_data;
unsigned long caps;
unsigned long quirks;
u64 base_addr;
int num_deps;
};
/**
@ -110,6 +130,7 @@ struct intel_vsec_platform_info {
* @priv_data: any private data needed
* @quirks: specified quirks
* @base_addr: base address of entries (if specified)
* @cap_id: the enumerated id of the vsec feature
*/
struct intel_vsec_device {
struct auxiliary_device auxdev;
@ -122,6 +143,44 @@ struct intel_vsec_device {
size_t priv_data_size;
unsigned long quirks;
u64 base_addr;
unsigned long cap_id;
};
/**
* struct oobmsm_plat_info - Platform information for a device instance
* @cdie_mask: Mask of all compute dies in the partition
* @package_id: CPU Package id
* @partition: Package partition id when multiple VSEC PCI devices per package
* @segment: PCI segment ID
* @bus_number: PCI bus number
* @device_number: PCI device number
* @function_number: PCI function number
*
* Structure to store platform data for a OOBMSM device instance.
*/
struct oobmsm_plat_info {
u16 cdie_mask;
u8 package_id;
u8 partition;
u8 segment;
u8 bus_number;
u8 device_number;
u8 function_number;
};
struct telemetry_region {
struct oobmsm_plat_info plat_info;
void __iomem *addr;
size_t size;
u32 guid;
u32 num_rmids;
};
struct pmt_feature_group {
enum pmt_feature_id id;
int count;
struct kref kref;
struct telemetry_region regions[];
};
int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent,
@ -141,11 +200,40 @@ static inline struct intel_vsec_device *auxdev_to_ivdev(struct auxiliary_device
#if IS_ENABLED(CONFIG_INTEL_VSEC)
int intel_vsec_register(struct pci_dev *pdev,
struct intel_vsec_platform_info *info);
int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info,
struct intel_vsec_device *vsec_dev);
struct oobmsm_plat_info *intel_vsec_get_mapping(struct pci_dev *pdev);
#else
static inline int intel_vsec_register(struct pci_dev *pdev,
struct intel_vsec_platform_info *info)
{
return -ENODEV;
}
static inline int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info,
struct intel_vsec_device *vsec_dev)
{
return -ENODEV;
}
static inline struct oobmsm_plat_info *intel_vsec_get_mapping(struct pci_dev *pdev)
{
return ERR_PTR(-ENODEV);
}
#endif
#if IS_ENABLED(CONFIG_INTEL_PMT_TELEMETRY)
struct pmt_feature_group *
intel_pmt_get_regions_by_feature(enum pmt_feature_id id);
void intel_pmt_put_feature_group(struct pmt_feature_group *feature_group);
#else
static inline struct pmt_feature_group *
intel_pmt_get_regions_by_feature(enum pmt_feature_id id)
{
return ERR_PTR(-ENODEV);
}
static inline void
intel_pmt_put_feature_group(struct pmt_feature_group *feature_group) {}
#endif
#endif